Restlet javax.net.ssl.SSLHandshakeException: null cert chain
Asked Answered
R

4

12

I am testing SSL communication between client and server locally. So I generated certificate using OpenSSL commands. Added this certificate in cacert file. Also generated .p12 file.

I am using the same .p12 file in server and client. This is the server code

Server server = component.getServers().add(Protocol.HTTPS, port);
Series<Parameter> params = server.getContext().getParameters();

params.add("keystorePath", ".p12 file path");
params.add("keystoreType", "PKCS12");
params.add("needClientAuthentication","true");

component.getDefaultHost().attach("", "/AA"), new AAClass());
component.start();

And this is client code:

Client client = trustAllCerts();
clientResource = new ClientResource(url);
clientResource.setNext(client);
try{
      clientText = clientResource.post"");
 }
 catch(ResourceException e){
    e.printStackTrace();
 }

public Client trustAllCerts() {
    Client client = null;
    try {
        client = new Client(new Context(), Protocol.HTTPS);
        Context context = client.getContext();


        final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        context.getAttributes().put("sslContextFactory", new SslContextFactory() {
            public void init(Series<Parameter> parameters) {

            }

            public SSLContext createSslContext() {
                return sslContext;
            }
        });
        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {                
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }

        });         

        sslContext.init(null, new TrustManager[] { tm }, null);         

    } catch (KeyManagementException e) {
        LOGGER.error("Exception in Key Management" + e);
    } catch (NoSuchAlgorithmException e) {
        LOGGER.error("Exception in Algorithm Used" + e);
    }
    return client;
}

I am getting following exception:

Restlet-1299242, fatal error: 42: null cert chain
javax.net.ssl.SSLHandshakeException: null cert chain
%% Invalidated:  [Session-25, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256]
Restlet-1299242, SEND TLSv1.2 ALERT:  fatal, description = bad_certificate
Restlet-1299242, WRITE: TLSv1.2 Alert, length = 2
Restlet-1299242, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLHandshakeException: null cert chain
Restlet-1299242, called closeInbound()
Restlet-1299242, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
Restlet-1299242, called closeOutbound()
Restlet-1299242, closeOutboundInternal()

I tried to add keystore and truststore using System.setProperty() but it didn't work.

Please help. Thanks in advance.

Rothman answered 10/9, 2015 at 15:42 Comment(2)
How do you create your self-signed certificate? Could you post your steps? And on when you are getting this exception (while server is starting, while client is starting, when client made a request to server, etc)? Have you tried to connect with other rest clients like browsers? If your certificate is up and ready on your server, and if you made a GET request through browser you must get a warning about your certificate provider.Kawasaki
Actually I downloaded certificate from one certificate provider(above example is for local reproducing the issue ), in the download bundle I get one root authority certificate, one intermediate authority certificate, one certificate and one key. I tried all the combination but still getting null cert chain exception. Also I am getting exception when client able to communicate with server. Also in the browser I saw warning about untrusted certificate, so I added the certificate in browser but still get null cert chain issue.Rothman
K
11

First, lets create a JKS formatted keystore. PKCS12 is usually used in browser and on default java applications uses JKS (as far as I know). Java also supports PKCS12 but I do not know exact parameters for it.

Preparing JKS File

Lets look in our PKCS12 file and get the certificate aliases that we want to extract our JKS file.

keytool -list \
        -keystore [*.p12 file] \
        -storepass [password] \
        -storetype PKCS12 \
        -v

Note the aliases you want to export. And now lets create a JKS file.

keytool -keystore [*.jks file path] -genkey -alias client

This will ask bunch of questions. You can fill them as you like. Now, you can export your aliases from *.p12 file to *.jks file.

keytool -importkeystore \
        -srckeystore [*.p12 file path] \
        -srcstoretype pkcs12 \
        -srcalias [alias from first command] \
        -destkeystore [*.jks file path] \
        -deststoretype jks \
        -deststorepass [*.jks file password] \
        -destalias [new alias]

If you do not have any PKCS12 file, or your certificates are in CER, DER or PEM format you can add your certificates to your keystore using the command below.

keytool -import \
        -alias [new alias] \
        -keystore [*.jks file path] \
        -file [*.DER file path]

And please be sure that you imported, your certificate, your certificate provider's certificate (intermediate certificate) and root certificate.

Now you can check that your JKS file contains all the certificates you are needed.

keytool -list \
        -keystore [*.jks file path] \
        -storepass [password] \
        -storetype jks \
        -v

Setting up Server

You can use your JKS file both on client and server side. According to Restlet documentation you can use JKS file like this to provide HTTPS connection.

Server server = component.getServers().add(Protocol.HTTPS, port);  
Series<Parameter> parameters = server.getContext().getParameters();
parameters.add("sslContextFactory","org.restlet.engine.ssl.DefaultSslContextFactory");
parameters.add("keyStorePath", "*.jks file");
parameters.add("keyStorePassword", "password");
parameters.add("keyPassword", "password");
parameters.add("keyStoreType", "JKS");

After that if you check your port from browser you must see a secure sign. Or you can use some online tool(like this one) to check your certificate.

Setting up Client

Now lets look at client side. Since you are developing both side of the application you can use already created JKS file.

Context con = new Context();
Series<Parameter> clParameters = con.getParameters();
clParameters.add("truststorePath", "*.jks file");
clParameters.add("truststorePassword", "password");
clParameters.add("truststoreType", "JKS");
Client restletClient = new Client(con, Protocol.HTTPS);

While testing or in other circumstances, your certificate hostname and your actual hostname may not match. In order to disable hostname checks you can add this block to your application.

static{
    javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
    new javax.net.ssl.HostnameVerifier(){

        public boolean verify(String hostname,
                javax.net.ssl.SSLSession sslSession ) {
            return true ;
        }
    });
}

Some Thoughts

Since I cannot test it on my locale, I am not exactly sure that your client and server JKS file must be the same. You may only need to add your own certificate to your server.jks. SSL and certificates are always tricky for me. I usually get it work after some trial and error. I hope this will help you.

Also, You may also want to consider, using a reverse proxy kind of web server like Apache2 or Nginx. If you want to use them, you must merge your certificates to a single file. If you look at your certificate file you see that each file (your own certificate, intermediate certificate and root certificate) is like this

-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----

You need to simply add one to other to create a merged certificate. And than use that certificate to end SSL on Apache2 or Nginx. This is what I usually do. But on client side you still need to create JKS files.

Kawasaki answered 14/9, 2015 at 18:15 Comment(3)
I tried what you have suggested but still getting same exception.Rothman
@vikasTheJavaDeveloper Do you use your version of client with custom SSLContextFactory and Trustmanager or use my version? Do you use your real certificate chain or with a self-signed certificate? Could you try to run both application with -Djavax.net.debug=all flag, to see certificate requests? And Could you post the logs somewhere (maybe gist) and add link to your question?Kawasaki
JKS is going to be replaced by P12 certificates on java 9+ (info from the key generator).Vector
R
5

I am using the same .p12 file in server and client

This is already a mistake. The client and the server are different identities and should not have the same private key, public key, or certificate.

I suggest you ditch all the OpenSSL stuff and start again with the keytool as follows:

  1. At the server, generate a keypair, and a certificate request; get it signed; import the signer's certificate chain with the -trustcacerts option; and import the signed certificate using the same alias you used when creating the keypair and CSR.
  2. At the client, ditto, but using (of course) a different keystore file.
  3. You're done. Forget about

    • OpenSSL
    • PKCS#12
    • self-signed certificates
    • all forms of trustAllCerts, custom TrustManagers, and custom code of any kind whatsoever
    • using the same keypair/certificate for the server and client
    • importing the server certificate to the client, and vice versa
    • any system properties other than those that identify the javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword
    • setting a password on the keypair or the imported signed certificate.

Steps (1) and (2) are how it is intended to be done. Depart from those and you are in for trouble and strife.

Rudnick answered 17/9, 2015 at 9:47 Comment(2)
I like your answer, but it seems a bit too broad. The issue can probably be solved by updating the p12 file, so starting all over and paying for new certificates seems a bit overkill. Although it would save him a lot of trouble the next time he wants to do it. The openssl dance is still widely documented as the way to go, unfortunately, probably because it's also valid for non-java users.Clynes
@greyfairer There's no evidence in the question that he's paid for anything yet. The question has every appearance of self-signed certifciates, right down to the insecure TrustManager code, which would be pointless otherwise. But even if he has, he is going to have to pay for at least one new certificate anyway, as using the same one for both ends isn't valid, and using OpenSSL in a Java context is just an avoidable PITA for the moment.Rudnick
L
1

One option is to read the p12/pfx-file, get the certificates and use them to programmatically construct KeyStores and TrustStores. If the input is one pfx-file containing a CA root certificate and a related client certificate, the methods shown in the class SslUtils below will let you do that.
There is one caveat though: the default Restlet server (version 2.3.4) will not pickup the certificates send by the client. I did manage to work-around this issue (it is not pretty though), see my answer on this question.

I will focus on configuring the secure connections here, but all source code and a working example is available in the restlet-clientcert Github project. The Github project is a result of me thinking I know what I'm doing, having no luck and no experience with Restlet, but biting the bullet anyway so I can feel a little bit better knowing that I could get this basic stuff to work.

On the server side, use a custom ServerSslContextFactory that programmatically configures the used SSLContext. Register the custom factory with:

ServerSslContextFactory sslCtx = new ServerSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes();
attribs.put("sslContextFactory", sslCtx);

and attach a "guard" to extract the client certificate info:

CertificateAuthenticator guard = new CertificateAuthenticator(server.getContext());
guard.setNext(MyRestlet.class);
component.getDefaultHost().attachDefault(guard);

The ServerSslContextFactory:

public class ServerSslContextFactory extends DefaultSslContextFactory {

    private static final Logger log = LoggerFactory.getLogger(ServerSslContextFactory.class);

    protected DefaultSslContext wrappedCtx;

    public void init(String certFileName, char[] certFilePwd) throws Exception {

        if (log.isDebugEnabled()) {
            log.debug("Loading certificates from [" + certFileName + "] and using " 
                    + (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password.");
        }
        Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI());
        KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd);
        KeyManager[] kms = kmf.getKeyManagers();
        List<X509Certificate> certs = SslUtils.getClientCaCerts(kms);
        TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0));
        TrustManager[] tms = tmf.getTrustManagers();

        super.setNeedClientAuthentication(true);

        SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL);
        ctx.init(kms, tms, null);
        wrappedCtx = (DefaultSslContext) createWrapper(ctx);
    }

    @Override
    public void init(Series<Parameter> parameters) { 
        log.debug("Not using parameters to initialize server SSL Context factory.");
    }

    @Override
    public SSLContext createSslContext() throws Exception {
        return wrappedCtx;
    }

    @Override
    public boolean isNeedClientAuthentication() {

        if (log.isDebugEnabled()) {
            //log.debug("Needing client auth: " + super.isNeedClientAuthentication(), new RuntimeException("trace"));
            log.debug("Needing client auth: " + super.isNeedClientAuthentication());
        }
        return super.isNeedClientAuthentication();
    }

}

On the client side, a similar thing:

ClientSslContextFactory sslCtx = new ClientSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
attribs.put("sslContextFactory", sslCtx);

Also set a hostnameVerifier (as shown in your question) to not verify hostnames.
The ClientSslContextFactory:

public class ClientSslContextFactory extends SslContextFactory {

    private static final Logger log = LoggerFactory.getLogger(ClientSslContextFactory.class);

    protected KeyManager[] kms;
    protected TrustManager[] tms;

    public void init(String certFileName, char[] certFilePwd) throws Exception {

        log.debug("Loading certificates from [" + certFileName + "] and using " 
                + (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password.");
        Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI());
        KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd);
        kms = kmf.getKeyManagers();
        /*
        List<X509Certificate> certs = SslUtils.getClientCaCerts(kms);
        TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0));
        tms = tmf.getTrustManagers();
        */
        tms = new TrustManager[1];
        tms[0] = new TrustServerCertAlways();
    }

    @Override
    public void init(Series<Parameter> parameters) {
        log.debug("Not using parameters to initialize client SSL Context factory.");
    }

    @Override
    public SSLContext createSslContext() throws Exception {

        SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL);
        ctx.init(kms, tms, null);
        return ctx;
    }

    static class TrustServerCertAlways implements X509TrustManager {

        @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
            log.debug("Trusting all client certificates.");
        }

        @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1)   throws CertificateException {
            log.debug("Trusting all server certificates.");
        }

        @Override public X509Certificate[] getAcceptedIssuers() {
            log.debug("No accepted issuers.");
            return null;
        }
    }

}

And finally the SslUtils class containing the "read and reconstruct" methods (full version including "get email-address from certificate" methods is available in the previously mentioned Github project):

import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStore.LoadStoreParameter;
import java.security.cert.X509Certificate;
import java.util.*;

import javax.net.ssl.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SslUtils {

    private static final Logger log = LoggerFactory.getLogger(SslUtils.class);

    /**
     * List of SSL protocols (SSLv3, TLSv1.2, etc.). See also {@link SslUtils#DEFAULT_SSL_PROTOCOL}.
     * <br>Documented at http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
     */
    public static final String[] SSL_PROTOCOLS = new String[] { "SSL", "SSLv2", "SSLv3", "TLS", "TLSv1", "TLSv1.1", "TLSv1.2" };

    /**
     * Default SSL protocol to use ("TLSv1.2").
     */
    public static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2";

    /**
     * Creates a default SSL context with an empty key-store and the default JRE trust-store.
     */
    public static SSLContext createDefaultSslContext() throws Exception {
        return createSslContext(null, null, null, null);
    }
    /**
     * Creates a default SSL socket factory.
     * <br>All system properties related to trust/key-stores are ignored, eveything is done programmatically.
     * This is because the Sun implementation reads the system-properties once and then caches the values.
     * Among other things, this fails the unit tests.
     * <br>For reference, the system properties (again, NOT USED):
     * <br> - javax.net.ssl.trustStore (default cacerts.jks)
     * <br> - javax.net.ssl.trustStorePassword
     * <br>and for client certificate:
     * <br> - javax.net.ssl.keyStore (set to "agent-cert.p12")
     * <br> - javax.net.ssl.keyStoreType (set to "pkcs12")
     * <br> - javax.net.ssl.keyStorePassword
     * <br>See for a discussion:
     * https://mcmap.net/q/99661/-trust-store-vs-key-store-creating-with-keytool
     * <br>See for client certificates in Java:
     * https://mcmap.net/q/102177/-java-https-client-certificate-authentication
     * @param keyStoreFileName The name (ending with pfx) of the file with client certificates.
     * @param trustStoreFileName The name (ending with jks) of the Java KeyStore with trusted (root) certificates.
     * @return null or the SSLContext.
     */
    public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd, 
            Path trustStoreFile, String trustStorePwd) throws Exception {
        return createSslContext(keyStoreFile, keyStorePwd, trustStoreFile, trustStorePwd, DEFAULT_SSL_PROTOCOL);
    }

    /**
     * See {@link #createSslContext(Path, String, Path, String)}.
     * @param sslProtocol a value from {@link #SSL_PROTOCOLS}.
     */
    public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd, 
            Path trustStoreFile, String trustStorePwd, String sslProtocol) throws Exception {

        KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray());
        TrustManagerFactory tmf = loadTrustStore(trustStoreFile, trustStorePwd == null ? null : trustStorePwd.toCharArray());
        //set an Authenticator to generate username and password
        SSLContext ctx = SSLContext.getInstance(sslProtocol);
        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return ctx;
    }

    /**
     * Calls {@link #createSslContextFromClientKeyStore(Path, String, Path, String)} with the {@link #DEFAULT_SSL_PROTOCOL}.
     */
    public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd, 
            String caAlias) throws Exception {
        return createSslContextFromClientKeyStore(keyStoreFile, keyStorePwd, caAlias, DEFAULT_SSL_PROTOCOL);
    }

    /**
     * Creates a SSL context from the given key-store containing a client certificate and a (CA) root certificate.
     * The root certificate is set in the trust-store of the SSL context.  
     * @param keyStoreFileName key-store file name (ending with .pfx).
     * @param keyStorePwd key-store password
     * @param caAlias the alias to use for the CA (root) certificate (e.g. "mycaroot").
     * @param sslProtocol the ssl-protocol (e.g. {@link #DEFAULT_SSL_PROTOCOL}).
     */
    public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd, 
            String caAlias, String sslProtocol) throws Exception {

        KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray());
        List<X509Certificate> certs = getClientCaCerts(kmf.getKeyManagers());
        if (certs.size() < 1) {
            throw new Exception("Cannot find CA (root) certificate in key-managers from key store "  + keyStoreFile.getFileName());
        }
        TrustManagerFactory tmf = createTrustStore(caAlias, certs.get(0));
        SSLContext ctx = SSLContext.getInstance(sslProtocol);
        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return ctx;
    }

    public static KeyManagerFactory loadKeyStore(Path storeFile) throws Exception {
        return loadKeyStore(storeFile, null);
    }

    public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd) throws Exception {
        return loadKeyStore(storeFile, storePwd, null, null);
    }

    public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        KeyManagerFactory kmf = null;
        if (storeFile == null) {
            kmf = loadKeyStore((InputStream)null, storePwd, storeType, algorithm);
        } else {
            try (InputStream storeIn = Files.newInputStream(storeFile)) {
                kmf = loadKeyStore(storeIn, storePwd, storeType, algorithm);
                log.info("Initialized certificate key-store from ["  + storeFile.getFileName() + "]");
            }
        }
        return kmf;
    }

    public static KeyManagerFactory loadKeyStore(InputStream storeIn, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        if (storePwd == null && storeIn != null) {
            storePwd = "changeit".toCharArray();
            log.debug("Using default key store password.");
        }
        if (storeType == null) {
            storeType = "pkcs12";
            log.debug("Using default key store type " + storeType);
        }
        if (algorithm == null) {
            algorithm = KeyManagerFactory.getDefaultAlgorithm(); // "SunX509"
            log.debug("Using default key store algorithm " + algorithm);
        }
        KeyManagerFactory kmf = null;
        KeyStore keyStore = loadStore(storeIn, storePwd, storeType);
        kmf = KeyManagerFactory.getInstance(algorithm);
        kmf.init(keyStore, storePwd);
        if (storeIn == null) {
            log.info("Initialized a default certificate key-store");
        }
        return kmf;
    }

    /**
     * Creates a trust-store with the given CA (root) certificate.
     * @param certAlias the alias for the certificate (e.g. "mycaroot")
     * @param caCert the CA (root) certificate
     * @return an initialized trust manager factory.
     */
    public static TrustManagerFactory createTrustStore(String certAlias, X509Certificate caCert) throws Exception {

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load((LoadStoreParameter)null); // must initialize the key-store
        ks.setCertificateEntry(certAlias, caCert);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        return tmf;
    }

    public static TrustManagerFactory loadTrustStore(Path storeFile) throws Exception {
        return loadTrustStore(storeFile, null);
    }

    public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd) throws Exception {
        return loadTrustStore(storeFile, storePwd, null, null);
    }

    public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        TrustManagerFactory tmf = null;
        if (storeFile == null) {
            tmf = loadTrustStore((InputStream)null, storePwd, storeType, algorithm);
        } else {
            try (InputStream storeIn = Files.newInputStream(storeFile)) {
                tmf = loadTrustStore(storeIn, storePwd, storeType, algorithm);
            }
            log.info("Initialized certificate trust-store from ["  + storeFile.getFileName() + "]");
        }
        return tmf;
    }

    public static TrustManagerFactory loadTrustStore(InputStream storeIn, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        if (storePwd == null && storeIn != null) {
            storePwd = "changeit".toCharArray();
            log.debug("Using default trust store password.");
        }
        if (storeType == null) {
            storeType = KeyStore.getDefaultType();
            log.debug("Using default trust store type " + storeType);
        }
        if (algorithm == null) {
            algorithm = TrustManagerFactory.getDefaultAlgorithm();
            log.debug("Using default trust store algorithm " + algorithm);
        }
        TrustManagerFactory tmf = null;
        KeyStore trustStore = loadStore(storeIn, storePwd, storeType);
        tmf = TrustManagerFactory.getInstance(algorithm);
        tmf.init(trustStore);
        if (storeIn == null) {
            log.info("Initialized a default certificate trust-store");
        }
        return tmf;
    }

    /**
     * Creates a default trust store containing the JRE certificates in {@code JAVA_HOME\lib\security\cacerts.jks}
     * <br>To view loaded certificates call 
     * <br>{@code System.setProperty("javax.net.debug", "ssl,trustmanager");}
     * <br>before calling this method.
     */
    public static TrustManagerFactory createDefaultTrustStore() throws Exception {

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore)null);
        return tmf;
    }

    /**
     * @param in if null, null is returned.
     */
    public static KeyStore loadStore(InputStream in, char[] pwd, String type) throws Exception {

        if (in == null) {
            return null;
        }
        KeyStore ks = KeyStore.getInstance(type);
        ks.load(in, pwd);
        return ks;
    }

    /**
     * Finds any CA (root) certificates present in client certificate chains.
     * <br>Uses {@link #getClientAliases(KeyManager)}
     * @param kms key-managers (from a key-store).
     * @return an empty list or a list containing CA (root) certificates.
     */
    public static List<X509Certificate> getClientCaCerts(KeyManager[] kms) {

        List<X509Certificate> caCerts = new LinkedList<X509Certificate>();
        for (int i = 0; i < kms.length; i++) {
            if (!(kms[i] instanceof X509KeyManager)) {
                continue;
            }
            X509KeyManager km = (X509KeyManager) kms[i];
            List<String> aliases = getClientAliases(km);
            for (String alias: aliases) {
                X509Certificate[] cchain = km.getCertificateChain(alias);
                if (cchain == null || cchain.length < 2) {
                    continue;
                }
                // first certificate in chain is the user certificate
                // last certificate is the CA (root certificate).
                caCerts.add(cchain[cchain.length-1]);
                if (log.isDebugEnabled()) {
                    log.debug("Found 1 root certificate from client certificate alias " + alias);
                }
            }
        }
        return caCerts;
    }

    /**
     * List of key types for client certificate aliases, used in {@link #getAliases(KeyManager)}
     * <br>List is documented at 
     * http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames
     */
    public static final String[] KEY_TYPES = new String[] {"RSA", "DSA", "DH_RSA", "DH_DSA", "EC", "EC_EC", "EC_RSA" };

    /**
     * Searches for client aliases in the given key-manager. 
     * Does nothing when the given key-manager is not an instance of {@link X509KeyManager}. 
     * @return an empty list or a list containing client aliases found in the key-manager.
     */
    public static List<String> getClientAliases(KeyManager keyManager) {

        List<String> aliases = new LinkedList<String>();
        if (keyManager instanceof X509KeyManager) {
            X509KeyManager km = (X509KeyManager) keyManager;
            for (String keyType: KEY_TYPES) {
                String[] kmAliases = km.getClientAliases(keyType, null);
                if (kmAliases != null) {
                    for (String alias: kmAliases) {
                        if (!isEmpty(alias)) {
                            aliases.add(alias);
                        }
                    }
                }
            } // for keytypes
        }
        return aliases;
    }

    /**
     * Sets the default authenticator which can be used for example with http-request that require basic authoriation.
     * <br>See also {@link Authenticator#setDefault(Authenticator)}.
     */
    public static void setDefaultAuthenticator(final String userName, final char[] pwd) throws Exception {

        Authenticator auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(userName, pwd);
            }
        };
        Authenticator.setDefault(auth);
    }

    /**
     * @return true if s is not null and not empty after trimming, false otherwise.
     */
    public static boolean isEmpty(String s) { return (s == null || s.trim().isEmpty()); }

}

On a side-node: Java is transitioning the default keystore type from JKS to PKCS12 (see JEP 229).

Lannielanning answered 17/9, 2015 at 9:39 Comment(0)
C
0

You probably didn't add the full certificate chain in your keystore, and just included the keypair itself. In that case, the client just receives the public key, but it cannot validate if that key can be trusted. The certificate chain is there to be able to check if the signatures on the public key match, and lead up to a trusted certificate authority.

See e.g: Adding certificate chain to p12(pfx) certificate

openssl pkcs12 -in certificate.p12 -out clientcert.pem -nodes -clcerts
openssl x509 -in trusted_ca.cer -inform DER -out trusted_ca.pem
openssl x509 -in root_ca.cer -inform DER -out root_ca.pem
cat clientcert.pem trusted_ca.pem root_ca.pem >> clientcertchain.pem
openssl pkcs12 -export -in clientcertchain.pem -out clientcertchain.pfx

You can do it the java way, too, using e.g. portecle: http://portecle.sourceforge.net/import-ca-reply.html, but you also need to combine the certificate chain in one file to import. Just copy-paste all certificates after one another, starting from your own, and ending with the root CA.

This way, the resulting pfx file can be used on the server to return the certificate chain to the client.

Clynes answered 17/9, 2015 at 10:14 Comment(1)
The keypair is wrapped in a self-signed certificate in the keystore, which is sent if requested and if it meets the criteria expressed by the peer. The peer would certainly not receive just the public key. It receives a certificate chain or nothing. Answer is completely incorrect.Rudnick

© 2022 - 2024 — McMap. All rights reserved.