directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From plusplusjia...@apache.org
Subject [1/2] directory-kerby git commit: DIRKRB-676 Add new authentication mechanism Kerberos-based token authentication.
Date Fri, 05 Jan 2018 06:07:33 GMT
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 <jiajia.li@intel.com>
Authored: Fri Jan 5 13:57:10 2018 +0800
Committer: plusplusjiajia <jiajia.li@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>


Mime
View raw message