Skip to content

Instantly share code, notes, and snippets.

@dain
Last active October 16, 2023 09:11
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save dain/29ce5c135796c007f9ec88e82ab21822 to your computer and use it in GitHub Desktop.
Save dain/29ce5c135796c007f9ec88e82ab21822 to your computer and use it in GitHub Desktop.
Create Java KeyStore from standard PEM encoded private key and certificate chain files
# To regenerate the test key and certificates
# Generate an RSA private key and convert it to PKCS8 wraped in PEM
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform pem -outform pem -nocrypt -out rsa.key
# Generate a certificate signing request with the private key
openssl req -new -key rsa.key -out rsa.csr
# Sign request with private key
openssl x509 -req -days 10000 -in rsa.csr -signkey rsa.key -out rsa.crt
# Generate an RSA private key and convert it to PKCS8 wraped in PEM
openssl ecparam -name prime256v1 -genkey | openssl pkcs8 -topk8 -inform pem -outform pem -nocrypt -out ec.key
# Generate an DSA private key and convert it to PKCS8 wraped in PEM
openssl dsaparam -genkey 2048 | openssl pkcs8 -topk8 -inform pem -outform pem -nocrypt -out dsa.key
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.CharBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
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.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static javax.crypto.Cipher.DECRYPT_MODE;
public final class PemReader
{
private static final Pattern CERT_PATTERN = Pattern.compile(
"-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header
"([a-z0-9+/=\\r\\n]+)" + // Base64 text
"-+END\\s+.*CERTIFICATE[^-]*-+", // Footer
CASE_INSENSITIVE);
private static final Pattern KEY_PATTERN = Pattern.compile(
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
"([a-z0-9+/=\\r\\n]+)" + // Base64 text
"-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer
CASE_INSENSITIVE);
private PemReader() {}
public static KeyStore loadTrustStore(File certificateChainFile)
throws IOException, GeneralSecurityException
{
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
List<X509Certificate> certificateChain = readCertificateChain(certificateChainFile);
for (X509Certificate certificate : certificateChain) {
X500Principal principal = certificate.getSubjectX500Principal();
keyStore.setCertificateEntry(principal.getName("RFC2253"), certificate);
}
return keyStore;
}
public static KeyStore loadKeyStore(File certificateChainFile, File privateKeyFile, Optional<String> keyPassword)
throws IOException, GeneralSecurityException
{
PKCS8EncodedKeySpec encodedKeySpec = readPrivateKey(privateKeyFile, keyPassword);
PrivateKey key;
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
key = keyFactory.generatePrivate(encodedKeySpec);
}
catch (InvalidKeySpecException ignore) {
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
key = keyFactory.generatePrivate(encodedKeySpec);
}
List<X509Certificate> certificateChain = readCertificateChain(certificateChainFile);
if (certificateChain.isEmpty()) {
throw new CertificateException("Certificate file does not contain any certificates: " + certificateChainFile);
}
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
keyStore.setKeyEntry("key", key, keyPassword.orElse("").toCharArray(), certificateChain.stream().toArray(Certificate[]::new));
return keyStore;
}
private static List<X509Certificate> readCertificateChain(File certificateChainFile)
throws IOException, GeneralSecurityException
{
String contents = readFile(certificateChainFile);
Matcher matcher = CERT_PATTERN.matcher(contents);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
List<X509Certificate> certificates = new ArrayList<>();
int start = 0;
while (matcher.find(start)) {
byte[] buffer = base64Decode(matcher.group(1));
certificates.add((X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(buffer)));
start = matcher.end();
}
return certificates;
}
private static PKCS8EncodedKeySpec readPrivateKey(File keyFile, Optional<String> keyPassword)
throws IOException, GeneralSecurityException
{
String content = readFile(keyFile);
Matcher matcher = KEY_PATTERN.matcher(content);
if (!matcher.find()) {
throw new KeyStoreException("found no private key: " + keyFile);
}
byte[] encodedKey = base64Decode(matcher.group(1));
if (!keyPassword.isPresent()) {
return new PKCS8EncodedKeySpec(encodedKey);
}
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
SecretKey secretKey = keyFactory.generateSecret(new PBEKeySpec(keyPassword.get().toCharArray()));
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
cipher.init(DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters());
return encryptedPrivateKeyInfo.getKeySpec(cipher);
}
private static byte[] base64Decode(String base64)
{
return Base64.getMimeDecoder().decode(base64.getBytes(US_ASCII));
}
private static String readFile(File file)
throws IOException
{
try (Reader reader = new InputStreamReader(new FileInputStream(file), US_ASCII)) {
StringBuilder stringBuilder = new StringBuilder();
CharBuffer buffer = CharBuffer.allocate(2048);
while (reader.read(buffer) != -1) {
buffer.flip();
stringBuilder.append(buffer);
buffer.clear();
}
return stringBuilder.toString();
}
}
}
import org.testng.annotations.Test;
import java.io.File;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
public class TestPemReader
{
@Test
public void testLoadKeyStore()
throws Exception
{
KeyStore keyStore = PemReader.loadKeyStore(getResourceFile("rsa.crt"), getResourceFile("rsa.key"), Optional.empty());
assertCertificateChain(keyStore);
assertNotNull(keyStore.getKey("key", new char[0]));
assertNotNull(keyStore.getCertificate("key"));
}
@Test
public void loadTrustStore()
throws Exception
{
KeyStore keyStore = PemReader.loadTrustStore(getResourceFile("rsa.crt"));
assertCertificateChain(keyStore);
}
private static void assertCertificateChain(KeyStore keyStore)
throws KeyStoreException
{
ArrayList<String> aliases = Collections.list(keyStore.aliases());
assertEquals(aliases.size(), 1);
assertNotNull(keyStore.getCertificate(aliases.get(0)));
}
private static File getResourceFile(String name)
{
URL resource = TestPemReader.class.getClassLoader().getResource(name);
if (resource == null) {
throw new IllegalArgumentException("Resource not found " + name);
}
return new File(resource.getFile());
}
}
@sureshg
Copy link

sureshg commented Apr 24, 2017

Also, the private key generated using the above openssl command is not creating PKCS#8 format

	at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
	at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)

@dain
Copy link
Author

dain commented Aug 30, 2017

@sureshg The code supports an key password, but I'm guessing we would need to strip the header.

@dain
Copy link
Author

dain commented Sep 13, 2017

@sureshg There are several private key formats that openssl will produce. This code only works with simple PKCS8 format, which you can convert most formats to with the following command

cat key_file | openssl pkcs8 -topk8 -inform pem -outform pem -nocrypt

I updated the example instructions above to include this conversion.

Here is some more information on the various formats (best link I could find): https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations

@sureshg
Copy link

sureshg commented Apr 20, 2018

@dain thanks for the info. By the way, these PEM reader/writer classes are super useful. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment