mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-04 23:48:33 +00:00
8347938: Add Support for the Latest ML-KEM and ML-DSA Private Key Encodings
Reviewed-by: mullan, bperez, mpowers
This commit is contained in:
parent
99bc98357d
commit
e51ccef9cb
@ -498,7 +498,7 @@ public final class ML_KEM {
|
||||
/*
|
||||
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 @@ public final class ML_KEM {
|
||||
}
|
||||
|
||||
//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 @@ public final class ML_KEM {
|
||||
// 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 @@ public final class ML_KEM {
|
||||
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 @@ public final class ML_KEM {
|
||||
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);
|
||||
|
||||
@ -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.KeyChoices;
|
||||
import sun.security.x509.NamedX509Key;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
@ -37,6 +40,20 @@ import javax.crypto.DecapsulateException;
|
||||
|
||||
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,27 @@ public final class ML_KEM_Impls {
|
||||
}
|
||||
|
||||
@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(),
|
||||
KeyChoices.writeToChoice(
|
||||
KeyChoices.getPreferred("mlkem"),
|
||||
seed, expanded),
|
||||
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 +113,39 @@ public final class ML_KEM_Impls {
|
||||
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 {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,15 +171,15 @@ public final class ML_KEM_Impls {
|
||||
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.ML_KEM_EncapsulateResult mlKemEncapsulateResult = null;
|
||||
ML_KEM mlKem = new ML_KEM(pname);
|
||||
ML_KEM.ML_KEM_EncapsulateResult mlKemEncapsulateResult;
|
||||
try {
|
||||
mlKemEncapsulateResult = mlKem.encapsulate(
|
||||
new ML_KEM.ML_KEM_EncapsulationKey(
|
||||
@ -145,49 +195,49 @@ public final class ML_KEM_Impls {
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
public K(String name) {
|
||||
super("ML-KEM", name);
|
||||
public K(String pname) {
|
||||
super("ML-KEM", new KF(pname));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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.NoSuchAlgorithmException;
|
||||
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,28 @@ import java.util.Arrays;
|
||||
/// identifier in the PKCS #8 encoding of the key is always a single OID derived
|
||||
/// from the parameter set name.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// If the `expanded` field is required by the algorithm, it is either
|
||||
/// [calculated from the PKCS #8 encoding][#NamedPKCS8Key(String, byte\[\], Expander)],
|
||||
/// or [provided directly][#internalCreate(String, String, byte\[\], byte\[\])].
|
||||
/// In the latter case, the caller must ensure the consistency of the `encoded`
|
||||
/// and `expanded` arguments. For example, seed and expanded key must match.
|
||||
///
|
||||
/// @see sun.security.provider.NamedKeyPairGenerator
|
||||
public final class NamedPKCS8Key extends PKCS8Key {
|
||||
@Serial
|
||||
@ -57,42 +77,64 @@ 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 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`.
|
||||
private NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded) {
|
||||
this.fname = fname;
|
||||
this.paramSpec = new NamedParameterSpec(pname);
|
||||
this.expanded = expanded;
|
||||
this.privKeyMaterial = 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.privKeyMaterial = 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 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
|
||||
/// @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`. 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;
|
||||
try {
|
||||
paramSpec = new NamedParameterSpec(algid.getName());
|
||||
if (algid.getEncodedParams() != null) {
|
||||
throw new InvalidKeyException("algorithm identifier has params");
|
||||
}
|
||||
rawBytes = new DerInputStream(privKeyMaterial).getOctetString();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeyException("Cannot parse input", e);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,9 +146,15 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
||||
}
|
||||
|
||||
/// 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 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 == null ? privKeyMaterial : expanded;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -127,9 +175,11 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws DestroyFailedException {
|
||||
Arrays.fill(rawBytes, (byte)0);
|
||||
public void destroy() {
|
||||
Arrays.fill(privKeyMaterial, (byte)0);
|
||||
if (expanded != null) {
|
||||
Arrays.fill(expanded, (byte)0);
|
||||
}
|
||||
if (encodedKey != null) {
|
||||
Arrays.fill(encodedKey, (byte)0);
|
||||
}
|
||||
@ -140,4 +190,17 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
||||
public boolean isDestroyed() {
|
||||
return destroyed;
|
||||
}
|
||||
|
||||
/// Expands from encoding format to expanded format.
|
||||
@FunctionalInterface
|
||||
public interface Expander {
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -568,6 +568,54 @@ public class ML_DSA {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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 = integerMatrixAlloc(mlDsa_k, ML_DSA_N);
|
||||
matrixVectorPointwiseMultiply(As1, keygenA, s1);
|
||||
|
||||
mlDsaVectorInverseNtt(As1);
|
||||
int[][] t = vectorAddPos(As1, sk.s2);
|
||||
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");
|
||||
}
|
||||
|
||||
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
|
||||
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);
|
||||
|
||||
@ -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.KeyChoices;
|
||||
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 class ML_DSA_Impls {
|
||||
// --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,26 @@ public class ML_DSA_Impls {
|
||||
}
|
||||
|
||||
@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())
|
||||
KeyChoices.writeToChoice(
|
||||
KeyChoices.getPreferred("mldsa"),
|
||||
seed, expanded),
|
||||
expanded
|
||||
};
|
||||
} finally {
|
||||
kp.privateKey().destroy();
|
||||
Arrays.fill(seed, (byte)0);
|
||||
Arrays.fill(seed, (byte) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,8 +138,39 @@ public class ML_DSA_Impls {
|
||||
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 {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,16 +194,16 @@ public class ML_DSA_Impls {
|
||||
|
||||
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());
|
||||
}
|
||||
public SIG(String name) {
|
||||
super("ML-DSA", name);
|
||||
public SIG(String pname) {
|
||||
super("ML-DSA", new KF(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 +220,10 @@ public class ML_DSA_Impls {
|
||||
}
|
||||
|
||||
@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 +236,18 @@ public class ML_DSA_Impls {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.SecureRandom;
|
||||
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]
|
||||
@ -50,21 +49,19 @@ import java.util.Objects;
|
||||
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 pnames the standard parameter set names, at least one is needed.
|
||||
protected NamedKEM(String fname, String... pnames) {
|
||||
/// @param fac the `KeyFactory` used to translate foreign keys and
|
||||
/// perform key validation
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,8 +73,7 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
"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 +88,15 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
"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 +105,7 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
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 +116,7 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
|
||||
@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 +141,46 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
|
||||
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 +191,11 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
///
|
||||
/// 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 +208,11 @@ public abstract class NamedKEM implements KEMSpi {
|
||||
///
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.NamedParameterSpec;
|
||||
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 @@ import java.util.Objects;
|
||||
///
|
||||
/// 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,92 +77,110 @@ public class NamedKeyFactory extends KeyFactorySpi {
|
||||
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
|
||||
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 bytes = rks.getKeyArr();
|
||||
return switch (keySpec) {
|
||||
case PKCS8EncodedKeySpec pspec -> {
|
||||
var bytes = pspec.getEncoded();
|
||||
try {
|
||||
return new NamedPKCS8Key(fname, pnames[0], bytes);
|
||||
yield fromPKCS8(bytes);
|
||||
} catch (InvalidKeyException 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 bytes = espec.getEncoded();
|
||||
try {
|
||||
return new NamedPKCS8Key(fname, pnames[0], bytes);
|
||||
} finally {
|
||||
Arrays.fill(bytes, (byte) 0);
|
||||
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)
|
||||
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 +201,7 @@ public class NamedKeyFactory extends KeyFactorySpi {
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
try {
|
||||
key = engineTranslateKey(key);
|
||||
key = toNamedKey(key);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidKeySpecException(e);
|
||||
}
|
||||
@ -225,6 +242,12 @@ public class NamedKeyFactory extends KeyFactorySpi {
|
||||
|
||||
@Override
|
||||
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
|
||||
// The base toNamedKey only makes sure key is translated into a NamedKey.
|
||||
// the key material is still the same as the input.
|
||||
return toNamedKey(key);
|
||||
}
|
||||
|
||||
protected Key toNamedKey(Key key) throws InvalidKeyException {
|
||||
if (key == null) {
|
||||
throw new InvalidKeyException("Key must not be null");
|
||||
}
|
||||
@ -242,27 +265,28 @@ public class NamedKeyFactory extends KeyFactorySpi {
|
||||
} 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());
|
||||
? fromRaw(pname, raw)
|
||||
: new NamedX509Key(fname, pname, raw);
|
||||
} else {
|
||||
throw new InvalidKeyException("Unsupported key type: " + key.getClass());
|
||||
}
|
||||
@ -270,19 +294,26 @@ public class NamedKeyFactory extends KeyFactorySpi {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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.ProviderException;
|
||||
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 @@ import java.util.Objects;
|
||||
/// 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)`, 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
|
||||
/// and output array argument. Precisely, the implementation must not retain
|
||||
@ -69,8 +74,8 @@ import java.util.Objects;
|
||||
/// 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,34 @@ import java.util.Objects;
|
||||
/// (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
|
||||
/// [key material][NamedPKCS8Key#getRawBytes()] inside a PKCS #8 file. 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
|
||||
/// [RFC 9881](https://datatracker.ietf.org/doc/html/rfc9881#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. 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` 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 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.
|
||||
///
|
||||
/// 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 +138,9 @@ import java.util.Objects;
|
||||
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 +159,22 @@ public abstract class NamedKeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
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 +194,21 @@ public abstract class NamedKeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
|
||||
@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]),
|
||||
NamedPKCS8Key.internalCreate(fname, tmpName, 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 in its encoding format, and
|
||||
/// the private key in its expanded format (in this order) in
|
||||
/// 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);
|
||||
}
|
||||
|
||||
@ -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.SecureRandom;
|
||||
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]
|
||||
@ -50,12 +49,12 @@ import java.util.Objects;
|
||||
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,26 +64,23 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
/// Creates a new `NamedSignature` object.
|
||||
///
|
||||
/// @param fname the family name
|
||||
/// @param pnames the standard parameter set names, at least one is needed.
|
||||
protected NamedSignature(String fname, String... pnames) {
|
||||
/// @param fac the `KeyFactory` used to translate foreign keys and
|
||||
/// perform key validation
|
||||
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;
|
||||
}
|
||||
|
||||
@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 +88,10 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
@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 +111,7 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
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,21 +122,21 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
@ -162,7 +157,7 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
|
||||
/// 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 +165,12 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
/// @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 +178,7 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
/// @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 +190,11 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
///
|
||||
/// 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 +207,11 @@ public abstract class NamedSignature extends SignatureSpi {
|
||||
///
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
289
src/java.base/share/classes/sun/security/util/KeyChoices.java
Normal file
289
src/java.base/share/classes/sun/security/util/KeyChoices.java
Normal file
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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:
|
||||
* <pre>
|
||||
* 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))
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* This class supports reading, writing, and converting between them.
|
||||
* <p>
|
||||
* Current code follows draft-ietf-lamps-kyber-certificates-11 and RFC 9881.
|
||||
*/
|
||||
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.<name>.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.
|
||||
* <p>
|
||||
* 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 <SEED_LEN of seed>
|
||||
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 <nn of expandedKey>
|
||||
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 <SEED_LEN of seed> 04 82 nn nn <nn of expandedKey>
|
||||
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<String, byte[], byte[]> 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<String, byte[], byte[]> 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);
|
||||
}
|
||||
}
|
||||
@ -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 final class NamedX509Key extends X509Key {
|
||||
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);
|
||||
|
||||
@ -1700,3 +1700,28 @@ jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128
|
||||
# com.sun.security.allowedAIALocations=http://some.company.com/cacert \
|
||||
# ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary
|
||||
com.sun.security.allowedAIALocations=
|
||||
|
||||
#
|
||||
# PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys
|
||||
#
|
||||
# draft-ietf-lamps-kyber-certificates-11 and RFC 9881 define three possible 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.
|
||||
#
|
||||
# The following security properties determine the encoding format used when a
|
||||
# new keypair is generated with a KeyPairGenerator, and the output of the
|
||||
# translateKey method on an existing key using a ML-KEM or ML-DSA KeyFactory.
|
||||
#
|
||||
# Valid values for these properties are "seed", "expandedKey", and "both"
|
||||
# (case-insensitive). The default is "seed".
|
||||
#
|
||||
# If a system property of the same name is also specified, it supersedes the
|
||||
# security property value defined here.
|
||||
#
|
||||
# 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 implementations or third-party security
|
||||
# providers.
|
||||
#
|
||||
#jdk.mlkem.pkcs8.encoding = seed
|
||||
#jdk.mldsa.pkcs8.encoding = seed
|
||||
|
||||
@ -41,6 +41,8 @@ import java.util.zip.ZipFile;
|
||||
* ML_DSA_Impls.version might be modified
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.security.provider
|
||||
* java.base/sun.security.util
|
||||
* java.base/com.sun.crypto.provider
|
||||
* @run main/othervm/timeout=480 Launcher
|
||||
*/
|
||||
|
||||
@ -50,6 +52,8 @@ import java.util.zip.ZipFile;
|
||||
* @summary Test verifying the intrinsic implementation.
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.security.provider
|
||||
* java.base/sun.security.util
|
||||
* java.base/com.sun.crypto.provider
|
||||
* @run main/othervm/timeout=480 -Xcomp Launcher
|
||||
*/
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import jdk.test.lib.Asserts;
|
||||
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 @@ public class ML_DSA_Test {
|
||||
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 @@ public class ML_DSA_Test {
|
||||
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 @@ public class ML_DSA_Test {
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
@ -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 @@ public class ML_KEM_Test {
|
||||
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 @@ public class ML_KEM_Test {
|
||||
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 @@ public class ML_KEM_Test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] oct(byte[] in) {
|
||||
return new DerOutputStream().putOctetString(in).toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2026, 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
|
||||
* @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.jca.JCAUtil;
|
||||
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 class NamedEdDSA {
|
||||
|
||||
public static class EdDSASignature extends NamedSignature {
|
||||
public EdDSASignature() {
|
||||
super("EdDSA", "Ed25519", "Ed448");
|
||||
super("EdDSA", new EdDSAKeyFactory());
|
||||
}
|
||||
|
||||
protected EdDSASignature(String pname) {
|
||||
super("EdDSA", pname);
|
||||
super("EdDSA", new EdDSAKeyFactory(pname));
|
||||
}
|
||||
|
||||
public static class Ed25519 extends EdDSASignature {
|
||||
@ -86,22 +90,32 @@ public class NamedEdDSA {
|
||||
}
|
||||
|
||||
@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 class NamedEdDSA {
|
||||
// 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) {
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2026, 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
|
||||
* @modules java.base/sun.security.x509
|
||||
* java.base/sun.security.pkcs
|
||||
* java.base/sun.security.provider
|
||||
@ -41,10 +41,13 @@ import sun.security.x509.NamedX509Key;
|
||||
|
||||
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 class NamedKeyFactoryTest {
|
||||
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 = NamedPKCS8Key.internalCreate("sHa", "SHa-256", RAW_SK, null);
|
||||
checkKey(pk, "sHa", "ShA-256");
|
||||
checkKey(sk, "sHa", "SHa-256");
|
||||
|
||||
@ -134,25 +137,27 @@ public class NamedKeyFactoryTest {
|
||||
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 class NamedKeyFactoryTest {
|
||||
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 @@ public class NamedKeyFactoryTest {
|
||||
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,24 @@ public class NamedKeyFactoryTest {
|
||||
public KF() {
|
||||
super("SHA", "SHA-256", "SHA-512");
|
||||
}
|
||||
}
|
||||
public static class KF1 extends NamedKeyFactory {
|
||||
public KF1() {
|
||||
super("SHA", "SHA-256");
|
||||
|
||||
public KF(String name) {
|
||||
super("SHA", name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] implExpand(String pname, byte[] input) throws InvalidKeyException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static class KF2 extends NamedKeyFactory {
|
||||
public static class KF1 extends KF {
|
||||
public KF1() {
|
||||
super("SHA-256");
|
||||
}
|
||||
}
|
||||
public static class KF2 extends KF {
|
||||
public KF2() {
|
||||
super("SHA", "SHA-512");
|
||||
super("SHA-512");
|
||||
}
|
||||
}
|
||||
public static class KPG extends NamedKeyPairGenerator {
|
||||
@ -243,8 +265,8 @@ public class NamedKeyFactoryTest {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
103
test/jdk/sun/security/provider/named/NamedKeys.java
Normal file
103
test/jdk/sun/security/provider/named/NamedKeys.java
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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
|
||||
* @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 {
|
||||
|
||||
// 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();
|
||||
|
||||
// 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("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());
|
||||
|
||||
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("SHA", "SHA-256", 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("SHA", enc);
|
||||
var pk2 = new NamedX509Key("SHA", 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());
|
||||
}
|
||||
}
|
||||
227
test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java
Normal file
227
test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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
|
||||
* @library /test/lib
|
||||
* @summary ensure ML-KEM and ML-DSA encodings consistent with
|
||||
* draft-ietf-lamps-kyber-certificates-11 and RFC 9881
|
||||
* @modules java.base/com.sun.crypto.provider
|
||||
* java.base/sun.security.pkcs
|
||||
* java.base/sun.security.provider
|
||||
* @run main/othervm PrivateKeyEncodings
|
||||
*/
|
||||
import com.sun.crypto.provider.ML_KEM_Impls;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.security.RepositoryFileReader;
|
||||
import jdk.test.lib.security.FixedSecureRandom;
|
||||
import sun.security.pkcs.NamedPKCS8Key;
|
||||
import sun.security.provider.ML_DSA_Impls;
|
||||
|
||||
import javax.crypto.KEM;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.NamedParameterSpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PrivateKeyEncodings {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Example keys and certificates draft-ietf-lamps-kyber-certificates-11, Appendix B
|
||||
// (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-11#autoid-17)
|
||||
// and RFC 9881, Appendix C.3
|
||||
// (https://datatracker.ietf.org/doc/html/rfc9881#name-example-certificates)
|
||||
//
|
||||
// These data can be retrieved from the following GitHub releases:
|
||||
// https://github.com/lamps-wg/kyber-certificates/releases/tag/draft-ietf-lamps-kyber-certificates-11
|
||||
// https://github.com/lamps-wg/dilithium-certificates/releases/tag/draft-ietf-lamps-dilithium-certificates-13
|
||||
//
|
||||
// Although the release tags include "draft", these values are the
|
||||
// same as those in the final RFC 9881.
|
||||
try (var kemReader = RepositoryFileReader.of(RepositoryFileReader.KYBER_CERTIFICATES.class,
|
||||
"kyber-certificates-draft-ietf-lamps-kyber-certificates-11/");
|
||||
var dsaReader = RepositoryFileReader.of(RepositoryFileReader.DILITHIUM_CERTIFICATES.class,
|
||||
"dilithium-certificates-draft-ietf-lamps-dilithium-certificates-13/")) {
|
||||
good(kemReader, dsaReader);
|
||||
badkem(kemReader);
|
||||
baddsa(dsaReader);
|
||||
}
|
||||
}
|
||||
|
||||
static void badkem(RepositoryFileReader f) 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, () ->
|
||||
kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "example/bad-ML-KEM-512-1.priv"))));
|
||||
|
||||
// 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 = kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "example/bad-ML-KEM-512-2.priv")));
|
||||
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 = kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "example/bad-ML-KEM-512-3.priv")));
|
||||
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, () ->
|
||||
kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "example/bad-ML-KEM-512-4.priv"))));
|
||||
}
|
||||
|
||||
static void baddsa(RepositoryFileReader f) 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, () ->
|
||||
kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "examples/bad-ML-DSA-44-1.priv"))));
|
||||
|
||||
// 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 = kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "examples/bad-ML-DSA-44-2.priv")));
|
||||
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 = kf.generatePrivate(new PKCS8EncodedKeySpec(
|
||||
readData(f, "examples/bad-ML-DSA-44-3.priv")));
|
||||
Asserts.assertThrows(IllegalArgumentException.class, () ->
|
||||
ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k3));
|
||||
}
|
||||
|
||||
static void good(RepositoryFileReader kemReader, RepositoryFileReader dsaReader)
|
||||
throws Exception {
|
||||
|
||||
var seed = new byte[64];
|
||||
for (var i = 0; i < seed.length; i++) {
|
||||
seed[i] = (byte) i;
|
||||
}
|
||||
var cf = CertificateFactory.getInstance("X.509");
|
||||
var allPublicKeys = new HashMap<String, PublicKey>();
|
||||
|
||||
for (var pname: List.of("ML-DSA-44", "ML-DSA-65", "ML-DSA-87", // DSA first, will sign KEM
|
||||
"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024")) {
|
||||
|
||||
var isKem = pname.startsWith("ML-KEM");
|
||||
KeyPairGenerator g = KeyPairGenerator.getInstance(isKem ? "ML-KEM" : "ML-DSA");
|
||||
var prop = isKem ? "mlkem" : "mldsa";
|
||||
var f = isKem ? kemReader : dsaReader;
|
||||
var example = isKem ? "example/" : "examples/";
|
||||
|
||||
g.initialize(new NamedParameterSpec(pname), new FixedSecureRandom(seed));
|
||||
var pk = g.generateKeyPair().getPublic();
|
||||
allPublicKeys.put(pname, pk);
|
||||
Asserts.assertEqualsByteArray(readData(f, example + pname + ".pub"), pk.getEncoded());
|
||||
|
||||
var in = new ByteArrayInputStream(readData(f, example + pname + ".crt"));
|
||||
var c = cf.generateCertificate(in);
|
||||
var signer = switch (pname) {
|
||||
case "ML-KEM-512" -> allPublicKeys.get("ML-DSA-44");
|
||||
case "ML-KEM-768" -> allPublicKeys.get("ML-DSA-65");
|
||||
case "ML-KEM-1024" -> allPublicKeys.get("ML-DSA-87");
|
||||
default -> c.getPublicKey();
|
||||
};
|
||||
c.verify(signer);
|
||||
Asserts.assertEquals(c.getPublicKey(), pk);
|
||||
|
||||
for (var type : List.of("seed", "expandedkey", "both")) {
|
||||
System.err.println(pname + " " + type);
|
||||
System.setProperty("jdk." + prop + ".pkcs8.encoding", type);
|
||||
g.initialize(new NamedParameterSpec(pname), new FixedSecureRandom(seed));
|
||||
var sk = g.generateKeyPair().getPrivate();
|
||||
if (type.equals("expandedkey")) type = "expanded";
|
||||
Asserts.assertEqualsByteArray(
|
||||
readData(f, example + pname + "-" + type + ".priv"), sk.getEncoded());
|
||||
checkInterop(pk, sk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures pk and sk interop with each other
|
||||
static void checkInterop(PublicKey pk, PrivateKey sk) throws Exception {
|
||||
if (pk.getAlgorithm().startsWith("ML-KEM")) {
|
||||
var kem = KEM.getInstance("ML-KEM");
|
||||
var enc = kem.newEncapsulator(pk).encapsulate();
|
||||
var k = kem.newDecapsulator(sk).decapsulate(enc.encapsulation());
|
||||
Asserts.assertEqualsByteArray(k.getEncoded(), enc.key().getEncoded());
|
||||
} else {
|
||||
var msg = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
var s = Signature.getInstance("ML-DSA");
|
||||
s.initSign(sk);
|
||||
s.update(msg);
|
||||
var sig = s.sign();
|
||||
s.initVerify(pk);
|
||||
s.update(msg);
|
||||
Asserts.assertTrue(s.verify(sig));
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] readData(RepositoryFileReader f, String entry) throws Exception {
|
||||
byte[] data = f.read(entry);
|
||||
var pem = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data)))
|
||||
.lines()
|
||||
.filter(s -> !s.contains("-----"))
|
||||
.collect(Collectors.joining());
|
||||
return Base64.getMimeDecoder().decode(pem);
|
||||
}
|
||||
}
|
||||
194
test/jdk/sun/security/provider/pqc/SeedOrExpanded.java
Normal file
194
test/jdk/sun/security/provider/pqc/SeedOrExpanded.java
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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
|
||||
* @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
|
||||
* java.base/sun.security.x509
|
||||
* @summary check key reading compatibility
|
||||
* @run main/othervm SeedOrExpanded
|
||||
*/
|
||||
|
||||
import com.sun.crypto.provider.ML_KEM_Impls;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.security.DerUtils;
|
||||
import jdk.test.lib.security.FixedSecureRandom;
|
||||
import jdk.test.lib.security.SeededSecureRandom;
|
||||
import sun.security.pkcs.NamedPKCS8Key;
|
||||
import sun.security.provider.ML_DSA_Impls;
|
||||
import sun.security.util.DerValue;
|
||||
|
||||
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();
|
||||
|
||||
// Property value is case-insensitive
|
||||
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();
|
||||
|
||||
// Invalid property value
|
||||
System.setProperty("jdk." + type + ".pkcs8.encoding", "bogus");
|
||||
g.initialize(-1, new FixedSecureRandom(seed));
|
||||
Asserts.assertThrows(IllegalArgumentException.class,
|
||||
() -> g.generateKeyPair().getPrivate());
|
||||
|
||||
byte[] kExpandedEncoded = kExpanded.getEncoded();
|
||||
byte[] kSeedEncoded = kSeed.getEncoded();
|
||||
byte[] kBothEncoded = kBoth.getEncoded();
|
||||
|
||||
// Ensure tags match the CHOICE definition
|
||||
Asserts.assertEquals((byte) 0x80, DerUtils.innerDerValue(kSeedEncoded, "2c").tag);
|
||||
Asserts.assertEquals((byte) 0x04, DerUtils.innerDerValue(kExpandedEncoded, "2c").tag);
|
||||
Asserts.assertEquals((byte) 0x30, DerUtils.innerDerValue(kBothEncoded, "2c").tag);
|
||||
|
||||
byte[] seedData = DerUtils.innerDerValue(kSeedEncoded, "2c")
|
||||
.withTag(DerValue.tag_OctetString).getOctetString();
|
||||
byte[] expandedData = DerUtils.innerDerValue(kExpandedEncoded, "2c").getOctetString();
|
||||
|
||||
Asserts.assertEqualsByteArray(seed, seedData);
|
||||
Asserts.assertEqualsByteArray(
|
||||
seedData,
|
||||
DerUtils.innerDerValue(kBothEncoded, "2c0").getOctetString());
|
||||
Asserts.assertEqualsByteArray(
|
||||
expandedData,
|
||||
DerUtils.innerDerValue(kBothEncoded, "2c1").getOctetString());
|
||||
|
||||
// Ensure seedToExpanded correctly called
|
||||
if (alg.contains("ML-KEM")) {
|
||||
Asserts.assertEqualsByteArray(expandedData,
|
||||
ML_KEM_Impls.seedToExpanded(alg, seedData));
|
||||
} else {
|
||||
Asserts.assertEqualsByteArray(expandedData,
|
||||
ML_DSA_Impls.seedToExpanded(alg, seedData));
|
||||
}
|
||||
|
||||
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(),
|
||||
kSeedEncoded);
|
||||
Asserts.assertTrue(kf.translateKey(kSeed) == kSeed);
|
||||
Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded));
|
||||
|
||||
System.setProperty("jdk." + type + ".pkcs8.encoding", "expandedkey");
|
||||
Asserts.assertEqualsByteArray(
|
||||
test(alg, pk, kf.translateKey(kBoth)).getEncoded(),
|
||||
kExpandedEncoded);
|
||||
Asserts.assertEqualsByteArray(
|
||||
test(alg, pk, kf.translateKey(kSeed)).getEncoded(),
|
||||
kExpandedEncoded);
|
||||
Asserts.assertTrue(kf.translateKey(kExpanded) == kExpanded);
|
||||
|
||||
System.setProperty("jdk." + type + ".pkcs8.encoding", "both");
|
||||
Asserts.assertTrue(kf.translateKey(kBoth) == kBoth);
|
||||
Asserts.assertEqualsByteArray(
|
||||
test(alg, pk, kf.translateKey(kSeed)).getEncoded(),
|
||||
kBothEncoded);
|
||||
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 kBothEncoded.clone(); }
|
||||
};
|
||||
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 {
|
||||
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());
|
||||
if (k instanceof NamedPKCS8Key npk) {
|
||||
Asserts.assertEqualsByteArray(
|
||||
ML_KEM_Impls.privKeyToPubKey(npk).getEncoded(), pk.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));
|
||||
if (k instanceof NamedPKCS8Key npk) {
|
||||
Asserts.assertEqualsByteArray(
|
||||
ML_DSA_Impls.privKeyToPubKey(npk).getEncoded(), pk.getEncoded());
|
||||
}
|
||||
}
|
||||
return sk;
|
||||
}
|
||||
}
|
||||
@ -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 class Proc {
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
||||
@ -148,4 +148,13 @@ public sealed interface RepositoryFileReader extends AutoCloseable {
|
||||
unpack = false)
|
||||
public static class CMS_ML_DSA {
|
||||
}
|
||||
|
||||
@Artifact(
|
||||
organization = "jpg.tests.jdk.repos.lamps-wg",
|
||||
name = "kyber-certificates",
|
||||
revision = "29f3215",
|
||||
extension = "zip",
|
||||
unpack = false)
|
||||
public static class KYBER_CERTIFICATES {
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user