You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by pl...@apache.org on 2018/01/05 06:07:33 UTC
[1/2] directory-kerby git commit: DIRKRB-676 Add new authentication
mechanism Kerberos-based token authentication.
Repository: directory-kerby
Updated Branches:
refs/heads/trunk 5a81d03fd -> 4410adf91
DIRKRB-676 Add new authentication mechanism Kerberos-based token authentication.
Project: http://git-wip-us.apache.org/repos/asf/directory-kerby/repo
Commit: http://git-wip-us.apache.org/repos/asf/directory-kerby/commit/e51a0ba0
Tree: http://git-wip-us.apache.org/repos/asf/directory-kerby/tree/e51a0ba0
Diff: http://git-wip-us.apache.org/repos/asf/directory-kerby/diff/e51a0ba0
Branch: refs/heads/trunk
Commit: e51a0ba06da4fb695221d473975cc87278422a66
Parents: bcf3ba1
Author: plusplusjiajia <ji...@intel.com>
Authored: Fri Jan 5 13:57:10 2018 +0800
Committer: plusplusjiajia <ji...@intel.com>
Committed: Fri Jan 5 13:57:10 2018 +0800
----------------------------------------------------------------------
has-project/has-client/pom.xml | 20 +
.../org/apache/kerby/has/client/HasClient.java | 625 +++++++++++++++++++
.../has/client/HasClientPluginRegistry.java | 63 ++
.../src/main/resources/ssl-client.conf.template | 20 +
.../apache/kerby/has/common/util/HasUtil.java | 12 +
.../has/server/HasServerPluginRegistry.java | 63 ++
.../kerby/has/server/kdc/HasKdcHandler.java | 315 ++++++++++
.../kerby/has/server/web/rest/HasApi.java | 151 +++++
.../server/web/rest/param/AuthTokenParam.java | 45 ++
.../kerby/has/server/web/rest/param/Param.java | 123 ++++
.../has/server/web/rest/param/StringParam.java | 66 ++
.../has/server/web/rest/param/TypeParam.java | 48 ++
pom.xml | 3 +
13 files changed, 1554 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-client/pom.xml
----------------------------------------------------------------------
diff --git a/has-project/has-client/pom.xml b/has-project/has-client/pom.xml
index 9be9333..8170128 100644
--- a/has-project/has-client/pom.xml
+++ b/has-project/has-client/pom.xml
@@ -27,6 +27,26 @@
<artifactId>has-common</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <version>${jersey.container.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-json</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ <version>${commons-text.version}</version>
+ </dependency>
</dependencies>
</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClient.java
----------------------------------------------------------------------
diff --git a/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClient.java b/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClient.java
new file mode 100755
index 0000000..848b0c4
--- /dev/null
+++ b/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClient.java
@@ -0,0 +1,625 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.client;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.text.CharacterPredicates;
+import org.apache.commons.text.RandomStringGenerator;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasConfigKey;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.common.ssl.SSLFactory;
+import org.apache.kerby.has.common.util.HasUtil;
+import org.apache.kerby.has.common.util.URLConnectionFactory;
+import org.apache.kerby.kerberos.kerb.KrbCodec;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.KrbRuntime;
+import org.apache.kerby.kerberos.kerb.crypto.EncryptionHandler;
+import org.apache.kerby.kerberos.kerb.provider.TokenEncoder;
+import org.apache.kerby.kerberos.kerb.type.base.AuthToken;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptedData;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
+import org.apache.kerby.kerberos.kerb.type.base.KeyUsage;
+import org.apache.kerby.kerberos.kerb.type.base.KrbError;
+import org.apache.kerby.kerberos.kerb.type.base.KrbMessage;
+import org.apache.kerby.kerberos.kerb.type.base.KrbMessageType;
+import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
+import org.apache.kerby.kerberos.kerb.type.kdc.EncAsRepPart;
+import org.apache.kerby.kerberos.kerb.type.kdc.EncKdcRepPart;
+import org.apache.kerby.kerberos.kerb.type.kdc.KdcRep;
+import org.apache.kerby.kerberos.kerb.type.ticket.TgtTicket;
+import org.apache.kerby.util.IOUtil;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+/**
+ * HAS client
+ */
+public class HasClient {
+
+ public static final Logger LOG = LoggerFactory.getLogger(HasClient.class);
+
+ public static final String JAVA_SECURITY_KRB5_CONF = "java.security.krb5.conf";
+ public static final String HAS_HTTP_PORT_DEFAULT = "9870";
+ public static final String HAS_CONFIG_DEFAULT = "/etc/has/has-client.conf";
+ public static final String CA_ROOT_DEFAULT = "/etc/has/ca-root.pem";
+
+ private String hadoopSecurityHas = null;
+ private String type;
+ private File clientConfigFolder;
+
+
+ public HasClient() { }
+
+ /**
+ * Create an instance of the HasClient.
+ *
+ * @param hadoopSecurityHas the has config
+ */
+ public HasClient(String hadoopSecurityHas) {
+ this.hadoopSecurityHas = hadoopSecurityHas;
+ }
+
+
+ public TgtTicket requestTgt() throws HasException {
+ HasConfig config;
+ if (hadoopSecurityHas == null) {
+ String hasClientConf = System.getenv("HAS_CLIENT_CONF");
+ if (hasClientConf == null) {
+ hasClientConf = HAS_CONFIG_DEFAULT;
+ }
+ LOG.debug("has-client conf path: " + hasClientConf);
+ File confFile = new File(hasClientConf);
+ if (!confFile.exists()) {
+ throw new HasException("The HAS client config file: " + hasClientConf
+ + " does not exist.");
+ }
+ try {
+ config = HasUtil.getHasConfig(confFile);
+ } catch (HasException e) {
+ LOG.error("Failed to get has client config: " + e.getMessage());
+ throw new HasException("Failed to get has client config: " + e);
+ }
+ } else {
+ config = new HasConfig();
+ String[] urls = hadoopSecurityHas.split(";");
+ String host = "";
+ int port = 0;
+ try {
+ for (String url : urls) {
+ URI uri = new URI(url.trim());
+
+ // parse host
+ host = host + uri.getHost() + ",";
+
+ // parse port
+ if (port == 0) {
+ port = uri.getPort();
+ } else {
+ if (port != uri.getPort()) {
+ throw new HasException("Invalid port: not even.");
+ }
+ }
+
+ // We will get the auth type from env first
+ type = System.getenv("auth_type");
+ // parse host
+ if (type == null) {
+ String[] strs = uri.getQuery().split("=");
+ if (strs[0].equals("auth_type")) {
+ type = strs[1];
+ } else {
+ LOG.warn("No auth type in conf.");
+ }
+ }
+ }
+ if (host == null || port == 0) {
+ throw new HasException("host is null.");
+ } else {
+ host = host.substring(0, host.length() - 1);
+ config.setString(HasConfigKey.HTTPS_HOST, host);
+ config.setInt(HasConfigKey.HTTPS_PORT, port);
+ config.setString(HasConfigKey.AUTH_TYPE, type);
+ }
+ } catch (URISyntaxException e) {
+ LOG.error("Errors occurred when getting web url. " + e.getMessage());
+ throw new HasException(
+ "Errors occurred when getting web url. " + e.getMessage());
+ }
+ }
+ if (config == null) {
+ throw new HasException("Failed to get HAS client config.");
+ }
+ clientConfigFolder = new File("/etc/has/" + config.getHttpsHost());
+ if (!clientConfigFolder.exists()) {
+ clientConfigFolder.mkdirs();
+ }
+
+ // get and set ssl-client/trustStore first
+ String sslClientConfPath = clientConfigFolder + "/ssl-client.conf";
+ loadSslClientConf(config, sslClientConfPath);
+ config.setString(HasConfigKey.SSL_CLIENT_CONF, sslClientConfPath);
+
+ HasClientPlugin plugin;
+ try {
+ plugin = getClientTokenPlugin(config);
+ } catch (HasException e) {
+ LOG.error("Failed to get client token plugin from config: " + e.getMessage());
+ throw new HasException(
+ "Failed to get client token plugin from config: " + e.getMessage());
+ }
+ AuthToken authToken;
+ try {
+ authToken = plugin.login(config);
+ } catch (HasLoginException e) {
+ LOG.error("Plugin login failed: " + e.getMessage());
+ throw new HasException(
+ "Plugin login failed: " + e.getMessage());
+ }
+ type = plugin.getLoginType();
+
+ LOG.info("The plugin type is: " + type);
+
+ return requestTgt(authToken, type, config);
+ }
+
+ private HasClientPlugin getClientTokenPlugin(HasConfig config) throws HasException {
+ String pluginName = config.getPluginName();
+ LOG.info("The plugin name getting from config is: " + pluginName);
+ HasClientPlugin clientPlugin;
+ if (pluginName != null) {
+ clientPlugin = HasClientPluginRegistry.createPlugin(pluginName);
+ } else {
+ throw new HasException("Please set the plugin name in has client conf");
+ }
+ if (clientPlugin == null) {
+ throw new HasException("Failed to create client plugin: " + pluginName);
+ }
+ LOG.info("The plugin class is: " + clientPlugin);
+
+ return clientPlugin;
+ }
+
+ /**
+ * Request a TGT with user token, plugin type and has config.
+ * @param authToken
+ * @param type
+ * @param config
+ * @return TGT
+ * @throws HasException e
+ */
+ public TgtTicket requestTgt(AuthToken authToken, String type, HasConfig config)
+ throws HasException {
+ TokenEncoder tokenEncoder = KrbRuntime.getTokenProvider("JWT").createTokenEncoder();
+
+ String tokenString;
+ try {
+ tokenString = tokenEncoder.encodeAsString(authToken);
+ } catch (KrbException e) {
+ LOG.debug("Failed to decode the auth token.");
+ throw new HasException("Failed to decode the auth token." + e.getMessage());
+ }
+
+ JSONObject json = null;
+ int responseStatus = 0;
+ boolean success = false;
+ if (config.getHttpsPort() != null && config.getHttpsHost() != null) {
+ String sslClientConfPath = clientConfigFolder + "/ssl-client.conf";
+ config.setString(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
+ config.setString(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfPath);
+ config.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, false);
+
+ URLConnectionFactory connectionFactory = URLConnectionFactory
+ .newDefaultURLConnectionFactory(config);
+
+ URL url;
+ String[] hosts = config.getHttpsHost().split(",");
+ for (String host : hosts) {
+ try {
+ url = new URL("https://" + host.trim() + ":" + config.getHttpsPort()
+ + "/has/v1?type=" + type + "&authToken=" + tokenString);
+ } catch (MalformedURLException e) {
+ LOG.warn("Failed to get url. " + e.toString());
+ continue;
+ }
+ HttpURLConnection conn;
+ try {
+ conn = (HttpURLConnection) connectionFactory.openConnection(url);
+ } catch (IOException e) {
+ LOG.warn("Failed to open connection. " + e.toString());
+ continue;
+ }
+
+ conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ try {
+ conn.setRequestMethod("PUT");
+ } catch (ProtocolException e) {
+ LOG.warn("Failed to set request method. " + e.toString());
+ continue;
+ }
+ conn.setDoOutput(true);
+ conn.setDoInput(true);
+
+ try {
+ conn.connect();
+
+ responseStatus = conn.getResponseCode();
+ switch (responseStatus) {
+ case 200:
+ case 201:
+ BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line + "\n");
+ }
+ br.close();
+
+ json = new JSONObject(sb.toString());
+ }
+
+ } catch (IOException | JSONException e) {
+ LOG.warn("ERROR! " + e.toString());
+ continue;
+ }
+
+ if (responseStatus == 200 || responseStatus == 201) {
+ success = true;
+ break;
+ }
+ }
+ if (!success) {
+ throw new HasException("Failed : HTTP error code : "
+ + responseStatus);
+ }
+ } else {
+ WebResource webResource;
+ Client client = Client.create();
+ String[] hosts = config.getHttpHost().split(",");
+ for (String host : hosts) {
+ webResource = client
+ .resource("http://" + host.trim() + ":" + config.getHttpPort()
+ + "/has/v1?type=" + type + "&authToken="
+ + tokenString);
+ try {
+ ClientResponse response = webResource.accept("application/json")
+ .put(ClientResponse.class);
+
+ if (response.getStatus() != 200) {
+ LOG.warn("WARN! " + response.getEntity(String.class));
+ responseStatus = response.getStatus();
+ continue;
+ }
+ json = response.getEntity(JSONObject.class);
+ success = true;
+ break;
+ } catch (ClientHandlerException e) {
+ LOG.warn("WARN! " + e.toString());
+ continue;
+ }
+ }
+ if (!success) {
+ throw new HasException("Failed : HTTP error code : "
+ + responseStatus);
+ }
+ }
+
+ LOG.debug("Return from Server .... \n");
+
+ try {
+ return handleResponse(json, (String) authToken.getAttributes().get("passPhrase"));
+ } catch (HasException e) {
+ LOG.debug("Failed to handle response when requesting tgt ticket in client."
+ + e.getMessage());
+ throw new HasException(e);
+ }
+ }
+
+ private File loadSslClientConf(HasConfig config, String sslClientConfPath) throws HasException {
+ File sslClientConf = new File(sslClientConfPath);
+ if (!sslClientConf.exists()) {
+ String httpHost = config.getHttpHost();
+ String httpPort = config.getHttpPort();
+ if (httpHost == null) {
+ LOG.info("Can't find the http host in config, the https host will be used.");
+ httpHost = config.getHttpsHost();
+ }
+ if (httpPort == null) {
+ LOG.info("Can't find the http port in config, the default http port will be used.");
+ httpPort = HAS_HTTP_PORT_DEFAULT;
+ }
+ X509Certificate certificate = getCertificate(httpHost, httpPort);
+ if (verifyCertificate(certificate)) {
+ String password = createTrustStore(config.getHttpsHost(), certificate);
+ createClientSSLConfig(password);
+ } else {
+ throw new HasException("The certificate from HAS server is invalid.");
+ }
+ }
+ return sslClientConf;
+ }
+
+ public KrbMessage getKrbMessage(JSONObject json) throws HasException {
+
+ LOG.debug("Starting to get the message from has server.");
+
+ try {
+ boolean success = json.getBoolean("success");
+ if (!success) {
+ throw new HasException("Failed: " + json.getString("krbMessage"));
+ }
+ } catch (JSONException e) {
+ LOG.debug("Failed to get message." + e);
+ throw new HasException("Failed to get message." + e);
+ }
+
+ String typeString;
+ try {
+ typeString = json.getString("type");
+ } catch (JSONException e) {
+ LOG.debug("Failed to get message." + e);
+ throw new HasException("Failed to get message." + e);
+ }
+
+ if (typeString != null && typeString.equals(type)) {
+ LOG.debug("The message type is " + type);
+ String krbMessageString = null;
+ try {
+ krbMessageString = json.getString("krbMessage");
+ } catch (JSONException e) {
+ LOG.debug("Failed to get the krbMessage. " + e);
+ }
+ Base64 base64 = new Base64(0);
+ byte[] krbMessage = base64.decode(krbMessageString);
+ ByteBuffer byteBuffer = ByteBuffer.wrap(krbMessage);
+ KrbMessage kdcRep;
+ try {
+ kdcRep = KrbCodec.decodeMessage(byteBuffer);
+ } catch (IOException e) {
+ throw new HasException("Krb decoding message failed", e);
+ }
+ return kdcRep;
+ } else {
+ throw new HasException("Can't get the right message from server.");
+ }
+ }
+
+ public TgtTicket handleResponse(JSONObject json, String passPhrase)
+ throws HasException {
+ KrbMessage kdcRep = getKrbMessage(json);
+
+ KrbMessageType messageType = kdcRep.getMsgType();
+ if (messageType == KrbMessageType.AS_REP) {
+ return processResponse((KdcRep) kdcRep, passPhrase);
+ } else if (messageType == KrbMessageType.KRB_ERROR) {
+ KrbError error = (KrbError) kdcRep;
+ LOG.error("KDC server response with message: "
+ + error.getErrorCode().getMessage());
+
+ throw new HasException(error.getEtext());
+ }
+ return null;
+ }
+
+ public TgtTicket processResponse(KdcRep kdcRep, String passPhrase)
+ throws HasException {
+
+ PrincipalName clientPrincipal = kdcRep.getCname();
+ String clientRealm = kdcRep.getCrealm();
+ clientPrincipal.setRealm(clientRealm);
+
+ // Get the client to decrypt the EncryptedData
+ EncryptionKey clientKey = null;
+ try {
+ clientKey = HasUtil.getClientKey(clientPrincipal.getName(),
+ passPhrase,
+ kdcRep.getEncryptedEncPart().getEType());
+ } catch (KrbException e) {
+ throw new HasException("Could not generate key. " + e.getMessage());
+ }
+
+ byte[] decryptedData = decryptWithClientKey(kdcRep.getEncryptedEncPart(),
+ KeyUsage.AS_REP_ENCPART, clientKey);
+ if ((decryptedData[0] & 0x1f) == 26) {
+ decryptedData[0] = (byte) (decryptedData[0] - 1);
+ }
+ EncKdcRepPart encKdcRepPart = new EncAsRepPart();
+ try {
+ encKdcRepPart.decode(decryptedData);
+ } catch (IOException e) {
+ throw new HasException("Failed to decode EncAsRepPart", e);
+ }
+ kdcRep.setEncPart(encKdcRepPart);
+
+ TgtTicket tgtTicket = getTicket(kdcRep);
+ LOG.info("Ticket expire time: " + tgtTicket.getEncKdcRepPart().getEndTime());
+ return tgtTicket;
+
+ }
+
+ protected byte[] decryptWithClientKey(EncryptedData data,
+ KeyUsage usage,
+ EncryptionKey clientKey) throws HasException {
+ if (clientKey == null) {
+ throw new HasException("Client key isn't available");
+ }
+ try {
+ return EncryptionHandler.decrypt(data, clientKey, usage);
+ } catch (KrbException e) {
+ throw new HasException("Errors occurred when decrypting the data." + e.getMessage());
+ }
+ }
+
+ /**
+ * Get the tgt ticket from KdcRep
+ *
+ * @param kdcRep
+ */
+ public TgtTicket getTicket(KdcRep kdcRep) {
+ TgtTicket tgtTicket = new TgtTicket(kdcRep.getTicket(),
+ (EncAsRepPart) kdcRep.getEncPart(), kdcRep.getCname());
+ return tgtTicket;
+ }
+
+ /**
+ * Get certificate from HAS server.
+ *
+ */
+ private X509Certificate getCertificate(String host, String port) throws HasException {
+ X509Certificate certificate;
+ Client client = Client.create();
+ WebResource webResource = client.resource("http://" + host + ":" + port + "/has/v1/getcert");
+ ClientResponse response = webResource.get(ClientResponse.class);
+ if (response.getStatus() != 200) {
+ throw new HasException(response.getEntity(String.class));
+ }
+ try {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ InputStream in = response.getEntityInputStream();
+ certificate = (X509Certificate) factory.generateCertificate(in);
+ } catch (CertificateException e) {
+ throw new HasException("Failed to get certificate from HAS server", e);
+ }
+
+ return certificate;
+ }
+
+ /**
+ * Verify certificate.
+ */
+ private boolean verifyCertificate(X509Certificate certificate) throws HasException {
+ // Check if certificate is expired
+ try {
+ Date date = new Date();
+ certificate.checkValidity(date);
+ } catch (GeneralSecurityException e) {
+ return false;
+ }
+
+ // Get certificate from ca root
+ X509Certificate caRoot;
+ try {
+ //Get the ca root path from env, client should export it.
+ String caRootPath = System.getenv("CA_ROOT");
+ if (caRootPath == null) {
+ caRootPath = CA_ROOT_DEFAULT;
+ }
+ File caRootFile;
+ if (caRootPath != null) {
+ caRootFile = new File(caRootPath);
+ if (!caRootFile.exists()) {
+ throw new HasException("CA_ROOT: " + caRootPath + " not exist.");
+ }
+ } else {
+ throw new HasException("Please set the CA_ROOT.");
+ }
+
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ FileInputStream in = new FileInputStream(caRootFile);
+ caRoot = (X509Certificate) factory.generateCertificate(in);
+ } catch (CertificateException | FileNotFoundException e) {
+ throw new HasException("Failed to get certificate from ca root file", e);
+ }
+
+ // Verify certificate with root certificate
+ try {
+ PublicKey publicKey = caRoot.getPublicKey();
+ certificate.verify(publicKey);
+ } catch (GeneralSecurityException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create and save truststore file based on certificate.
+ *
+ */
+ private String createTrustStore(String host, X509Certificate certificate) throws HasException {
+ KeyStore trustStore;
+
+ // Create password
+ RandomStringGenerator generator = new RandomStringGenerator.Builder()
+ .withinRange('a', 'z')
+ .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
+ .build();
+ String password = generator.generate(15);
+
+ File trustStoreFile = new File(clientConfigFolder + "/truststore.jks");
+ try {
+ trustStore = KeyStore.getInstance("jks");
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry(host, certificate);
+ FileOutputStream out = new FileOutputStream(trustStoreFile);
+ trustStore.store(out, password.toCharArray());
+ out.close();
+ } catch (IOException | GeneralSecurityException e) {
+ throw new HasException("Failed to create and save truststore file", e);
+ }
+ return password;
+ }
+
+ /**
+ * Create ssl configuration file for client.
+ *
+ */
+ private void createClientSSLConfig(String password) throws HasException {
+ String resourcePath = "/ssl-client.conf.template";
+ InputStream templateResource = getClass().getResourceAsStream(resourcePath);
+ try {
+ String content = IOUtil.readInput(templateResource);
+ content = content.replaceAll("_location_", clientConfigFolder.getAbsolutePath()
+ + "/truststore.jks");
+ content = content.replaceAll("_password_", password);
+
+ IOUtil.writeFile(content, new File(clientConfigFolder + "/ssl-client.conf"));
+ } catch (IOException e) {
+ throw new HasException("Failed to create client ssl configuration file", e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClientPluginRegistry.java
----------------------------------------------------------------------
diff --git a/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClientPluginRegistry.java b/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClientPluginRegistry.java
new file mode 100644
index 0000000..45cd193
--- /dev/null
+++ b/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasClientPluginRegistry.java
@@ -0,0 +1,63 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.client;
+
+import org.apache.kerby.has.common.HasException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class HasClientPluginRegistry {
+ static final Logger LOG = LoggerFactory.getLogger(HasClientPluginRegistry.class);
+
+ private static Map<String, Class> allPlugins = new ConcurrentHashMap<>();
+
+ static {
+ ServiceLoader<HasClientPlugin> plugins = ServiceLoader.load(HasClientPlugin.class);
+
+ for (HasClientPlugin plugin : plugins) {
+ allPlugins.put(plugin.getLoginType(), plugin.getClass());
+ }
+ }
+
+ public static Set<String> registeredPlugins() {
+ return Collections.unmodifiableSet(allPlugins.keySet());
+ }
+
+ public static boolean registeredPlugin(String name) {
+ return allPlugins.containsKey(name);
+ }
+
+ public static HasClientPlugin createPlugin(String name) throws HasException {
+ if (!registeredPlugin(name)) {
+ throw new HasException("Unregistered plugin " + name);
+ }
+ try {
+ HasClientPlugin clientPlugin = (HasClientPlugin) allPlugins.get(name).newInstance();
+ return clientPlugin;
+ } catch (Exception e) {
+ LOG.error("Create {} plugin failed", name, e);
+ throw new HasException(e.getMessage());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-client/src/main/resources/ssl-client.conf.template
----------------------------------------------------------------------
diff --git a/has-project/has-client/src/main/resources/ssl-client.conf.template b/has-project/has-client/src/main/resources/ssl-client.conf.template
new file mode 100644
index 0000000..c5ca70a
--- /dev/null
+++ b/has-project/has-client/src/main/resources/ssl-client.conf.template
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+ssl.client.truststore.location = _location_
+ssl.client.truststore.password = _password_
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
index 3ccd749..4fbdff8 100644
--- a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
@@ -19,6 +19,10 @@ package org.apache.kerby.has.common.util;
import org.apache.kerby.has.common.HasConfig;
import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.crypto.EncryptionHandler;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
import java.io.BufferedReader;
import java.io.File;
@@ -78,4 +82,12 @@ public class HasUtil {
+ hasConfFile.getAbsolutePath());
}
}
+
+ public static EncryptionKey getClientKey(String userName, String passPhrase,
+ EncryptionType type) throws KrbException {
+ EncryptionKey clientKey = EncryptionHandler.string2Key(userName,
+ passPhrase, type);
+ return clientKey;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServerPluginRegistry.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServerPluginRegistry.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServerPluginRegistry.java
new file mode 100644
index 0000000..d75b714
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServerPluginRegistry.java
@@ -0,0 +1,63 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server;
+
+import org.apache.kerby.has.common.HasException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class HasServerPluginRegistry {
+ static final Logger LOG = LoggerFactory.getLogger(HasServerPluginRegistry.class);
+
+ private static Map<String, Class> allPlugins = new ConcurrentHashMap<>();
+
+ static {
+ ServiceLoader<HasServerPlugin> plugins = ServiceLoader.load(HasServerPlugin.class);
+
+ for (HasServerPlugin plugin : plugins) {
+ allPlugins.put(plugin.getLoginType(), plugin.getClass());
+ }
+ }
+
+ public static Set<String> registeredPlugins() {
+ return Collections.unmodifiableSet(allPlugins.keySet());
+ }
+
+ public static boolean registeredPlugin(String name) {
+ return allPlugins.containsKey(name);
+ }
+
+ public static HasServerPlugin createPlugin(String name) throws HasException {
+ if (!registeredPlugin(name)) {
+ throw new HasException("Unregistered plugin " + name);
+ }
+ try {
+ HasServerPlugin serverPlugin = (HasServerPlugin) allPlugins.get(name).newInstance();
+ return serverPlugin;
+ } catch (Exception e) {
+ LOG.error("Create {} plugin failed", name, e);
+ throw new HasException(e.getMessage());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/kdc/HasKdcHandler.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/kdc/HasKdcHandler.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/kdc/HasKdcHandler.java
new file mode 100644
index 0000000..ce60739
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/kdc/HasKdcHandler.java
@@ -0,0 +1,315 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.kerby.has.server.kdc;
+
+import org.apache.kerby.has.common.util.HasUtil;
+import org.apache.kerby.has.server.HasServer;
+import org.apache.kerby.kerberos.kerb.KrbCodec;
+import org.apache.kerby.kerberos.kerb.KrbErrorCode;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.client.KrbContext;
+import org.apache.kerby.kerberos.kerb.common.EncryptionUtil;
+import org.apache.kerby.kerberos.kerb.common.KrbUtil;
+import org.apache.kerby.kerberos.kerb.server.KdcConfigKey;
+import org.apache.kerby.kerberos.kerb.server.KdcContext;
+import org.apache.kerby.kerberos.kerb.server.KdcRecoverableException;
+import org.apache.kerby.kerberos.kerb.server.KdcServer;
+import org.apache.kerby.kerberos.kerb.server.preauth.PreauthHandler;
+import org.apache.kerby.kerberos.kerb.server.request.AsRequest;
+import org.apache.kerby.kerberos.kerb.server.request.KdcRequest;
+import org.apache.kerby.kerberos.kerb.type.KerberosTime;
+import org.apache.kerby.kerberos.kerb.type.base.AuthToken;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
+import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
+import org.apache.kerby.kerberos.kerb.type.base.HostAddress;
+import org.apache.kerby.kerberos.kerb.type.base.HostAddresses;
+import org.apache.kerby.kerberos.kerb.type.base.KrbError;
+import org.apache.kerby.kerberos.kerb.type.base.KrbMessage;
+import org.apache.kerby.kerberos.kerb.type.base.KrbToken;
+import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
+import org.apache.kerby.kerberos.kerb.type.base.TokenFormat;
+import org.apache.kerby.kerberos.kerb.type.kdc.AsReq;
+import org.apache.kerby.kerberos.kerb.type.kdc.KdcOption;
+import org.apache.kerby.kerberos.kerb.type.kdc.KdcOptions;
+import org.apache.kerby.kerberos.kerb.type.kdc.KdcReqBody;
+import org.apache.kerby.kerberos.kerb.type.pa.PaData;
+import org.apache.kerby.kerberos.kerb.type.pa.PaDataEntry;
+import org.apache.kerby.kerberos.kerb.type.pa.PaDataType;
+import org.apache.kerby.kerberos.kerb.type.pa.token.PaTokenRequest;
+import org.apache.kerby.kerberos.kerb.type.pa.token.TokenInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HasKdcHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(HasKdcHandler.class);
+
+ private KdcContext kdcContext;
+ private KrbContext krbContext;
+ private KdcServer kdcServer;
+
+ /**
+ * Constructor with has server.
+ *
+ * @param hasServer has server
+ */
+ public HasKdcHandler(HasServer hasServer) {
+ this.krbContext = new KrbContext();
+ this.krbContext.init(hasServer.getKrbSetting());
+ this.kdcServer = hasServer.getKdcServer();
+ prepareHandler(kdcServer);
+ }
+
+ public KrbContext getKrbContext() {
+ return krbContext;
+ }
+
+ public KdcContext getKdcContext() {
+ return kdcContext;
+ }
+
+ private KdcServer getKdcServer() {
+ return kdcServer;
+ }
+
+ private void prepareHandler(KdcServer kdcServer) {
+ this.kdcContext = new KdcContext(kdcServer.getKdcSetting());
+ this.kdcContext.setIdentityService(kdcServer.getIdentityService());
+ PreauthHandler preauthHandler = new PreauthHandler();
+ preauthHandler.init();
+ this.kdcContext.setPreauthHandler(preauthHandler);
+ }
+
+ private String getAudience(String name) {
+ return name + "/" + getKdcContext().getKdcRealm() + "@" + getKdcContext().getKdcRealm();
+ }
+
+ public KrbMessage getResponse(AuthToken authToken, String passPhrase) {
+ KrbMessage krbMessage = null;
+ try {
+ krbMessage = handleMessage(authToken, passPhrase);
+ } catch (KrbException e) {
+ LOG.error("Failed to handle message. " + e.getMessage());
+ }
+ return krbMessage;
+ }
+
+ /**
+ * Process the client request message.
+ */
+ public KrbMessage handleMessage(AuthToken authToken, String passPhrase) throws KrbException {
+
+ // set the audiences
+ List<String> auds = new ArrayList<String>();
+ String audience = getAudience("krbtgt");
+ auds.add(audience);
+ authToken.setAudiences(auds);
+
+ AsReq asReq = createAsReq(authToken);
+ KdcRequest kdcRequest = new AsRequest(asReq, kdcContext);
+ kdcRequest.setHttps(true);
+ List<EncryptionType> requestedTypes = getEncryptionTypes();
+ EncryptionType bestType = EncryptionUtil.getBestEncryptionType(requestedTypes,
+ kdcContext.getConfig().getEncryptionTypes());
+
+ if (bestType == null) {
+ LOG.error("Can't get the best encryption type.");
+ throw new KrbException(KrbErrorCode.KDC_ERR_ETYPE_NOSUPP);
+ }
+
+ PrincipalName clientPrincipal = new PrincipalName(authToken.getSubject());
+ String clientRealm = asReq.getReqBody().getRealm();
+ if (clientRealm == null || clientRealm.isEmpty()) {
+ clientRealm = getKdcContext().getKdcRealm();
+ }
+ clientPrincipal.setRealm(clientRealm);
+
+ // Set the client key
+ EncryptionKey clientKey = HasUtil.getClientKey(clientPrincipal.getName(),
+ passPhrase, bestType);
+ kdcRequest.setClientKey(clientKey);
+
+ // Set the token issuers
+ getKdcServer().getKdcConfig().setString(KdcConfigKey.TOKEN_ISSUERS, "has");
+
+ KrbMessage krbResponse;
+
+ try {
+ kdcRequest.process();
+ krbResponse = kdcRequest.getReply();
+ } catch (KrbException e) {
+ LOG.error("Error occurred when request tgt. " + e.getMessage());
+ if (e instanceof KdcRecoverableException) {
+ krbResponse = handleRecoverableException(
+ (KdcRecoverableException) e, kdcRequest);
+ } else {
+ KrbError krbError = new KrbError();
+ krbError.setStime(KerberosTime.now());
+ krbError.setSusec(100);
+ if (e.getKrbErrorCode() != null) {
+ krbError.setErrorCode(e.getKrbErrorCode());
+ } else {
+ krbError.setErrorCode(KrbErrorCode.UNKNOWN_ERR);
+ }
+ krbError.setCrealm(kdcContext.getKdcRealm());
+ if (kdcRequest.getClientPrincipal() != null) {
+ krbError.setCname(kdcRequest.getClientPrincipal());
+ }
+ krbError.setRealm(kdcContext.getKdcRealm());
+ if (kdcRequest.getServerPrincipal() != null) {
+ krbError.setSname(kdcRequest.getServerPrincipal());
+ } else {
+ PrincipalName serverPrincipal = kdcRequest.getKdcReq().getReqBody().getSname();
+ serverPrincipal.setRealm(kdcRequest.getKdcReq().getReqBody().getRealm());
+ krbError.setSname(serverPrincipal);
+ }
+ if (KrbErrorCode.KRB_AP_ERR_BAD_INTEGRITY.equals(e.getKrbErrorCode())) {
+ krbError.setEtext("PREAUTH_FAILED");
+ } else {
+ krbError.setEtext(e.getMessage());
+ }
+ krbResponse = krbError;
+ }
+ }
+ return krbResponse;
+ }
+
+ /**
+ * Process the recoverable exception.
+ *
+ * @param e The exception return by kdc
+ * @param kdcRequest kdc request
+ * @return The KrbError
+ */
+ private KrbMessage handleRecoverableException(KdcRecoverableException e,
+ KdcRequest kdcRequest)
+ throws KrbException {
+ LOG.info("KRB error occurred while processing request:"
+ + e.getMessage());
+
+ KrbError error = e.getKrbError();
+ error.setStime(KerberosTime.now());
+ error.setSusec(100);
+ error.setErrorCode(e.getKrbError().getErrorCode());
+ error.setRealm(kdcContext.getKdcRealm());
+ if (kdcRequest != null) {
+ error.setSname(kdcRequest.getKdcReq().getReqBody().getCname());
+ } else {
+ error.setSname(new PrincipalName("NONE"));
+ }
+ error.setEtext(e.getMessage());
+ return error;
+ }
+
+ public AsReq createAsReq(AuthToken authToken) throws KrbException {
+ AsReq asReq = new AsReq();
+ KdcReqBody body = makeReqBody();
+ asReq.setReqBody(body);
+
+ PaTokenRequest tokenPa = new PaTokenRequest();
+ KrbToken krbToken = new KrbToken(authToken, TokenFormat.JWT);
+ tokenPa.setToken(krbToken);
+ TokenInfo info = new TokenInfo();
+ info.setTokenVendor(authToken.getIssuer());
+ tokenPa.setTokenInfo(info);
+
+ PaDataEntry paDataEntry = new PaDataEntry();
+ paDataEntry.setPaDataType(PaDataType.TOKEN_REQUEST);
+ paDataEntry.setPaDataValue(KrbCodec.encode(tokenPa));
+
+ PaData paData = new PaData();
+ paData.addElement(paDataEntry);
+ asReq.setPaData(paData);
+ return asReq;
+ }
+
+ /**
+ * Create the KdcReqBody
+ *
+ * @return KdcReqBody
+ *
+ * @throws KrbException e
+ */
+ protected KdcReqBody makeReqBody() throws KrbException {
+ KdcReqBody body = new KdcReqBody();
+
+ long startTime = System.currentTimeMillis();
+ body.setFrom(new KerberosTime(startTime));
+
+ // set the client principal as null
+ PrincipalName cName = null;
+ body.setCname(cName);
+
+ body.setRealm(getKrbContext().getKrbSetting().getKdcRealm());
+
+ PrincipalName sName = getServerPrincipal();
+ body.setSname(sName);
+
+ body.setTill(new KerberosTime(startTime + krbContext.getTicketValidTime()));
+
+ int nonce = krbContext.generateNonce();
+ body.setNonce(nonce);
+// setChosenNonce(nonce);
+
+ body.setKdcOptions(getKdcOptions());
+
+ HostAddresses addresses = getHostAddresses();
+ if (addresses != null) {
+ body.setAddresses(addresses);
+ }
+
+ body.setEtypes(getEncryptionTypes());
+
+ return body;
+ }
+
+ private PrincipalName getServerPrincipal() {
+ return KrbUtil.makeTgsPrincipal(getKrbContext().getKrbSetting().getKdcRealm());
+ }
+
+ private KdcOptions getKdcOptions() {
+ KdcOptions kdcOptions = new KdcOptions();
+ // By default enforce these flags
+ kdcOptions.setFlag(KdcOption.FORWARDABLE);
+ kdcOptions.setFlag(KdcOption.PROXIABLE);
+ kdcOptions.setFlag(KdcOption.RENEWABLE_OK);
+ return kdcOptions;
+ }
+
+ public HostAddresses getHostAddresses() {
+ List<HostAddress> hostAddresses = new ArrayList<HostAddress>();
+ HostAddresses addresses = null;
+ //empty
+ if (!hostAddresses.isEmpty()) {
+ addresses = new HostAddresses();
+ for (HostAddress ha : hostAddresses) {
+ addresses.addElement(ha);
+ }
+ }
+ return addresses;
+ }
+
+ public List<EncryptionType> getEncryptionTypes() {
+ List<EncryptionType> encryptionTypes = krbContext.getConfig().getEncryptionTypes();
+ return EncryptionUtil.orderEtypesByStrength(encryptionTypes);
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HasApi.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HasApi.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HasApi.java
new file mode 100644
index 0000000..eaa3587
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HasApi.java
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.hadoop.http.JettyUtils;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.server.HasAuthenException;
+import org.apache.kerby.has.server.HasServer;
+import org.apache.kerby.has.server.HasServerPlugin;
+import org.apache.kerby.has.server.HasServerPluginRegistry;
+import org.apache.kerby.has.server.kdc.HasKdcHandler;
+import org.apache.kerby.has.server.web.WebServer;
+import org.apache.kerby.has.server.web.rest.param.AuthTokenParam;
+import org.apache.kerby.has.server.web.rest.param.TypeParam;
+import org.apache.kerby.kerberos.kerb.KrbRuntime;
+import org.apache.kerby.kerberos.kerb.provider.TokenDecoder;
+import org.apache.kerby.kerberos.kerb.type.base.AuthToken;
+import org.apache.kerby.kerberos.kerb.type.base.KrbMessage;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * HAS web methods implementation.
+ */
+@Path("")
+public class HasApi {
+
+ @Context
+ private ServletContext context;
+
+ @Context
+ private HttpServletRequest httpRequest;
+
+
+ /**
+ * Handle HTTP PUT request.
+ */
+ @PUT
+ @Produces({MediaType.APPLICATION_OCTET_STREAM + "; " + JettyUtils.UTF_8,
+ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8})
+ public Response asRequest(
+ @QueryParam(TypeParam.NAME) @DefaultValue(TypeParam.DEFAULT)
+ final TypeParam type,
+ @QueryParam(AuthTokenParam.NAME) @DefaultValue(AuthTokenParam.DEFAULT)
+ final AuthTokenParam authToken
+ ) {
+ return asRequest(type.getValue(), authToken.getValue());
+ }
+
+ private Response asRequest(String type, String tokenStr) {
+ if (httpRequest.isSecure()) {
+ final HasServer hasServer = WebServer.getHasServerFromContext(context);
+ String errMessage = null;
+ String js = null;
+ ObjectMapper mapper = new ObjectMapper();
+ final Map<String, Object> m = new TreeMap<String, Object>();
+
+ if (hasServer.getKdcServer() == null) {
+ errMessage = "Please start the has KDC server.";
+ } else if (!tokenStr.isEmpty() && tokenStr != null) {
+ HasKdcHandler kdcHandler = new HasKdcHandler(hasServer);
+
+ TokenDecoder tokenDecoder = KrbRuntime.getTokenProvider("JWT").createTokenDecoder();
+
+ AuthToken authToken = null;
+ try {
+ authToken = tokenDecoder.decodeFromString(tokenStr);
+ } catch (IOException e) {
+ errMessage = "Failed to decode the token string." + e.getMessage();
+ WebServer.LOG.error(errMessage);
+ }
+ HasServerPlugin tokenPlugin = null;
+ try {
+ tokenPlugin = HasServerPluginRegistry.createPlugin(type);
+ } catch (HasException e) {
+ errMessage = "Fail to get the plugin: " + type + ". " + e.getMessage();
+ WebServer.LOG.error(errMessage);
+ }
+ AuthToken verifiedAuthToken;
+ try {
+ verifiedAuthToken = tokenPlugin.authenticate(authToken);
+ } catch (HasAuthenException e) {
+ errMessage = "Failed to verify auth token: " + e.getMessage();
+ WebServer.LOG.error(errMessage);
+ verifiedAuthToken = null;
+ }
+
+ if (verifiedAuthToken != null) {
+ KrbMessage asRep = kdcHandler.getResponse(verifiedAuthToken,
+ (String) verifiedAuthToken.getAttributes().get("passPhrase"));
+
+ Base64 base64 = new Base64(0);
+ try {
+ m.put("type", tokenPlugin.getLoginType());
+ m.put("success", "true");
+ m.put("krbMessage", base64.encodeToString(asRep.encode()));
+ } catch (IOException e) {
+ errMessage = "Failed to encode KrbMessage." + e.getMessage();
+ WebServer.LOG.error(errMessage);
+ }
+
+ }
+ } else {
+ errMessage = "The token string should not be empty.";
+ WebServer.LOG.error(errMessage);
+ }
+
+ if (errMessage != null) {
+ m.put("success", "false");
+ m.put("krbMessage", errMessage);
+ }
+ try {
+ js = mapper.writeValueAsString(m);
+ } catch (JsonProcessingException e) {
+ WebServer.LOG.error("Failed write values to string." + e.getMessage());
+ }
+ return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
+ }
+ return Response.status(403).entity("HTTPS required.\n").build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/AuthTokenParam.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/AuthTokenParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/AuthTokenParam.java
new file mode 100644
index 0000000..a0273e1
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/AuthTokenParam.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest.param;
+
+public class AuthTokenParam extends StringParam {
+ /**
+ * Parameter name.
+ */
+ public static final String NAME = "authToken";
+ /**
+ * Default parameter value.
+ */
+ public static final String DEFAULT = "";
+
+ private static final StringParam.Domain DOMAIN = new StringParam.Domain(NAME, null);
+
+ /**
+ * Constructor.
+ *
+ * @param str a string representation of the parameter value.
+ */
+ public AuthTokenParam(final String str) {
+ super(DOMAIN, str == null || str.equals(DEFAULT) ? null : str);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/Param.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/Param.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/Param.java
new file mode 100644
index 0000000..4314ae2
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/Param.java
@@ -0,0 +1,123 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest.param;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Base class of parameters.
+ */
+public abstract class Param<T, D extends Param.Domain<T>> {
+ static final String NULL = "null";
+
+ static final Comparator<Param<?, ?>> NAME_CMP = new Comparator<Param<?, ?>>() {
+ @Override
+ public int compare(Param<?, ?> left, Param<?, ?> right) {
+ return left.getName().compareTo(right.getName());
+ }
+ };
+
+ /** Convert the parameters to a sorted String.
+ *
+ * @param separator URI parameter separator character
+ * @param parameters parameters to encode into a string
+ * @return the encoded URI string
+ */
+ public static String toSortedString(final String separator,
+ final Param<?, ?>... parameters) {
+ Arrays.sort(parameters, NAME_CMP);
+ final StringBuilder b = new StringBuilder();
+ try {
+ for (Param<?, ?> p : parameters) {
+ if (p.getValue() != null) {
+ b.append(separator)
+ .append(URLEncoder.encode(p.getName(), "UTF-8"))
+ .append("=")
+ .append(URLEncoder.encode(p.getValueString(), "UTF-8"));
+ }
+ }
+ } catch (UnsupportedEncodingException e) {
+ // Sane systems know about UTF-8, so this should never happen.
+ throw new RuntimeException(e);
+ }
+ return b.toString();
+ }
+
+ /** The domain of the parameter. */
+ final D domain;
+ /** The actual parameter value. */
+ final T value;
+
+ Param(final D domain, final T value) {
+ this.domain = domain;
+ this.value = value;
+ }
+
+ /** @return the parameter value. */
+ public final T getValue() {
+ return value;
+ }
+
+ /** @return the parameter value as a string */
+ public abstract String getValueString();
+
+ /** @return the parameter name. */
+ public abstract String getName();
+
+ @Override
+ public String toString() {
+ return getName() + "=" + value;
+ }
+
+ /** Base class of parameter domains. */
+ abstract static class Domain<T> {
+ /** Parameter name. */
+ final String paramName;
+
+ Domain(final String paramName) {
+ this.paramName = paramName;
+ }
+
+ /** @return the parameter name. */
+ public final String getParamName() {
+ return paramName;
+ }
+
+ /** @return a string description of the domain of the parameter. */
+ public abstract String getDomain();
+
+ /** @return the parameter value represented by the string. */
+ abstract T parse(String str);
+
+ /** Parse the given string.
+ * @return the parameter value represented by the string.
+ */
+ public final T parse(final String varName, final String str) {
+ try {
+ return str != null && str.trim().length() > 0 ? parse(str) : null;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse \"" + str
+ + "\" for the parameter " + varName
+ + ". The value must be in the domain " + getDomain(), e);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/StringParam.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/StringParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/StringParam.java
new file mode 100644
index 0000000..cfdbd17
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/StringParam.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest.param;
+
+import java.util.regex.Pattern;
+
+/**
+ * String parameter.
+ */
+abstract class StringParam extends Param<String, StringParam.Domain> {
+ StringParam(final Domain domain, String str) {
+ super(domain, domain.parse(str));
+ }
+
+ /**
+ * @return the parameter value as a string
+ */
+ @Override
+ public String getValueString() {
+ return value;
+ }
+
+ /**
+ * The domain of the parameter.
+ */
+ static final class Domain extends Param.Domain<String> {
+ /**
+ * The pattern defining the domain; null .
+ */
+ private final Pattern pattern;
+
+ Domain(final String paramName, final Pattern pattern) {
+ super(paramName);
+ this.pattern = pattern;
+ }
+
+ @Override
+ public String getDomain() {
+ return pattern == null ? "<String>" : pattern.pattern();
+ }
+
+ @Override
+ String parse(String str) {
+ if (str != null && pattern != null && !pattern.matcher(str).matches()) {
+ throw new IllegalArgumentException("Invalid value: \"" + str
+ + "\" does not belong to the domain " + getDomain());
+ }
+ return str;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/TypeParam.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/TypeParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/TypeParam.java
new file mode 100644
index 0000000..41b830e
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/TypeParam.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kerby.has.server.web.rest.param;
+
+public class TypeParam extends StringParam {
+
+ /**
+ * Parameter name.
+ */
+ public static final String NAME = "type";
+ /**
+ * Default parameter value.
+ */
+ public static final String DEFAULT = "";
+
+ private static final Domain DOMAIN = new Domain(NAME, null);
+
+ /**
+ * Constructor.
+ *
+ * @param str a string representation of the parameter value.
+ */
+ public TypeParam(final String str) {
+ super(DOMAIN, str == null || str.equals(DEFAULT) ? null : str);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
+
+
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/e51a0ba0/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 6dc0c70..c71beb2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,9 @@
<netty.version>4.0.52.Final</netty.version>
<hadoop.version>3.0.0</hadoop.version>
<bouncycastle.version>1.58</bouncycastle.version>
+ <jersey.container.version>2.17</jersey.container.version>
+ <jersey.version>1.19</jersey.version>
+ <commons-text.version>1.1</commons-text.version>
</properties>
<prerequisites>
[2/2] directory-kerby git commit: Merge remote-tracking branch
'asf/trunk' into trunk
Posted by pl...@apache.org.
Merge remote-tracking branch 'asf/trunk' into trunk
Project: http://git-wip-us.apache.org/repos/asf/directory-kerby/repo
Commit: http://git-wip-us.apache.org/repos/asf/directory-kerby/commit/4410adf9
Tree: http://git-wip-us.apache.org/repos/asf/directory-kerby/tree/4410adf9
Diff: http://git-wip-us.apache.org/repos/asf/directory-kerby/diff/4410adf9
Branch: refs/heads/trunk
Commit: 4410adf91b406013f11ab87c7ce31b2ca34a7c8a
Parents: e51a0ba 5a81d03
Author: plusplusjiajia <ji...@intel.com>
Authored: Fri Jan 5 13:58:32 2018 +0800
Committer: plusplusjiajia <ji...@intel.com>
Committed: Fri Jan 5 13:58:32 2018 +0800
----------------------------------------------------------------------
.../kerb/server/request/KdcRequest.java | 7 +++-
.../kerb/server/request/TgsRequest.java | 34 +++++++++++++-------
2 files changed, 29 insertions(+), 12 deletions(-)
----------------------------------------------------------------------