From 67e9752ac4e544134e2451fcb2d917d6f44492dd Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Wed, 30 Apr 2025 11:38:44 -0400 Subject: [PATCH 1/6] the fix --- .../com/sun/crypto/provider/ML_KEM.java | 20 +- .../com/sun/crypto/provider/ML_KEM_Impls.java | 123 +++-- .../sun/security/pkcs/NamedPKCS8Key.java | 83 ++-- .../classes/sun/security/provider/ML_DSA.java | 33 ++ .../sun/security/provider/ML_DSA_Impls.java | 125 ++++- .../sun/security/provider/NamedKEM.java | 51 +- .../security/provider/NamedKeyFactory.java | 77 +-- .../provider/NamedKeyPairGenerator.java | 79 ++- .../sun/security/provider/NamedSignature.java | 47 +- .../classes/sun/security/util/KeyUtil.java | 140 ++++++ .../sun/security/x509/NamedX509Key.java | 5 +- .../share/conf/security/java.security | 42 ++ .../sun/security/provider/acvp/Launcher.java | 4 + .../security/provider/acvp/ML_DSA_Test.java | 14 +- .../security/provider/acvp/ML_KEM_Test.java | 19 +- .../provider/{ => named}/NamedEdDSA.java | 39 +- .../{ => named}/NamedKeyFactoryTest.java | 57 ++- .../security/provider/named/NamedKeys.java | 103 ++++ .../security/provider/pqc/BadPrivateKeys.java | 460 ++++++++++++++++++ .../security/provider/pqc/SeedOrExpanded.java | 140 ++++++ test/lib/jdk/test/lib/process/Proc.java | 11 +- 21 files changed, 1437 insertions(+), 235 deletions(-) rename test/jdk/sun/security/provider/{ => named}/NamedEdDSA.java (84%) rename test/jdk/sun/security/provider/{ => named}/NamedKeyFactoryTest.java (85%) create mode 100644 test/jdk/sun/security/provider/named/NamedKeys.java create mode 100644 test/jdk/sun/security/provider/pqc/BadPrivateKeys.java create mode 100644 test/jdk/sun/security/provider/pqc/SeedOrExpanded.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index b45b655e1f3e0..d5f682a7e4e57 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -498,7 +498,7 @@ protected Object checkPrivateKey(byte[] sk) throws InvalidKeyException { /* Main internal algorithms from Section 6 of specification */ - protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { + protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d_z) { MessageDigest mlKemH; try { mlKemH = MessageDigest.getInstance(HASH_H_NAME); @@ -508,7 +508,8 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { } //Generate K-PKE keys - var kPkeKeyPair = generateK_PkeKeyPair(kem_d); + //The 1st 32-byte `d` is used in K-PKE key pair generation + var kPkeKeyPair = generateK_PkeKeyPair(kem_d_z); //encaps key = kPke encryption key byte[] encapsKey = kPkeKeyPair.publicKey.keyBytes; @@ -527,7 +528,8 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { // This should never happen. throw new RuntimeException(e); } - System.arraycopy(kem_z, 0, decapsKey, + // The 2nd 32-byte `z` is copied into decapsKey + System.arraycopy(kem_d_z, 32, decapsKey, kPkePrivateKey.length + encapsKey.length + 32, 32); return new ML_KEM_KeyPair( @@ -535,6 +537,12 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { new ML_KEM_DecapsulationKey(decapsKey)); } + public byte[] privKeyToPubKey(byte[] decapsKey) { + int pkLen = (mlKem_k * ML_KEM_N * 12) / 8 + 32 /* rho */; + int skLen = (mlKem_k * ML_KEM_N * 12) / 8; + return Arrays.copyOfRange(decapsKey, skLen, skLen + pkLen); + } + protected ML_KEM_EncapsulateResult encapsulate( ML_KEM_EncapsulationKey encapsulationKey, byte[] randomMessage) { MessageDigest mlKemH; @@ -648,10 +656,12 @@ private K_PKE_KeyPair generateK_PkeKeyPair(byte[] seed) { throw new RuntimeException(e); } - mlKemG.update(seed); + // Note: only the 1st 32-byte in the seed is used + mlKemG.update(seed, 0, 32); mlKemG.update((byte)mlKem_k); var rhoSigma = mlKemG.digest(); + mlKemG.reset(); var rho = Arrays.copyOfRange(rhoSigma, 0, 32); var sigma = Arrays.copyOfRange(rhoSigma, 32, 64); Arrays.fill(rhoSigma, (byte)0); diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java index 2ce5b3324e76a..c0340d4b3ad0a 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,9 +26,12 @@ package com.sun.crypto.provider; import sun.security.jca.JCAUtil; +import sun.security.pkcs.NamedPKCS8Key; import sun.security.provider.NamedKEM; import sun.security.provider.NamedKeyFactory; import sun.security.provider.NamedKeyPairGenerator; +import sun.security.util.KeyUtil; +import sun.security.x509.NamedX509Key; import java.security.*; import java.util.Arrays; @@ -37,6 +40,20 @@ public final class ML_KEM_Impls { + private static final int SEED_LEN = 64; + + public static byte[] seedToExpanded(String pname, byte[] seed) { + return new ML_KEM(pname).generateKemKeyPair(seed) + .decapsulationKey() + .keyBytes(); + } + + public static NamedX509Key privKeyToPubKey(NamedPKCS8Key npk) { + return new NamedX509Key(npk.getAlgorithm(), + npk.getParams().getName(), + new ML_KEM(npk.getParams().getName()).privKeyToPubKey(npk.getExpanded())); + } + public sealed static class KPG extends NamedKeyPairGenerator permits KPG2, KPG3, KPG5 { @@ -50,25 +67,25 @@ protected KPG(String pname) { } @Override - protected byte[][] implGenerateKeyPair(String name, SecureRandom random) { - byte[] seed = new byte[32]; + protected byte[][] implGenerateKeyPair(String pname, SecureRandom random) { + byte[] seed = new byte[SEED_LEN]; var r = random != null ? random : JCAUtil.getDefSecureRandom(); r.nextBytes(seed); - byte[] z = new byte[32]; - r.nextBytes(z); - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); ML_KEM.ML_KEM_KeyPair kp; + kp = mlKem.generateKemKeyPair(seed); + var expanded = kp.decapsulationKey().keyBytes(); + try { - kp = mlKem.generateKemKeyPair(seed, z); + return new byte[][]{ + kp.encapsulationKey().keyBytes(), + KeyUtil.writeToChoices(pname, "mlkem", seed, expanded, null), + expanded + }; } finally { - Arrays.fill(seed, (byte)0); - Arrays.fill(z, (byte)0); + Arrays.fill(seed, (byte) 0); } - return new byte[][] { - kp.encapsulationKey().keyBytes(), - kp.decapsulationKey().keyBytes() - }; } } @@ -94,8 +111,56 @@ public sealed static class KF extends NamedKeyFactory permits KF2, KF3, KF5 { public KF() { super("ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"); } - public KF(String name) { - super("ML-KEM", name); + public KF(String pname) { + super("ML-KEM", pname); + } + + @Override + protected byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException { + var parts = KeyUtil.splitChoices(SEED_LEN, input); + if (parts[0] != null && parts[1] != null) { + var calculated = seedToExpanded(pname, parts[0]); + if (!Arrays.equals(parts[1], calculated)) { + throw new InvalidKeyException("seed and expandedKey do not match"); + } + Arrays.fill(calculated, (byte)0); + } + try { + if (parts[1] != null) { + return parts[1]; + } + return seedToExpanded(pname, parts[0]); + } finally { + if (parts[0] != null) { + Arrays.fill(parts[0], (byte)0); + } + } + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + var nk = toNamedKey(key); + if (nk instanceof NamedPKCS8Key npk) { + var parts = KeyUtil.splitChoices(SEED_LEN, npk.getRawBytes()); + var encoding = KeyUtil.writeToChoices(npk.getParams().getName(), + "mlkem", parts[0], parts[1], + ML_KEM_Impls::seedToExpanded); + if (parts[0] != null) Arrays.fill(parts[0], (byte)0); + if (parts[1] != null) Arrays.fill(parts[1], (byte)0); + if (encoding == null) { + throw new InvalidKeyException("key contains not enough info to translate"); + } + nk = new NamedPKCS8Key( + npk.getAlgorithm(), + npk.getParams().getName(), + encoding, + npk.getExpanded().clone()); + if (npk != key) { + npk.destroy(); + } + } + return nk; } } @@ -121,14 +186,14 @@ public sealed static class K extends NamedKEM permits K2, K3, K5 { private static final int SEED_SIZE = 32; @Override - protected byte[][] implEncapsulate(String name, byte[] encapsulationKey, + protected byte[][] implEncapsulate(String pname, byte[] encapsulationKey, Object ek, SecureRandom secureRandom) { byte[] randomBytes = new byte[SEED_SIZE]; var r = secureRandom != null ? secureRandom : JCAUtil.getDefSecureRandom(); r.nextBytes(randomBytes); - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); ML_KEM.ML_KEM_EncapsulateResult mlKemEncapsulateResult = null; try { mlKemEncapsulateResult = mlKem.encapsulate( @@ -145,49 +210,49 @@ protected byte[][] implEncapsulate(String name, byte[] encapsulationKey, } @Override - protected byte[] implDecapsulate(String name, byte[] decapsulationKey, + protected byte[] implDecapsulate(String pname, byte[] decapsulationKey, Object dk, byte[] cipherText) throws DecapsulateException { - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); var kpkeCipherText = new ML_KEM.K_PKE_CipherText(cipherText); return mlKem.decapsulate(new ML_KEM.ML_KEM_DecapsulationKey( decapsulationKey), kpkeCipherText); } @Override - protected int implSecretSize(String name) { + protected int implSecretSize(String pname) { return ML_KEM.SECRET_SIZE; } @Override - protected int implEncapsulationSize(String name) { - ML_KEM mlKem = new ML_KEM(name); + protected int implEncapsulationSize(String pname) { + ML_KEM mlKem = new ML_KEM(pname); return mlKem.getEncapsulationSize(); } @Override - protected Object implCheckPublicKey(String name, byte[] pk) + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); return mlKem.checkPublicKey(pk); } @Override - protected Object implCheckPrivateKey(String name, byte[] sk) + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); return mlKem.checkPrivateKey(sk); } public K() { - super("ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"); + super("ML-KEM", new KF(), "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"); } - public K(String name) { - super("ML-KEM", name); + public K(String pname) { + super("ML-KEM", new KF(pname), pname); } } diff --git a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java index 88a2909cfac1c..0d16dd7d8a626 100644 --- a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java +++ b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,8 @@ package sun.security.pkcs; -import sun.security.util.DerInputStream; -import sun.security.util.DerValue; import sun.security.x509.AlgorithmId; -import javax.security.auth.DestroyFailedException; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; @@ -39,6 +36,7 @@ import java.security.ProviderException; import java.security.spec.NamedParameterSpec; import java.util.Arrays; +import java.util.Objects; /// Represents a private key from an algorithm family that is specialized /// with a named parameter set. @@ -50,6 +48,14 @@ /// identifier in the PKCS #8 encoding of the key is always a single OID derived /// from the parameter set name. /// +/// Besides the existing [PKCS8Key#key] field, this class contains an +/// expanded format stored in [#expanded]. While `key` always represents +/// the format used for encoding, the expanded format is always used +/// in computations. The expanded format must be self-sufficient for +/// cryptographic computations without requiring the encoding format. +/// +/// Both fields must be present. +/// /// @see sun.security.provider.NamedKeyPairGenerator public final class NamedPKCS8Key extends PKCS8Key { @Serial @@ -57,42 +63,47 @@ public final class NamedPKCS8Key extends PKCS8Key { private final String fname; private final transient NamedParameterSpec paramSpec; - private final byte[] rawBytes; + private final transient byte[] expanded; private transient boolean destroyed = false; - /// Ctor from family name, parameter set name, raw key bytes. - /// Key bytes won't be cloned, caller must relinquish ownership - public NamedPKCS8Key(String fname, String pname, byte[] rawBytes) { + /// Creates a `NamedPKCS8Key` from raw key bytes. + /// + /// `encoded` and `expanded` won't be cloned, caller + /// must relinquish ownership. + /// + /// @param fname family name + /// @param pname parameter set name + /// @param encoded raw key bytes, not null + /// @param expanded expanded key format, not null + public NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded) { this.fname = fname; this.paramSpec = new NamedParameterSpec(pname); + this.expanded = Objects.requireNonNull(expanded); + this.key = Objects.requireNonNull(encoded); try { this.algid = AlgorithmId.get(pname); } catch (NoSuchAlgorithmException e) { throw new ProviderException(e); } - this.rawBytes = rawBytes; - - DerValue val = new DerValue(DerValue.tag_OctetString, rawBytes); - try { - this.key = val.toByteArray(); - } finally { - val.clear(); - } } - /// Ctor from family name, and PKCS #8 bytes - public NamedPKCS8Key(String fname, byte[] encoded) throws InvalidKeyException { + /// Creates a `NamedPKCS8Key` from family name and PKCS #8 encoding. + /// + /// @param fname family name + /// @param encoded PKCS #8 encoding. It is copied so caller can modify + /// it after the method call. + /// @param expander a function that is able to calculate the expanded + /// format from the encoding format inside `encoded`.The + /// ownership of the result is fully granted to this object. + public NamedPKCS8Key(String fname, byte[] encoded, Expander expander) + throws InvalidKeyException { super(encoded); this.fname = fname; - try { - paramSpec = new NamedParameterSpec(algid.getName()); - if (algid.getEncodedParams() != null) { - throw new InvalidKeyException("algorithm identifier has params"); - } - rawBytes = new DerInputStream(key).getOctetString(); - } catch (IOException e) { - throw new InvalidKeyException("Cannot parse input", e); + this.expanded = expander.expand(algid.getName(), this.key); + paramSpec = new NamedParameterSpec(algid.getName()); + if (algid.getEncodedParams() != null) { + throw new InvalidKeyException("algorithm identifier has params"); } } @@ -104,9 +115,15 @@ public String toString() { } /// Returns the reference to the internal key. Caller must not modify - /// the content or keep a reference. + /// the content or pass the reference to untrusted application code. public byte[] getRawBytes() { - return rawBytes; + return key; + } + + /// Returns the reference to the key in expanded format. Caller must not + /// modify the content or pass the reference to untrusted application code. + public byte[] getExpanded() { + return expanded; } @Override @@ -127,9 +144,9 @@ private void readObject(ObjectInputStream stream) } @Override - public void destroy() throws DestroyFailedException { - Arrays.fill(rawBytes, (byte)0); + public void destroy() { Arrays.fill(key, (byte)0); + Arrays.fill(expanded, (byte)0); if (encodedKey != null) { Arrays.fill(encodedKey, (byte)0); } @@ -140,4 +157,10 @@ public void destroy() throws DestroyFailedException { public boolean isDestroyed() { return destroyed; } + + /// Expands from encoding format to expanded format. + public interface Expander { + /// The expand method. + byte[] expand(String pname, byte[] input) throws InvalidKeyException; + } } diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA.java b/src/java.base/share/classes/sun/security/provider/ML_DSA.java index ff25eb527efdb..bf8d760a857df 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA.java @@ -568,6 +568,39 @@ public ML_DSA_KeyPair generateKeyPairInternal(byte[] randomBytes) { return new ML_DSA_KeyPair(sk, pk); } + public ML_DSA_PublicKey privKeyToPubKey(ML_DSA_PrivateKey sk) { + //Sample A + int[][][] keygenA = generateA(sk.rho); //A is in NTT domain + + //Compute t and tr + mlDsaVectorNtt(sk.s1); //s1 now in NTT domain + int[][] As1 = new int[mlDsa_k][ML_DSA_N]; + matrixVectorPointwiseMultiply(As1, keygenA, sk.s1); + mlDsaVectorInverseNtt(sk.s1); //take s1 out of NTT domain + + mlDsaVectorInverseNtt(As1); + int[][] t = vectorAddPos(As1, sk.s2); + int[][] t0 = new int[mlDsa_k][ML_DSA_N]; + int[][] t1 = new int[mlDsa_k][ML_DSA_N]; + power2Round(t, t0, t1); + if (!Arrays.deepEquals(t0, sk.t0)) { + throw new IllegalArgumentException("t0 does not patch"); + } + + var crHash = new SHAKE256(TR_LEN); + + ML_DSA_PublicKey pk = new ML_DSA_PublicKey(sk.rho, t1); + byte[] publicKeyBytes = pkEncode(pk); + crHash.update(publicKeyBytes); + byte[] tr = crHash.digest(); + if (!Arrays.equals(tr, sk.tr)) { + throw new IllegalArgumentException("tr does not patch"); + } + + //Encode PK and SK + return new ML_DSA_PublicKey(sk.rho, t1); + } + public ML_DSA_Signature signInternal(byte[] message, byte[] rnd, byte[] skBytes) { //Decode private key and initialize hash function ML_DSA_PrivateKey sk = skDecode(skBytes); diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java index dffe7c5cdb184..aa6c870edb0ca 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,35 @@ package sun.security.provider; import sun.security.jca.JCAUtil; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.util.KeyUtil; +import sun.security.x509.NamedX509Key; + import java.security.*; import java.security.SecureRandom; import java.util.Arrays; public class ML_DSA_Impls { + private static final int SEED_LEN = 32; + + public static byte[] seedToExpanded(String pname, byte[] seed) { + var impl = new ML_DSA(name2int(pname)); + var sk = impl.generateKeyPairInternal(seed).privateKey(); + try { + return impl.skEncode(sk); + } finally { + sk.destroy(); + } + } + + public static NamedX509Key privKeyToPubKey(NamedPKCS8Key npk) { + var dsa = new ML_DSA(name2int(npk.getParams().getName())); + return new NamedX509Key(npk.getAlgorithm(), + npk.getParams().getName(), + dsa.pkEncode(dsa.privKeyToPubKey(dsa.skDecode(npk.getExpanded())))); + } + public enum Version { DRAFT, FINAL } @@ -43,16 +66,16 @@ public enum Version { // --add-exports java.base/sun.security.provider=ALL-UNNAMED public static Version version = Version.FINAL; - static int name2int(String name) { - if (name.endsWith("44")) { + static int name2int(String pname) { + if (pname.endsWith("44")) { return 2; - } else if (name.endsWith("65")) { + } else if (pname.endsWith("65")) { return 3; - } else if (name.endsWith("87")) { + } else if (pname.endsWith("87")) { return 5; } else { // should not happen - throw new ProviderException("Unknown name " + name); + throw new ProviderException("Unknown name " + pname); } } @@ -69,20 +92,24 @@ public KPG(String pname) { } @Override - protected byte[][] implGenerateKeyPair(String name, SecureRandom sr) { - byte[] seed = new byte[32]; - var r = sr != null ? sr : JCAUtil.getDefSecureRandom(); + protected byte[][] implGenerateKeyPair(String pname, SecureRandom random) { + byte[] seed = new byte[SEED_LEN]; + var r = random != null ? random : JCAUtil.getDefSecureRandom(); r.nextBytes(seed); - ML_DSA mlDsa = new ML_DSA(name2int(name)); + + ML_DSA mlDsa = new ML_DSA(name2int(pname)); ML_DSA.ML_DSA_KeyPair kp = mlDsa.generateKeyPairInternal(seed); + var expanded = mlDsa.skEncode(kp.privateKey()); + try { return new byte[][]{ mlDsa.pkEncode(kp.publicKey()), - mlDsa.skEncode(kp.privateKey()) + KeyUtil.writeToChoices(pname, "mldsa", seed, expanded, null), + expanded }; } finally { kp.privateKey().destroy(); - Arrays.fill(seed, (byte)0); + Arrays.fill(seed, (byte) 0); } } } @@ -109,8 +136,56 @@ public sealed static class KF extends NamedKeyFactory permits KF2, KF3, KF5 { public KF() { super("ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"); } - public KF(String name) { - super("ML-DSA", name); + public KF(String pname) { + super("ML-DSA", pname); + } + + @Override + protected byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException { + var parts = KeyUtil.splitChoices(SEED_LEN, input); + if (parts[0] != null && parts[1] != null) { + var calculated = seedToExpanded(pname, parts[0]); + if (!Arrays.equals(parts[1], calculated)) { + throw new InvalidKeyException("seed and expandedKey do not match"); + } + Arrays.fill(calculated, (byte)0); + } + try { + if (parts[1] != null) { + return parts[1]; + } + return seedToExpanded(pname, parts[0]); + } finally { + if (parts[0] != null) { + Arrays.fill(parts[0], (byte)0); + } + } + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + var nk = toNamedKey(key); + if (nk instanceof NamedPKCS8Key npk) { + var parts = KeyUtil.splitChoices(SEED_LEN, npk.getRawBytes()); + var encoding = KeyUtil.writeToChoices(npk.getParams().getName(), + "mldsa", parts[0], parts[1], + ML_DSA_Impls::seedToExpanded); + if (parts[0] != null) Arrays.fill(parts[0], (byte)0); + if (parts[1] != null) Arrays.fill(parts[1], (byte)0); + if (encoding == null) { + throw new InvalidKeyException("key contains not enough info to translate"); + } + nk = new NamedPKCS8Key( + npk.getAlgorithm(), + npk.getParams().getName(), + encoding, + npk.getExpanded().clone()); + if (npk != key) { + npk.destroy(); + } + } + return nk; } } @@ -134,16 +209,16 @@ public KF5() { public sealed static class SIG extends NamedSignature permits SIG2, SIG3, SIG5 { public SIG() { - super("ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"); + super("ML-DSA", new KF(), "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"); } - public SIG(String name) { - super("ML-DSA", name); + public SIG(String pname) { + super("ML-DSA", new KF(pname), pname); } @Override - protected byte[] implSign(String name, byte[] skBytes, + protected byte[] implSign(String pname, byte[] skBytes, Object sk2, byte[] msg, SecureRandom sr) { - var size = name2int(name); + var size = name2int(pname); var r = sr != null ? sr : JCAUtil.getDefSecureRandom(); byte[] rnd = new byte[32]; r.nextBytes(rnd); @@ -160,10 +235,10 @@ protected byte[] implSign(String name, byte[] skBytes, } @Override - protected boolean implVerify(String name, byte[] pkBytes, + protected boolean implVerify(String pname, byte[] pkBytes, Object pk2, byte[] msg, byte[] sigBytes) throws SignatureException { - var size = name2int(name); + var size = name2int(pname); var mlDsa = new ML_DSA(size); if (version == Version.FINAL) { // FIPS 204 Algorithm 3 ML-DSA.Verify prepend {0, len(ctx)} @@ -176,18 +251,18 @@ protected boolean implVerify(String name, byte[] pkBytes, } @Override - protected Object implCheckPublicKey(String name, byte[] pk) + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { - ML_DSA mlDsa = new ML_DSA(name2int(name)); + ML_DSA mlDsa = new ML_DSA(name2int(pname)); return mlDsa.checkPublicKey(pk); } @Override - protected Object implCheckPrivateKey(String name, byte[] sk) + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { - ML_DSA mlDsa = new ML_DSA(name2int(name)); + ML_DSA mlDsa = new ML_DSA(name2int(pname)); return mlDsa.checkPrivateKey(sk); } } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKEM.java b/src/java.base/share/classes/sun/security/provider/NamedKEM.java index 2731b3460af3b..61ae66cecc5e6 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKEM.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKEM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,6 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.NamedParameterSpec; import java.util.Arrays; -import java.util.Objects; /// A base class for all `KEM` implementations that can be /// configured with a named parameter set. See [NamedKeyPairGenerator] @@ -51,12 +50,15 @@ public abstract class NamedKEM implements KEMSpi { private final String fname; // family name private final String[] pnames; // allowed parameter set name (at least one) + private final NamedKeyFactory fac; /// Creates a new `NamedKEM` object. /// /// @param fname the family name + /// @param fac the `KeyFactory` used to translate foreign keys and + /// perform key validation /// @param pnames the standard parameter set names, at least one is needed. - protected NamedKEM(String fname, String... pnames) { + protected NamedKEM(String fname, NamedKeyFactory fac, String... pnames) { if (fname == null) { throw new AssertionError("fname cannot be null"); } @@ -65,6 +67,7 @@ protected NamedKEM(String fname, String... pnames) { } this.fname = fname; this.pnames = pnames; + this.fac = fac; } @Override @@ -76,8 +79,7 @@ public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, "The " + fname + " algorithm does not take any parameters"); } // translate also check the key - var nk = (NamedX509Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(publicKey); + var nk = (NamedX509Key) fac.toNamedKey(publicKey); var pk = nk.getRawBytes(); return getKeyConsumerImpl(this, nk.getParams(), pk, implCheckPublicKey(nk.getParams().getName(), pk), secureRandom); @@ -92,16 +94,15 @@ public DecapsulatorSpi engineNewDecapsulator( "The " + fname + " algorithm does not take any parameters"); } // translate also check the key - var nk = (NamedPKCS8Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(privateKey); - var sk = nk.getRawBytes(); + var nk = (NamedPKCS8Key) fac.toNamedKey(privateKey); + var sk = nk.getExpanded(); return getKeyConsumerImpl(this, nk.getParams(), sk, implCheckPrivateKey(nk.getParams().getName(), sk), null); } // We don't have a flag on whether key is public key or private key. // The correct method should always be called. - private record KeyConsumerImpl(NamedKEM kem, String name, int sslen, + private record KeyConsumerImpl(NamedKEM kem, String pname, int sslen, int clen, byte[] key, Object k2, SecureRandom sr) implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { @Override @@ -110,7 +111,7 @@ public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, if (encapsulation.length != clen) { throw new DecapsulateException("Invalid key encapsulation message length"); } - var ss = kem.implDecapsulate(name, key, k2, encapsulation); + var ss = kem.implDecapsulate(pname, key, k2, encapsulation); try { return new SecretKeySpec(ss, from, to - from, algorithm); @@ -121,7 +122,7 @@ public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, @Override public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { - var enc = kem.implEncapsulate(name, key, k2, sr); + var enc = kem.implEncapsulate(pname, key, k2, sr); try { return new KEM.Encapsulated( new SecretKeySpec(enc[1], @@ -146,46 +147,46 @@ public int engineEncapsulationSize() { private static KeyConsumerImpl getKeyConsumerImpl(NamedKEM kem, NamedParameterSpec nps, byte[] key, Object k2, SecureRandom sr) { - String name = nps.getName(); - return new KeyConsumerImpl(kem, name, kem.implSecretSize(name), kem.implEncapsulationSize(name), + String pname = nps.getName(); + return new KeyConsumerImpl(kem, pname, kem.implSecretSize(pname), kem.implEncapsulationSize(pname), key, k2, sr); } /// User-defined encap function. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @param pk2 parsed public key, `null` if none. See [#implCheckPublicKey]. /// @param sr SecureRandom object, `null` if not initialized /// @return the key encapsulation message and the shared key (in this order) /// @throws ProviderException if there is an internal error - protected abstract byte[][] implEncapsulate(String name, byte[] pk, Object pk2, SecureRandom sr); + protected abstract byte[][] implEncapsulate(String pname, byte[] pk, Object pk2, SecureRandom sr); /// User-defined decap function. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @param sk2 parsed private key, `null` if none. See [#implCheckPrivateKey]. /// @param encap the key encapsulation message /// @return the shared key /// @throws ProviderException if there is an internal error /// @throws DecapsulateException if there is another error - protected abstract byte[] implDecapsulate(String name, byte[] sk, Object sk2, byte[] encap) + protected abstract byte[] implDecapsulate(String pname, byte[] sk, Object sk2, byte[] encap) throws DecapsulateException; /// User-defined function returning shared secret key length. /// - /// @param name parameter name + /// @param pname parameter name /// @return shared secret key length /// @throws ProviderException if there is an internal error - protected abstract int implSecretSize(String name); + protected abstract int implSecretSize(String pname); /// User-defined function returning key encapsulation message length. /// - /// @param name parameter name + /// @param pname parameter name /// @return key encapsulation message length /// @throws ProviderException if there is an internal error - protected abstract int implEncapsulationSize(String name); + protected abstract int implEncapsulationSize(String pname); /// User-defined function to validate a public key. /// @@ -196,11 +197,11 @@ protected abstract byte[] implDecapsulate(String name, byte[] sk, Object sk2, by /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException { + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { return null; } @@ -213,11 +214,11 @@ protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyExc /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPrivateKey(String name, byte[] sk) throws InvalidKeyException { + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { return null; } } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java index 727358dd07491..f2e801d1fb2d4 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,6 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; -import java.util.Objects; /// A base class for all `KeyFactory` implementations that can be /// configured with a named parameter set. See [NamedKeyPairGenerator] @@ -58,7 +57,7 @@ /// /// When reading from a RAW format, it needs enough info to derive the /// parameter set name. -public class NamedKeyFactory extends KeyFactorySpi { +public abstract class NamedKeyFactory extends KeyFactorySpi { private final String fname; // family name private final String[] pnames; // allowed parameter set name (at least one) @@ -78,14 +77,14 @@ protected NamedKeyFactory(String fname, String... pnames) { this.pnames = pnames; } - private String checkName(String name) throws InvalidKeyException { - for (var pname : pnames) { - if (pname.equalsIgnoreCase(name)) { + private String checkName(String pname) throws InvalidKeyException { + for (var n : pnames) { + if (n.equalsIgnoreCase(pname)) { // return the stored standard name - return pname; + return n; } } - throw new InvalidKeyException("Unsupported parameter set name: " + name); + throw new InvalidKeyException("Unsupported parameter set name: " + pname); } @Override @@ -129,11 +128,11 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) } } else if (keySpec instanceof RawKeySpec rks) { if (pnames.length == 1) { - var bytes = rks.getKeyArr(); + var raw = rks.getKeyArr(); try { - return new NamedPKCS8Key(fname, pnames[0], bytes); - } finally { - Arrays.fill(bytes, (byte) 0); + return new NamedPKCS8Key(fname, pnames[0], raw, implExpand(pnames[0], raw)); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException("Invalid key input", e); } } else { throw new InvalidKeySpecException("Parameter set name unavailable"); @@ -141,11 +140,11 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) } else if (keySpec instanceof EncodedKeySpec espec && espec.getFormat().equalsIgnoreCase("RAW")) { if (pnames.length == 1) { - var bytes = espec.getEncoded(); + var raw = espec.getEncoded(); try { - return new NamedPKCS8Key(fname, pnames[0], bytes); - } finally { - Arrays.fill(bytes, (byte) 0); + return new NamedPKCS8Key(fname, pnames[0], raw, implExpand(pnames[0], raw)); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException("Invalid key input", e); } } else { throw new InvalidKeySpecException("Parameter set name unavailable"); @@ -156,14 +155,14 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) } private PrivateKey fromPKCS8(byte[] bytes) - throws InvalidKeyException, InvalidKeySpecException { - var k = new NamedPKCS8Key(fname, bytes); + throws InvalidKeyException { + var k = new NamedPKCS8Key(fname, bytes, this::implExpand); checkName(k.getParams().getName()); return k; } private PublicKey fromX509(byte[] bytes) - throws InvalidKeyException, InvalidKeySpecException { + throws InvalidKeyException { var k = new NamedX509Key(fname, bytes); checkName(k.getParams().getName()); return k; @@ -184,7 +183,7 @@ public String getFormat() { protected T engineGetKeySpec(Key key, Class keySpec) throws InvalidKeySpecException { try { - key = engineTranslateKey(key); + key = toNamedKey(key); } catch (InvalidKeyException e) { throw new InvalidKeySpecException(e); } @@ -225,6 +224,10 @@ protected T engineGetKeySpec(Key key, Class keySpec) @Override protected Key engineTranslateKey(Key key) throws InvalidKeyException { + return toNamedKey(key); + } + + protected Key toNamedKey(Key key) throws InvalidKeyException { if (key == null) { throw new InvalidKeyException("Key must not be null"); } @@ -242,27 +245,28 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { } else if (format.equalsIgnoreCase("RAW")) { var kAlg = key.getAlgorithm(); if (key instanceof AsymmetricKey pk) { - String name; + String pname; // Three cases that we can find the parameter set name from a RAW key: // 1. getParams() returns one // 2. getAlgorithm() returns param set name (some provider does this) // 3. getAlgorithm() returns family name but this KF is for param set name if (pk.getParams() instanceof NamedParameterSpec nps) { - name = checkName(nps.getName()); + pname = checkName(nps.getName()); } else { if (kAlg.equalsIgnoreCase(fname)) { if (pnames.length == 1) { - name = pnames[0]; + pname = pnames[0]; } else { throw new InvalidKeyException("No parameter set info"); } } else { - name = checkName(kAlg); + pname = checkName(kAlg); } } + var raw = key.getEncoded(); return key instanceof PrivateKey - ? new NamedPKCS8Key(fname, name, key.getEncoded()) - : new NamedX509Key(fname, name, key.getEncoded()); + ? new NamedPKCS8Key(fname, pname, raw, implExpand(pname, raw)) + : new NamedX509Key(fname, pname, raw); } else { throw new InvalidKeyException("Unsupported key type: " + key.getClass()); } @@ -270,19 +274,26 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { var bytes = key.getEncoded(); try { return fromPKCS8(bytes); - } catch (InvalidKeySpecException e) { - throw new InvalidKeyException("Invalid PKCS#8 key", e); } finally { Arrays.fill(bytes, (byte) 0); } } else if (format.equalsIgnoreCase("X.509") && key instanceof PublicKey) { - try { - return fromX509(key.getEncoded()); - } catch (InvalidKeySpecException e) { - throw new InvalidKeyException("Invalid X.509 key", e); - } + return fromX509(key.getEncoded()); } else { throw new InvalidKeyException("Unsupported key format: " + key.getFormat()); } } + + /// User-defined function to generate the expanded format of + /// a [NamedPKCS8Key] from its encoding format. + /// + /// This method is called when the key factory is constructing a private + /// key. The ownership of the result is fully granted to the caller. + /// + /// @param pname the parameter set name + /// @param input the encoding, could be any format + /// @return the expanded key, not null + /// @throws InvalidKeyException if `input` is invalid + protected abstract byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException; } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java index 5be2b2b2a08b0..f1f2e2d4fb1cb 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.NamedParameterSpec; -import java.util.Objects; /// A base class for all `KeyPairGenerator` implementations that can be /// configured with a named parameter set. @@ -52,15 +51,21 @@ /// with `getAlgorithm` returning the family name, and `getParams` returning /// the parameter set name as a [NamedParameterSpec] object. /// -/// An implementation must include a zero-argument public constructor that -/// calls `super(fname, pnames)`, where `fname` is the family name of the -/// algorithm and `pnames` are its supported parameter set names. `pnames` -/// must contain at least one element. For an implementation of -/// `NamedKeyPairGenerator`, the first element becomes its default parameter -/// set, i.e. the parameter set to be used in key pair generation unless +/// A `NamedKeyPairGenerator` or `NamedKeyFactory` implementation must include +/// a zero-argument public constructor that calls `super(fname, pnames)`, where +/// `fname` is the family name of the algorithm and `pnames` are its supported +/// parameter set names. `pnames` must contain at least one element. For an +/// implementation of `NamedKeyPairGenerator`, the first element becomes its +/// default parameter set, i.e. the parameter set used by generated keys unless /// [#initialize(AlgorithmParameterSpec, java.security.SecureRandom)] /// is called on a different parameter set. /// +/// A `NamedKEM` or `NamedSignature` implementation must include a zero-argument +/// public constructor that calls `super(fname, factory, pnames)`, where +/// `fname` is the family name of the algorithm and `pnames` are its supported +/// parameter set names. `pnames` must contain at least one element. `factory` +/// is the `NamedKeyFactory` object that is used to translate foreign keys. +/// /// An implementation must implement all abstract methods. For all these /// methods, the implementation must relinquish any "ownership" of any input /// and output array argument. Precisely, the implementation must not retain @@ -69,8 +74,8 @@ /// array argument and must not retain any reference to an input array argument /// after the call. /// -/// Also, an implementation must not keep any extra copy of a private key. -/// For key generation, the only copy is the one returned in the +/// Also, an implementation must not keep any extra copy of a private key in +/// any format. For key generation, the only copy is the one returned in the /// [#implGenerateKeyPair] call. For all other methods, it must not make /// a copy of the input private key. A `KEM` implementation also must not /// keep a copy of the shared secret key, no matter if it's an encapsulator @@ -84,6 +89,30 @@ /// (For example, `implSign`) later. An implementation must not retain /// a reference of the parsed key. /// +/// The private key, represented as a byte array when used in `NamedKEM` or +/// `NamedSignature`, is referred to as its expanded format. For some +/// algorithms, this format may differ from the encoding format used in a +/// PKCS #8 file (i.e. the [NamedPKCS8Key#key] field). For example, +/// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) +/// Table 2 defines the ML-DSA-65 private key as a 4032-byte array, which is +/// used in the ML-DSA.Sign function in Algorithm 2, representing the +/// expanded format. However, in +/// [draft-ietf-lamps-dilithium-certificates-08](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates#name-private-key-format), +/// a private key can be encoded into a CHOICE of three formats, none in the +/// same as the FIPS 204 format. A `NamedKeyPairGenerator` implementation +/// should return both the expanded key and a preferred encoding in its +/// [#implGenerateKeyPair] method. +/// +/// A `NamedKeyFactory` that must override the `implExpand` method to +/// derive the expanded format from an encoding format. +/// Implementations may support multiple encoding formats. +/// +/// A `NamedKeyFactory` must not modify the `key` field, ensuring that when +/// re-encoded, the key retains its original encoding format. +/// +/// A `NamedKeyFactory` can choose a differnt encoding format when +/// `translateKey` is called. +/// /// When constructing a [NamedX509Key] or [NamedPKCS8Key] object from raw key /// bytes, the key bytes are directly referenced within the object, so the /// caller must not modify them afterward. Similarly, the key's `getRawBytes` @@ -105,9 +134,9 @@ public abstract class NamedKeyPairGenerator extends KeyPairGeneratorSpi { private final String fname; // family name - private final String[] pnames; // allowed parameter set name (at least one) + private final String[] pnames; // allowed parameter set names (at least one) - protected String name; // init as + protected String pname; // parameter set name, if can be determined private SecureRandom secureRandom; /// Creates a new `NamedKeyPairGenerator` object. @@ -126,22 +155,22 @@ protected NamedKeyPairGenerator(String fname, String... pnames) { this.pnames = pnames; } - private String checkName(String name) throws InvalidAlgorithmParameterException { - for (var pname : pnames) { - if (pname.equalsIgnoreCase(name)) { - // return the stored standard name - return pname; + private String checkName(String pname) throws InvalidAlgorithmParameterException { + for (var n : pnames) { + if (n.equalsIgnoreCase(pname)) { + // return the stored standard pname + return n; } } throw new InvalidAlgorithmParameterException( - "Unsupported parameter set name: " + name); + "Unsupported parameter set name: " + pname); } @Override public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException { if (params instanceof NamedParameterSpec spec) { - name = checkName(spec.getName()); + pname = checkName(spec.getName()); } else { throw new InvalidAlgorithmParameterException( "Unsupported AlgorithmParameterSpec: " + params); @@ -161,17 +190,19 @@ public void initialize(int keysize, SecureRandom random) { @Override public KeyPair generateKeyPair() { - String pname = name != null ? name : pnames[0]; - var keys = implGenerateKeyPair(pname, secureRandom); - return new KeyPair(new NamedX509Key(fname, pname, keys[0]), - new NamedPKCS8Key(fname, pname, keys[1])); + String tmpName = pname != null ? pname : pnames[0]; + var keys = implGenerateKeyPair(tmpName, secureRandom); + return new KeyPair(new NamedX509Key(fname, tmpName, keys[0]), + new NamedPKCS8Key(fname, tmpName, keys[1], keys[2])); } /// User-defined key pair generator. /// /// @param pname parameter set name /// @param sr `SecureRandom` object, `null` if not initialized - /// @return public key and private key (in this order) in raw bytes + /// @return the public key, the private key in its encoding format, and + /// the private key in its expanded format (in this order) in + /// raw bytes. /// @throws ProviderException if there is an internal error protected abstract byte[][] implGenerateKeyPair(String pname, SecureRandom sr); } diff --git a/src/java.base/share/classes/sun/security/provider/NamedSignature.java b/src/java.base/share/classes/sun/security/provider/NamedSignature.java index 921a39cfc926d..b1f93b1b3026e 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedSignature.java +++ b/src/java.base/share/classes/sun/security/provider/NamedSignature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,6 @@ import java.security.SignatureException; import java.security.SignatureSpi; import java.security.spec.AlgorithmParameterSpec; -import java.util.Objects; /// A base class for all `Signature` implementations that can be /// configured with a named parameter set. See [NamedKeyPairGenerator] @@ -51,11 +50,12 @@ public abstract class NamedSignature extends SignatureSpi { private final String fname; // family name private final String[] pnames; // allowed parameter set name (at least one) + private final NamedKeyFactory fac; private final ByteArrayOutputStream bout = new ByteArrayOutputStream(); // init with... - private String name; + private String pname; private byte[] secKey; private byte[] pubKey; @@ -65,8 +65,10 @@ public abstract class NamedSignature extends SignatureSpi { /// Creates a new `NamedSignature` object. /// /// @param fname the family name + /// @param fac the `KeyFactory` used to translate foreign keys and + /// perform key validation /// @param pnames the standard parameter set names, at least one is needed. - protected NamedSignature(String fname, String... pnames) { + protected NamedSignature(String fname, NamedKeyFactory fac, String... pnames) { if (fname == null) { throw new AssertionError("fname cannot be null"); } @@ -75,16 +77,16 @@ protected NamedSignature(String fname, String... pnames) { } this.fname = fname; this.pnames = pnames; + this.fac = fac; } @Override protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { // translate also check the key - var nk = (NamedX509Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(publicKey); - name = nk.getParams().getName(); + var nk = (NamedX509Key) fac.toNamedKey(publicKey); + pname = nk.getParams().getName(); pubKey = nk.getRawBytes(); - pk2 = implCheckPublicKey(name, pubKey); + pk2 = implCheckPublicKey(pname, pubKey); secKey = null; bout.reset(); } @@ -92,11 +94,10 @@ protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException @Override protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { // translate also check the key - var nk = (NamedPKCS8Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(privateKey); - name = nk.getParams().getName(); - secKey = nk.getRawBytes(); - sk2 = implCheckPrivateKey(name, secKey); + var nk = (NamedPKCS8Key) fac.toNamedKey(privateKey); + pname = nk.getParams().getName(); + secKey = nk.getExpanded(); + sk2 = implCheckPrivateKey(pname, secKey); pubKey = null; bout.reset(); } @@ -116,7 +117,7 @@ protected byte[] engineSign() throws SignatureException { if (secKey != null) { var msg = bout.toByteArray(); bout.reset(); - return implSign(name, secKey, sk2, msg, appRandom); + return implSign(pname, secKey, sk2, msg, appRandom); } else { throw new SignatureException("No private key"); } @@ -127,7 +128,7 @@ protected boolean engineVerify(byte[] sig) throws SignatureException { if (pubKey != null) { var msg = bout.toByteArray(); bout.reset(); - return implVerify(name, pubKey, pk2, msg, sig); + return implVerify(pname, pubKey, pk2, msg, sig); } else { throw new SignatureException("No public key"); } @@ -162,7 +163,7 @@ protected AlgorithmParameters engineGetParameters() { /// User-defined sign function. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @param sk2 parsed private key, `null` if none. See [#implCheckPrivateKey]. /// @param msg the message @@ -170,12 +171,12 @@ protected AlgorithmParameters engineGetParameters() { /// @return the signature /// @throws ProviderException if there is an internal error /// @throws SignatureException if there is another error - protected abstract byte[] implSign(String name, byte[] sk, Object sk2, + protected abstract byte[] implSign(String pname, byte[] sk, Object sk2, byte[] msg, SecureRandom sr) throws SignatureException; /// User-defined verify function. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @param pk2 parsed public key, `null` if none. See [#implCheckPublicKey]. /// @param msg the message @@ -183,7 +184,7 @@ protected abstract byte[] implSign(String name, byte[] sk, Object sk2, /// @return true if verified /// @throws ProviderException if there is an internal error /// @throws SignatureException if there is another error - protected abstract boolean implVerify(String name, byte[] pk, Object pk2, + protected abstract boolean implVerify(String pname, byte[] pk, Object pk2, byte[] msg, byte[] sig) throws SignatureException; /// User-defined function to validate a public key. @@ -195,11 +196,11 @@ protected abstract boolean implVerify(String name, byte[] pk, Object pk2, /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException { + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { return null; } @@ -212,11 +213,11 @@ protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyExc /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPrivateKey(String name, byte[] sk) throws InvalidKeyException { + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { return null; } } diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index 95223ec0b127c..6a0e3a5b03747 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -31,6 +31,8 @@ import java.security.interfaces.*; import java.security.spec.*; import java.util.Arrays; +import java.util.Locale; +import java.util.function.BiFunction; import javax.crypto.SecretKey; import javax.crypto.interfaces.DHKey; import javax.crypto.interfaces.DHPublicKey; @@ -457,5 +459,143 @@ public static boolean isSupportedKeyAgreementOutputAlgorithm(String alg) { return alg.equalsIgnoreCase("TlsPremasterSecret") || alg.equalsIgnoreCase("Generic"); } + + /** + * Writes one of the ML-KEM or ML-DSA private key formats. + * + * For example: + * ML-KEM-1024-PrivateKey ::= CHOICE { + * seed [0] OCTET STRING (SIZE (64)), + * expandedKey OCTET STRING (SIZE (3168)), + * both SEQUENCE { + * seed OCTET STRING (SIZE (64)), + * expandedKey OCTET STRING (SIZE (3168)) + * } + * } + * + * This method returns one of the choices depending on the system/security + * property jdk.type.pkcs8.encoding. + * + * @param pname parameter set name + * @param type the type string in property name, "mlkem" or "mldsa" + * @param seed the seed, could be null + * @param expanded the expanded key, could be null + * @param expand function to calculate expanded from seed, could be null + * if there is already expanded provided + * @returns one of the choices, null if seed not provided but the output + * requires it. Note that the expanded key will always be + * generated even if not provided in the input + */ + public static byte[] writeToChoices( + String pname, String type, byte[] seed, byte[] expanded, + BiFunction expand) { + byte[] skOctets; + var prop = SecurityProperties.getOverridableProperty( + "jdk." + type + ".pkcs8.encoding"); + if (prop == null) prop = "seed"; + + // Ensures using one-byte len in DER + assert seed == null || seed.length < 128; + // Ensures using two-byte len in DER + assert expanded == null || expanded.length > 256 && expanded.length < 60000; + + switch (prop.toLowerCase(Locale.ROOT)) { + case "seed" -> { + if (seed == null) return null; + skOctets = new byte[seed.length + 2]; + skOctets[0] = (byte)0x80; + skOctets[1] = (byte) seed.length; + System.arraycopy(seed, 0, skOctets, 2, seed.length); + } + case "expandedkey" -> { + if (expanded == null) expanded = expand.apply(pname, seed); + skOctets = new byte[expanded.length + 4]; + skOctets[0] = 0x04; + writeShortLength(skOctets, 1, expanded.length); + System.arraycopy(expanded, 0, skOctets, 4, expanded.length); + } + case "both" -> { + if (seed == null) return null; + if (expanded == null) expanded = expand.apply(pname, seed); + skOctets = new byte[10 + seed.length + expanded.length]; + skOctets[0] = 0x30; + writeShortLength(skOctets, 1, 6 + seed.length + expanded.length); + skOctets[4] = 0x04; + skOctets[5] = (byte)seed.length; + System.arraycopy(seed, 0, skOctets, 6, seed.length); + skOctets[6 + seed.length] = 0x04; + writeShortLength(skOctets, 7 + seed.length, expanded.length); + System.arraycopy(expanded, 0, skOctets, 10 + seed.length, expanded.length); + } + default -> throw new IllegalArgumentException("Unknown format: " + prop); + } + return skOctets; + } + + /** + * Splits one of the ML-KEM or ML-DSA private key formats into + * seed and expandedKey, if exists. + * + * @param seedLen correct seed length + * @param input input bytes + * @returns seed and expandedkey, each could be null if not inside + * the input. Results are newly allocated arrays + * @throws InvalidKeyException if input is invalid + */ + public static byte[][] splitChoices(int seedLen, byte[] input) + throws InvalidKeyException { + if (input.length < seedLen + 2) { + throw new InvalidKeyException("Too short"); + } + return switch (input[0]) { + case (byte) 0x80 -> { + // 80 SEED_LEN + if (input[1] != seedLen && input.length != seedLen + 2) { + throw new InvalidKeyException("Invalid seed"); + } + yield new byte[][] { Arrays.copyOfRange(input, 2, seedLen + 2), null }; + } + case 0x04 -> { + // 04 82 nn nn + if (readShortLength(input, 1) != input.length - 4) { + throw new InvalidKeyException("Invalid expandedKey"); + } + yield new byte[][] { null, Arrays.copyOfRange(input, 4, input.length) }; + } + case 0x30 -> { + // 30 82 mm mm 04 SEED_LEN 04 82 nn nn + if (input.length < 6 + seedLen + 4) { + throw new InvalidKeyException("Too short"); + } + if (readShortLength(input, 1) != input.length - 4 + || input[4] != 0x04 + || input[5] != (byte)seedLen + || input[seedLen + 6] != 0x04 + || readShortLength(input, seedLen + 7) + != input.length - 10 - seedLen) { + throw new InvalidKeyException("Invalid both"); + } + yield new byte[][] { + Arrays.copyOfRange(input, 6, 6 + seedLen), + Arrays.copyOfRange(input, seedLen + 10, input.length)}; + } + default -> throw new InvalidKeyException("Wrong tag: " + input[0]); + }; + } + + // Reads a 2 bytes length from DER encoding + private static int readShortLength(byte[] input, int from) throws InvalidKeyException { + if (input[from] != (byte)0x82) { + throw new InvalidKeyException("Unexpected length"); + } + return ((input[from + 1] & 0xff) << 8) + (input[from + 2] & 0xff); + } + + // Writes a 2 bytes length to DER encoding + private static void writeShortLength(byte[] input, int from, int value) { + input[from] = (byte)0x82; + input[from + 1] = (byte) (value >> 8); + input[from + 2] = (byte) (value); + } } diff --git a/src/java.base/share/classes/sun/security/x509/NamedX509Key.java b/src/java.base/share/classes/sun/security/x509/NamedX509Key.java index dc36bd3b9b306..0c3fe2bf12124 100644 --- a/src/java.base/share/classes/sun/security/x509/NamedX509Key.java +++ b/src/java.base/share/classes/sun/security/x509/NamedX509Key.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,7 +71,8 @@ public NamedX509Key(String fname, String pname, byte[] rawBytes) { setKey(new BitArray(rawBytes.length * 8, rawBytes)); } - /// Ctor from family name, and X.509 bytes + /// Ctor from family name, and X.509 bytes. Input byte array + /// is copied. Caller can modify it after the method call. public NamedX509Key(String fname, byte[] encoded) throws InvalidKeyException { this.fname = fname; decode(encoded); diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index b115d47983848..44ab0eaba0945 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1549,3 +1549,45 @@ jdk.tls.alpnCharset=ISO_8859_1 # security property value defined here. # #jdk.security.krb5.name.case.sensitive=false + +# +# The privateKey field for newly generated ML-KEM private keys in PKCS #8 +# +# draft-ietf-lamps-kyber-certificates specifies three formats for an ML-KEM +# private key: a 64-octet seed, an (expanded) private key, or both. +# +# This values can be "seed", "expandedKey", or "both" (case-insensitive). +# The default value is "seed". +# +# When a new keypair is generated, its private key encoding will be determined +# by this property. +# +# If a system property of the same name is also specified, it supersedes the +# security property value defined here. +# +# Note: This property is currently used by the SunJCE provider in the JDK +# Reference implementation. It is not guaranteed to be supported by other +# SE implementations. +# +#jdk.mlkem.pkcs8.encoding = seed + +# +# The privateKey field for newly generated ML-DSA private keys in PKCS #8 +# +# draft-ietf-lamps-dilithium-certificates specifies three formats for an ML-DSA +# private key: a 32-octet seed, an (expanded) private key, or both. +# +# When a new keypair is generated, its private key encoding will be determined +# by this property. +# +# This values can be "seed", "expandedKey", or "both" (case-insensitive). +# The default value is "seed". +# +# If a system property of the same name is also specified, it supersedes the +# security property value defined here. +# +# Note: This property is currently used by the SUN provider in the JDK +# Reference implementation. It is not guaranteed to be supported by other +# SE implementations. +# +#jdk.mldsa.pkcs8.encoding = seed diff --git a/test/jdk/sun/security/provider/acvp/Launcher.java b/test/jdk/sun/security/provider/acvp/Launcher.java index c07b7929d89df..cfc18cc9c5ee8 100644 --- a/test/jdk/sun/security/provider/acvp/Launcher.java +++ b/test/jdk/sun/security/provider/acvp/Launcher.java @@ -37,6 +37,8 @@ * @bug 8342442 8345057 * @library /test/lib * @modules java.base/sun.security.provider + * java.base/sun.security.util + * java.base/com.sun.crypto.provider * @run main Launcher */ @@ -46,6 +48,8 @@ * @bug 8342442 8345057 * @library /test/lib * @modules java.base/sun.security.provider + * java.base/sun.security.util + * java.base/com.sun.crypto.provider * @run main/othervm -Xcomp Launcher */ diff --git a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java index 281bb415305b0..ac56642b8d7b0 100644 --- a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java +++ b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java @@ -24,6 +24,7 @@ import jdk.test.lib.json.JSONValue; import jdk.test.lib.security.FixedSecureRandom; import sun.security.provider.ML_DSA_Impls; +import sun.security.util.DerOutputStream; import java.security.*; import java.security.spec.EncodedKeySpec; @@ -68,12 +69,13 @@ static void keyGenTest(JSONValue kat, Provider p) throws Exception { System.out.println(">> " + pname); for (var c : t.get("tests").asArray()) { System.out.print(c.get("tcId").asString() + " "); - g.initialize(np, new FixedSecureRandom(toByteArray(c.get("seed").asString()))); + var seed = toByteArray(c.get("seed").asString()); + g.initialize(np, new FixedSecureRandom(seed)); var kp = g.generateKeyPair(); var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); - var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); Asserts.assertEqualsByteArray(toByteArray(c.get("pk").asString()), pk); - Asserts.assertEqualsByteArray(toByteArray(c.get("sk").asString()), sk); + Asserts.assertEqualsByteArray(toByteArray(c.get("sk").asString()), + ML_DSA_Impls.seedToExpanded(pname, seed)); } System.out.println(); } @@ -106,7 +108,7 @@ static void sigGenTest(JSONValue kat, Provider p) throws Exception { var sk = new PrivateKey() { public String getAlgorithm() { return pname; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return toByteArray(c.get("sk").asString()); } + public byte[] getEncoded() { return oct(toByteArray(c.get("sk").asString())); } }; var sr = new FixedSecureRandom( det ? new byte[32] : toByteArray(c.get("rnd").asString())); @@ -119,6 +121,10 @@ static void sigGenTest(JSONValue kat, Provider p) throws Exception { } } + static byte[] oct(byte[] in) { + return new DerOutputStream().putOctetString(in).toByteArray(); + } + static void sigVerTest(JSONValue kat, Provider p) throws Exception { var s = p == null ? Signature.getInstance("ML-DSA") diff --git a/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java index c46c6a99e6da1..35c1ce611daef 100644 --- a/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java +++ b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -20,9 +20,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +import com.sun.crypto.provider.ML_KEM_Impls; import jdk.test.lib.Asserts; import jdk.test.lib.json.JSONValue; import jdk.test.lib.security.FixedSecureRandom; +import sun.security.util.DerOutputStream; import javax.crypto.KEM; import java.security.*; @@ -65,13 +67,14 @@ static void keyGenTest(JSONValue kat, Provider p) throws Exception { System.out.println(">> " + pname); for (var c : t.get("tests").asArray()) { System.out.print(c.get("tcId").asString() + " "); - g.initialize(np, new FixedSecureRandom( - toByteArray(c.get("d").asString()), toByteArray(c.get("z").asString()))); + var seed = toByteArray(c.get("d").asString() + c.get("z").asString()); + g.initialize(np, new FixedSecureRandom(seed)); var kp = g.generateKeyPair(); var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); - var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); Asserts.assertEqualsByteArray(toByteArray(c.get("ek").asString()), pk); - Asserts.assertEqualsByteArray(toByteArray(c.get("dk").asString()), sk); + Asserts.assertEqualsByteArray( + toByteArray(c.get("dk").asString()), + ML_KEM_Impls.seedToExpanded(pname, seed)); } System.out.println(); } @@ -106,7 +109,7 @@ static void encapDecapTest(JSONValue kat, Provider p) throws Exception { var dk = new PrivateKey() { public String getAlgorithm() { return pname; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return toByteArray(t.get("dk").asString()); } + public byte[] getEncoded() { return oct(toByteArray(t.get("dk").asString())); } }; for (var c : t.get("tests").asArray()) { System.out.print(c.get("tcId").asString() + " "); @@ -118,4 +121,8 @@ static void encapDecapTest(JSONValue kat, Provider p) throws Exception { } } } + + static byte[] oct(byte[] in) { + return new DerOutputStream().putOctetString(in).toByteArray(); + } } diff --git a/test/jdk/sun/security/provider/NamedEdDSA.java b/test/jdk/sun/security/provider/named/NamedEdDSA.java similarity index 84% rename from test/jdk/sun/security/provider/NamedEdDSA.java rename to test/jdk/sun/security/provider/named/NamedEdDSA.java index 4d0e3e9228aac..e571ced91b317 100644 --- a/test/jdk/sun/security/provider/NamedEdDSA.java +++ b/test/jdk/sun/security/provider/named/NamedEdDSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,11 +23,12 @@ /* * @test - * @bug 8340327 + * @bug 8340327 8347938 8347941 * @modules java.base/sun.security.ec.ed * java.base/sun.security.ec.point * java.base/sun.security.jca * java.base/sun.security.provider + * java.base/sun.security.util * @library /test/lib */ @@ -40,7 +41,10 @@ import sun.security.provider.NamedKeyFactory; import sun.security.provider.NamedKeyPairGenerator; import sun.security.provider.NamedSignature; +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; +import java.io.IOException; import java.security.*; import java.security.spec.EdDSAParameterSpec; import java.security.spec.NamedParameterSpec; @@ -66,11 +70,11 @@ public ProviderImpl() { public static class EdDSASignature extends NamedSignature { public EdDSASignature() { - super("EdDSA", "Ed25519", "Ed448"); + super("EdDSA", new EdDSAKeyFactory(), "Ed25519", "Ed448"); } protected EdDSASignature(String pname) { - super("EdDSA", pname); + super("EdDSA", new EdDSAKeyFactory(pname), pname); } public static class Ed25519 extends EdDSASignature { @@ -86,22 +90,32 @@ public Ed448() { } @Override - public byte[] implSign(String name, byte[] sk, Object sk2, byte[] msg, SecureRandom sr) throws SignatureException { - return getOps(name).sign(plain, sk, msg); + public byte[] implSign(String pname, byte[] sk, Object sk2, byte[] msg, SecureRandom sr) { + return getOps(pname).sign(plain, sk, msg); } @Override - public boolean implVerify(String name, byte[] pk, Object pk2, byte[] msg, byte[] sig) throws SignatureException { - return getOps(name).verify(plain, (AffinePoint) pk2, pk, msg, sig); + public boolean implVerify(String pname, byte[] pk, Object pk2, byte[] msg, byte[] sig) throws SignatureException { + return getOps(pname).verify(plain, (AffinePoint) pk2, pk, msg, sig); } @Override - public Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException { - return getOps(name).decodeAffinePoint(InvalidKeyException::new, pk); + public Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { + return getOps(pname).decodeAffinePoint(InvalidKeyException::new, pk); } } public static class EdDSAKeyFactory extends NamedKeyFactory { + @Override + protected byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException { + try { + return new DerValue(input).getOctetString(); + } catch (IOException e) { + throw new InvalidKeyException(e); + } + } + public EdDSAKeyFactory() { super("EdDSA", "Ed25519", "Ed448"); } @@ -157,7 +171,10 @@ public byte[][] implGenerateKeyPair(String pname, SecureRandom sr) { // set the high-order bit of the encoded point byte msb = (byte) (point.isXOdd() ? 0x80 : 0); encodedPoint[encodedPoint.length - 1] |= msb; - return new byte[][] { encodedPoint, sk }; + return new byte[][] { + encodedPoint, + new DerOutputStream().putOctetString(sk).toByteArray(), + sk}; } private static void swap(byte[] arr, int i, int j) { diff --git a/test/jdk/sun/security/provider/NamedKeyFactoryTest.java b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java similarity index 85% rename from test/jdk/sun/security/provider/NamedKeyFactoryTest.java rename to test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java index 1ca179bc04690..1d40fe1092e19 100644 --- a/test/jdk/sun/security/provider/NamedKeyFactoryTest.java +++ b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8340327 + * @bug 8340327 8347938 8347941 * @modules java.base/sun.security.x509 * java.base/sun.security.pkcs * java.base/sun.security.provider @@ -41,10 +41,13 @@ import java.security.*; import java.security.spec.*; +import java.util.Arrays; public class NamedKeyFactoryTest { private static final SeededSecureRandom RAND = SeededSecureRandom.one(); + private static final byte[] RAW_SK = RAND.nBytes(16); + private static final byte[] RAW_PK = RAND.nBytes(16); public static void main(String[] args) throws Exception { Security.addProvider(new ProviderImpl()); @@ -78,8 +81,8 @@ public static void main(String[] args) throws Exception { g.initialize(new NamedParameterSpec("ShA-256")); checkKeyPair(g.generateKeyPair(), "SHA", "SHA-256"); - var pk = new NamedX509Key("sHa", "ShA-256", RAND.nBytes(2)); - var sk = new NamedPKCS8Key("sHa", "SHa-256", RAND.nBytes(2)); + var pk = new NamedX509Key("sHa", "ShA-256", RAW_PK); + var sk = new NamedPKCS8Key("sHa", "SHa-256", RAW_SK, RAW_SK); checkKey(pk, "sHa", "ShA-256"); checkKey(sk, "sHa", "SHa-256"); @@ -134,25 +137,27 @@ public static void main(String[] args) throws Exception { Asserts.assertEquals("RAW", srk2.getFormat()); Asserts.assertEqualsByteArray(srk2.getEncoded(), sk.getRawBytes()); + checkKey(kf2.generatePrivate(srk), "SHA", "SHA-256"); Asserts.assertEqualsByteArray(kf2.generatePrivate(srk).getEncoded(), sk.getEncoded()); Utils.runAndCheckException(() -> kf.generatePrivate(srk), InvalidKeySpecException.class); // no pname + checkKey(kf2.generatePrivate(srk), "SHA", "SHA-256"); Asserts.assertEqualsByteArray(kf2.generatePrivate(srk2).getEncoded(), sk.getEncoded()); Utils.runAndCheckException(() -> kf.generatePrivate(srk2), InvalidKeySpecException.class); // no pname var pk1 = new PublicKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_PK; } }; var pk2 = new PublicKey() { public String getAlgorithm() { return "sHA-256"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_PK; } }; var pk3 = new PublicKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_PK; } public AlgorithmParameterSpec getParams() { return new NamedParameterSpec("sHA-256"); } }; @@ -167,17 +172,17 @@ public static void main(String[] args) throws Exception { var sk1 = new PrivateKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_SK; } }; var sk2 = new PrivateKey() { public String getAlgorithm() { return "sHA-256"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_SK; } }; var sk3 = new PrivateKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_SK; } public AlgorithmParameterSpec getParams() { return new NamedParameterSpec("sHA-256"); } }; @@ -201,6 +206,14 @@ static void checkKey(Key k, String algName, String pname) { if (k instanceof AsymmetricKey ak && ak.getParams() instanceof NamedParameterSpec nps) { Asserts.assertEquals(pname, nps.getName()); } + if (k instanceof NamedPKCS8Key nsk) { + var raw = nsk.getRawBytes(); + Asserts.assertEqualsByteArray(Arrays.copyOf(RAW_SK, raw.length), raw); + } + if (k instanceof NamedX509Key npk) { + var raw = npk.getRawBytes(); + Asserts.assertEqualsByteArray(Arrays.copyOf(RAW_PK, raw.length), raw); + } } // Provider @@ -217,18 +230,27 @@ public ProviderImpl() { } } public static class KF extends NamedKeyFactory { + @Override + protected byte[] implExpand(String pname, byte[] input) { + return input; + } + public KF() { super("SHA", "SHA-256", "SHA-512"); } + + public KF(String name) { + super("SHA", name); + } } - public static class KF1 extends NamedKeyFactory { + public static class KF1 extends KF { public KF1() { - super("SHA", "SHA-256"); + super("SHA-256"); } } - public static class KF2 extends NamedKeyFactory { + public static class KF2 extends KF { public KF2() { - super("SHA", "SHA-512"); + super("SHA-512"); } } public static class KPG extends NamedKeyPairGenerator { @@ -242,9 +264,10 @@ public KPG(String pname) { @Override public byte[][] implGenerateKeyPair(String name, SecureRandom sr) { - var out = new byte[2][]; - out[0] = RAND.nBytes(name.endsWith("256") ? 2 : 4); - out[1] = RAND.nBytes(name.endsWith("256") ? 2 : 4); + var out = new byte[3][]; + out[0] = name.endsWith("256") ? Arrays.copyOf(RAW_PK, 8) : RAW_PK; + out[1] = name.endsWith("256") ? Arrays.copyOf(RAW_SK, 8) : RAW_SK; + out[2] = out[1]; return out; } } diff --git a/test/jdk/sun/security/provider/named/NamedKeys.java b/test/jdk/sun/security/provider/named/NamedKeys.java new file mode 100644 index 0000000000000..a4ce4cb4d2a71 --- /dev/null +++ b/test/jdk/sun/security/provider/named/NamedKeys.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8347938 8347941 + * @modules java.base/sun.security.pkcs + * java.base/sun.security.x509 + * @library /test/lib + * @summary check the Named***Key behavior + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.security.SeededSecureRandom; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.x509.NamedX509Key; + +import java.util.Arrays; + +public class NamedKeys { + public static void main(String[] args) throws Exception { + + var r = SeededSecureRandom.one(); + var raw = r.nBytes(32); + + Asserts.assertThrows(NullPointerException.class, + () -> new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, null)); + + // Create a key using raw bytes + var sk = new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, raw); + var enc = sk.getEncoded().clone(); + + // The raw bytes array is re-used + Asserts.assertTrue(sk.getRawBytes() == sk.getRawBytes()); + // but the encoding is different + Asserts.assertTrue(sk.getEncoded() != sk.getEncoded()); + + // When source change + Arrays.fill(raw, (byte)0); + // Internal raw bytes also changes + Asserts.assertEqualsByteArray(sk.getRawBytes(), new byte[32]); + // No guarantee on getEncoded() output, could be cached + + // Create a key using encoding + Asserts.assertThrows(NullPointerException.class, + () -> new NamedPKCS8Key("ML-DSA", enc, null)); + var sk1 = new NamedPKCS8Key("ML-DSA", enc, (_, n) -> n); + var sk2 = new NamedPKCS8Key("ML-DSA", enc, (_, n) -> n); + var raw1 = sk1.getRawBytes(); + Asserts.assertTrue(raw1 != sk2.getRawBytes()); + Asserts.assertTrue(sk1.getEncoded() != sk2.getEncoded()); + + var encCopy = enc.clone(); // store a copy + Arrays.fill(enc, (byte)0); // clean the source and the key unchanged + Asserts.assertEqualsByteArray(encCopy, sk1.getEncoded()); + + // Same with public key + // Create a key using raw bytes + var pk = new NamedX509Key("ML-DSA", "ML-DSA-44", raw); + var enc2 = pk.getEncoded().clone(); + + // The raw bytes array is re-used + Asserts.assertTrue(pk.getRawBytes() == pk.getRawBytes()); + // but the encoding is different + Asserts.assertTrue(pk.getEncoded() != pk.getEncoded()); + + // When source change + Arrays.fill(raw, (byte)0); + // Internal raw bytes also changes + Asserts.assertEqualsByteArray(pk.getRawBytes(), new byte[32]); + // No guarantee on getEncoded() output, could be cached + + // Create a key using encoding + var pk1 = new NamedX509Key("ML-DSA", enc2); + var pk2 = new NamedX509Key("ML-DSA", enc2); + raw1 = pk1.getRawBytes(); + Asserts.assertTrue(raw1 != pk2.getRawBytes()); + Asserts.assertTrue(pk1.getEncoded() != pk2.getEncoded()); + + encCopy = enc2.clone(); // store a copy + Arrays.fill(enc2, (byte)0); // clean the source and the key unchanged + Asserts.assertEqualsByteArray(encCopy, pk1.getEncoded()); + } +} diff --git a/test/jdk/sun/security/provider/pqc/BadPrivateKeys.java b/test/jdk/sun/security/provider/pqc/BadPrivateKeys.java new file mode 100644 index 0000000000000..a7679306672b5 --- /dev/null +++ b/test/jdk/sun/security/provider/pqc/BadPrivateKeys.java @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8347938 8347941 + * @library /test/lib + * @modules java.base/com.sun.crypto.provider + * java.base/sun.security.pkcs + * java.base/sun.security.provider + * java.base/sun.security.util + * @summary ensure bad keys can be detected + * @run main/othervm BadPrivateKeys + */ + +import com.sun.crypto.provider.ML_KEM_Impls; +import jdk.test.lib.Asserts; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.provider.ML_DSA_Impls; + +import javax.crypto.KEM; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.stream.Collectors; + +public class BadPrivateKeys { + + public static void main(String[] args) throws Exception { + badkem(); + baddsa(); + } + + static void badkem() throws Exception { + var kf = KeyFactory.getInstance("ML-KEM"); + + // The first ML-KEM-512-PrivateKey example includes the both CHOICE, + // i.e., both seed and expandedKey are included. The seed and expanded + // values can be checked for inconsistencies. + Asserts.assertThrows(InvalidKeySpecException.class, + () -> readKey(kf, BAD_KEM_1)); + + // The second ML-KEM-512-PrivateKey example includes only expandedKey. + // The expanded private key has a mutated s_0 and a valid public key hash, + // but a pairwise consistency check would find that the public key + // fails to match private. + var k2 = readKey(kf, BAD_KEM_2); + var pk2 = ML_KEM_Impls.privKeyToPubKey((NamedPKCS8Key) k2); + var enc = KEM.getInstance("ML-KEM").newEncapsulator(pk2).encapsulate(); + var dk = KEM.getInstance("ML-KEM").newDecapsulator(k2).decapsulate(enc.encapsulation()); + Asserts.assertNotEqualsByteArray(enc.key().getEncoded(), dk.getEncoded()); + + // The third ML-KEM-512-PrivateKey example includes only expandedKey. + // The expanded private key has a mutated H(ek); both a public key + // digest check and a pairwise consistency check should fail. + var k3 = readKey(kf, BAD_KEM_3); + Asserts.assertThrows(InvalidKeyException.class, + () -> KEM.getInstance("ML-KEM").newDecapsulator(k3)); + + // The fourth ML-KEM-512-PrivateKey example includes the both CHOICE, + // i.e., both seed and expandedKey are included. There is mismatch + // of the seed and expanded private key in only the z implicit rejection + // secret; here the private and public vectors match and the pairwise + // consistency check passes, but z is different. + Asserts.assertThrows(InvalidKeySpecException.class, + () -> readKey(kf, BAD_KEM_4)); + } + + static void baddsa() throws Exception { + var kf = KeyFactory.getInstance("ML-DSA"); + + // The first ML-DSA-PrivateKey example includes the both CHOICE, i.e., + // both seed and expandedKey are included. The seed and expanded values + // can be checked for inconsistencies. + Asserts.assertThrows(InvalidKeySpecException.class, + () -> readKey(kf, BAD_DSA_1)); + + // The second ML-DSA-PrivateKey example includes only expandedKey. + // The public key fails to match the tr hash value in the private key. + var k2 = readKey(kf, BAD_DSA_2); + Asserts.assertThrows(IllegalArgumentException.class, + () -> ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k2)); + + // The third ML-DSA-PrivateKey example also includes only expandedKey. + // The private s_1 and s_2 vectors imply a t vector whose private low + // bits do not match the t_0 vector portion of the private key + // (its high bits t_1 are the primary content of the public key). + var k3 = readKey(kf, BAD_DSA_3); + Asserts.assertThrows(IllegalArgumentException.class, + () -> ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k3)); + } + + private static PrivateKey readKey(KeyFactory kf, String input) throws Exception { + var pem = input.lines() + .filter(s -> !s.contains("-----")) + .collect(Collectors.joining()); + return kf.generatePrivate( + new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pem))); + } + + // https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-10#name-examples-of-bad-private-key + static final String BAD_KEM_1 = """ + -----BEGIN PRIVATE KEY----- + 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 + P0A= + -----END PRIVATE KEY-----"""; + + static final String BAD_KEM_2 = """ + -----BEGIN PRIVATE KEY----- + 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 + JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== + -----END PRIVATE KEY-----"""; + + static final String BAD_KEM_3 = """ + -----BEGIN PRIVATE KEY----- + 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 + JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== + -----END PRIVATE KEY-----"""; + + static final String BAD_KEM_4 = """ + -----BEGIN PRIVATE KEY----- + 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 + Pj4= + -----END PRIVATE KEY-----"""; + + // https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates#name-example-inconsistent-privat + static final String BAD_DSA_1 = """ + -----BEGIN PRIVATE KEY----- + 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 + -----END PRIVATE KEY-----"""; + + static final String BAD_DSA_2 = """ + -----BEGIN PRIVATE KEY----- + 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 + -----END PRIVATE KEY-----"""; + + static final String BAD_DSA_3 = """ + -----BEGIN PRIVATE KEY----- + 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 + -----END PRIVATE KEY-----"""; +} diff --git a/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java b/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java new file mode 100644 index 0000000000000..6e172c083aee3 --- /dev/null +++ b/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8347938 8347941 + * @library /test/lib + * @modules java.base/com.sun.crypto.provider + * java.base/sun.security.provider + * java.base/sun.security.util + * @summary check key reading compatibility + * @run main/othervm SeedOrExpanded + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.security.FixedSecureRandom; +import jdk.test.lib.security.SeededSecureRandom; + +import javax.crypto.KEM; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; + +public class SeedOrExpanded { + + static final SeededSecureRandom RAND = SeededSecureRandom.one(); + + public static void main(String[] args) throws Exception { + test("mlkem", "ML-KEM-768"); + test("mldsa", "ML-DSA-65"); + } + + static void test(String type, String alg) throws Exception { + + var seed = RAND.nBytes(alg.contains("ML-KEM") ? 64 : 32); + var g = KeyPairGenerator.getInstance(alg); + + // Generation + + g.initialize(-1, new FixedSecureRandom(seed)); + var kp = g.generateKeyPair(); + var pk = kp.getPublic(); + var kDefault = kp.getPrivate(); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "seed"); + g.initialize(-1, new FixedSecureRandom(seed)); + var kSeed = g.generateKeyPair().getPrivate(); + System.setProperty("jdk." + type + ".pkcs8.encoding", "expandedkey"); + g.initialize(-1, new FixedSecureRandom(seed)); + var kExpanded = g.generateKeyPair().getPrivate(); + System.setProperty("jdk." + type + ".pkcs8.encoding", "both"); + g.initialize(-1, new FixedSecureRandom(seed)); + var kBoth = g.generateKeyPair().getPrivate(); + + Asserts.assertTrue(kExpanded.getEncoded().length > kSeed.getEncoded().length); + Asserts.assertTrue(kBoth.getEncoded().length > kExpanded.getEncoded().length); + Asserts.assertEqualsByteArray(kSeed.getEncoded(), kDefault.getEncoded()); + + test(alg, pk, kSeed); + test(alg, pk, kExpanded); + test(alg, pk, kBoth); + + var kf = KeyFactory.getInstance(alg); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "seed"); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kBoth)).getEncoded(), + kSeed.getEncoded()); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kSeed)).getEncoded(), + kSeed.getEncoded()); + Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded)); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "expandedkey"); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kBoth)).getEncoded(), + kExpanded.getEncoded()); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kSeed)).getEncoded(), + kExpanded.getEncoded()); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kExpanded)).getEncoded(), + kExpanded.getEncoded()); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "both"); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kBoth)).getEncoded(), + kBoth.getEncoded()); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kSeed)).getEncoded(), + kBoth.getEncoded()); + Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded)); + } + + static PrivateKey test(String alg, PublicKey pk, Key k) throws Exception { + var sk = (PrivateKey) k; + if (alg.contains("ML-KEM")) { + var kem = KEM.getInstance("ML-KEM"); + var e = kem.newEncapsulator(pk, RAND); + var enc = e.encapsulate(); + var k1 = kem.newDecapsulator(sk).decapsulate(enc.encapsulation()); + Asserts.assertEqualsByteArray(k1.getEncoded(), enc.key().getEncoded()); + } else { + var s = Signature.getInstance("ML-DSA"); + var rnd = RAND.nBytes(32); // randomness for signature generation + var msg = RAND.nBytes(20); + s.initSign(sk, new FixedSecureRandom(rnd)); + s.update(msg); + var sig1 = s.sign(); + s.initVerify(pk); + s.update(msg); + Asserts.assertTrue(s.verify(sig1)); + } + return sk; + } +} diff --git a/test/lib/jdk/test/lib/process/Proc.java b/test/lib/jdk/test/lib/process/Proc.java index 2fe802fed6cbd..a989906b2abea 100644 --- a/test/lib/jdk/test/lib/process/Proc.java +++ b/test/lib/jdk/test/lib/process/Proc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -256,6 +256,15 @@ public Proc start() throws IOException { } } } + String patchPath = System.getProperty("test.patch.path"); + if (patchPath != null) { + try (var subs = Files.newDirectoryStream(Path.of(patchPath))) { + for (var sub : subs) { + var name = sub.getFileName(); + cmd.add("--patch-module=" + name + "=" + sub); + } + } + } var lcp = fullcp(); if (lcp != null) { From 1adbad42a8c8b27db68c452ab1acb0f6ecd45acb Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 1 May 2025 08:56:49 -0400 Subject: [PATCH 2/6] safer privKeyToPubKey; updated desciptions for the properties; adding braces to if blocks --- .../com/sun/crypto/provider/ML_KEM_Impls.java | 8 +++-- .../classes/sun/security/provider/ML_DSA.java | 20 +++++++++--- .../sun/security/provider/ML_DSA_Impls.java | 8 +++-- .../classes/sun/security/util/KeyUtil.java | 20 +++++++++--- .../share/conf/security/java.security | 32 +++++++++++-------- 5 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java index c0340d4b3ad0a..7ea95dec19445 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java @@ -146,8 +146,12 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { var encoding = KeyUtil.writeToChoices(npk.getParams().getName(), "mlkem", parts[0], parts[1], ML_KEM_Impls::seedToExpanded); - if (parts[0] != null) Arrays.fill(parts[0], (byte)0); - if (parts[1] != null) Arrays.fill(parts[1], (byte)0); + if (parts[0] != null) { + Arrays.fill(parts[0], (byte)0); + } + if (parts[1] != null) { + Arrays.fill(parts[1], (byte)0); + } if (encoding == null) { throw new InvalidKeyException("key contains not enough info to translate"); } diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA.java b/src/java.base/share/classes/sun/security/provider/ML_DSA.java index bf8d760a857df..7651e0bac4ba7 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA.java @@ -568,15 +568,25 @@ public ML_DSA_KeyPair generateKeyPairInternal(byte[] randomBytes) { return new ML_DSA_KeyPair(sk, pk); } + private static int[][] deepClone(int[][] array) { + int[][] clone = new int[array.length][]; + for (int i = 0; i < array.length; i++) { + clone[i] = array[i].clone(); + } + return clone; + } + public ML_DSA_PublicKey privKeyToPubKey(ML_DSA_PrivateKey sk) { - //Sample A + // Sample A int[][][] keygenA = generateA(sk.rho); //A is in NTT domain - //Compute t and tr - mlDsaVectorNtt(sk.s1); //s1 now in NTT domain + // Compute t and tr + // make a copy of sk.s1 and modify it. Although we can also + // take it out of NTT domain later, it was modified for a while. + var s1 = deepClone(sk.s1); + mlDsaVectorNtt(s1); //s1 now in NTT domain int[][] As1 = new int[mlDsa_k][ML_DSA_N]; - matrixVectorPointwiseMultiply(As1, keygenA, sk.s1); - mlDsaVectorInverseNtt(sk.s1); //take s1 out of NTT domain + matrixVectorPointwiseMultiply(As1, keygenA, s1); mlDsaVectorInverseNtt(As1); int[][] t = vectorAddPos(As1, sk.s2); diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java index aa6c870edb0ca..4c2e78268cca9 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java @@ -171,8 +171,12 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { var encoding = KeyUtil.writeToChoices(npk.getParams().getName(), "mldsa", parts[0], parts[1], ML_DSA_Impls::seedToExpanded); - if (parts[0] != null) Arrays.fill(parts[0], (byte)0); - if (parts[1] != null) Arrays.fill(parts[1], (byte)0); + if (parts[0] != null) { + Arrays.fill(parts[0], (byte)0); + } + if (parts[1] != null) { + Arrays.fill(parts[1], (byte)0); + } if (encoding == null) { throw new InvalidKeyException("key contains not enough info to translate"); } diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index 6a0e3a5b03747..98ef74dd422e1 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -492,7 +492,9 @@ public static byte[] writeToChoices( byte[] skOctets; var prop = SecurityProperties.getOverridableProperty( "jdk." + type + ".pkcs8.encoding"); - if (prop == null) prop = "seed"; + if (prop == null) { + prop = "seed"; + } // Ensures using one-byte len in DER assert seed == null || seed.length < 128; @@ -501,22 +503,30 @@ public static byte[] writeToChoices( switch (prop.toLowerCase(Locale.ROOT)) { case "seed" -> { - if (seed == null) return null; + if (seed == null) { + return null; + } skOctets = new byte[seed.length + 2]; skOctets[0] = (byte)0x80; skOctets[1] = (byte) seed.length; System.arraycopy(seed, 0, skOctets, 2, seed.length); } case "expandedkey" -> { - if (expanded == null) expanded = expand.apply(pname, seed); + if (expanded == null) { + expanded = expand.apply(pname, seed); + } skOctets = new byte[expanded.length + 4]; skOctets[0] = 0x04; writeShortLength(skOctets, 1, expanded.length); System.arraycopy(expanded, 0, skOctets, 4, expanded.length); } case "both" -> { - if (seed == null) return null; - if (expanded == null) expanded = expand.apply(pname, seed); + if (seed == null) { + return null; + } + if (expanded == null) { + expanded = expand.apply(pname, seed); + } skOctets = new byte[10 + seed.length + expanded.length]; skOctets[0] = 0x30; writeShortLength(skOctets, 1, 6 + seed.length + expanded.length); diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 44ab0eaba0945..23043b27c020e 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1551,16 +1551,18 @@ jdk.tls.alpnCharset=ISO_8859_1 #jdk.security.krb5.name.case.sensitive=false # -# The privateKey field for newly generated ML-KEM private keys in PKCS #8 +# The privateKey field for newly created ML-KEM private keys in PKCS #8 # -# draft-ietf-lamps-kyber-certificates specifies three formats for an ML-KEM -# private key: a 64-octet seed, an (expanded) private key, or both. +# The draft-ietf-lamps-kyber-certificates specification defines three formats +# for an ML-KEM private key: a 64-byte seed, an expanded private key, +# or a sequence containing both. # -# This values can be "seed", "expandedKey", or "both" (case-insensitive). -# The default value is "seed". +# Valid values for this property are "seed", "expandedKey", and "both" +# (case-insensitive). The default is "seed". # -# When a new keypair is generated, its private key encoding will be determined -# by this property. +# This property determines the encoding format used when a new keypair is +# generated using a KeyPairGenerator, as well as the output of the translateKey +# method on an existing key using a KeyFactory. # # If a system property of the same name is also specified, it supersedes the # security property value defined here. @@ -1572,16 +1574,18 @@ jdk.tls.alpnCharset=ISO_8859_1 #jdk.mlkem.pkcs8.encoding = seed # -# The privateKey field for newly generated ML-DSA private keys in PKCS #8 +# The privateKey field for newly created ML-DSA private keys in PKCS #8 # -# draft-ietf-lamps-dilithium-certificates specifies three formats for an ML-DSA -# private key: a 32-octet seed, an (expanded) private key, or both. +# The draft-ietf-lamps-dilithium-certificates specification defines three formats +# for an ML-DSA private key: a 32-byte seed, an expanded private key, +# or a sequence containing both. # -# When a new keypair is generated, its private key encoding will be determined -# by this property. +# Valid values for this property are "seed", "expandedKey", and "both" +# (case-insensitive). The default is "seed". # -# This values can be "seed", "expandedKey", or "both" (case-insensitive). -# The default value is "seed". +# This property determines the encoding format used when a new keypair is +# generated using a KeyPairGenerator, as well as the output of the translateKey +# method on an existing key using a KeyFactory. # # If a system property of the same name is also specified, it supersedes the # security property value defined here. From eee45744855b44197a68e8072695d7750d196bd4 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 5 Jun 2025 16:11:28 -0400 Subject: [PATCH 3/6] allow expanded to be null --- .../sun/security/pkcs/NamedPKCS8Key.java | 40 +++++++++++++------ .../security/provider/NamedKeyFactory.java | 2 +- .../provider/NamedKeyPairGenerator.java | 21 ++++++---- .../provider/named/NamedKeyFactoryTest.java | 15 ++++--- .../security/provider/named/NamedKeys.java | 22 +++++----- 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java index 56d5858b8bb80..942cff8a482d5 100644 --- a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java +++ b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java @@ -48,13 +48,21 @@ /// identifier in the PKCS #8 encoding of the key is always a single OID derived /// from the parameter set name. /// -/// Besides the existing [PKCS8Key#key] field, this class contains an -/// expanded format stored in [#expanded]. While `key` always represents -/// the format used for encoding, the expanded format is always used +/// Besides the existing [PKCS8Key#privKeyMaterial] field, this class optionally +/// supports an expanded format stored in [#expanded]. While `privKeyMaterial` +/// always represents the format used for encoding, `expanded` is always used /// in computations. The expanded format must be self-sufficient for /// cryptographic computations without requiring the encoding format. /// -/// Both fields must be present. +/// 1. If only `privKeyMaterial` is present, it's also the expanded format. +/// 2. If both `privKeyMaterial` and `expanded` are available, `privKeyMaterial` +/// is the encoding format, and `expanded` is the expanded format. +/// +/// If the two formats are the same, only `privKeyMaterial` is included, and +/// `expanded` must be `null`. Some implementations might be tempted to put the +/// same value into `privKeyMaterial` and `expanded`. However, problems can +/// arise if they happen to be the same object. To avoid ambiguity, always set +/// `expanded` to `null`. /// /// @see sun.security.provider.NamedKeyPairGenerator public final class NamedPKCS8Key extends PKCS8Key { @@ -75,11 +83,11 @@ public final class NamedPKCS8Key extends PKCS8Key { /// @param fname family name /// @param pname parameter set name /// @param encoded raw key bytes, not null - /// @param expanded expanded key format, not null + /// @param expanded expanded key format, can be `null`. public NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded) { this.fname = fname; this.paramSpec = new NamedParameterSpec(pname); - this.expanded = Objects.requireNonNull(expanded); + this.expanded = expanded; this.privKeyMaterial = Objects.requireNonNull(encoded); try { this.algid = AlgorithmId.get(pname); @@ -94,13 +102,18 @@ public NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded /// @param encoded PKCS #8 encoding. It is copied so caller can modify /// it after the method call. /// @param expander a function that is able to calculate the expanded - /// format from the encoding format inside `encoded`.The + /// format from the encoding format inside `encoded`. If it recognizes + /// the input already in expanded format, it must return `null`. + /// This argument must be `null` if the algorithm's expanded format + /// is always the same as its encoding format. Whatever the case, the /// ownership of the result is fully granted to this object. public NamedPKCS8Key(String fname, byte[] encoded, Expander expander) throws InvalidKeyException { super(encoded); this.fname = fname; - this.expanded = expander.expand(algid.getName(), this.privKeyMaterial); + this.expanded = expander == null + ? null + : expander.expand(algid.getName(), this.privKeyMaterial); paramSpec = new NamedParameterSpec(algid.getName()); if (algid.getEncodedParams() != null) { throw new InvalidKeyException("algorithm identifier has params"); @@ -117,13 +130,13 @@ public String toString() { /// Returns the reference to the internal key. Caller must not modify /// the content or pass the reference to untrusted application code. public byte[] getRawBytes() { - return key; + return privKeyMaterial; } /// Returns the reference to the key in expanded format. Caller must not /// modify the content or pass the reference to untrusted application code. public byte[] getExpanded() { - return expanded; + return expanded == null ? privKeyMaterial : expanded; } @Override @@ -146,7 +159,9 @@ private void readObject(ObjectInputStream stream) @Override public void destroy() { Arrays.fill(privKeyMaterial, (byte)0); - Arrays.fill(expanded, (byte)0); + if (expanded != null) { + Arrays.fill(expanded, (byte)0); + } if (encodedKey != null) { Arrays.fill(encodedKey, (byte)0); } @@ -160,7 +175,8 @@ public boolean isDestroyed() { /// Expands from encoding format to expanded format. public interface Expander { - /// The expand method. + /// The expand method, returns `null` if `input` is already + /// in expanded format. byte[] expand(String pname, byte[] input) throws InvalidKeyException; } } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java index f2e801d1fb2d4..d92c96963f813 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java @@ -77,7 +77,7 @@ protected NamedKeyFactory(String fname, String... pnames) { this.pnames = pnames; } - private String checkName(String pname) throws InvalidKeyException { + private String checkName(String pname) throws InvalidKeyException { for (var n : pnames) { if (n.equalsIgnoreCase(pname)) { // return the stored standard name diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java index f1f2e2d4fb1cb..2daf66a936b0e 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java @@ -103,14 +103,17 @@ /// should return both the expanded key and a preferred encoding in its /// [#implGenerateKeyPair] method. /// -/// A `NamedKeyFactory` that must override the `implExpand` method to -/// derive the expanded format from an encoding format. +/// A `NamedKeyFactory` that must override the `implExpand` method to derive +/// the expanded format from an encoding format, or return `null` if there +/// is no difference. +/// /// Implementations may support multiple encoding formats. /// -/// A `NamedKeyFactory` must not modify the `key` field, ensuring that when -/// re-encoded, the key retains its original encoding format. +/// A `NamedKeyFactory` must not modify the [NamedPKCS8Key#privKeyMaterial] +/// field, ensuring that when re-encoded, the key retains its original encoding +/// format. /// -/// A `NamedKeyFactory` can choose a differnt encoding format when +/// A `NamedKeyFactory` can choose a different encoding format when /// `translateKey` is called. /// /// When constructing a [NamedX509Key] or [NamedPKCS8Key] object from raw key @@ -155,7 +158,7 @@ protected NamedKeyPairGenerator(String fname, String... pnames) { this.pnames = pnames; } - private String checkName(String pname) throws InvalidAlgorithmParameterException { + private String checkName(String pname) throws InvalidAlgorithmParameterException { for (var n : pnames) { if (n.equalsIgnoreCase(pname)) { // return the stored standard pname @@ -193,7 +196,8 @@ public KeyPair generateKeyPair() { String tmpName = pname != null ? pname : pnames[0]; var keys = implGenerateKeyPair(tmpName, secureRandom); return new KeyPair(new NamedX509Key(fname, tmpName, keys[0]), - new NamedPKCS8Key(fname, tmpName, keys[1], keys[2])); + new NamedPKCS8Key(fname, tmpName, keys[1], + keys.length == 2 ? null : keys[2])); } /// User-defined key pair generator. @@ -202,7 +206,8 @@ public KeyPair generateKeyPair() { /// @param sr `SecureRandom` object, `null` if not initialized /// @return the public key, the private key in its encoding format, and /// the private key in its expanded format (in this order) in - /// raw bytes. + /// raw bytes. If the expanded format of the private key is the + /// same as its encoding format, the 3rd element must be omitted. /// @throws ProviderException if there is an internal error protected abstract byte[][] implGenerateKeyPair(String pname, SecureRandom sr); } diff --git a/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java index 1d40fe1092e19..15ea1829cbd95 100644 --- a/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java +++ b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java @@ -82,7 +82,7 @@ public static void main(String[] args) throws Exception { checkKeyPair(g.generateKeyPair(), "SHA", "SHA-256"); var pk = new NamedX509Key("sHa", "ShA-256", RAW_PK); - var sk = new NamedPKCS8Key("sHa", "SHa-256", RAW_SK, RAW_SK); + var sk = new NamedPKCS8Key("sHa", "SHa-256", RAW_SK, null); checkKey(pk, "sHa", "ShA-256"); checkKey(sk, "sHa", "SHa-256"); @@ -230,11 +230,6 @@ public ProviderImpl() { } } public static class KF extends NamedKeyFactory { - @Override - protected byte[] implExpand(String pname, byte[] input) { - return input; - } - public KF() { super("SHA", "SHA-256", "SHA-512"); } @@ -242,6 +237,11 @@ public KF() { public KF(String name) { super("SHA", name); } + + @Override + protected byte[] implExpand(String pname, byte[] input) throws InvalidKeyException { + return null; + } } public static class KF1 extends KF { public KF1() { @@ -264,10 +264,9 @@ public KPG(String pname) { @Override public byte[][] implGenerateKeyPair(String name, SecureRandom sr) { - var out = new byte[3][]; + var out = new byte[2][]; out[0] = name.endsWith("256") ? Arrays.copyOf(RAW_PK, 8) : RAW_PK; out[1] = name.endsWith("256") ? Arrays.copyOf(RAW_SK, 8) : RAW_SK; - out[2] = out[1]; return out; } } diff --git a/test/jdk/sun/security/provider/named/NamedKeys.java b/test/jdk/sun/security/provider/named/NamedKeys.java index a4ce4cb4d2a71..982f6e51075c8 100644 --- a/test/jdk/sun/security/provider/named/NamedKeys.java +++ b/test/jdk/sun/security/provider/named/NamedKeys.java @@ -42,11 +42,8 @@ public static void main(String[] args) throws Exception { var r = SeededSecureRandom.one(); var raw = r.nBytes(32); - Asserts.assertThrows(NullPointerException.class, - () -> new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, null)); - // Create a key using raw bytes - var sk = new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, raw); + var sk = new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, null); var enc = sk.getEncoded().clone(); // The raw bytes array is re-used @@ -61,10 +58,8 @@ public static void main(String[] args) throws Exception { // No guarantee on getEncoded() output, could be cached // Create a key using encoding - Asserts.assertThrows(NullPointerException.class, - () -> new NamedPKCS8Key("ML-DSA", enc, null)); - var sk1 = new NamedPKCS8Key("ML-DSA", enc, (_, n) -> n); - var sk2 = new NamedPKCS8Key("ML-DSA", enc, (_, n) -> n); + var sk1 = new NamedPKCS8Key("ML-DSA", enc, null); + var sk2 = new NamedPKCS8Key("ML-DSA", enc, null); var raw1 = sk1.getRawBytes(); Asserts.assertTrue(raw1 != sk2.getRawBytes()); Asserts.assertTrue(sk1.getEncoded() != sk2.getEncoded()); @@ -75,8 +70,9 @@ public static void main(String[] args) throws Exception { // Same with public key // Create a key using raw bytes + raw = r.nBytes(32); var pk = new NamedX509Key("ML-DSA", "ML-DSA-44", raw); - var enc2 = pk.getEncoded().clone(); + enc = pk.getEncoded().clone(); // The raw bytes array is re-used Asserts.assertTrue(pk.getRawBytes() == pk.getRawBytes()); @@ -90,14 +86,14 @@ public static void main(String[] args) throws Exception { // No guarantee on getEncoded() output, could be cached // Create a key using encoding - var pk1 = new NamedX509Key("ML-DSA", enc2); - var pk2 = new NamedX509Key("ML-DSA", enc2); + var pk1 = new NamedX509Key("ML-DSA", enc); + var pk2 = new NamedX509Key("ML-DSA", enc); raw1 = pk1.getRawBytes(); Asserts.assertTrue(raw1 != pk2.getRawBytes()); Asserts.assertTrue(pk1.getEncoded() != pk2.getEncoded()); - encCopy = enc2.clone(); // store a copy - Arrays.fill(enc2, (byte)0); // clean the source and the key unchanged + encCopy = enc.clone(); // store a copy + Arrays.fill(enc, (byte)0); // clean the source and the key unchanged Asserts.assertEqualsByteArray(encCopy, pk1.getEncoded()); } } From 540963a0171dea4bde23bd9a8ba2671a265b1320 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Tue, 10 Jun 2025 10:36:37 -0400 Subject: [PATCH 4/6] KeyChoices class --- .../com/sun/crypto/provider/ML_KEM_Impls.java | 67 ++-- .../sun/security/pkcs/NamedPKCS8Key.java | 37 ++- .../classes/sun/security/provider/ML_DSA.java | 7 +- .../sun/security/provider/ML_DSA_Impls.java | 65 ++-- .../sun/security/provider/NamedKEM.java | 8 +- .../security/provider/NamedKeyFactory.java | 118 ++++--- .../provider/NamedKeyPairGenerator.java | 21 +- .../sun/security/provider/NamedSignature.java | 12 +- .../classes/sun/security/util/KeyChoices.java | 288 ++++++++++++++++++ .../classes/sun/security/util/KeyUtil.java | 148 --------- .../security/provider/named/NamedEdDSA.java | 4 +- .../provider/named/NamedKeyFactoryTest.java | 2 +- .../security/provider/named/NamedKeys.java | 12 +- .../security/provider/pqc/SeedOrExpanded.java | 24 +- 14 files changed, 478 insertions(+), 335 deletions(-) create mode 100644 src/java.base/share/classes/sun/security/util/KeyChoices.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java index 7ea95dec19445..117f26e69810c 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java @@ -30,7 +30,7 @@ import sun.security.provider.NamedKEM; import sun.security.provider.NamedKeyFactory; import sun.security.provider.NamedKeyPairGenerator; -import sun.security.util.KeyUtil; +import sun.security.util.KeyChoices; import sun.security.x509.NamedX509Key; import java.security.*; @@ -80,7 +80,9 @@ protected byte[][] implGenerateKeyPair(String pname, SecureRandom random) { try { return new byte[][]{ kp.encapsulationKey().keyBytes(), - KeyUtil.writeToChoices(pname, "mlkem", seed, expanded, null), + KeyChoices.writeToChoice( + KeyChoices.getPreferred("mlkem"), + seed, expanded), expanded }; } finally { @@ -118,50 +120,29 @@ public KF(String pname) { @Override protected byte[] implExpand(String pname, byte[] input) throws InvalidKeyException { - var parts = KeyUtil.splitChoices(SEED_LEN, input); - if (parts[0] != null && parts[1] != null) { - var calculated = seedToExpanded(pname, parts[0]); - if (!Arrays.equals(parts[1], calculated)) { - throw new InvalidKeyException("seed and expandedKey do not match"); - } - Arrays.fill(calculated, (byte)0); - } - try { - if (parts[1] != null) { - return parts[1]; - } - return seedToExpanded(pname, parts[0]); - } finally { - if (parts[0] != null) { - Arrays.fill(parts[0], (byte)0); - } - } + return KeyChoices.choiceToExpanded(pname, SEED_LEN, input, + ML_KEM_Impls::seedToExpanded); } @Override protected Key engineTranslateKey(Key key) throws InvalidKeyException { var nk = toNamedKey(key); if (nk instanceof NamedPKCS8Key npk) { - var parts = KeyUtil.splitChoices(SEED_LEN, npk.getRawBytes()); - var encoding = KeyUtil.writeToChoices(npk.getParams().getName(), - "mlkem", parts[0], parts[1], - ML_KEM_Impls::seedToExpanded); - if (parts[0] != null) { - Arrays.fill(parts[0], (byte)0); - } - if (parts[1] != null) { - Arrays.fill(parts[1], (byte)0); - } - if (encoding == null) { - throw new InvalidKeyException("key contains not enough info to translate"); - } - nk = new NamedPKCS8Key( - npk.getAlgorithm(), - npk.getParams().getName(), - encoding, - npk.getExpanded().clone()); - if (npk != key) { - npk.destroy(); + var type = KeyChoices.getPreferred("mlkem"); + if (KeyChoices.typeOfChoice(npk.getRawBytes()) != type) { + var encoding = KeyChoices.choiceToChoice( + type, + npk.getParams().getName(), + SEED_LEN, npk.getRawBytes(), + ML_KEM_Impls::seedToExpanded); + nk = NamedPKCS8Key.internalCreate( + npk.getAlgorithm(), + npk.getParams().getName(), + encoding, + npk.getExpanded().clone()); + if (npk != key) { // npk is neither input or output + npk.destroy(); + } } } return nk; @@ -198,7 +179,7 @@ protected byte[][] implEncapsulate(String pname, byte[] encapsulationKey, r.nextBytes(randomBytes); ML_KEM mlKem = new ML_KEM(pname); - ML_KEM.ML_KEM_EncapsulateResult mlKemEncapsulateResult = null; + ML_KEM.ML_KEM_EncapsulateResult mlKemEncapsulateResult; try { mlKemEncapsulateResult = mlKem.encapsulate( new ML_KEM.ML_KEM_EncapsulationKey( @@ -252,11 +233,11 @@ protected Object implCheckPrivateKey(String pname, byte[] sk) } public K() { - super("ML-KEM", new KF(), "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"); + super("ML-KEM", new KF()); } public K(String pname) { - super("ML-KEM", new KF(pname), pname); + super("ML-KEM", new KF(pname)); } } diff --git a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java index 942cff8a482d5..9bcd325348631 100644 --- a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java +++ b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java @@ -56,7 +56,7 @@ /// /// 1. If only `privKeyMaterial` is present, it's also the expanded format. /// 2. If both `privKeyMaterial` and `expanded` are available, `privKeyMaterial` -/// is the encoding format, and `expanded` is the expanded format. +/// is the encoding format, and `expanded` is the expanded format. /// /// If the two formats are the same, only `privKeyMaterial` is included, and /// `expanded` must be `null`. Some implementations might be tempted to put the @@ -64,6 +64,10 @@ /// arise if they happen to be the same object. To avoid ambiguity, always set /// `expanded` to `null`. /// +/// A `NamedPKCS8Key`, when created, must include `expanded` if required, its +/// `privKeyMaterial` must have already been validated for internal consistency. +/// For example, seed and expanded key should match. +/// /// @see sun.security.provider.NamedKeyPairGenerator public final class NamedPKCS8Key extends PKCS8Key { @Serial @@ -75,16 +79,13 @@ public final class NamedPKCS8Key extends PKCS8Key { private transient boolean destroyed = false; - /// Creates a `NamedPKCS8Key` from raw key bytes. - /// - /// `encoded` and `expanded` won't be cloned, caller - /// must relinquish ownership. + /// Creates a `NamedPKCS8Key` from raw components. /// /// @param fname family name /// @param pname parameter set name /// @param encoded raw key bytes, not null /// @param expanded expanded key format, can be `null`. - public NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded) { + private NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded) { this.fname = fname; this.paramSpec = new NamedParameterSpec(pname); this.expanded = expanded; @@ -96,6 +97,21 @@ public NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded } } + /// Creates a `NamedPKCS8Key` from raw components. + /// + /// `encoded` and `expanded` won't be cloned, caller must relinquish + /// ownership. This caller must ensure `encoded` and `expanded` match + /// each other and `encoded` is valid and internally-consistent. + /// + /// @param fname family name + /// @param pname parameter set name + /// @param encoded raw key bytes, not null + /// @param expanded expanded key format, can be `null`. + public static NamedPKCS8Key internalCreate(String fname, String pname, + byte[] encoded, byte[] expanded) { + return new NamedPKCS8Key(fname, pname, encoded, expanded); + } + /// Creates a `NamedPKCS8Key` from family name and PKCS #8 encoding. /// /// @param fname family name @@ -175,8 +191,13 @@ public boolean isDestroyed() { /// Expands from encoding format to expanded format. public interface Expander { - /// The expand method, returns `null` if `input` is already - /// in expanded format. + /// The expand method + /// + /// @param pname parameter set name + /// @param input input encoding + /// @return the expanded key, `null` if `input` is already in expanded + /// @throws InvalidKeyException if `input` is invalid, for example, + /// wrong encoding, or internal inconsistency byte[] expand(String pname, byte[] input) throws InvalidKeyException; } } diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA.java b/src/java.base/share/classes/sun/security/provider/ML_DSA.java index 319fa4b1fac8c..ace296cbccda3 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA.java @@ -576,6 +576,11 @@ private static int[][] deepClone(int[][] array) { return clone; } + // This is similar to the generateKeyPairInternal method. Instead of + // generating from a seed, it uses stored fields inside the private key + // to calculate the public key. It performs several checks during the + // calculation to make sure the private key is a valid one. Otherwise, + // an IllegalArgumentException is thrown. public ML_DSA_PublicKey privKeyToPubKey(ML_DSA_PrivateKey sk) { // Sample A int[][][] keygenA = generateA(sk.rho); //A is in NTT domain @@ -607,7 +612,7 @@ public ML_DSA_PublicKey privKeyToPubKey(ML_DSA_PrivateKey sk) { throw new IllegalArgumentException("tr does not patch"); } - //Encode PK and SK + //Encode PK return new ML_DSA_PublicKey(sk.rho, t1); } diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java index 4c2e78268cca9..730e253f407f8 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java @@ -27,7 +27,7 @@ import sun.security.jca.JCAUtil; import sun.security.pkcs.NamedPKCS8Key; -import sun.security.util.KeyUtil; +import sun.security.util.KeyChoices; import sun.security.x509.NamedX509Key; import java.security.*; @@ -104,7 +104,9 @@ protected byte[][] implGenerateKeyPair(String pname, SecureRandom random) { try { return new byte[][]{ mlDsa.pkEncode(kp.publicKey()), - KeyUtil.writeToChoices(pname, "mldsa", seed, expanded, null), + KeyChoices.writeToChoice( + KeyChoices.getPreferred("mldsa"), + seed, expanded), expanded }; } finally { @@ -143,50 +145,29 @@ public KF(String pname) { @Override protected byte[] implExpand(String pname, byte[] input) throws InvalidKeyException { - var parts = KeyUtil.splitChoices(SEED_LEN, input); - if (parts[0] != null && parts[1] != null) { - var calculated = seedToExpanded(pname, parts[0]); - if (!Arrays.equals(parts[1], calculated)) { - throw new InvalidKeyException("seed and expandedKey do not match"); - } - Arrays.fill(calculated, (byte)0); - } - try { - if (parts[1] != null) { - return parts[1]; - } - return seedToExpanded(pname, parts[0]); - } finally { - if (parts[0] != null) { - Arrays.fill(parts[0], (byte)0); - } - } + return KeyChoices.choiceToExpanded(pname, SEED_LEN, input, + ML_DSA_Impls::seedToExpanded); } @Override protected Key engineTranslateKey(Key key) throws InvalidKeyException { var nk = toNamedKey(key); if (nk instanceof NamedPKCS8Key npk) { - var parts = KeyUtil.splitChoices(SEED_LEN, npk.getRawBytes()); - var encoding = KeyUtil.writeToChoices(npk.getParams().getName(), - "mldsa", parts[0], parts[1], - ML_DSA_Impls::seedToExpanded); - if (parts[0] != null) { - Arrays.fill(parts[0], (byte)0); - } - if (parts[1] != null) { - Arrays.fill(parts[1], (byte)0); - } - if (encoding == null) { - throw new InvalidKeyException("key contains not enough info to translate"); - } - nk = new NamedPKCS8Key( - npk.getAlgorithm(), - npk.getParams().getName(), - encoding, - npk.getExpanded().clone()); - if (npk != key) { - npk.destroy(); + var type = KeyChoices.getPreferred("mldsa"); + if (KeyChoices.typeOfChoice(npk.getRawBytes()) != type) { + var encoding = KeyChoices.choiceToChoice( + type, + npk.getParams().getName(), + SEED_LEN, npk.getRawBytes(), + ML_DSA_Impls::seedToExpanded); + nk = NamedPKCS8Key.internalCreate( + npk.getAlgorithm(), + npk.getParams().getName(), + encoding, + npk.getExpanded().clone()); + if (npk != key) { // npk is neither input or output + npk.destroy(); + } } } return nk; @@ -213,10 +194,10 @@ public KF5() { public sealed static class SIG extends NamedSignature permits SIG2, SIG3, SIG5 { public SIG() { - super("ML-DSA", new KF(), "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"); + super("ML-DSA", new KF()); } public SIG(String pname) { - super("ML-DSA", new KF(pname), pname); + super("ML-DSA", new KF(pname)); } @Override diff --git a/src/java.base/share/classes/sun/security/provider/NamedKEM.java b/src/java.base/share/classes/sun/security/provider/NamedKEM.java index 61ae66cecc5e6..60449396d4d85 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKEM.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKEM.java @@ -49,7 +49,6 @@ public abstract class NamedKEM implements KEMSpi { private final String fname; // family name - private final String[] pnames; // allowed parameter set name (at least one) private final NamedKeyFactory fac; /// Creates a new `NamedKEM` object. @@ -57,16 +56,11 @@ public abstract class NamedKEM implements KEMSpi { /// @param fname the family name /// @param fac the `KeyFactory` used to translate foreign keys and /// perform key validation - /// @param pnames the standard parameter set names, at least one is needed. - protected NamedKEM(String fname, NamedKeyFactory fac, String... pnames) { + protected NamedKEM(String fname, NamedKeyFactory fac) { if (fname == null) { throw new AssertionError("fname cannot be null"); } - if (pnames == null || pnames.length == 0) { - throw new AssertionError("pnames cannot be null or empty"); - } this.fname = fname; - this.pnames = pnames; this.fac = fac; } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java index d92c96963f813..aaaaad88780fc 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java @@ -90,68 +90,86 @@ private String checkName(String pname) throws InvalidKeyException { @Override protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { - if (keySpec instanceof X509EncodedKeySpec xspec) { - try { - return fromX509(xspec.getEncoded()); - } catch (InvalidKeyException e) { - throw new InvalidKeySpecException(e); + return switch (keySpec) { + case X509EncodedKeySpec xspec -> { + try { + yield fromX509(xspec.getEncoded()); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException(e); + } } - } else if (keySpec instanceof RawKeySpec rks) { - if (pnames.length == 1) { - return new NamedX509Key(fname, pnames[0], rks.getKeyArr()); - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); + case RawKeySpec rks -> { + if (pnames.length == 1) { + yield new NamedX509Key(fname, pnames[0], rks.getKeyArr()); + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); + } } - } else if (keySpec instanceof EncodedKeySpec espec - && espec.getFormat().equalsIgnoreCase("RAW")) { - if (pnames.length == 1) { - return new NamedX509Key(fname, pnames[0], espec.getEncoded()); - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); + case EncodedKeySpec espec when espec.getFormat().equalsIgnoreCase("RAW") -> { + if (pnames.length == 1) { + yield new NamedX509Key(fname, pnames[0], espec.getEncoded()); + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); + } } - } else { - throw new InvalidKeySpecException("Unsupported keyspec: " + keySpec); - } + case null -> throw new InvalidKeySpecException( + "keySpec must not be null"); + default -> + throw new InvalidKeySpecException(keySpec.getClass().getName() + + " not supported."); + }; } @Override protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { - if (keySpec instanceof PKCS8EncodedKeySpec pspec) { - var bytes = pspec.getEncoded(); - try { - return fromPKCS8(bytes); - } catch (InvalidKeyException e) { - throw new InvalidKeySpecException(e); - } finally { - Arrays.fill(bytes, (byte) 0); - } - } else if (keySpec instanceof RawKeySpec rks) { - if (pnames.length == 1) { - var raw = rks.getKeyArr(); + return switch (keySpec) { + case PKCS8EncodedKeySpec pspec -> { + var bytes = pspec.getEncoded(); try { - return new NamedPKCS8Key(fname, pnames[0], raw, implExpand(pnames[0], raw)); + yield fromPKCS8(bytes); } catch (InvalidKeyException e) { - throw new InvalidKeySpecException("Invalid key input", e); + throw new InvalidKeySpecException(e); + } finally { + Arrays.fill(bytes, (byte) 0); } - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); } - } else if (keySpec instanceof EncodedKeySpec espec - && espec.getFormat().equalsIgnoreCase("RAW")) { - if (pnames.length == 1) { - var raw = espec.getEncoded(); - try { - return new NamedPKCS8Key(fname, pnames[0], raw, implExpand(pnames[0], raw)); - } catch (InvalidKeyException e) { - throw new InvalidKeySpecException("Invalid key input", e); + case RawKeySpec rks -> { + if (pnames.length == 1) { + var raw = rks.getKeyArr(); + try { + yield fromRaw(pnames[0], raw); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException("Invalid key input", e); + } + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); } - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); } - } else { - throw new InvalidKeySpecException("Unsupported keyspec: " + keySpec); - } + case EncodedKeySpec espec when espec.getFormat().equalsIgnoreCase("RAW") -> { + if (pnames.length == 1) { + var raw = espec.getEncoded(); + try { + yield fromRaw(pnames[0], raw); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException("Invalid key input", e); + } + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); + } + } + case null -> throw new InvalidKeySpecException( + "keySpec must not be null"); + default -> + throw new InvalidKeySpecException(keySpec.getClass().getName() + + " not supported."); + }; + } + + private PrivateKey fromRaw(String pname, byte[] raw) + throws InvalidKeyException { + return NamedPKCS8Key.internalCreate( + fname, pname, raw, implExpand(pname, raw)); } private PrivateKey fromPKCS8(byte[] bytes) @@ -224,6 +242,8 @@ protected T engineGetKeySpec(Key key, Class keySpec) @Override protected Key engineTranslateKey(Key key) throws InvalidKeyException { + // The base toNamedKey only make sure key is translated into a NamedKey. + // the key material is still the same as the input. return toNamedKey(key); } @@ -265,7 +285,7 @@ protected Key toNamedKey(Key key) throws InvalidKeyException { } var raw = key.getEncoded(); return key instanceof PrivateKey - ? new NamedPKCS8Key(fname, pname, raw, implExpand(pname, raw)) + ? fromRaw(pname, raw) : new NamedX509Key(fname, pname, raw); } else { throw new InvalidKeyException("Unsupported key type: " + key.getClass()); diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java index 2daf66a936b0e..651fa80d6f29e 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java @@ -61,10 +61,10 @@ /// is called on a different parameter set. /// /// A `NamedKEM` or `NamedSignature` implementation must include a zero-argument -/// public constructor that calls `super(fname, factory, pnames)`, where -/// `fname` is the family name of the algorithm and `pnames` are its supported -/// parameter set names. `pnames` must contain at least one element. `factory` -/// is the `NamedKeyFactory` object that is used to translate foreign keys. +/// public constructor that calls `super(fname, factory)`, where `fname` is the +/// family name of the algorithm and `factory` is the `NamedKeyFactory` object +/// that is used to translate foreign keys. `factory` only recognizes +/// parameter sets supported by this implementation. /// /// An implementation must implement all abstract methods. For all these /// methods, the implementation must relinquish any "ownership" of any input @@ -99,19 +99,20 @@ /// expanded format. However, in /// [draft-ietf-lamps-dilithium-certificates-08](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates#name-private-key-format), /// a private key can be encoded into a CHOICE of three formats, none in the -/// same as the FIPS 204 format. A `NamedKeyPairGenerator` implementation +/// same as the FIPS 204 format. The choices are defined in +/// [sun.security.util.KeyChoices]. A `NamedKeyPairGenerator` implementation /// should return both the expanded key and a preferred encoding in its /// [#implGenerateKeyPair] method. /// -/// A `NamedKeyFactory` that must override the `implExpand` method to derive +/// A `NamedKeyFactory` must override the `implExpand` method to derive /// the expanded format from an encoding format, or return `null` if there /// is no difference. /// /// Implementations may support multiple encoding formats. /// -/// A `NamedKeyFactory` must not modify the [NamedPKCS8Key#privKeyMaterial] -/// field, ensuring that when re-encoded, the key retains its original encoding -/// format. +/// A `NamedKeyFactory` must not modify the encoding when generating a key +/// from a `KeySpec` object, ensuring that when re-encoded, the key retains +/// its original encoding format. /// /// A `NamedKeyFactory` can choose a different encoding format when /// `translateKey` is called. @@ -196,7 +197,7 @@ public KeyPair generateKeyPair() { String tmpName = pname != null ? pname : pnames[0]; var keys = implGenerateKeyPair(tmpName, secureRandom); return new KeyPair(new NamedX509Key(fname, tmpName, keys[0]), - new NamedPKCS8Key(fname, tmpName, keys[1], + NamedPKCS8Key.internalCreate(fname, tmpName, keys[1], keys.length == 2 ? null : keys[2])); } diff --git a/src/java.base/share/classes/sun/security/provider/NamedSignature.java b/src/java.base/share/classes/sun/security/provider/NamedSignature.java index b1f93b1b3026e..07d20828c3c12 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedSignature.java +++ b/src/java.base/share/classes/sun/security/provider/NamedSignature.java @@ -49,7 +49,6 @@ public abstract class NamedSignature extends SignatureSpi { private final String fname; // family name - private final String[] pnames; // allowed parameter set name (at least one) private final NamedKeyFactory fac; private final ByteArrayOutputStream bout = new ByteArrayOutputStream(); @@ -67,16 +66,11 @@ public abstract class NamedSignature extends SignatureSpi { /// @param fname the family name /// @param fac the `KeyFactory` used to translate foreign keys and /// perform key validation - /// @param pnames the standard parameter set names, at least one is needed. - protected NamedSignature(String fname, NamedKeyFactory fac, String... pnames) { + protected NamedSignature(String fname, NamedKeyFactory fac) { if (fname == null) { throw new AssertionError("fname cannot be null"); } - if (pnames == null || pnames.length == 0) { - throw new AssertionError("pnames cannot be null or empty"); - } this.fname = fname; - this.pnames = pnames; this.fac = fac; } @@ -135,14 +129,14 @@ protected boolean engineVerify(byte[] sig) throws SignatureException { } @Override - @SuppressWarnings("deprecation") + @Deprecated protected void engineSetParameter(String param, Object value) throws InvalidParameterException { throw new InvalidParameterException("setParameter() not supported"); } @Override - @SuppressWarnings("deprecation") + @Deprecated protected Object engineGetParameter(String param) throws InvalidParameterException { throw new InvalidParameterException("getParameter() not supported"); } diff --git a/src/java.base/share/classes/sun/security/util/KeyChoices.java b/src/java.base/share/classes/sun/security/util/KeyChoices.java new file mode 100644 index 0000000000000..1487c10178fd4 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/KeyChoices.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.util; + +import java.security.*; +import java.util.Arrays; +import java.util.Locale; +import java.util.function.BiFunction; + +/** + * The content of an ML-KEM or ML-DSA private key is defined as a CHOICE + * among three different representations. For example: + *
+ *  ML-KEM-1024-PrivateKey ::= CHOICE {
+ *       seed [0] OCTET STRING (SIZE (64)),
+ *       expandedKey OCTET STRING (SIZE (3168)),
+ *       both SEQUENCE {
+ *           seed OCTET STRING (SIZE (64)),
+ *           expandedKey OCTET STRING (SIZE (3168))
+ *           }
+ *       }
+ * 
+ * This class supports reading, writing, and convert between them. + */ +public final class KeyChoices { + + public enum Type { SEED, EXPANDED_KEY, BOTH } + + private record Choice(Type type, byte[] seed, byte[] expanded) {} + + /** + * Gets the preferred choice type for an algorithm, defined as an + * overridable security property "jdk..pkcs8.encoding". + * + * @param name "mlkem" or "mldsa". + * @throws IllegalArgumentException if property is invalid value + * @return the type + */ + public static Type getPreferred(String name) { + var prop = SecurityProperties.getOverridableProperty( + "jdk." + name + ".pkcs8.encoding"); + if (prop == null) { + return Type.SEED; + } + return switch (prop.toLowerCase(Locale.ROOT)) { + case "seed" -> Type.SEED; + case "expandedkey" -> Type.EXPANDED_KEY; + case "both" -> Type.BOTH; + default -> throw new IllegalArgumentException("Unknown format: " + prop); + }; + } + + /** + * Writes one of the ML-KEM or ML-DSA private key formats. + *

+ * This method does not check the length of the inputs or whether + * they match each other. The caller must make sure `seed` and/or + * `expanded` are provided if `type` requires any of them. + * + * @param type preferred output choice type + * @param seed the seed, could be null + * @param expanded the expanded key, could be null + * @return one of the choices + */ + public static byte[] writeToChoice(Type type, byte[] seed, byte[] expanded) { + byte[] skOctets; + // Ensures using one-byte len in DER + assert seed == null || seed.length < 128; + // Ensures using two-byte len in DER + assert expanded == null || expanded.length > 256 && expanded.length < 60000; + + return switch (type) { + case SEED -> { + assert seed != null; + skOctets = new byte[seed.length + 2]; + skOctets[0] = (byte)0x80; + skOctets[1] = (byte) seed.length; + System.arraycopy(seed, 0, skOctets, 2, seed.length); + yield skOctets; + } + case EXPANDED_KEY -> { + assert expanded != null; + skOctets = new byte[expanded.length + 4]; + skOctets[0] = 0x04; + writeShortLength(skOctets, 1, expanded.length); + System.arraycopy(expanded, 0, skOctets, 4, expanded.length); + yield skOctets; + } + case BOTH -> { + assert seed != null; + assert expanded != null; + skOctets = new byte[10 + seed.length + expanded.length]; + skOctets[0] = 0x30; + writeShortLength(skOctets, 1, 6 + seed.length + expanded.length); + skOctets[4] = 0x04; + skOctets[5] = (byte)seed.length; + System.arraycopy(seed, 0, skOctets, 6, seed.length); + skOctets[6 + seed.length] = 0x04; + writeShortLength(skOctets, 7 + seed.length, expanded.length); + System.arraycopy(expanded, 0, skOctets, 10 + seed.length, expanded.length); + yield skOctets; + } + }; + } + + /** + * Gets the type of input. + * + * @param input input bytes + * @return the type + * @throws InvalidKeyException if input is invalid + */ + public static Type typeOfChoice(byte[] input) throws InvalidKeyException { + if (input.length < 1) { + throw new InvalidKeyException("Empty key"); + } + return switch (input[0]) { + case (byte) 0x80 -> Type.SEED; + case 0x04 -> Type.EXPANDED_KEY; + case 0x30 -> Type.BOTH; + default -> throw new InvalidKeyException("Wrong tag: " + input[0]); + }; + } + + /** + * Splits one of the ML-KEM or ML-DSA private key formats into + * seed and expandedKey, if exists. + * + * @param seedLen correct seed length + * @param input input bytes + * @return a {@code Choice} object. Byte arrays inside are newly allocated + * @throws InvalidKeyException if input is invalid + */ + private static Choice readFromChoice(int seedLen, byte[] input) + throws InvalidKeyException { + if (input.length < seedLen + 2) { + throw new InvalidKeyException("Too short"); + } + return switch (input[0]) { + case (byte) 0x80 -> { + // 80 SEED_LEN + if (input[1] != seedLen && input.length != seedLen + 2) { + throw new InvalidKeyException("Invalid seed"); + } + yield new Choice(Type.SEED, + Arrays.copyOfRange(input, 2, seedLen + 2), null); + } + case 0x04 -> { + // 04 82 nn nn + if (readShortLength(input, 1) != input.length - 4) { + throw new InvalidKeyException("Invalid expandedKey"); + } + yield new Choice(Type.EXPANDED_KEY, + null, Arrays.copyOfRange(input, 4, input.length)); + } + case 0x30 -> { + // 30 82 mm mm 04 SEED_LEN 04 82 nn nn + if (input.length < 6 + seedLen + 4) { + throw new InvalidKeyException("Too short"); + } + if (readShortLength(input, 1) != input.length - 4 + || input[4] != 0x04 + || input[5] != (byte)seedLen + || input[seedLen + 6] != 0x04 + || readShortLength(input, seedLen + 7) + != input.length - 10 - seedLen) { + throw new InvalidKeyException("Invalid both"); + } + yield new Choice(Type.BOTH, + Arrays.copyOfRange(input, 6, 6 + seedLen), + Arrays.copyOfRange(input, seedLen + 10, input.length)); + } + default -> throw new InvalidKeyException("Wrong tag: " + input[0]); + }; + } + + /** + * Reads from any encoding and write to the specified type. + * + * @param type preferred output choice type + * @param pname parameter set name + * @param seedLen seed length + * @param input the input encoding + * @param expander function to calculate expanded from seed, could be null + * if there is already expanded in input + * @return the preferred encoding + * @throws InvalidKeyException if input is invalid or does not have enough + * information to generate the output + */ + public static byte[] choiceToChoice(Type type, String pname, + int seedLen, byte[] input, + BiFunction expander) + throws InvalidKeyException { + var choice = readFromChoice(seedLen, input); + try { + if (type != Type.EXPANDED_KEY && choice.type == Type.EXPANDED_KEY) { + throw new InvalidKeyException( + "key contains not enough info to translate"); + } + var expanded = (choice.expanded == null && type != Type.SEED) + ? expander.apply(pname, choice.seed) + : choice.expanded; + return writeToChoice(type, choice.seed, expanded); + } finally { + if (choice.seed != null) { + Arrays.fill(choice.seed, (byte) 0); + } + if (choice.expanded != null) { + Arrays.fill(choice.expanded, (byte) 0); + } + } + } + + /** + * Reads from any choice of encoding and return the expanded format. + * + * @param pname parameter set name + * @param seedLen seed length + * @param input input encoding + * @param expander function to calculate expanded from seed, could be null + * if there is already expanded in input + * @return the expanded key + * @throws InvalidKeyException if input is invalid + */ + public static byte[] choiceToExpanded(String pname, + int seedLen, byte[] input, + BiFunction expander) + throws InvalidKeyException { + var choice = readFromChoice(seedLen, input); + if (choice.type == Type.BOTH) { + var calculated = expander.apply(pname, choice.seed); + if (!Arrays.equals(choice.expanded, calculated)) { + throw new InvalidKeyException("seed and expandedKey do not match"); + } + Arrays.fill(calculated, (byte)0); + } + try { + if (choice.expanded != null) { + return choice.expanded; + } + return expander.apply(pname, choice.seed); + } finally { + if (choice.seed != null) { + Arrays.fill(choice.seed, (byte)0); + } + } + } + + // Reads a 2 bytes length from DER encoding + private static int readShortLength(byte[] input, int from) + throws InvalidKeyException { + if (input[from] != (byte)0x82) { + throw new InvalidKeyException("Unexpected length"); + } + return ((input[from + 1] & 0xff) << 8) + (input[from + 2] & 0xff); + } + + // Writes a 2 bytes length to DER encoding + private static void writeShortLength(byte[] input, int from, int value) { + input[from] = (byte)0x82; + input[from + 1] = (byte) (value >> 8); + input[from + 2] = (byte) (value); + } +} + diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index 8ada6f1620f17..7a58ac0d4e9ab 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -31,8 +31,6 @@ import java.security.interfaces.*; import java.security.spec.*; import java.util.Arrays; -import java.util.Locale; -import java.util.function.BiFunction; import javax.crypto.SecretKey; import javax.crypto.interfaces.DHKey; import javax.crypto.interfaces.DHPublicKey; @@ -547,152 +545,6 @@ public static AlgorithmId getAlgorithmId(byte[] encoded) throws IOException { throw new IOException("No algorithm detected"); } - /** - * Writes one of the ML-KEM or ML-DSA private key formats. - * - * For example: - * ML-KEM-1024-PrivateKey ::= CHOICE { - * seed [0] OCTET STRING (SIZE (64)), - * expandedKey OCTET STRING (SIZE (3168)), - * both SEQUENCE { - * seed OCTET STRING (SIZE (64)), - * expandedKey OCTET STRING (SIZE (3168)) - * } - * } - * - * This method returns one of the choices depending on the system/security - * property jdk.type.pkcs8.encoding. - * - * @param pname parameter set name - * @param type the type string in property name, "mlkem" or "mldsa" - * @param seed the seed, could be null - * @param expanded the expanded key, could be null - * @param expand function to calculate expanded from seed, could be null - * if there is already expanded provided - * @returns one of the choices, null if seed not provided but the output - * requires it. Note that the expanded key will always be - * generated even if not provided in the input - */ - public static byte[] writeToChoices( - String pname, String type, byte[] seed, byte[] expanded, - BiFunction expand) { - byte[] skOctets; - var prop = SecurityProperties.getOverridableProperty( - "jdk." + type + ".pkcs8.encoding"); - if (prop == null) { - prop = "seed"; - } - - // Ensures using one-byte len in DER - assert seed == null || seed.length < 128; - // Ensures using two-byte len in DER - assert expanded == null || expanded.length > 256 && expanded.length < 60000; - - switch (prop.toLowerCase(Locale.ROOT)) { - case "seed" -> { - if (seed == null) { - return null; - } - skOctets = new byte[seed.length + 2]; - skOctets[0] = (byte)0x80; - skOctets[1] = (byte) seed.length; - System.arraycopy(seed, 0, skOctets, 2, seed.length); - } - case "expandedkey" -> { - if (expanded == null) { - expanded = expand.apply(pname, seed); - } - skOctets = new byte[expanded.length + 4]; - skOctets[0] = 0x04; - writeShortLength(skOctets, 1, expanded.length); - System.arraycopy(expanded, 0, skOctets, 4, expanded.length); - } - case "both" -> { - if (seed == null) { - return null; - } - if (expanded == null) { - expanded = expand.apply(pname, seed); - } - skOctets = new byte[10 + seed.length + expanded.length]; - skOctets[0] = 0x30; - writeShortLength(skOctets, 1, 6 + seed.length + expanded.length); - skOctets[4] = 0x04; - skOctets[5] = (byte)seed.length; - System.arraycopy(seed, 0, skOctets, 6, seed.length); - skOctets[6 + seed.length] = 0x04; - writeShortLength(skOctets, 7 + seed.length, expanded.length); - System.arraycopy(expanded, 0, skOctets, 10 + seed.length, expanded.length); - } - default -> throw new IllegalArgumentException("Unknown format: " + prop); - } - return skOctets; - } - /** - * Splits one of the ML-KEM or ML-DSA private key formats into - * seed and expandedKey, if exists. - * - * @param seedLen correct seed length - * @param input input bytes - * @returns seed and expandedkey, each could be null if not inside - * the input. Results are newly allocated arrays - * @throws InvalidKeyException if input is invalid - */ - public static byte[][] splitChoices(int seedLen, byte[] input) - throws InvalidKeyException { - if (input.length < seedLen + 2) { - throw new InvalidKeyException("Too short"); - } - return switch (input[0]) { - case (byte) 0x80 -> { - // 80 SEED_LEN - if (input[1] != seedLen && input.length != seedLen + 2) { - throw new InvalidKeyException("Invalid seed"); - } - yield new byte[][] { Arrays.copyOfRange(input, 2, seedLen + 2), null }; - } - case 0x04 -> { - // 04 82 nn nn - if (readShortLength(input, 1) != input.length - 4) { - throw new InvalidKeyException("Invalid expandedKey"); - } - yield new byte[][] { null, Arrays.copyOfRange(input, 4, input.length) }; - } - case 0x30 -> { - // 30 82 mm mm 04 SEED_LEN 04 82 nn nn - if (input.length < 6 + seedLen + 4) { - throw new InvalidKeyException("Too short"); - } - if (readShortLength(input, 1) != input.length - 4 - || input[4] != 0x04 - || input[5] != (byte)seedLen - || input[seedLen + 6] != 0x04 - || readShortLength(input, seedLen + 7) - != input.length - 10 - seedLen) { - throw new InvalidKeyException("Invalid both"); - } - yield new byte[][] { - Arrays.copyOfRange(input, 6, 6 + seedLen), - Arrays.copyOfRange(input, seedLen + 10, input.length)}; - } - default -> throw new InvalidKeyException("Wrong tag: " + input[0]); - }; - } - - // Reads a 2 bytes length from DER encoding - private static int readShortLength(byte[] input, int from) throws InvalidKeyException { - if (input[from] != (byte)0x82) { - throw new InvalidKeyException("Unexpected length"); - } - return ((input[from + 1] & 0xff) << 8) + (input[from + 2] & 0xff); - } - - // Writes a 2 bytes length to DER encoding - private static void writeShortLength(byte[] input, int from, int value) { - input[from] = (byte)0x82; - input[from + 1] = (byte) (value >> 8); - input[from + 2] = (byte) (value); - } } diff --git a/test/jdk/sun/security/provider/named/NamedEdDSA.java b/test/jdk/sun/security/provider/named/NamedEdDSA.java index e571ced91b317..be771a9337b12 100644 --- a/test/jdk/sun/security/provider/named/NamedEdDSA.java +++ b/test/jdk/sun/security/provider/named/NamedEdDSA.java @@ -70,11 +70,11 @@ public ProviderImpl() { public static class EdDSASignature extends NamedSignature { public EdDSASignature() { - super("EdDSA", new EdDSAKeyFactory(), "Ed25519", "Ed448"); + super("EdDSA", new EdDSAKeyFactory()); } protected EdDSASignature(String pname) { - super("EdDSA", new EdDSAKeyFactory(pname), pname); + super("EdDSA", new EdDSAKeyFactory(pname)); } public static class Ed25519 extends EdDSASignature { diff --git a/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java index 15ea1829cbd95..9129a050c9ae3 100644 --- a/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java +++ b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java @@ -82,7 +82,7 @@ public static void main(String[] args) throws Exception { checkKeyPair(g.generateKeyPair(), "SHA", "SHA-256"); var pk = new NamedX509Key("sHa", "ShA-256", RAW_PK); - var sk = new NamedPKCS8Key("sHa", "SHa-256", RAW_SK, null); + var sk = NamedPKCS8Key.internalCreate("sHa", "SHa-256", RAW_SK, null); checkKey(pk, "sHa", "ShA-256"); checkKey(sk, "sHa", "SHa-256"); diff --git a/test/jdk/sun/security/provider/named/NamedKeys.java b/test/jdk/sun/security/provider/named/NamedKeys.java index 982f6e51075c8..1ef5a2d126bb6 100644 --- a/test/jdk/sun/security/provider/named/NamedKeys.java +++ b/test/jdk/sun/security/provider/named/NamedKeys.java @@ -43,7 +43,7 @@ public static void main(String[] args) throws Exception { var raw = r.nBytes(32); // Create a key using raw bytes - var sk = new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, null); + var sk = NamedPKCS8Key.internalCreate("SHA", "SHA-256", raw, null); var enc = sk.getEncoded().clone(); // The raw bytes array is re-used @@ -58,8 +58,8 @@ public static void main(String[] args) throws Exception { // No guarantee on getEncoded() output, could be cached // Create a key using encoding - var sk1 = new NamedPKCS8Key("ML-DSA", enc, null); - var sk2 = new NamedPKCS8Key("ML-DSA", enc, null); + var sk1 = new NamedPKCS8Key("SHA", enc, null); + var sk2 = new NamedPKCS8Key("SHA", enc, null); var raw1 = sk1.getRawBytes(); Asserts.assertTrue(raw1 != sk2.getRawBytes()); Asserts.assertTrue(sk1.getEncoded() != sk2.getEncoded()); @@ -71,7 +71,7 @@ public static void main(String[] args) throws Exception { // Same with public key // Create a key using raw bytes raw = r.nBytes(32); - var pk = new NamedX509Key("ML-DSA", "ML-DSA-44", raw); + var pk = new NamedX509Key("SHA", "SHA-256", raw); enc = pk.getEncoded().clone(); // The raw bytes array is re-used @@ -86,8 +86,8 @@ public static void main(String[] args) throws Exception { // No guarantee on getEncoded() output, could be cached // Create a key using encoding - var pk1 = new NamedX509Key("ML-DSA", enc); - var pk2 = new NamedX509Key("ML-DSA", enc); + var pk1 = new NamedX509Key("SHA", enc); + var pk2 = new NamedX509Key("SHA", enc); raw1 = pk1.getRawBytes(); Asserts.assertTrue(raw1 != pk2.getRawBytes()); Asserts.assertTrue(pk1.getEncoded() != pk2.getEncoded()); diff --git a/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java b/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java index 6e172c083aee3..e1f909e022f76 100644 --- a/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java +++ b/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java @@ -90,9 +90,7 @@ static void test(String type, String alg) throws Exception { Asserts.assertEqualsByteArray( test(alg, pk, kf.translateKey(kBoth)).getEncoded(), kSeed.getEncoded()); - Asserts.assertEqualsByteArray( - test(alg, pk, kf.translateKey(kSeed)).getEncoded(), - kSeed.getEncoded()); + Asserts.assertTrue(kf.translateKey(kSeed) == kSeed); Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded)); System.setProperty("jdk." + type + ".pkcs8.encoding", "expandedkey"); @@ -102,18 +100,26 @@ static void test(String type, String alg) throws Exception { Asserts.assertEqualsByteArray( test(alg, pk, kf.translateKey(kSeed)).getEncoded(), kExpanded.getEncoded()); - Asserts.assertEqualsByteArray( - test(alg, pk, kf.translateKey(kExpanded)).getEncoded(), - kExpanded.getEncoded()); + Asserts.assertTrue(kf.translateKey(kExpanded) == kExpanded); System.setProperty("jdk." + type + ".pkcs8.encoding", "both"); - Asserts.assertEqualsByteArray( - test(alg, pk, kf.translateKey(kBoth)).getEncoded(), - kBoth.getEncoded()); + Asserts.assertTrue(kf.translateKey(kBoth) == kBoth); Asserts.assertEqualsByteArray( test(alg, pk, kf.translateKey(kSeed)).getEncoded(), kBoth.getEncoded()); Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded)); + + // The following makes sure key is not mistakenly cleaned during + // translations. + var xk = new PrivateKey() { + public String getAlgorithm() { return alg; } + public String getFormat() { return "PKCS#8"; } + public byte[] getEncoded() { return kBoth.getEncoded(); } + }; + test(alg, pk, xk); + var xk2 = (PrivateKey) kf.translateKey(xk); + test(alg, pk, xk2); + test(alg, pk, xk); } static PrivateKey test(String alg, PublicKey pk, Key k) throws Exception { From 94a7a437240b039d0a7bb0ebde3e3f1ecc9aca4b Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Wed, 30 Jul 2025 11:41:10 -0400 Subject: [PATCH 5/6] combine security properties description; remove one test --- .../share/conf/security/java.security | 44 +- .../security/provider/named/NamedKeys.java | 6 +- .../security/provider/pqc/BadPrivateKeys.java | 460 ------------------ 3 files changed, 16 insertions(+), 494 deletions(-) delete mode 100644 test/jdk/sun/security/provider/pqc/BadPrivateKeys.java diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 373e2c6a382f9..3d27730edcea3 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1576,47 +1576,25 @@ jdk.tls.alpnCharset=ISO_8859_1 jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 # -# The privateKey field for newly created ML-KEM private keys in PKCS #8 +# Newly created ML-KEM and ML-DSA private key formats in PKCS #8 # -# The draft-ietf-lamps-kyber-certificates specification defines three formats -# for an ML-KEM private key: a 64-byte seed, an expanded private key, -# or a sequence containing both. +# The draft-ietf-lamps-kyber-certificates and draft-ietf-lamps-dilithium-certificates +# specifications define three formats for a private key: a seed (64 bytes for ML-KEM, +# 32 bytes for ML-DSA), an expanded private key, or a sequence containing both. # -# Valid values for this property are "seed", "expandedKey", and "both" +# Valid values for these properties are "seed", "expandedKey", and "both" # (case-insensitive). The default is "seed". # -# This property determines the encoding format used when a new keypair is -# generated using a KeyPairGenerator, as well as the output of the translateKey -# method on an existing key using a KeyFactory. +# These properties determine the encoding format used when a new keypair is generated +# using a KeyPairGenerator, as well as the output of the translateKey method on an +# existing key using a ML-KEM or ML-DSA KeyFactory. # # If a system property of the same name is also specified, it supersedes the # security property value defined here. # -# Note: This property is currently used by the SunJCE provider in the JDK -# Reference implementation. It is not guaranteed to be supported by other -# SE implementations. +# Note: These properties are currently used by the SunJCE (for ML-KEM) and SUN +# (for ML-DSA) providers in the JDK Reference implementation. They are not guaranteed +# to be supported by other SE implementations or third-party security providers. # #jdk.mlkem.pkcs8.encoding = seed - -# -# The privateKey field for newly created ML-DSA private keys in PKCS #8 -# -# The draft-ietf-lamps-dilithium-certificates specification defines three formats -# for an ML-DSA private key: a 32-byte seed, an expanded private key, -# or a sequence containing both. -# -# Valid values for this property are "seed", "expandedKey", and "both" -# (case-insensitive). The default is "seed". -# -# This property determines the encoding format used when a new keypair is -# generated using a KeyPairGenerator, as well as the output of the translateKey -# method on an existing key using a KeyFactory. -# -# If a system property of the same name is also specified, it supersedes the -# security property value defined here. -# -# Note: This property is currently used by the SUN provider in the JDK -# Reference implementation. It is not guaranteed to be supported by other -# SE implementations. -# #jdk.mldsa.pkcs8.encoding = seed diff --git a/test/jdk/sun/security/provider/named/NamedKeys.java b/test/jdk/sun/security/provider/named/NamedKeys.java index 1ef5a2d126bb6..c6204902ec45f 100644 --- a/test/jdk/sun/security/provider/named/NamedKeys.java +++ b/test/jdk/sun/security/provider/named/NamedKeys.java @@ -39,12 +39,16 @@ public class NamedKeys { public static void main(String[] args) throws Exception { + // This test uses fictional key algorithms SHA and SHA-256, + // simply because they look like a family name and parameter + // set name and SHA-256 already have its OID defined. + var r = SeededSecureRandom.one(); var raw = r.nBytes(32); // Create a key using raw bytes var sk = NamedPKCS8Key.internalCreate("SHA", "SHA-256", raw, null); - var enc = sk.getEncoded().clone(); + var enc = sk.getEncoded(); // The raw bytes array is re-used Asserts.assertTrue(sk.getRawBytes() == sk.getRawBytes()); diff --git a/test/jdk/sun/security/provider/pqc/BadPrivateKeys.java b/test/jdk/sun/security/provider/pqc/BadPrivateKeys.java deleted file mode 100644 index a7679306672b5..0000000000000 --- a/test/jdk/sun/security/provider/pqc/BadPrivateKeys.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @bug 8347938 8347941 - * @library /test/lib - * @modules java.base/com.sun.crypto.provider - * java.base/sun.security.pkcs - * java.base/sun.security.provider - * java.base/sun.security.util - * @summary ensure bad keys can be detected - * @run main/othervm BadPrivateKeys - */ - -import com.sun.crypto.provider.ML_KEM_Impls; -import jdk.test.lib.Asserts; -import sun.security.pkcs.NamedPKCS8Key; -import sun.security.provider.ML_DSA_Impls; - -import javax.crypto.KEM; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.PrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; -import java.util.stream.Collectors; - -public class BadPrivateKeys { - - public static void main(String[] args) throws Exception { - badkem(); - baddsa(); - } - - static void badkem() throws Exception { - var kf = KeyFactory.getInstance("ML-KEM"); - - // The first ML-KEM-512-PrivateKey example includes the both CHOICE, - // i.e., both seed and expandedKey are included. The seed and expanded - // values can be checked for inconsistencies. - Asserts.assertThrows(InvalidKeySpecException.class, - () -> readKey(kf, BAD_KEM_1)); - - // The second ML-KEM-512-PrivateKey example includes only expandedKey. - // The expanded private key has a mutated s_0 and a valid public key hash, - // but a pairwise consistency check would find that the public key - // fails to match private. - var k2 = readKey(kf, BAD_KEM_2); - var pk2 = ML_KEM_Impls.privKeyToPubKey((NamedPKCS8Key) k2); - var enc = KEM.getInstance("ML-KEM").newEncapsulator(pk2).encapsulate(); - var dk = KEM.getInstance("ML-KEM").newDecapsulator(k2).decapsulate(enc.encapsulation()); - Asserts.assertNotEqualsByteArray(enc.key().getEncoded(), dk.getEncoded()); - - // The third ML-KEM-512-PrivateKey example includes only expandedKey. - // The expanded private key has a mutated H(ek); both a public key - // digest check and a pairwise consistency check should fail. - var k3 = readKey(kf, BAD_KEM_3); - Asserts.assertThrows(InvalidKeyException.class, - () -> KEM.getInstance("ML-KEM").newDecapsulator(k3)); - - // The fourth ML-KEM-512-PrivateKey example includes the both CHOICE, - // i.e., both seed and expandedKey are included. There is mismatch - // of the seed and expanded private key in only the z implicit rejection - // secret; here the private and public vectors match and the pairwise - // consistency check passes, but z is different. - Asserts.assertThrows(InvalidKeySpecException.class, - () -> readKey(kf, BAD_KEM_4)); - } - - static void baddsa() throws Exception { - var kf = KeyFactory.getInstance("ML-DSA"); - - // The first ML-DSA-PrivateKey example includes the both CHOICE, i.e., - // both seed and expandedKey are included. The seed and expanded values - // can be checked for inconsistencies. - Asserts.assertThrows(InvalidKeySpecException.class, - () -> readKey(kf, BAD_DSA_1)); - - // The second ML-DSA-PrivateKey example includes only expandedKey. - // The public key fails to match the tr hash value in the private key. - var k2 = readKey(kf, BAD_DSA_2); - Asserts.assertThrows(IllegalArgumentException.class, - () -> ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k2)); - - // The third ML-DSA-PrivateKey example also includes only expandedKey. - // The private s_1 and s_2 vectors imply a t vector whose private low - // bits do not match the t_0 vector portion of the private key - // (its high bits t_1 are the primary content of the public key). - var k3 = readKey(kf, BAD_DSA_3); - Asserts.assertThrows(IllegalArgumentException.class, - () -> ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k3)); - } - - private static PrivateKey readKey(KeyFactory kf, String input) throws Exception { - var pem = input.lines() - .filter(s -> !s.contains("-----")) - .collect(Collectors.joining()); - return kf.generatePrivate( - new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pem))); - } - - // https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-10#name-examples-of-bad-private-key - static final String BAD_KEM_1 = """ - -----BEGIN PRIVATE KEY----- - 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 - P0A= - -----END PRIVATE KEY-----"""; - - static final String BAD_KEM_2 = """ - -----BEGIN PRIVATE KEY----- - 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 - JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== - -----END PRIVATE KEY-----"""; - - static final String BAD_KEM_3 = """ - -----BEGIN PRIVATE KEY----- - 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 - JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== - -----END PRIVATE KEY-----"""; - - static final String BAD_KEM_4 = """ - -----BEGIN PRIVATE KEY----- - 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 - Pj4= - -----END PRIVATE KEY-----"""; - - // https://datatracker.ietf.org/doc/html/draft-ietf-lamps-dilithium-certificates#name-example-inconsistent-privat - static final String BAD_DSA_1 = """ - -----BEGIN PRIVATE KEY----- - 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 - -----END PRIVATE KEY-----"""; - - static final String BAD_DSA_2 = """ - -----BEGIN PRIVATE KEY----- - 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 - -----END PRIVATE KEY-----"""; - - static final String BAD_DSA_3 = """ - -----BEGIN PRIVATE KEY----- - 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 - -----END PRIVATE KEY-----"""; -} From 281af2c050b07b965c301c969ecf00d1bbbbea21 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Tue, 5 Aug 2025 10:28:25 -0400 Subject: [PATCH 6/6] Ben comment --- .../share/classes/sun/security/provider/ML_DSA.java | 8 ++++---- .../share/classes/sun/security/util/KeyChoices.java | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA.java b/src/java.base/share/classes/sun/security/provider/ML_DSA.java index ace296cbccda3..a55f7d258b169 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -590,13 +590,13 @@ public ML_DSA_PublicKey privKeyToPubKey(ML_DSA_PrivateKey sk) { // take it out of NTT domain later, it was modified for a while. var s1 = deepClone(sk.s1); mlDsaVectorNtt(s1); //s1 now in NTT domain - int[][] As1 = new int[mlDsa_k][ML_DSA_N]; + int[][] As1 = integerMatrixAlloc(mlDsa_k, ML_DSA_N); matrixVectorPointwiseMultiply(As1, keygenA, s1); mlDsaVectorInverseNtt(As1); int[][] t = vectorAddPos(As1, sk.s2); - int[][] t0 = new int[mlDsa_k][ML_DSA_N]; - int[][] t1 = new int[mlDsa_k][ML_DSA_N]; + int[][] t0 = integerMatrixAlloc(mlDsa_k, ML_DSA_N); + int[][] t1 = integerMatrixAlloc(mlDsa_k, ML_DSA_N); power2Round(t, t0, t1); if (!Arrays.deepEquals(t0, sk.t0)) { throw new IllegalArgumentException("t0 does not patch"); diff --git a/src/java.base/share/classes/sun/security/util/KeyChoices.java b/src/java.base/share/classes/sun/security/util/KeyChoices.java index 1487c10178fd4..68f260e443d2d 100644 --- a/src/java.base/share/classes/sun/security/util/KeyChoices.java +++ b/src/java.base/share/classes/sun/security/util/KeyChoices.java @@ -285,4 +285,3 @@ private static void writeShortLength(byte[] input, int from, int value) { input[from + 2] = (byte) (value); } } -