javasslkeystorepemsslsocketfactory

How to build a SSLSocketFactory from PEM certificate and key without converting to keystore?


I'm given a self-signed client certificate kit that is to be used to access a server via HTTPS. The kit consists of the following PEM files:

  1. client.crt (client certificate)
  2. client.key (client private key)
  3. ca.crt (CA certificate)

One way to solve the task is to generate a Java keystore:

  1. Use openssl to convert client certificate and key to PKCS12 keystore
  2. Use keytool to import CA certificate to the store

... and then use code like the following to build SSLSocketFactory instance:

InputStream stream = new ByteArrayInputStream(pksData);         
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(stream, password);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(
    KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
    TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(keyStore);
TrustManager[] trustManagers = tmfactory.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
sslSocketFactory = sslContext.getSocketFactory();

... which is later used to init http library.

So we obtain a KeyStore, then init KeyManagers and TrustManagers with its help and finally we build SSLSocketFactory instance with them.

The question is: is there a way to avoid that keystore file creation and somehow build SSLSocketFactory starting with PublicKey and Certificate instance (which, for example, can be obtained from PEM files using bouncycastle's PemReader)?


Solution

  • It turned out that a KeyStore instance still has to be built, but it can be done in memory (starting with PEM files as input), without using an intermediate keystore file build with keytool.

    To build that in-memory KeyStore, code like the following may be used:

    private static final String TEMPORARY_KEY_PASSWORD = "changeit";
    
    private KeyStore getKeyStore() throws ConfigurationException {
        try {
            Certificate clientCertificate = loadCertificate(certificatePem);
            PrivateKey privateKey = loadPrivateKey(privateKeyPem);
            Certificate caCertificate = loadCertificate(caPem);
    
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca-cert", caCertificate);
            keyStore.setCertificateEntry("client-cert", clientCertificate);
            keyStore.setKeyEntry("client-key", privateKey, TEMPORARY_KEY_PASSWORD.toCharArray(), new Certificate[]{clientCertificate});
            return keyStore;
        } catch (GeneralSecurityException | IOException e) {
            throw new ConfigurationException("Cannot build keystore", e);
        }
    }
    
    private Certificate loadCertificate(String certificatePem) throws IOException, GeneralSecurityException {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
        final byte[] content = readPemContent(certificatePem);
        return certificateFactory.generateCertificate(new ByteArrayInputStream(content));
    }
    
    private PrivateKey loadPrivateKey(String privateKeyPem) throws IOException, GeneralSecurityException {
        return pemLoadPrivateKeyPkcs1OrPkcs8Encoded(privateKeyPem);
    }
    
    private byte[] readPemContent(String pem) throws IOException {
        final byte[] content;
        try (PemReader pemReader = new PemReader(new StringReader(pem))) {
            final PemObject pemObject = pemReader.readPemObject();
            content = pemObject.getContent();
        }
        return content;
    }
    
    private static PrivateKey pemLoadPrivateKeyPkcs1OrPkcs8Encoded(
            String privateKeyPem) throws GeneralSecurityException, IOException {
        // PKCS#8 format
        final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----";
        final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----";
    
        // PKCS#1 format
        final String PEM_RSA_PRIVATE_START = "-----BEGIN RSA PRIVATE KEY-----";
        final String PEM_RSA_PRIVATE_END = "-----END RSA PRIVATE KEY-----";
    
        if (privateKeyPem.contains(PEM_PRIVATE_START)) { // PKCS#8 format
            privateKeyPem = privateKeyPem.replace(PEM_PRIVATE_START, "").replace(PEM_PRIVATE_END, "");
            privateKeyPem = privateKeyPem.replaceAll("\\s", "");
    
            byte[] pkcs8EncodedKey = Base64.getDecoder().decode(privateKeyPem);
    
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
    
        } else if (privateKeyPem.contains(PEM_RSA_PRIVATE_START)) {  // PKCS#1 format
    
            privateKeyPem = privateKeyPem.replace(PEM_RSA_PRIVATE_START, "").replace(PEM_RSA_PRIVATE_END, "");
            privateKeyPem = privateKeyPem.replaceAll("\\s", "");
    
            DerInputStream derReader = new DerInputStream(Base64.getDecoder().decode(privateKeyPem));
    
            DerValue[] seq = derReader.getSequence(0);
    
            if (seq.length < 9) {
                throw new GeneralSecurityException("Could not parse a PKCS1 private key.");
            }
    
            // skip version seq[0];
            BigInteger modulus = seq[1].getBigInteger();
            BigInteger publicExp = seq[2].getBigInteger();
            BigInteger privateExp = seq[3].getBigInteger();
            BigInteger prime1 = seq[4].getBigInteger();
            BigInteger prime2 = seq[5].getBigInteger();
            BigInteger exp1 = seq[6].getBigInteger();
            BigInteger exp2 = seq[7].getBigInteger();
            BigInteger crtCoef = seq[8].getBigInteger();
    
            RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2,
                    exp1, exp2, crtCoef);
    
            KeyFactory factory = KeyFactory.getInstance("RSA");
    
            return factory.generatePrivate(keySpec);
        }
    
        throw new GeneralSecurityException("Not supported format of a private key");
    }
    

    The idea is taken from Programmatically Obtain KeyStore from PEM