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 9808a0133032e..c35974b14fd87 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 @@ -217,7 +217,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); @@ -227,7 +227,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; @@ -246,7 +247,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( @@ -367,10 +369,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..8352facb5aff7 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 @@ -29,7 +29,9 @@ import sun.security.provider.NamedKEM; import sun.security.provider.NamedKeyFactory; import sun.security.provider.NamedKeyPairGenerator; +import sun.security.util.DerValue; +import java.io.IOException; import java.security.*; import java.util.Arrays; @@ -37,6 +39,12 @@ public final class ML_KEM_Impls { + public static byte[] seedToTransformed(String pname, byte[] seed) { + return new ML_KEM(pname).generateKemKeyPair(seed) + .decapsulationKey() + .keyBytes(); + } + public sealed static class KPG extends NamedKeyPairGenerator permits KPG2, KPG3, KPG5 { @@ -51,22 +59,16 @@ protected KPG(String pname) { @Override protected byte[][] implGenerateKeyPair(String name, SecureRandom random) { - byte[] seed = new byte[32]; + byte[] seedAndZ = new byte[64]; var r = random != null ? random : JCAUtil.getDefSecureRandom(); - r.nextBytes(seed); - byte[] z = new byte[32]; - r.nextBytes(z); + r.nextBytes(seedAndZ); ML_KEM mlKem = new ML_KEM(name); ML_KEM.ML_KEM_KeyPair kp; - try { - kp = mlKem.generateKemKeyPair(seed, z); - } finally { - Arrays.fill(seed, (byte)0); - Arrays.fill(z, (byte)0); - } - return new byte[][] { + kp = mlKem.generateKemKeyPair(seedAndZ); + return new byte[][]{ kp.encapsulationKey().keyBytes(), + seedAndZ, kp.decapsulationKey().keyBytes() }; } @@ -97,6 +99,21 @@ public KF() { public KF(String name) { super("ML-KEM", name); } + + @Override + protected byte[] implTransform(String name, byte[] input) { + if (input.length == 64) { // seed + return seedToTransformed(name, input); + } else if (input.length > 1 && input[0] == 4) { // jdk24 + try { + return new DerValue(input).getOctetString(); + } catch (IOException e) { + return null; + } + } else { + return null; + } + } } public final static class KF2 extends KF { @@ -183,11 +200,11 @@ protected Object implCheckPrivateKey(String name, byte[] 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); + super("ML-KEM", new KF(name), name); } } 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..5a7b0eccbbd43 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,8 +25,6 @@ package sun.security.pkcs; -import sun.security.util.DerInputStream; -import sun.security.util.DerValue; import sun.security.x509.AlgorithmId; import javax.security.auth.DestroyFailedException; @@ -39,6 +37,7 @@ import java.security.ProviderException; import java.security.spec.NamedParameterSpec; import java.util.Arrays; +import java.util.function.BiFunction; /// Represents a private key from an algorithm family that is specialized /// with a named parameter set. @@ -50,6 +49,30 @@ /// 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 optionally supports a +/// transformed format stored in [#transformed]. While `key` always represents +/// the base format used for encoding, an algorithm may perform pre-computation +/// to derive a transformed format, which may accelerate future operations. +/// The transformed format must be self-sufficient for cryptographic +/// computations without requiring the base format. +/// +/// 1. If only `key` is present, it is used for both encoding and computations. +/// 2. If both `key` and `transformed` are available, `key` is used for encoding, +/// and `transformed` is used for computations. +/// +/// For algorithms that do not define a transformed key format, only `key` is +/// included, and `transformed` must be `null`. +/// +/// Note: When a transformed format is not defined, `key` and `transformed` +/// may hold the same value. However, subtle differences can arise depending +/// on if they are the same object. To avoid ambiguity, always set `transformed` +/// to `null`. +/// +/// The encoding in `NamedPKCS8Key` differs from that of XDH and EdDSA keys. +/// While `key` is always placed inside an `OneAsymmetricKey` structure as an +/// OCTET STRING , for XDH and EdDSA, the `key` field itself is an OCTET STRING. +/// `NamedPKCS8Key` treats `key` as a generic opaque byte array. +/// /// @see sun.security.provider.NamedKeyPairGenerator public final class NamedPKCS8Key extends PKCS8Key { @Serial @@ -57,42 +80,50 @@ public final class NamedPKCS8Key extends PKCS8Key { private final String fname; private final transient NamedParameterSpec paramSpec; - private final byte[] rawBytes; + private final transient byte[] transformed; 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. + /// + /// `rawBytes` and `transformed` won't be cloned, caller + /// must relinquish ownership. + /// + /// @param fname family name + /// @param pname parameter set name + /// @param rawBytes raw key bytes + /// @param transformed transformed key format, can be `null`. + public NamedPKCS8Key(String fname, String pname, byte[] rawBytes, byte[] transformed) { this.fname = fname; this.paramSpec = new NamedParameterSpec(pname); + this.transformed = transformed; 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(); - } + this.key = rawBytes; } - /// 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 transform a function that is able to calculate the transformed + /// format from the base format inside `encoded`. If it recognizes + /// the input already in transformed format, it must return `null`. + /// If there is no transformed key format, `transform` must be `null`. + /// Whatever the case, the ownership of the result is fully granted + /// to this `NamedPKCS8Key` object. + public NamedPKCS8Key(String fname, byte[] encoded, + BiFunction transform) 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.transformed = transform == null ? null : transform.apply(algid.getName(), this.key); + paramSpec = new NamedParameterSpec(algid.getName()); + if (algid.getEncodedParams() != null) { + throw new InvalidKeyException("algorithm identifier has params"); } } @@ -104,9 +135,16 @@ 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 that will be used in computations. + /// Caller must not modify the content or pass the reference to untrusted + /// application code. + public byte[] getTransformed() { + return transformed == null ? key : transformed; } @Override @@ -128,8 +166,10 @@ private void readObject(ObjectInputStream stream) @Override public void destroy() throws DestroyFailedException { - Arrays.fill(rawBytes, (byte)0); Arrays.fill(key, (byte)0); + if (transformed != null) { + Arrays.fill(transformed, (byte)0); + } if (encodedKey != null) { Arrays.fill(encodedKey, (byte)0); } 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..c2a059361021e 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,24 @@ package sun.security.provider; import sun.security.jca.JCAUtil; +import sun.security.util.DerValue; + +import java.io.IOException; import java.security.*; import java.security.SecureRandom; -import java.util.Arrays; public class ML_DSA_Impls { + public static byte[] seedToTransformed(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 enum Version { DRAFT, FINAL } @@ -78,11 +90,11 @@ protected byte[][] implGenerateKeyPair(String name, SecureRandom sr) { try { return new byte[][]{ mlDsa.pkEncode(kp.publicKey()), + seed, mlDsa.skEncode(kp.privateKey()) }; } finally { kp.privateKey().destroy(); - Arrays.fill(seed, (byte)0); } } } @@ -112,6 +124,21 @@ public KF() { public KF(String name) { super("ML-DSA", name); } + + @Override + protected byte[] implTransform(String name, byte[] input) { + if (input.length == 32) { // seed + return seedToTransformed(name, input); + } else if (input.length > 1 && input[0] == 4) { // jdk24 + try { + return new DerValue(input).getOctetString(); + } catch (IOException e) { + return null; + } + } else { + return null; + } + } } public final static class KF2 extends KF { @@ -134,10 +161,10 @@ 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); + super("ML-DSA", new KF(name), name); } @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 2731b3460af3b..c129d61156d6e 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.engineTranslateKey(publicKey); var pk = nk.getRawBytes(); return getKeyConsumerImpl(this, nk.getParams(), pk, implCheckPublicKey(nk.getParams().getName(), pk), secureRandom); @@ -92,9 +94,8 @@ 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.engineTranslateKey(privateKey); + var sk = nk.getTransformed(); return getKeyConsumerImpl(this, nk.getParams(), sk, implCheckPrivateKey(nk.getParams().getName(), sk), 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..fd01b885aefba 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] @@ -129,24 +128,16 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) } } else if (keySpec instanceof RawKeySpec rks) { if (pnames.length == 1) { - var bytes = rks.getKeyArr(); - try { - return new NamedPKCS8Key(fname, pnames[0], bytes); - } finally { - Arrays.fill(bytes, (byte) 0); - } + var raw = rks.getKeyArr(); + return new NamedPKCS8Key(fname, pnames[0], raw, implTransform(pnames[0], raw)); } else { throw new InvalidKeySpecException("Parameter set name unavailable"); } } else if (keySpec instanceof EncodedKeySpec espec && espec.getFormat().equalsIgnoreCase("RAW")) { if (pnames.length == 1) { - var bytes = espec.getEncoded(); - try { - return new NamedPKCS8Key(fname, pnames[0], bytes); - } finally { - Arrays.fill(bytes, (byte) 0); - } + var raw = espec.getEncoded(); + return new NamedPKCS8Key(fname, pnames[0], raw, implTransform(pnames[0], raw)); } else { throw new InvalidKeySpecException("Parameter set name unavailable"); } @@ -157,7 +148,7 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) private PrivateKey fromPKCS8(byte[] bytes) throws InvalidKeyException, InvalidKeySpecException { - var k = new NamedPKCS8Key(fname, bytes); + var k = new NamedPKCS8Key(fname, bytes, this::implTransform); checkName(k.getParams().getName()); return k; } @@ -260,9 +251,10 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { name = 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, name, raw, implTransform(name, raw)) + : new NamedX509Key(fname, name, raw); } else { throw new InvalidKeyException("Unsupported key type: " + key.getClass()); } @@ -285,4 +277,17 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { throw new InvalidKeyException("Unsupported key format: " + key.getFormat()); } } + + /// User-defined function to generate the transformed format of + /// a [NamedPKCS8Key]. + /// + /// This method is called when the key factory is constructing a private + /// key. If `input` is in base format, the transformed key must be returned. + /// If `input` is in transformed format, `null` must be returned. Whatever + /// the case, the ownership of the result is fully granted to the caller. + /// + /// The default implementation returns `null`. + protected byte[] implTransform(String name, byte[] input) { + return null; + } } 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..f83bc64a60f42 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. @@ -95,6 +94,19 @@ /// classes should not be considered immutable, strictly adhering to the rules /// above will ensure data integrity is maintained. /// +/// For some algorithms, the private key is encoded in a base format, but the +/// computations use a transformed format. The [#implGenerateKeyPair] would +/// return the base format as the 2nd element and the transformed format as the +/// 3rd element. All computations inside `NamedKEM` or `NamedSignature` use the +/// transformed format. The `NamedKeyFactory` must override the `implTransform` +/// method to compute the transformed format from the base format. +/// +/// If `implTransform` detects that the input is already in the transformed +/// format, it indicates that the base format may have been lost, and it must +/// return `null`. For maximum interoperability, it may also convert other +/// formats into the transformed format. Note that `key` remains unchanged, +/// so when re-encoded, it uses its original encoding format. +/// /// Note: A limitation of `NamedKeyPairGenerator` and `NamedKeyFactory` is /// that the keys generated by their implementations will always be of type /// `NamedX509Key` or `NamedPKCS8Key`. Existing implementations of algorithms @@ -164,14 +176,18 @@ 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])); + new NamedPKCS8Key(fname, pname, keys[1], + keys.length == 2 ? null : 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, and the transformed + /// format of the private key (in this order) in raw bytes. + /// If there is no transformed 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/src/java.base/share/classes/sun/security/provider/NamedSignature.java b/src/java.base/share/classes/sun/security/provider/NamedSignature.java index 921a39cfc926d..f840ebd09d711 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,6 +50,7 @@ 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(); @@ -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,13 +77,13 @@ 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); + var nk = (NamedX509Key) fac.engineTranslateKey(publicKey); name = nk.getParams().getName(); pubKey = nk.getRawBytes(); pk2 = implCheckPublicKey(name, pubKey); @@ -92,10 +94,9 @@ 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); + var nk = (NamedPKCS8Key) fac.engineTranslateKey(privateKey); name = nk.getParams().getName(); - secKey = nk.getRawBytes(); + secKey = nk.getTransformed(); sk2 = implCheckPrivateKey(name, secKey); pubKey = null; bout.reset(); 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/test/jdk/sun/security/provider/acvp/Launcher.java b/test/jdk/sun/security/provider/acvp/Launcher.java index 1405847de5d14..79d3717e13b6e 100644 --- a/test/jdk/sun/security/provider/acvp/Launcher.java +++ b/test/jdk/sun/security/provider/acvp/Launcher.java @@ -39,6 +39,8 @@ * @bug 8342442 8345057 * @library /test/lib * @modules java.base/sun.security.provider + * java.base/com.sun.crypto.provider + * @run main/othervm Launcher */ /// This test runs on `internalProjection.json`-style files generated by NIST's 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..d5cf559314a7d 100644 --- a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java +++ b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java @@ -73,7 +73,8 @@ static void keyGenTest(JSONValue kat, Provider p) throws Exception { 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.seedToTransformed(pname, sk)); } System.out.println(); } 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..657e4c3e33314 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,6 +20,7 @@ * 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; @@ -71,7 +72,9 @@ static void keyGenTest(JSONValue kat, Provider p) throws Exception { 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.seedToTransformed(pname, sk)); } System.out.println(); } diff --git a/test/jdk/sun/security/provider/NamedEdDSA.java b/test/jdk/sun/security/provider/named/NamedEdDSA.java similarity index 91% rename from test/jdk/sun/security/provider/NamedEdDSA.java rename to test/jdk/sun/security/provider/named/NamedEdDSA.java index 4d0e3e9228aac..ac4c149a16bdc 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 { @@ -87,7 +91,11 @@ 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); + try { + return getOps(name).sign(plain, new DerValue(sk).getOctetString(), msg); + } catch (IOException e) { + throw new SignatureException(e); + } } @Override @@ -157,7 +165,9 @@ 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()}; } 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 86% rename from test/jdk/sun/security/provider/NamedKeyFactoryTest.java rename to test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java index 1ca179bc04690..ffb224ee5a1ad 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, null); 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 @@ -220,15 +233,19 @@ public static class KF extends NamedKeyFactory { 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 { @@ -243,8 +260,8 @@ 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); + out[0] = name.endsWith("256") ? Arrays.copyOf(RAW_PK, 8) : RAW_PK; + out[1] = name.endsWith("256") ? Arrays.copyOf(RAW_SK, 8) : RAW_SK; 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..982f6e51075c8 --- /dev/null +++ b/test/jdk/sun/security/provider/named/NamedKeys.java @@ -0,0 +1,99 @@ +/* + * 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); + + // Create a key using raw bytes + var sk = new NamedPKCS8Key("ML-DSA", "ML-DSA-44", raw, null); + 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 + 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()); + + 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 + raw = r.nBytes(32); + var pk = new NamedX509Key("ML-DSA", "ML-DSA-44", raw); + enc = 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", enc); + var pk2 = new NamedX509Key("ML-DSA", enc); + raw1 = pk1.getRawBytes(); + Asserts.assertTrue(raw1 != pk2.getRawBytes()); + Asserts.assertTrue(pk1.getEncoded() != pk2.getEncoded()); + + encCopy = enc.clone(); // store a copy + Arrays.fill(enc, (byte)0); // clean the source and the key unchanged + Asserts.assertEqualsByteArray(encCopy, pk1.getEncoded()); + } +} diff --git a/test/jdk/sun/security/provider/pqc/SeedOrTransformed.java b/test/jdk/sun/security/provider/pqc/SeedOrTransformed.java new file mode 100644 index 0000000000000..70beee8bef1b2 --- /dev/null +++ b/test/jdk/sun/security/provider/pqc/SeedOrTransformed.java @@ -0,0 +1,151 @@ +/* + * 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 SeedOrTransformed + */ + +import com.sun.crypto.provider.ML_KEM_Impls; +import jdk.test.lib.Asserts; +import jdk.test.lib.security.FixedSecureRandom; +import jdk.test.lib.security.SeededSecureRandom; +import sun.security.provider.ML_DSA_Impls; +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; +import sun.security.util.KnownOIDs; +import sun.security.util.ObjectIdentifier; + +import javax.crypto.KEM; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; + +public class SeedOrTransformed { + + static final SeededSecureRandom RAND = SeededSecureRandom.one(); + + public static void main(String[] args) throws Exception { + test("ML-KEM-768"); + test("ML-DSA-65"); + } + + static void test(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 encSeed = kp.getPrivate().getEncoded(); + + var ex = alg.contains("ML-KEM") + ? ML_KEM_Impls.seedToTransformed(alg, seed) + : ML_DSA_Impls.seedToTransformed(alg, seed); + var encTransformed = new DerOutputStream().write(DerValue.tag_Sequence, + new DerOutputStream().putInteger(0) + .write(DerValue.tag_Sequence, new DerOutputStream() + .putOID(ObjectIdentifier.of(KnownOIDs.findMatch(alg)))) + .putOctetString(ex)) + .toByteArray(); + var enc24 = new DerOutputStream().write(DerValue.tag_Sequence, + new DerOutputStream().putInteger(0) + .write(DerValue.tag_Sequence, new DerOutputStream() + .putOID(ObjectIdentifier.of(KnownOIDs.findMatch(alg)))) + .putOctetString( + new DerOutputStream().putOctetString(ex).toByteArray())) + .toByteArray(); + + // Seed encoding is usually shorter than transformed + Asserts.assertTrue(encSeed.length < encTransformed.length); + Asserts.assertEqualsByteArray( // ... and encoding ends with seed + Arrays.copyOfRange(encSeed, encSeed.length - seed.length, encSeed.length), + seed); + + // Key loading + + var f = KeyFactory.getInstance(alg); + var sk1 = f.generatePrivate(new PKCS8EncodedKeySpec(encSeed)); + var sk2 = f.generatePrivate(new PKCS8EncodedKeySpec(encTransformed)); + var sk24 = f.generatePrivate(new PKCS8EncodedKeySpec(enc24)); + var sk3 = f.translateKey(new PrivateKey() { + public String getAlgorithm() { return alg; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return seed.clone(); } + }); + var sk4 = f.translateKey(new PrivateKey() { + public String getAlgorithm() { return alg; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return ex.clone(); } + }); + // Key factory never tries to reformat keys + Asserts.assertEqualsByteArray(sk1.getEncoded(), encSeed); + Asserts.assertEqualsByteArray(sk2.getEncoded(), encTransformed); + Asserts.assertEqualsByteArray(sk24.getEncoded(), enc24); + Asserts.assertEqualsByteArray(sk3.getEncoded(), encSeed); + Asserts.assertEqualsByteArray(sk4.getEncoded(), encTransformed); + + // Key using + + if (alg.contains("ML-KEM")) { + var kem = KEM.getInstance("ML-KEM"); + var e = kem.newEncapsulator(kp.getPublic(), RAND); + var enc = e.encapsulate(); + var k1 = kem.newDecapsulator(sk1).decapsulate(enc.encapsulation()); + var k2 = kem.newDecapsulator(sk2).decapsulate(enc.encapsulation()); + var k24 = kem.newDecapsulator(sk24).decapsulate(enc.encapsulation()); + Asserts.assertEqualsByteArray(k1.getEncoded(), k2.getEncoded()); + Asserts.assertEqualsByteArray(k1.getEncoded(), k24.getEncoded()); + 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(sk1, new FixedSecureRandom(rnd)); + s.update(msg); + var sig1 = s.sign(); + s.initSign(sk2, new FixedSecureRandom(rnd)); + s.update(msg); + var sig2 = s.sign(); + s.initSign(sk24, new FixedSecureRandom(rnd)); + s.update(msg); + var sig24 = s.sign(); + Asserts.assertEqualsByteArray(sig1, sig2); + Asserts.assertEqualsByteArray(sig1, sig24); + s.initVerify(kp.getPublic()); + s.update(msg); + Asserts.assertTrue(s.verify(sig1)); + } + } +} 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) {