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:
One way to solve the task is to generate a Java keystore:
... 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)?
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.
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.
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...
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With