Skip to content

BC ML-DSA PKCS#12 KeyStore interoperability issue starting with JDK 24+ #2467

Description

@bsanchezb

I'm not sure where is the original problem, but it seems like an interoperability issue exists between BouncyCastle (tested with BC v1.85) and JDK (tested with JDK 24 and 26).

The problem occurs on attempt to access the keys from a PKCS#12 KeyStore loaded with the JDK "SUN" provider on the keys created by "BC" provider.

Here is a test to reproduce the error:

private static final String BC = "BC";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Test
    void test() throws Exception {

        // -----------------------------------------------------------------
        // 1. Generate ML-DSA key pair using Bouncy Castle
        // -----------------------------------------------------------------

        KeyPairGenerator keyPairGenerator =
                KeyPairGenerator.getInstance("ML-DSA-65", BC);

        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        PrivateKey originalPrivateKey = keyPair.getPrivate();

        System.out.println("=== Original BC key ===");
        System.out.println("Private key class     : "
                + originalPrivateKey.getClass().getName());
        System.out.println("Private key algorithm : "
                + originalPrivateKey.getAlgorithm());
        System.out.println("Private key format    : "
                + originalPrivateKey.getFormat());

        System.out.println("Public key class      : "
                + keyPair.getPublic().getClass().getName());
        System.out.println("Public key algorithm  : "
                + keyPair.getPublic().getAlgorithm());

        // -----------------------------------------------------------------
        // 2. Create an X.509 certificate using Bouncy Castle
        // -----------------------------------------------------------------

        X500Name subject = new X500Name("CN=ML-DSA Test");

        Date notBefore = new Date();
        Date notAfter = new Date(
                notBefore.getTime() + 365L * 24 * 60 * 60 * 1000);

        JcaX509v3CertificateBuilder certificateBuilder =
                new JcaX509v3CertificateBuilder(
                        subject,
                        BigInteger.valueOf(System.currentTimeMillis()),
                        notBefore,
                        notAfter,
                        subject,
                        keyPair.getPublic());

        ContentSigner contentSigner =
                new JcaContentSignerBuilder("ML-DSA-65")
                        .setProvider(BC)
                        .build(keyPair.getPrivate());

        X509Certificate certificate =
                new JcaX509CertificateConverter()
                        .setProvider(BC)
                        .getCertificate(
                                certificateBuilder.build(contentSigner));

        System.out.println("=== BC certificate ===");
        System.out.println("Public key class      : "
                + certificate.getPublicKey().getClass().getName());
        System.out.println("Public key algorithm  : "
                + certificate.getPublicKey().getAlgorithm());

        // this succeeds  (Expected : ML-DSA-65 ; Actual : ML-DSA-65)
        assertEquals(keyPair.getPrivate().getAlgorithm(), certificate.getPublicKey().getAlgorithm());

        // -----------------------------------------------------------------
        // 3. Store to KeyStore using BouncyCastle
        // -----------------------------------------------------------------

        char[] password = "test".toCharArray();

        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);

        keyStore.load(null, null);

        keyStore.setKeyEntry(
                "test",
                keyPair.getPrivate(),
                password,
                new Certificate[]{certificate});

        ByteArrayOutputStream output = new ByteArrayOutputStream();

        keyStore.store(output, password);

        byte[] pkcs12 = output.toByteArray();

        // -----------------------------------------------------------------
        // 4. Reload using the JDK SUN implementation
        // -----------------------------------------------------------------

        KeyStore reloaded = KeyStore.getInstance("PKCS12", "SUN");

        reloaded.load(
                new ByteArrayInputStream(pkcs12),
                password);

        // -----------------------------------------------------------------
        // 5. Inspect what the JDK reconstructed
        // -----------------------------------------------------------------

        PrivateKey restoredPrivateKey =
                (PrivateKey) reloaded.getKey("test", password);

        X509Certificate restoredCertificate =
                (X509Certificate) reloaded.getCertificate("test");

        System.out.println("=== After KeyStore reloaded with JDK SUN ===");

        System.out.println("Private key class     : "
                + restoredPrivateKey.getClass().getName());

        System.out.println("Private key algorithm : "
                + restoredPrivateKey.getAlgorithm());

        System.out.println("Private key format    : "
                + restoredPrivateKey.getFormat());

        System.out.println("Public key class      : "
                + restoredCertificate.getPublicKey().getClass().getName());

        System.out.println("Public key algorithm  : "
                + restoredCertificate.getPublicKey().getAlgorithm());

        // This fails (Expected : ML-DSA-65 ; Actual : ML-DSA)
        assertEquals(restoredPrivateKey.getAlgorithm(), restoredCertificate.getPublicKey().getAlgorithm());

    }

The test produces the following output:

=== Original BC key ===
Private key class     : org.bouncycastle.jcajce.provider.asymmetric.mldsa.BCMLDSAPrivateKey
Private key algorithm : ML-DSA-65
Private key format    : PKCS#8
Public key class      : org.bouncycastle.jcajce.provider.asymmetric.mldsa.BCMLDSAPublicKey
Public key algorithm  : ML-DSA-65
=== BC certificate ===
Public key class      : org.bouncycastle.jcajce.provider.asymmetric.mldsa.BCMLDSAPublicKey
Public key algorithm  : ML-DSA-65
=== After KeyStore reloaded with JDK SUN ===
Private key class     : org.bouncycastle.jcajce.provider.asymmetric.mldsa.BCMLDSAPrivateKey
Private key algorithm : ML-DSA-65
Private key format    : PKCS#8
Public key class      : sun.security.x509.NamedX509Key
Public key algorithm  : ML-DSA

And eventually fails in the last assertion, see JDK returns ML-DSA against BC's ML-DSA-65.

When using BC provider on KeyStore re-load, the test succeeds.

What is the issue behind the problem? Can it be BC or JDK bug?

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions