TL;DR
keytool
from OpenJDK16 creates PKCS12 keystore files that cannot be read from Java 8, 9, 10 and 11. Is this a bug? How to create a PKCS12 keystore that works with Java 8?
Context
I build a Maven project which produces an executable JAR file that must run on any JRE from version 8 to version 16. That JAR file spawns an HTTPS server (using com.sun.net.httpserver.HttpsServer).
During the build, I use keytool
to generate a key pair and store it in a PKCS12 keystore that is bundled in the JAR (actually, I’m using keytool-maven-plugin):
$ /path/to/jdk16/bin/keytool -genkeypair -keystore /tmp/keystore.p12 -storepass password -storetype PKCS12 -alias https -dname "CN=localhost, OU=My HTTP Server, O=Sentry Software, C=FR" -keypass password -validity 3650 -keyalg RSA -sigalg SHA256withRSA
The Java code uses this automatically-generated keystore to start the HTTPS server:
// initialize the HTTPS server httpsServer = HttpsServer.create(socketAddress, 0); // initialize the keystore KeyStore keyStore = KeyStore.getInstance("PKCS12"); // Load the self-certificate that is bundled with the JAR (see pom.xml) InputStream ksStream = this.getClass().getResourceAsStream("/keystore.p12"); keyStore.load(ksStream, "password".toCharArray()); // Exception here // Rest of the code (only for context purpose) // setup the key manager factory String defaultKeyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(defaultKeyManagerAlgorithm); keyManagerFactory.init(keyStore, "password".toCharArray()); // setup the trust manager factory String defaultTrustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(defaultTrustManagerAlgorithm); trustManagerFactory.init(keyStore); // setup the HTTPS context and parameters SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); // Sets the default SSL configuration (no need for extra code here!) httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
Problem
When the JAR is build with a OpenJDK 16 JDK (and keytool
from OpenJDK 16 is used) and then executed in a Java 8 JRE, we get this exception on keyStore.load()
:
IOException: parseAlgParameters failed: ObjectIdentifier() -- data isn't an object ID (tag = 48)
When the same JAR is executed in OpenJDK 11.0.7+10, we get this exception:
IOException: Integrity check failed: java.security.NoSuchAlgorithmException: Algorithm HmacPBESHA256 not available
However, when the same JAR is executed with OpenJDK 14, 15 or 16, no exception and everything works.
Here’s a table that summarizes the versions of keytool
, and whether the PKCS12 key store created with each version of keytool
can be loaded in various JRE versions:
JRE 8 | JRE 11 | JRE 14 | JRE 16 | |
---|---|---|---|---|
keytool 8 | ✅ | ✅ | ✅ | ✅ |
keytool 11 | ✅ | ✅ | ✅ | ✅ |
keytool 14 | ✅ | ✅ | ✅ | ✅ |
keytool 15 | ✅ | ✅ | ✅ | ✅ |
keytool 16 | ⛔ | ⛔ | ✅ | ✅ |
Questions
Is this a bug in keytool
, or in the KeyStore class?
How to create a PKCS12 key store using OpenJDK16 that will work when loaded with JRE 8?
What is HmacPBESHA256? I haven’t specified this algorithm in my keytool
command line.
Advertisement
Answer
It’s not a bug in keytool
or KeyStore. keytool
in OpenJDK 16 has been improved to use more secure algorithms by default, which are not supported with Java 8 and Java 11 (see JDK-8228481).
2 options to solve the problem:
- Use JKS instead of PKCS12 for the key store
- Use the
-J-Dkeystore.pkcs12.legacy
option withkeytool
to set thekeystore.pkcs12.legacy
system property and force OpenJDK 16’skeytool
to use the older algorithms (which are supported by Java 8 and 11)
For the keytool-maven-plugin Maven plugin, use the below configuration:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>keytool-maven-plugin</artifactId> <version>1.5</version> <executions> <execution> <id>create-https-certificate</id> <phase>generate-resources</phase> <goals> <goal>generateKeyPair</goal> </goals> <configuration> <alias>https</alias> <dname>CN=localhost, OU=${project.name}, O=Sentry Software, C=FR</dname> <keyalg>RSA</keyalg> <keypass>password</keypass> <keystore>${project.build.outputDirectory}/keystore.p12</keystore> <sigalg>SHA256withRSA</sigalg> <skipIfExist>true</skipIfExist> <storepass>password</storepass> <storetype>PKCS12</storetype> <validity>3650</validity> <workingDirectory>${project.build.directory}</workingDirectory> <arguments> <!-- Important! Use this to make sure the PKCS12 keystore can be used --> <!-- with Java 8 and 11 --> <!-- See https://bugs.openjdk.java.net/browse/JDK-8228481 --> <!-- See https://stackoverflow.com/questions/67766268/ --> <argument>-J-Dkeystore.pkcs12.legacy</argument> </arguments> </configuration> </execution> </executions> </plugin>
Note: All credits go to @dave_thompson_085