15

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.

Bertrand Martin
  • 533
  • 1
  • 3
  • 16
  • 8
    It's a _change_ in keytool exposing a _bug_ (malformed PBKDF2) through 11.0.0 (including all 8) and a _limitation_ in 11.0.1-11. See [the release notes](https://www.oracle.com/java/technologies/javase/16-relnotes.html#JDK-8153005) and either (1) set the security properties as described in the file or (2) create with `-storetype jks` or `jceks` which are deprecated but still supported for compatibility, and alter your code accordingly. – dave_thompson_085 May 31 '21 at 02:45
  • 1
    Or of course use older keytool, or if for some reason you don't want multiple javas, you _can_ also use `openssl req -new -x509 ...; openssl pkcs12 -export ...` (as described in innumerable other Qs) to create a keypair and selfsigned cert in an old-style PKCS12. PS: the server does not need its own cert in its TrustManager, unless you are _also_ using it as a DIY CA to issue certs to clients. – dave_thompson_085 May 31 '21 at 02:52
  • How is this ever going to work? Who is ever going to trust your certificate? How? – user207421 May 31 '21 at 04:15
  • 1
    @dave_thompson_085 Thank you! Here is the [corresponding official issue](https://bugs.openjdk.java.net/browse/JDK-8228481). As this is a self-certificate, I don't need the extra security so I will use the `-J-Dkeystore.pkcs12.legacy` option with `keytool`. – Bertrand Martin May 31 '21 at 09:09
  • @dave_thompson_085 Good point about *TrustManager*, thank you again! – Bertrand Martin May 31 '21 at 09:10
  • 1
    @dave_thompson_085 Please add your answer as a "real" answer so I can mark it as correct, thank you! :-) (or I'll just add the answer myself) – Bertrand Martin Jun 01 '21 at 22:49
  • 2
    I think all of this has been fixed in the most recent versions of all supported lines. For example, Java 1.80_333 seems able to read keystores of this type. – Christopher Schultz Jun 08 '22 at 19:09
  • See also this issue : [JDK-8153005](https://bugs.openjdk.org/browse/JDK-8153005) – Guillaume Husta Nov 18 '22 at 13:00
  • Update: my comment about `openssl pkcs12 -export` was true when written, but since then OpenSSL 3.0 (2021-09) and 3.1 (2023-03) have changed it to also be incompatible by default; in those versions you need `openssl pkcs12 -export -legacy` to be compatible with older versions of Java. @ChristopherSchultz: yes 8u301 (2021-07) up also have the fix. – dave_thompson_085 Apr 30 '23 at 21:25

1 Answers1

14

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 with keytool to set the keystore.pkcs12.legacy system property and force OpenJDK 16's keytool 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

Bertrand Martin
  • 533
  • 1
  • 3
  • 16
  • 4
    I was using openssl to export the certificate, but this answer pointed me into the right direction. Adding the -legacy parameter to the openssl export command line worked. – Klaws Jun 06 '22 at 07:22