Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)?

like image 267
Roman Puchkovskiy Avatar asked Mar 08 '17 15:03

Roman Puchkovskiy


People also ask

Is PEM a keystore?

Answer. A PEM encoded file contains a private key or a certificate. PFX is a keystore format used by some applications. A PFX keystore can contain private keys or public keys.

What is PEM file for keystore?

The tool will prompt us for the PKCS#12 KeyStore password and a PEM passphrase for each alias. The PEM passphrase is used to encrypt the resulting private key. keystore. pem will contain all of the keys and certificates from the KeyStore.

How do I convert Cacerts to JKS?

There is no need to convert cacerts to JKS because it is already in that format. One approach to solving this is to find the server. xml file in Tomca'ts conf directory (in the client's server), then find out how the certificates or trust store are specified in the <Connector element, then...


2 Answers

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

like image 60
Roman Puchkovskiy Avatar answered Oct 09 '22 14:10

Roman Puchkovskiy


I dropped earlier a comment on your answer when facing a similar challenge and now I came back to provide an alternative for loading the pem file. I have created a library out of it to make it easier for myself and others, see here: GitHub - SSLContext Kickstart I hope you like it :)

Add the following dependency:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-pem</artifactId>
    <version>7.0.0</version>
</dependency>

The pem files can be loaded with the following snippet:

var keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem");
var trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");

var sslFactory = SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();

var sslContext = sslFactory.getSslContext();
var sslSocketFactory = sslFactory.getSslSocketFactory();

Coming back to your main question, I also discovered that it is not possible to create a SSLSocketFactory without the KeyStores. And an in-memory KeyStore works perfectly as you suggested for this use case.

like image 36
Hakan54 Avatar answered Oct 09 '22 13:10

Hakan54