使用com.sun.net.httpsserver进行客户端证书身份验证

时间:2018-01-25 21:33:41

标签: java android authentication ssl https

我已合并client-certificate-with-com-sun-net-httpserver-httpsserver 使用simple-java-https-server,但我总是收到错误消息

 SSL-Peer could not be verified.

我调用setWantClientAuth(true)并通过调用

验证Authentification
Certificate[] peerCerts = pHttpsExchange.getSSLSession().getPeerCertificates();

服务器使用JDK 1.8运行,客户端在Android上运行。服务器代码是:

package de.org.vnetz;

import java.io.*;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.*;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.net.ssl.SSLContext;
import javax.security.auth.x500.X500Principal;

import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class clsHTTPSServer {
    final static String SERVER_PWD = "xxxxxx";
    final static String KST_SERVER = "server.jks";
    final static String TST_SERVER = "servertrust.jks";
    private static final int PORT = 9999;
    public static class MyHandler implements HttpHandler {

        // whether to use client cert authentication 
        private final boolean useClientCertAuth = true; 
        private List<LdapName> allowedPrincipals = new ArrayList<LdapName>(); 
        private final boolean extendedClientCheck = true; 
        private static final String CLIENTAUTH_OID = "1.3.6.1.5.5.7.3.2"; 


        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "Hallo Natalie!";
            HttpsExchange httpsExchange = (HttpsExchange) t;
            boolean auth;
            try
            {
                checkAuthentication(httpsExchange);
                auth = true;
            }
            catch (Exception ex)
            {
                response = ex.getMessage();
                auth = false;
            }
            boolean res = httpsExchange.getSSLSession().isValid();
            if (res) {
                String qry = httpsExchange.getRequestURI().getQuery();
                if (qry!=null && qry.startsWith("qry=")) {
                    httpsExchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
                    httpsExchange.sendResponseHeaders(200, response.length());
                    OutputStream os = t.getResponseBody();
                    os.write(response.getBytes());
                    os.close();
                }
                else
                {
                    httpsExchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
                    httpsExchange.sendResponseHeaders(200, response.length());
                    OutputStream os = t.getResponseBody();
                    os.write((response + " no query!").getBytes());
                    os.close();
                }
            }
        }

        // Verify https certs if its Https request and we have SSL auth enabled. Will be called before 
        // handling the request 
        protected void checkAuthentication(HttpExchange pHttpExchange) throws SecurityException { 
            // Cast will always work since this handler is only used for Http 
            HttpsExchange httpsExchange = (HttpsExchange) pHttpExchange; 
            if (useClientCertAuth) { 
                checkCertForClientUsage(httpsExchange); 
                checkCertForAllowedPrincipals(httpsExchange); 
            } 
        } 

        // Check the cert's principal against the list of given allowedPrincipals. 
        // If no allowedPrincipals are given than every principal is allowed. 
        // If an empty list as allowedPrincipals is given, no one is allowed to access 
        private void checkCertForClientUsage(HttpsExchange pHttpsExchange) { 
            try {
                String host = pHttpsExchange.getSSLSession().getPeerHost();
                //Principal p = pHttpsExchange.getSSLSession().getPeerPrincipal();
                String pr = pHttpsExchange.getSSLSession().getProtocol();
                Certificate[] peerCerts = pHttpsExchange.getSSLSession().getPeerCertificates(); 
                if (peerCerts != null && peerCerts.length > 0) { 
                    X509Certificate clientCert = (X509Certificate) peerCerts[0]; 

                    // We required that the extended key usage must be present if we are using 
                    // client cert authentication 
                    if (extendedClientCheck && 
                        (clientCert.getExtendedKeyUsage() == null || !clientCert.getExtendedKeyUsage().contains(CLIENTAUTH_OID))) { 
                        throw new SecurityException("No extended key usage available"); 
                    } 
                } 
            } catch (ClassCastException e) { 
                throw new SecurityException("No X509 client certificate"); 
            } catch (CertificateParsingException e) { 
                throw new SecurityException("Can't parse client cert"); 
            } catch (SSLPeerUnverifiedException e) { 
                throw new SecurityException("SSL Peer couldn't be verified"); 
            } 
        } 

        private void checkCertForAllowedPrincipals(HttpsExchange pHttpsExchange) { 
            if (allowedPrincipals != null) { 
                X500Principal certPrincipal; 
                try { 
                    certPrincipal = (X500Principal) pHttpsExchange.getSSLSession().getPeerPrincipal(); 
                    Set<Rdn> certPrincipalRdns = getPrincipalRdns(certPrincipal); 
                    for (LdapName principal : allowedPrincipals) { 
                        for (Rdn rdn : principal.getRdns()) { 
                            if (!certPrincipalRdns.contains(rdn)) { 
                                throw new SecurityException("Principal " + certPrincipal + " not allowed"); 
                            } 
                        } 
                    } 
                } catch (SSLPeerUnverifiedException e) { 
                    throw new SecurityException("SSLPeer unverified"); 
                } catch (ClassCastException e) { 
                    throw new SecurityException("Internal: Invalid Principal class provided " + e); 
                } 
            } 
        } 

        private Set<Rdn> getPrincipalRdns(X500Principal principal) { 
            try { 
                LdapName certAsLdapName =new LdapName(principal.getName()); 
                return new HashSet<Rdn>(certAsLdapName.getRdns()); 
            } catch (InvalidNameException e) { 
                throw new SecurityException("Cannot parse '" + principal + "' as LDAP name"); 
            } 
        } 

    }


    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {

        try {
            // setup the socket address
            InetSocketAddress address = new InetSocketAddress(PORT);

            // initialise the HTTPS server
            HttpsServer httpsServer = HttpsServer.create(address, 0);
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // initialise the keystore
            // char[] password = "password".toCharArray();
            KeyStore ks = KeyStore.getInstance("JKS");
            FileInputStream fis = new FileInputStream(KST_SERVER);// ("testkey.jks");
            ks.load(fis, SERVER_PWD.toCharArray());// password);

            // setup the key manager factory
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, SERVER_PWD.toCharArray());

            // setup the trust manager factory
            // TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            // tmf.init(ks);

            KeyStore ts = KeyStore.getInstance("JKS");
            ts.load(new FileInputStream(TST_SERVER), SERVER_PWD.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(ts);

            // setup the HTTPS context and parameters
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

            SSLParameters sslp = sslContext.getSupportedSSLParameters();
            //sslp.setNeedClientAuth(true);
            sslp.setWantClientAuth(true);


            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
                public void configure(HttpsParameters params) {
                    try {
                        // initialise the SSL context
                        SSLContext c = SSLContext.getDefault();
                        SSLEngine engine = c.createSSLEngine();
                        //params.setNeedClientAuth(true);
                        params.setWantClientAuth(true);
                        params.setCipherSuites(engine.getEnabledCipherSuites());
                        params.setProtocols(engine.getEnabledProtocols());

                        // get the default parameters
                        SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
                        SSLParameters sslParams = sslContext.getDefaultSSLParameters();
                        //sslParams.setNeedClientAuth(true);
                        sslParams.setWantClientAuth(true);

                        params.setSSLParameters(defaultSSLParameters);

                    } catch (Exception ex) {
                        System.out.println("Failed to create HTTPS port");
                    }
                }
            });
            httpsServer.createContext("/test", new MyHandler());
            httpsServer.setExecutor(
                    new ThreadPoolExecutor(4, 80, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000))); // creates
                                                                                                                    // a
                                                                                                                    // default
                                                                                                                    // executor
            httpsServer.start();

        } catch (Exception exception) {
            System.out.println("Failed to create HTTPS server on port " + 62112 + " of localhost");
            exception.printStackTrace();

        }
    }

}

客户端代码是:

package vnetz.de.org.vnetz;

import android.content.Context;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.SocketException;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class clsHTTPS {

    private static final String MYURL = "https://localhost:9999/test?qry=test";
    static String NO_KEYSTORE = "";
    static String UNAUTH_KEYSTORE = "unauthclient.bks"; // Doesn't exist in server trust store, should fail authentication.
    static String AUTH_KEYSTORE = "authclient.bks"; // Exists in server trust store, should pass authentication.
    static String TRUSTSTORE = "clienttrust.bks";
    static String CLIENT_PWD = "xxxxxx";
    private static Context context = null;

    public clsHTTPS(Context context) {
        this.context = context;
    }

    public static void main(String[] args) throws Exception {


    }

    public String connect(String jksFile) {
        try {
            String https_url = MYURL;
            URL url;
            url = new URL(https_url);
            HttpsURLConnection.setDefaultHostnameVerifier(new NullHostNameVerifier());
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(getSSLFactory(jksFile));

            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setUseCaches(false);

            // Print response
            //SSLContext context = SSLContext.getInstance("TLS");
            //context.init(null, new X509TrustManager[]{new NullX509TrustManager()}, new SecureRandom());
            //HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());

            BufferedReader bir = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            StringBuilder sbline = new StringBuilder();
            String line;
            while ((line = bir.readLine()) != null) {
                System.out.println(line);
                sbline.append(line);
            }
            bir.close();
            conn.disconnect();
            return sbline.toString();
        } catch (SSLHandshakeException | SocketException e) {
            System.out.println(e.getMessage());
            System.out.println("");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static SSLSocketFactory getSSLFactory(String jksFile) throws Exception {
        // Create key store
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        KeyManager[] kmfs = null;
        if (jksFile.length() > 0) {
            keyStore.load(context.getAssets().open(jksFile), CLIENT_PWD.toCharArray());
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, CLIENT_PWD.toCharArray());
            kmfs = kmf.getKeyManagers();
        }

        // create trust store (validates the self-signed server!)
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(context.getAssets().open(TRUSTSTORE), CLIENT_PWD.toCharArray());
        TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(
                TrustManagerFactory.getDefaultAlgorithm());
        trustFactory.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmfs, trustFactory.getTrustManagers(), null);

        return sslContext.getSocketFactory();
    }

    private class NullHostNameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String s, SSLSession sslSession)
        {
            return s.equalsIgnoreCase("localhost");
        }
    }

    private class NullX509TrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
        {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
        {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[0];
        }
    }
}

1 个答案:

答案 0 :(得分:1)

服务器中的“未验证对等体”意味着客户端未发送证书,这可能意味着其签名者不在服务器的信任库中。当服务器请求客户端证书时,它会提供可接受的签名者列表,并且客户端不得发送未经其中一个签名的证书。

否则服务器根本不会要求客户端证书。在这种情况下不适用。

在你的情况下,使用needClientAuth要简单得多,因为握手会失败而你不必像getPeerCertificates()那样远。

NB:

  1. SSLSession 有效,否则您将无法建立SSL连接。它变为无效的唯一方法是调用invalidate(),这会导致下一个I / O完全重新握手。你正在测试错误的东西。
  2. 检查允许的主体是授权,而不是身份验证。