Copy //PemUtils.java
package com.example.mtls;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
/**
* <h1>PEM utilities.</h1>
* Reference:
* <ul>
* <li>https://milosgarunovic.com/posts/java-mtls-http-client/</li>
* <li>https://egkatzioura.com/2021/10/20/execute-mtls-calls-using-java/</li>
* <li>https://stackoverflow.com/a/41947163</li>
* <li>https://github.com/spotify/docker-client/blob/master/src/main/java/com/spotify/docker/client/DockerCertificates.java#L130</li>
* <li>https://github.com/Hakky54/sslcontext-kickstart</li>
* <li>http://www.javawenti.com/?post=35759</li>
* <li>https://github.com/aws-samples/serverless-mutual-tls/blob/main/software/3-lambda-using-parameter-store/src/main/java/com/amazon/aws/example/AppClient.java#L61</li>
* </ul>
*
* <h2>Dependencies:</h2>
* <ul>
* <li>
* org.bouncycastle » bcprov-jdk15on » 1.70 <br/>
* https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
* </li>
* <li>
* org.bouncycastle » bcpkix-jdk15on » 1.70 <br/>
* https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on
* </li>
* </ul>
*/
public class PemUtils {
private static final Set<String> PRIVATE_KEY_ALGS = new HashSet<String>(Arrays.asList("RSA", "EC"));
private static final Logger LOG = LogManager.getLogger(PemUtils.class);
/**
* Get Key Managers by public certificate and private key
* @param clientPublicCert - The public certificate
* @param clientPrivateKey - The private key
* @param keyStorePassword - The keystore password to be set
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws KeyStoreException
* @throws UnrecoverableKeyException
*/
public static KeyManager[] getKeyManagers(Certificate clientPublicCert, PrivateKey clientPrivateKey,
String keyStorePassword) throws IOException, NoSuchAlgorithmException, CertificateException,
KeyStoreException, UnrecoverableKeyException {
// Create a KeyStore containing public cert and private key
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
char[] password = keyStorePassword.toCharArray();
Certificate[] chain = new Certificate[] { clientPublicCert };
// keyStore.setCertificateEntry("client-cert", clientPublicKey);
keyStore.setKeyEntry("client-key", clientPrivateKey, password, chain);
// Create a KeyManager that trusts the keystore
String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
kmf.init(keyStore, password);
return kmf.getKeyManagers();
}
/**
* Get Trust Managers by CA Certificate
* @param caCert
* @return
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws IOException
*/
public static TrustManager[] getTrustManagers(Certificate caCert)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
// Create a KeyStore containing our trusted CAs
KeyStore trustKeyStore = KeyStore.getInstance("JKS");
trustKeyStore.load(null, null);
trustKeyStore.setCertificateEntry("ca-cert", caCert);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trustKeyStore);
return tmf.getTrustManagers();
}
/**
* Read certificate from PEM content
*/
public static Certificate loadCertificate(String certificatePem) throws IOException, GeneralSecurityException {
final byte[] content = readPemContent(certificatePem);
return loadCertificate(content);
}
/**
* Read certificate from bytes
*/
public static Certificate loadCertificate(byte[] certBytes) throws CertificateException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
return certificateFactory.generateCertificate(new ByteArrayInputStream(certBytes));
}
/**
* Read PEM byte content from string
* @param pem
* @return
* @throws IOException
*/
public static byte[] readPemContent(String pem) throws IOException {
final byte[] content;
PemReader pemReader = new PemReader(new StringReader(pem));
final PemObject pemObject = pemReader.readPemObject();
content = pemObject.getContent();
pemReader.close();
return content;
}
/**
* Read PrivateKey from elliptic curve private key PEM content
* <p>Example usage</p>
* <pre>{@code
* String ecPrivateKeyPem = "-----BEGIN EC PRIVATE KEY-----\n"
* + "MHQCAQEEIDzESrZFmTaOozu2NyiS8LMZGqkHfpSOoI/qA9Lw+d4NoAcGBSuBBAAK\n"
* + "oUQDQgAE7kIqoSQzC/UUXdFdQ9Xvu1Lri7pFfd7xDbQWhSqHaDtj+XY36Z1Cznun\n"
* + "GDxlA0AavdVDuoGXxNQPIed3FxPE3Q==\n"
* + "-----END EC PRIVATE KEY-----\n");
* PrivateKey privateKey = loadEcPrivateKey(ecPrivateKeyPem);
* System.out.println(privateKey);
* }</pre>
*
* Refer: <a>https://github.com/spotify/docker-client/blob/9a7c3b4994be9a55988f821f7177029201ff6aef/src/main/java/com/spotify/docker/client/DockerCertificates.java#L130</a>
*/
public static PrivateKey loadEcPrivateKey(String ecPrivateKeyPem) throws IOException {
// removes the EC PARAMETERS footer and header, if any
int beginIndex = ecPrivateKeyPem.indexOf("-----BEGIN EC PRIVATE KEY-----");
String privateKey = ecPrivateKeyPem.substring(beginIndex);
final Reader reader = new StringReader(privateKey);
final PEMParser pemParser = new PEMParser(reader);
try {
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
PEMDecryptorProvider decryptionProv = new JcePEMDecryptorProviderBuilder().build(null);
Object keyPair = pemParser.readObject();
PrivateKeyInfo keyInfo;
if (keyPair instanceof PEMEncryptedKeyPair) {
PEMKeyPair decryptedKeyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProv);
keyInfo = decryptedKeyPair.getPrivateKeyInfo();
LOG.info("load EC Private Key from PEMEncryptedKeyPair");
} else {
keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
LOG.info("load EC Private Key from PEMKeyPair");
}
return converter.getPrivateKey(keyInfo);
} catch (IOException e) {
throw e;
} finally {
reader.close();
pemParser.close();
}
}
/**
* Read PrivateKey from PKCS#8 private key PEM content using "RSA" algorithm,
* retry with "EC" algorithm if failed to read from "RSA" algorithm
*/
public static PrivateKey loadPkcs8PrivateKey(String privateKeyPem)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// removes PKCS#8 footer, header, carriage return, line breaks
String privateString = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\r", "")
.replaceAll("\n", "");
// adds BouncyCastle Provider
Security.addProvider(new BouncyCastleProvider());
// decode the content using Base64 decoder
byte[] encoded = Base64.getDecoder().decode(privateString);
// Creates a new PKCS8EncodedKeySpec with the given encoded key.
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
// Try generate PrivateKey from RSA and EC algorithm
return tryGeneratePrivateKey(keySpec, PRIVATE_KEY_ALGS);
}
private static PrivateKey tryGeneratePrivateKey(final PKCS8EncodedKeySpec spec, final Set<String> algorithms)
throws InvalidKeySpecException {
KeyFactory kf;
PrivateKey key;
for (final String algorithm : algorithms) {
try {
kf = KeyFactory.getInstance(algorithm);
key = kf.generatePrivate(spec);
LOG.info("Generated private key from spec using the '{}' algorithm", algorithm);
return key;
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
LOG.info("Tried generating private key from spec using the '{}' algorithm", algorithm, e);
}
}
final String error = String.format("Could not generate private key from spec. Tried using %s",
String.join(",", algorithms));
throw new InvalidKeySpecException(error);
}
/**
* Gets a HostVerifier that accepts all connections.
* You should implement your own HostVerifier with custom verify condition if required.
*/
public static HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
/**
* Gets a TrustManager that will accept all certificates presented from the server.
* Usually when using self signed certificates we need to use a TrustManager that will accept them.
* You should generate the TrustManager by the CA certificate or keystore if required.
*/
public static TrustManager[] acceptAllTrustManager = {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
}
}
};
}