0

I have combined client-certificate-with-com-sun-net-httpserver-httpsserver with simple-java-https-server but I always get the error message

 SSL-Peer could not be verified.

I call setWantClientAuth(true) and verify Authentification by calling

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

The server is running with JDK 1.8 and the client is running on Android. The server Code is:

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();

        }
    }

}

The client code is:

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];
        }
    }
}
0

1 Answer 1

1

'Peer not verified' in a server means that the client didn't send a certificate, which probably means that its signer isn't in your server's truststore. When the server requests the client certificate, it supplies a list of acceptable signers, and the client must not send a certificate that isn't signed by one of those.

Or else the server didn't ask for a client certificate at all. Doesn't apply in this case.

In your case it would be a lot simpler to use needClientAuth, as that will just fail the handshake without you having to get as a far as getPeerCertificates().

NB:

  1. The SSLSession is valid, otherwise you wouldn't have an SSL connection. The only way it becomes invalid is if you call invalidate(), which causes a full re-handshake on the next I/O. You're testing the wrong thing.
  2. Checking for allowed principals is authorization, not authentication.
Sign up to request clarification or add additional context in comments.

3 Comments

As shown in stackoverflow.com/questions/36819113/… needClientAuth has no effect.......
@Hans-MartinGoebel On the contrary. It shows that it closes the connection if the client doesn't supply a certificate. See my comment dated April 25, 2016, 10:19. You can't seriously suppose that a major Java security feature 'has no effect'.
The code works now with proper setting of 'needClientAuth' ,

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.