Importing PEM certificate into Java KeyStore programmatically

怎甘沉沦 提交于 2019-12-24 11:17:32

问题


I have a client certificate composed of two files (.crt and .key) which I wish to import to a java KeyStore to then use in a SSLContext to sent HTTP requests with Apache's HTTPClient. However, I can't seem to find a way to do this programmatically, most other questions I've found either point to external tools or aren't fit for my case.

My certificate is encoded with the typical 'BEGIN CERTIFICATE' followed by a Base64 encoded string, and the key with 'BEGIN RSA PRIVATE KEY' and then another Base64 encoded string.

This is what I got so far:

private static SSLContext createSSLContext(File certFile, File keyFile) throws IOException {
    try {
        PEMParser pemParser = new PEMParser(new FileReader(keyFile));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider());
        Object object = pemParser.readObject();
        KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
        PrivateKey privateKey = kp.getPrivate();

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        FileInputStream stream = new FileInputStream(certFile);
        X509Certificate cert = (X509Certificate) certFactory.generateCertificate(stream);

        KeyStore store = KeyStore.getInstance("JKS");
        store.load(null);
        store.setCertificateEntry("certificate", cert);
        store.setKeyEntry("private-key", privateKey, "changeit".toCharArray(), new Certificate[] { cert });

        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(store, "changeit".toCharArray())
                .build();
        return sslContext;
    } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException | UnrecoverableKeyException e) {
        throw new IOException(e);
    }
}

Stacktrace:

java.io.IOException: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format at me.failedshack.ssltest.SSLTest.createSSLContext(SSLTest.java:80) at me.failedshack.ssltest.SSLTest.main(SSLTest.java:31)

Caused by: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:216) at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:390) at me.failedshack.ssltest.SSLTest.createSSLContext(SSLTest.java:62) ... 1 more

Caused by: java.security.InvalidKeyException: invalid key format at java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:330) at java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:355) at java.base/sun.security.rsa.RSAPrivateCrtKeyImpl.(RSAPrivateCrtKeyImpl.java:91) at java.base/sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75) at java.base/sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:315) at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:212) ... 3 more

Sadly I keep getting an InvalidKeyException when generating the private key from the file.


回答1:


A PEM file of type RSA PRIVATE KEY is base64 not binary and more importantly is in PKCS1 format NOT PKCS8 and thus cannot be processed as a PKCS8EncodedKeySpec.

Your choices are:

  • convert the PKCS1 PEM format to PKCS8 (unencrypted) PEM format; read that and drop the header and trailer lines and decode the base64 to binary and put that in PKCS8EncodedKeySpec -- but you say you don't want external tools, plus it's just as easy to convert the privatekey PLUS cert (or chain) into a PKCS12 (DER) which is already a Java keystore and avoid the issue

  • convert the PKCS1 PEM format to PKCS8 (unencrypted) DER format, which you can just read as binary and put in PKCS8EncodedKeySpec -- ditto

  • if the PKCS1 PEM is unencrypted, read and decode it as above to PKCS1 DER then manually construct the PKCS8 (unencrypted) encoding, and use that

  • if the PKCS1 PEM is encrypted, which you can detect because its body contains two 822-style header lines in addition to the base64, you have to replicate OpenSSL's 'legacy' key file decryption, PLUS construct the PKCS8 (unencrypted) encoding

  • if you can use BouncyCastle specifically bcpkix, it can directly read and parse all the PEM variants used by OpenSSL for privatekeys, including decrypting the encrypted ones; however, if you're not already using it, that's an additional jar to install and/or deploy

See one or more of these dupes:
Load certificate to KeyStore (JAVA) (Q constructs PKCS8 using BouncyCastle)
Java: Convert DKIM private key from RSA to DER for JavaMail (my answer constructs PKCS8 'by hand')
How to Load RSA Private Key From File (reads using BouncyCastle)
Read RSA private key of format PKCS1 in JAVA (reads using BouncyCastle)
Get a PrivateKey from a RSA .pem file (decrypt using BC)
Decrypting an OpenSSL PEM Encoded RSA private key with Java? (decrypt manually)
maybe PKCS#1 and PKCS#8 format for RSA private key (background)
and Differences between "BEGIN RSA PRIVATE KEY" and "BEGIN PRIVATE KEY" (background)



来源:https://stackoverflow.com/questions/51352762/importing-pem-certificate-into-java-keystore-programmatically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!