mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8314323: Implement JEP 527: TLS 1.3 Hybrid Key Exchange
Co-authored-by: Jamil Nimeh <jnimeh@openjdk.org> Co-authored-by: Weijun Wang <weijun@openjdk.org> Reviewed-by: wetmore, mullan
This commit is contained in:
parent
5ba91fed34
commit
21dc41f744
254
src/java.base/share/classes/sun/security/ssl/DHasKEM.java
Normal file
254
src/java.base/share/classes/sun/security/ssl/DHasKEM.java
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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.ssl;
|
||||
|
||||
import sun.security.util.ArrayUtil;
|
||||
import sun.security.util.CurveDB;
|
||||
import sun.security.util.ECUtil;
|
||||
import sun.security.util.NamedCurve;
|
||||
|
||||
import javax.crypto.DecapsulateException;
|
||||
import javax.crypto.KEM;
|
||||
import javax.crypto.KEMSpi;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.interfaces.ECKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.XECKey;
|
||||
import java.security.interfaces.XECPublicKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.NamedParameterSpec;
|
||||
import java.security.spec.XECPublicKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The DHasKEM class presents a KEM abstraction layer over traditional
|
||||
* DH-based key exchange, which can be used for either straight
|
||||
* ECDH/XDH or TLS hybrid key exchanges.
|
||||
*
|
||||
* This class can be alongside standard full post-quantum KEMs
|
||||
* when hybrid implementations are required.
|
||||
*/
|
||||
public class DHasKEM implements KEMSpi {
|
||||
|
||||
@Override
|
||||
public EncapsulatorSpi engineNewEncapsulator(
|
||||
PublicKey publicKey, AlgorithmParameterSpec spec,
|
||||
SecureRandom secureRandom) throws InvalidKeyException {
|
||||
return new Handler(publicKey, null, secureRandom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey,
|
||||
AlgorithmParameterSpec spec) throws InvalidKeyException {
|
||||
return new Handler(null, privateKey, null);
|
||||
}
|
||||
|
||||
private static final class Handler
|
||||
implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi {
|
||||
private final PublicKey pkR;
|
||||
private final PrivateKey skR;
|
||||
private final SecureRandom sr;
|
||||
private final Params params;
|
||||
|
||||
Handler(PublicKey pk, PrivateKey sk, SecureRandom sr)
|
||||
throws InvalidKeyException {
|
||||
this.pkR = pk;
|
||||
this.skR = sk;
|
||||
this.sr = sr;
|
||||
this.params = paramsFromKey(pk == null ? sk : pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KEM.Encapsulated engineEncapsulate(int from, int to,
|
||||
String algorithm) {
|
||||
KeyPair kpE = params.generateKeyPair(sr);
|
||||
PrivateKey skE = kpE.getPrivate();
|
||||
PublicKey pkE = kpE.getPublic();
|
||||
byte[] pkEm = params.SerializePublicKey(pkE);
|
||||
try {
|
||||
SecretKey dh = params.DH(algorithm, skE, pkR);
|
||||
return new KEM.Encapsulated(
|
||||
sub(dh, from, to),
|
||||
pkEm, null);
|
||||
} catch (Exception e) {
|
||||
throw new ProviderException("internal error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSecretSize() {
|
||||
return params.secretLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineEncapsulationSize() {
|
||||
return params.publicKeyLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey engineDecapsulate(byte[] encapsulation, int from,
|
||||
int to, String algorithm) throws DecapsulateException {
|
||||
if (encapsulation.length != params.publicKeyLen) {
|
||||
throw new DecapsulateException("incorrect encapsulation size");
|
||||
}
|
||||
try {
|
||||
PublicKey pkE = params.DeserializePublicKey(encapsulation);
|
||||
SecretKey dh = params.DH(algorithm, skR, pkE);
|
||||
return sub(dh, from, to);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new DecapsulateException("Cannot decapsulate", e);
|
||||
} catch (Exception e) {
|
||||
throw new ProviderException("internal error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey sub(SecretKey key, int from, int to) {
|
||||
if (from == 0 && to == params.secretLen) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Key slicing should never happen. Otherwise, there might be
|
||||
// a programming error.
|
||||
throw new AssertionError(
|
||||
"Unexpected key slicing: from=" + from + ", to=" + to);
|
||||
}
|
||||
|
||||
// This KEM is designed to be able to represent every ECDH and XDH
|
||||
private Params paramsFromKey(Key k) throws InvalidKeyException {
|
||||
if (k instanceof ECKey eckey) {
|
||||
if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) {
|
||||
return Params.P256;
|
||||
} else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) {
|
||||
return Params.P384;
|
||||
} else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) {
|
||||
return Params.P521;
|
||||
}
|
||||
} else if (k instanceof XECKey xkey
|
||||
&& xkey.getParams() instanceof NamedParameterSpec ns) {
|
||||
if (ns.getName().equalsIgnoreCase(
|
||||
NamedParameterSpec.X25519.getName())) {
|
||||
return Params.X25519;
|
||||
} else if (ns.getName().equalsIgnoreCase(
|
||||
NamedParameterSpec.X448.getName())) {
|
||||
return Params.X448;
|
||||
}
|
||||
}
|
||||
throw new InvalidKeyException("Unsupported key");
|
||||
}
|
||||
}
|
||||
|
||||
private enum Params {
|
||||
|
||||
P256(32, 2 * 32 + 1,
|
||||
"ECDH", "EC", CurveDB.P_256),
|
||||
|
||||
P384(48, 2 * 48 + 1,
|
||||
"ECDH", "EC", CurveDB.P_384),
|
||||
|
||||
P521(66, 2 * 66 + 1,
|
||||
"ECDH", "EC", CurveDB.P_521),
|
||||
|
||||
X25519(32, 32,
|
||||
"XDH", "XDH", NamedParameterSpec.X25519),
|
||||
|
||||
X448(56, 56,
|
||||
"XDH", "XDH", NamedParameterSpec.X448);
|
||||
|
||||
private final int secretLen;
|
||||
private final int publicKeyLen;
|
||||
private final String kaAlgorithm;
|
||||
private final String keyAlgorithm;
|
||||
private final AlgorithmParameterSpec spec;
|
||||
|
||||
Params(int secretLen, int publicKeyLen, String kaAlgorithm,
|
||||
String keyAlgorithm, AlgorithmParameterSpec spec) {
|
||||
this.spec = spec;
|
||||
this.secretLen = secretLen;
|
||||
this.publicKeyLen = publicKeyLen;
|
||||
this.kaAlgorithm = kaAlgorithm;
|
||||
this.keyAlgorithm = keyAlgorithm;
|
||||
}
|
||||
|
||||
private boolean isEC() {
|
||||
return this == P256 || this == P384 || this == P521;
|
||||
}
|
||||
|
||||
private KeyPair generateKeyPair(SecureRandom sr) {
|
||||
try {
|
||||
KeyPairGenerator g = KeyPairGenerator.getInstance(keyAlgorithm);
|
||||
g.initialize(spec, sr);
|
||||
return g.generateKeyPair();
|
||||
} catch (Exception e) {
|
||||
throw new ProviderException("internal error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] SerializePublicKey(PublicKey k) {
|
||||
if (isEC()) {
|
||||
ECPoint w = ((ECPublicKey) k).getW();
|
||||
return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve());
|
||||
} else {
|
||||
byte[] uArray = ((XECPublicKey) k).getU().toByteArray();
|
||||
ArrayUtil.reverse(uArray);
|
||||
return Arrays.copyOf(uArray, publicKeyLen);
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey DeserializePublicKey(byte[] data) throws
|
||||
IOException, NoSuchAlgorithmException,
|
||||
InvalidKeySpecException {
|
||||
KeySpec keySpec;
|
||||
if (isEC()) {
|
||||
NamedCurve curve = (NamedCurve) this.spec;
|
||||
keySpec = new ECPublicKeySpec(
|
||||
ECUtil.decodePoint(data, curve.getCurve()), curve);
|
||||
} else {
|
||||
data = data.clone();
|
||||
ArrayUtil.reverse(data);
|
||||
keySpec = new XECPublicKeySpec(
|
||||
this.spec, new BigInteger(1, data));
|
||||
}
|
||||
return KeyFactory.getInstance(keyAlgorithm).
|
||||
generatePublic(keySpec);
|
||||
}
|
||||
|
||||
private SecretKey DH(String alg, PrivateKey skE, PublicKey pkR)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm);
|
||||
ka.init(skE);
|
||||
ka.doPhase(pkR, true);
|
||||
return ka.generateSecret(alg);
|
||||
}
|
||||
}
|
||||
}
|
||||
474
src/java.base/share/classes/sun/security/ssl/Hybrid.java
Normal file
474
src/java.base/share/classes/sun/security/ssl/Hybrid.java
Normal file
@ -0,0 +1,474 @@
|
||||
/*
|
||||
* 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.ssl;
|
||||
|
||||
import sun.security.util.ArrayUtil;
|
||||
import sun.security.util.CurveDB;
|
||||
import sun.security.util.ECUtil;
|
||||
import sun.security.util.RawKeySpec;
|
||||
import sun.security.x509.X509Key;
|
||||
|
||||
import javax.crypto.DecapsulateException;
|
||||
import javax.crypto.KEM;
|
||||
import javax.crypto.KEMSpi;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyFactorySpi;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyPairGeneratorSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.ProviderException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
// The Hybrid class wraps two underlying algorithms (left and right sides)
|
||||
// in a single TLS hybrid named group.
|
||||
// It implements:
|
||||
// - Hybrid KeyPair generation
|
||||
// - Hybrid KeyFactory for decoding concatenated hybrid public keys
|
||||
// - Hybrid KEM implementation for performing encapsulation and
|
||||
// decapsulation over two underlying algorithms (traditional
|
||||
// algorithm and post-quantum KEM algorithm)
|
||||
|
||||
public class Hybrid {
|
||||
|
||||
public static final NamedParameterSpec X25519_MLKEM768 =
|
||||
new NamedParameterSpec("X25519MLKEM768");
|
||||
|
||||
public static final NamedParameterSpec SECP256R1_MLKEM768 =
|
||||
new NamedParameterSpec("SecP256r1MLKEM768");
|
||||
|
||||
public static final NamedParameterSpec SECP384R1_MLKEM1024 =
|
||||
new NamedParameterSpec("SecP384r1MLKEM1024");
|
||||
|
||||
private static AlgorithmParameterSpec getSpec(String name) {
|
||||
if (name.startsWith("secp")) {
|
||||
return new ECGenParameterSpec(name);
|
||||
} else {
|
||||
return new NamedParameterSpec(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyPairGenerator getKeyPairGenerator(String name) throws
|
||||
NoSuchAlgorithmException {
|
||||
if (name.startsWith("secp")) {
|
||||
name = "EC";
|
||||
}
|
||||
return KeyPairGenerator.getInstance(name);
|
||||
}
|
||||
|
||||
private static KeyFactory getKeyFactory(String name) throws
|
||||
NoSuchAlgorithmException {
|
||||
if (name.startsWith("secp")) {
|
||||
name = "EC";
|
||||
}
|
||||
return KeyFactory.getInstance(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a KEM instance for each side of the hybrid algorithm.
|
||||
* For traditional key exchange algorithms, we use the DH-based KEM
|
||||
* implementation provided by DHasKEM class.
|
||||
* For ML-KEM post-quantum algorithms, we obtain a KEM instance
|
||||
* with "ML-KEM". This is done to work with 3rd-party providers that
|
||||
* only have "ML-KEM" KEM algorithm.
|
||||
*/
|
||||
private static KEM getKEM(String name) throws NoSuchAlgorithmException {
|
||||
if (name.startsWith("secp") || name.equals("X25519")) {
|
||||
return KEM.getInstance("DH", HybridProvider.PROVIDER);
|
||||
} else {
|
||||
return KEM.getInstance("ML-KEM");
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyPairGeneratorImpl extends KeyPairGeneratorSpi {
|
||||
private final KeyPairGenerator left;
|
||||
private final KeyPairGenerator right;
|
||||
private final AlgorithmParameterSpec leftSpec;
|
||||
private final AlgorithmParameterSpec rightSpec;
|
||||
|
||||
public KeyPairGeneratorImpl(String leftAlg, String rightAlg)
|
||||
throws NoSuchAlgorithmException {
|
||||
left = getKeyPairGenerator(leftAlg);
|
||||
right = getKeyPairGenerator(rightAlg);
|
||||
leftSpec = getSpec(leftAlg);
|
||||
rightSpec = getSpec(rightAlg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(AlgorithmParameterSpec params,
|
||||
SecureRandom random)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
left.initialize(leftSpec, random);
|
||||
right.initialize(rightSpec, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(int keysize, SecureRandom random) {
|
||||
// NO-OP (do nothing)
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateKeyPair() {
|
||||
var kp1 = left.generateKeyPair();
|
||||
var kp2 = right.generateKeyPair();
|
||||
return new KeyPair(
|
||||
new PublicKeyImpl("Hybrid", kp1.getPublic(),
|
||||
kp2.getPublic()),
|
||||
new PrivateKeyImpl("Hybrid", kp1.getPrivate(),
|
||||
kp2.getPrivate()));
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyFactoryImpl extends KeyFactorySpi {
|
||||
private final KeyFactory left;
|
||||
private final KeyFactory right;
|
||||
private final int leftlen;
|
||||
private final String leftname;
|
||||
private final String rightname;
|
||||
|
||||
public KeyFactoryImpl(String left, String right)
|
||||
throws NoSuchAlgorithmException {
|
||||
this.left = getKeyFactory(left);
|
||||
this.right = getKeyFactory(right);
|
||||
this.leftlen = leftPublicLength(left);
|
||||
this.leftname = left;
|
||||
this.rightname = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec == null) {
|
||||
throw new InvalidKeySpecException("keySpec must not be null");
|
||||
}
|
||||
|
||||
if (keySpec instanceof RawKeySpec rks) {
|
||||
byte[] key = rks.getKeyArr();
|
||||
if (key == null) {
|
||||
throw new InvalidKeySpecException(
|
||||
"RawkeySpec contains null key data");
|
||||
}
|
||||
if (key.length <= leftlen) {
|
||||
throw new InvalidKeySpecException(
|
||||
"Hybrid key length " + key.length +
|
||||
" is too short and its left key length is " +
|
||||
leftlen);
|
||||
}
|
||||
|
||||
byte[] leftKeyBytes = Arrays.copyOfRange(key, 0, leftlen);
|
||||
byte[] rightKeyBytes = Arrays.copyOfRange(key, leftlen,
|
||||
key.length);
|
||||
PublicKey leftKey, rightKey;
|
||||
|
||||
try {
|
||||
if (leftname.startsWith("secp")) {
|
||||
var curve = CurveDB.lookup(leftname);
|
||||
var ecSpec = new ECPublicKeySpec(
|
||||
ECUtil.decodePoint(leftKeyBytes,
|
||||
curve.getCurve()), curve);
|
||||
leftKey = left.generatePublic(ecSpec);
|
||||
} else if (leftname.startsWith("ML-KEM")) {
|
||||
leftKey = left.generatePublic(new RawKeySpec(
|
||||
leftKeyBytes));
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Unsupported left" +
|
||||
" algorithm" + leftname);
|
||||
}
|
||||
|
||||
if (rightname.equals("X25519")) {
|
||||
ArrayUtil.reverse(rightKeyBytes);
|
||||
var xecSpec = new XECPublicKeySpec(
|
||||
new NamedParameterSpec(rightname),
|
||||
new BigInteger(1, rightKeyBytes));
|
||||
rightKey = right.generatePublic(xecSpec);
|
||||
} else if (rightname.startsWith("ML-KEM")) {
|
||||
rightKey = right.generatePublic(new RawKeySpec(
|
||||
rightKeyBytes));
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Unsupported right" +
|
||||
" algorithm: " + rightname);
|
||||
}
|
||||
|
||||
return new PublicKeyImpl("Hybrid", leftKey, rightKey);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidKeySpecException("Failed to decode " +
|
||||
"hybrid key", e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidKeySpecException(
|
||||
"KeySpec type:" +
|
||||
keySpec.getClass().getName() + " not supported");
|
||||
}
|
||||
|
||||
private static int leftPublicLength(String name) {
|
||||
return switch (name.toLowerCase(Locale.ROOT)) {
|
||||
case "secp256r1" -> 65;
|
||||
case "secp384r1" -> 97;
|
||||
case "ml-kem-768" -> 1184;
|
||||
default -> throw new IllegalArgumentException(
|
||||
"Unknown named group: " + name);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws
|
||||
InvalidKeySpecException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key,
|
||||
Class<T> keySpec) throws InvalidKeySpecException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static class KEMImpl implements KEMSpi {
|
||||
private final KEM left;
|
||||
private final KEM right;
|
||||
|
||||
public KEMImpl(String left, String right)
|
||||
throws NoSuchAlgorithmException {
|
||||
this.left = getKEM(left);
|
||||
this.right = getKEM(right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey,
|
||||
AlgorithmParameterSpec spec, SecureRandom secureRandom) throws
|
||||
InvalidAlgorithmParameterException, InvalidKeyException {
|
||||
if (publicKey instanceof PublicKeyImpl pk) {
|
||||
return new Handler(left.newEncapsulator(pk.left, secureRandom),
|
||||
right.newEncapsulator(pk.right, secureRandom),
|
||||
null, null);
|
||||
}
|
||||
throw new InvalidKeyException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey,
|
||||
AlgorithmParameterSpec spec)
|
||||
throws InvalidAlgorithmParameterException, InvalidKeyException {
|
||||
if (privateKey instanceof PrivateKeyImpl pk) {
|
||||
return new Handler(null, null, left.newDecapsulator(pk.left),
|
||||
right.newDecapsulator(pk.right));
|
||||
}
|
||||
throw new InvalidKeyException();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] concat(byte[]... inputs) {
|
||||
int outLen = 0;
|
||||
for (byte[] in : inputs) {
|
||||
outLen += in.length;
|
||||
}
|
||||
byte[] out = new byte[outLen];
|
||||
int pos = 0;
|
||||
for (byte[] in : inputs) {
|
||||
System.arraycopy(in, 0, out, pos, in.length);
|
||||
pos += in.length;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private record Handler(KEM.Encapsulator le, KEM.Encapsulator re,
|
||||
KEM.Decapsulator ld, KEM.Decapsulator rd)
|
||||
implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi {
|
||||
@Override
|
||||
public KEM.Encapsulated engineEncapsulate(int from, int to,
|
||||
String algorithm) {
|
||||
int expectedSecretSize = engineSecretSize();
|
||||
if (!(from == 0 && to == expectedSecretSize)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid range for encapsulation: from = " + from +
|
||||
" to = " + to + ", expected total secret size = " +
|
||||
expectedSecretSize);
|
||||
}
|
||||
|
||||
var left = le.encapsulate();
|
||||
var right = re.encapsulate();
|
||||
return new KEM.Encapsulated(
|
||||
new SecretKeyImpl(left.key(), right.key()),
|
||||
concat(left.encapsulation(), right.encapsulation()),
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSecretSize() {
|
||||
if (le != null) {
|
||||
return le.secretSize() + re.secretSize();
|
||||
} else {
|
||||
return ld.secretSize() + rd.secretSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineEncapsulationSize() {
|
||||
if (le != null) {
|
||||
return le.encapsulationSize() + re.encapsulationSize();
|
||||
} else {
|
||||
return ld.encapsulationSize() + rd.encapsulationSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey engineDecapsulate(byte[] encapsulation, int from,
|
||||
int to, String algorithm) throws DecapsulateException {
|
||||
int expectedEncSize = engineEncapsulationSize();
|
||||
if (encapsulation.length != expectedEncSize) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid key encapsulation message length: " +
|
||||
encapsulation.length +
|
||||
", expected = " + expectedEncSize);
|
||||
}
|
||||
|
||||
int expectedSecretSize = engineSecretSize();
|
||||
if (!(from == 0 && to == expectedSecretSize)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid range for decapsulation: from = " + from +
|
||||
" to = " + to + ", expected total secret size = " +
|
||||
expectedSecretSize);
|
||||
}
|
||||
|
||||
var left = Arrays.copyOf(encapsulation, ld.encapsulationSize());
|
||||
var right = Arrays.copyOfRange(encapsulation,
|
||||
ld.encapsulationSize(), encapsulation.length);
|
||||
return new SecretKeyImpl(
|
||||
ld.decapsulate(left),
|
||||
rd.decapsulate(right)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Package-private
|
||||
record SecretKeyImpl(SecretKey k1, SecretKey k2)
|
||||
implements SecretKey {
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "Generic";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hybrid public key combines two underlying public keys (left and right).
|
||||
* Public keys can be transmitted/encoded because the hybrid protocol
|
||||
* requires the public component to be sent.
|
||||
*/
|
||||
// Package-private
|
||||
record PublicKeyImpl(String algorithm, PublicKey left,
|
||||
PublicKey right) implements PublicKey {
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
// getFormat() returns "RAW" as hybrid key uses RAW concatenation
|
||||
// of underlying encodings.
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return "RAW";
|
||||
}
|
||||
|
||||
// getEncoded() returns the concatenation of the encoded bytes of the
|
||||
// left and right public keys.
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return concat(onlyKey(left), onlyKey(right));
|
||||
}
|
||||
|
||||
static byte[] onlyKey(PublicKey key) {
|
||||
if (key instanceof X509Key xk) {
|
||||
return xk.getKeyAsBytes();
|
||||
}
|
||||
|
||||
// Fallback for 3rd-party providers
|
||||
if (!"X.509".equalsIgnoreCase(key.getFormat())) {
|
||||
throw new ProviderException("Invalid public key encoding " +
|
||||
"format");
|
||||
}
|
||||
var xk = new X509Key();
|
||||
try {
|
||||
xk.decode(key.getEncoded());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new ProviderException("Invalid public key encoding", e);
|
||||
}
|
||||
return xk.getKeyAsBytes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hybrid private key combines two underlying private keys (left and right).
|
||||
* It is for internal use only. The private keys should never be exported.
|
||||
*/
|
||||
private record PrivateKeyImpl(String algorithm, PrivateKey left,
|
||||
PrivateKey right) implements PrivateKey {
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
// getFormat() returns null because there is no standard
|
||||
// format for a hybrid private key.
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// getEncoded() returns an empty byte array because there is no
|
||||
// standard encoding format for a hybrid private key.
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/java.base/share/classes/sun/security/ssl/HybridProvider.java
Normal file
130
src/java.base/share/classes/sun/security/ssl/HybridProvider.java
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.ssl;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static sun.security.util.SecurityConstants.PROVIDER_VER;
|
||||
|
||||
// This is an internal provider used in the JSSE code for DH-as-KEM
|
||||
// and Hybrid KEM support. It doesn't actually get installed in the
|
||||
// system's list of security providers that is searched at runtime.
|
||||
// JSSE loads this provider internally.
|
||||
// It registers Hybrid KeyPairGenerator, KeyFactory, and KEM
|
||||
// implementations for hybrid named groups as Provider services.
|
||||
|
||||
public class HybridProvider {
|
||||
|
||||
public static final Provider PROVIDER = new ProviderImpl();
|
||||
|
||||
private static final class ProviderImpl extends Provider {
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
ProviderImpl() {
|
||||
super("HybridAndDHAsKEM", PROVIDER_VER,
|
||||
"Hybrid and DHAsKEM provider");
|
||||
put("KEM.DH", DHasKEM.class.getName());
|
||||
|
||||
// Hybrid KeyPairGenerator/KeyFactory/KEM
|
||||
|
||||
// The order of shares in the concatenation for group name
|
||||
// X25519MLKEM768 has been reversed as per the current
|
||||
// draft RFC.
|
||||
var attrs = Map.of("name", "X25519MLKEM768", "left", "ML-KEM-768",
|
||||
"right", "X25519");
|
||||
putService(new HybridService(this, "KeyPairGenerator",
|
||||
"X25519MLKEM768",
|
||||
"sun.security.ssl.Hybrid$KeyPairGeneratorImpl",
|
||||
null, attrs));
|
||||
putService(new HybridService(this, "KEM",
|
||||
"X25519MLKEM768",
|
||||
"sun.security.ssl.Hybrid$KEMImpl",
|
||||
null, attrs));
|
||||
putService(new HybridService(this, "KeyFactory",
|
||||
"X25519MLKEM768",
|
||||
"sun.security.ssl.Hybrid$KeyFactoryImpl",
|
||||
null, attrs));
|
||||
|
||||
attrs = Map.of("name", "SecP256r1MLKEM768", "left", "secp256r1",
|
||||
"right", "ML-KEM-768");
|
||||
putService(new HybridService(this, "KeyPairGenerator",
|
||||
"SecP256r1MLKEM768",
|
||||
"sun.security.ssl.Hybrid$KeyPairGeneratorImpl",
|
||||
null, attrs));
|
||||
putService(new HybridService(this, "KEM",
|
||||
"SecP256r1MLKEM768",
|
||||
"sun.security.ssl.Hybrid$KEMImpl",
|
||||
null, attrs));
|
||||
putService(new HybridService(this, "KeyFactory",
|
||||
"SecP256r1MLKEM768",
|
||||
"sun.security.ssl.Hybrid$KeyFactoryImpl",
|
||||
null, attrs));
|
||||
|
||||
attrs = Map.of("name", "SecP384r1MLKEM1024", "left", "secp384r1",
|
||||
"right", "ML-KEM-1024");
|
||||
putService(new HybridService(this, "KeyPairGenerator",
|
||||
"SecP384r1MLKEM1024",
|
||||
"sun.security.ssl.Hybrid$KeyPairGeneratorImpl",
|
||||
null, attrs));
|
||||
putService(new HybridService(this, "KEM",
|
||||
"SecP384r1MLKEM1024",
|
||||
"sun.security.ssl.Hybrid$KEMImpl",
|
||||
null, attrs));
|
||||
putService(new HybridService(this, "KeyFactory",
|
||||
"SecP384r1MLKEM1024",
|
||||
"sun.security.ssl.Hybrid$KeyFactoryImpl",
|
||||
null, attrs));
|
||||
}
|
||||
}
|
||||
|
||||
private static class HybridService extends Provider.Service {
|
||||
|
||||
HybridService(Provider p, String type, String algo, String cn,
|
||||
List<String> aliases, Map<String, String> attrs) {
|
||||
super(p, type, algo, cn, aliases, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newInstance(Object ctrParamObj)
|
||||
throws NoSuchAlgorithmException {
|
||||
String type = getType();
|
||||
return switch (type) {
|
||||
case "KeyPairGenerator" -> new Hybrid.KeyPairGeneratorImpl(
|
||||
getAttribute("left"), getAttribute("right"));
|
||||
case "KeyFactory" -> new Hybrid.KeyFactoryImpl(
|
||||
getAttribute("left"), getAttribute("right"));
|
||||
case "KEM" -> new Hybrid.KEMImpl(
|
||||
getAttribute("left"), getAttribute("right"));
|
||||
default -> throw new NoSuchAlgorithmException(
|
||||
"Unexpected value: " + type);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,10 @@
|
||||
*/
|
||||
package sun.security.ssl;
|
||||
|
||||
import sun.security.util.RawKeySpec;
|
||||
|
||||
import javax.crypto.KDF;
|
||||
import javax.crypto.KEM;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.HKDFParameterSpec;
|
||||
@ -32,9 +35,11 @@ import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.SecureRandom;
|
||||
import sun.security.util.KeyUtil;
|
||||
|
||||
/**
|
||||
@ -46,15 +51,32 @@ public class KAKeyDerivation implements SSLKeyDerivation {
|
||||
private final HandshakeContext context;
|
||||
private final PrivateKey localPrivateKey;
|
||||
private final PublicKey peerPublicKey;
|
||||
private final byte[] keyshare;
|
||||
private final Provider provider;
|
||||
|
||||
// Constructor called by Key Agreement
|
||||
KAKeyDerivation(String algorithmName,
|
||||
HandshakeContext context,
|
||||
PrivateKey localPrivateKey,
|
||||
PublicKey peerPublicKey) {
|
||||
this(algorithmName, null, context, localPrivateKey,
|
||||
peerPublicKey, null);
|
||||
}
|
||||
|
||||
// When the constructor called by KEM: store the client's public key or the
|
||||
// encapsulated message in keyshare.
|
||||
KAKeyDerivation(String algorithmName,
|
||||
NamedGroup namedGroup,
|
||||
HandshakeContext context,
|
||||
PrivateKey localPrivateKey,
|
||||
PublicKey peerPublicKey,
|
||||
byte[] keyshare) {
|
||||
this.algorithmName = algorithmName;
|
||||
this.context = context;
|
||||
this.localPrivateKey = localPrivateKey;
|
||||
this.peerPublicKey = peerPublicKey;
|
||||
this.keyshare = keyshare;
|
||||
this.provider = (namedGroup != null) ? namedGroup.getProvider() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -94,22 +116,15 @@ public class KAKeyDerivation implements SSLKeyDerivation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the TLSv1.3 objects, which use the HKDF algorithms.
|
||||
*/
|
||||
private SecretKey t13DeriveKey(String type)
|
||||
throws IOException {
|
||||
SecretKey sharedSecret = null;
|
||||
private SecretKey deriveHandshakeSecret(String label,
|
||||
SecretKey sharedSecret)
|
||||
throws GeneralSecurityException, IOException {
|
||||
SecretKey earlySecret = null;
|
||||
SecretKey saltSecret = null;
|
||||
try {
|
||||
KeyAgreement ka = KeyAgreement.getInstance(algorithmName);
|
||||
ka.init(localPrivateKey);
|
||||
ka.doPhase(peerPublicKey, true);
|
||||
sharedSecret = ka.generateSecret("TlsPremasterSecret");
|
||||
|
||||
CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg;
|
||||
SSLKeyDerivation kd = context.handshakeKeyDerivation;
|
||||
CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg;
|
||||
SSLKeyDerivation kd = context.handshakeKeyDerivation;
|
||||
try {
|
||||
if (kd == null) { // No PSK is in use.
|
||||
// If PSK is not in use, Early Secret will still be
|
||||
// HKDF-Extract(0, 0).
|
||||
@ -129,12 +144,90 @@ public class KAKeyDerivation implements SSLKeyDerivation {
|
||||
// the handshake secret key derivation (below) as it may not
|
||||
// work with the "sharedSecret" obj.
|
||||
KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm);
|
||||
return hkdf.deriveKey(type, HKDFParameterSpec.ofExtract()
|
||||
.addSalt(saltSecret).addIKM(sharedSecret).extractOnly());
|
||||
var spec = HKDFParameterSpec.ofExtract().addSalt(saltSecret);
|
||||
if (sharedSecret instanceof Hybrid.SecretKeyImpl hsk) {
|
||||
spec = spec.addIKM(hsk.k1()).addIKM(hsk.k2());
|
||||
} else {
|
||||
spec = spec.addIKM(sharedSecret);
|
||||
}
|
||||
|
||||
return hkdf.deriveKey(label, spec.extractOnly());
|
||||
} finally {
|
||||
KeyUtil.destroySecretKeys(earlySecret, saltSecret);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This method is called by the server to perform KEM encapsulation.
|
||||
* It uses the client's public key (sent by the client as a keyshare)
|
||||
* to encapsulate a shared secret and returns the encapsulated message.
|
||||
*
|
||||
* Package-private, used from KeyShareExtension.SHKeyShareProducer::
|
||||
* produce().
|
||||
*/
|
||||
KEM.Encapsulated encapsulate(String algorithm, SecureRandom random)
|
||||
throws IOException {
|
||||
SecretKey sharedSecret = null;
|
||||
|
||||
if (keyshare == null) {
|
||||
throw new IOException("No keyshare available for KEM " +
|
||||
"encapsulation");
|
||||
}
|
||||
|
||||
try {
|
||||
KeyFactory kf = (provider != null) ?
|
||||
KeyFactory.getInstance(algorithmName, provider) :
|
||||
KeyFactory.getInstance(algorithmName);
|
||||
var pk = kf.generatePublic(new RawKeySpec(keyshare));
|
||||
|
||||
KEM kem = (provider != null) ?
|
||||
KEM.getInstance(algorithmName, provider) :
|
||||
KEM.getInstance(algorithmName);
|
||||
KEM.Encapsulator e = kem.newEncapsulator(pk, random);
|
||||
KEM.Encapsulated enc = e.encapsulate();
|
||||
sharedSecret = enc.key();
|
||||
|
||||
SecretKey derived = deriveHandshakeSecret(algorithm, sharedSecret);
|
||||
|
||||
return new KEM.Encapsulated(derived, enc.encapsulation(), null);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new SSLHandshakeException("Could not generate secret", gse);
|
||||
} finally {
|
||||
KeyUtil.destroySecretKeys(sharedSecret, earlySecret, saltSecret);
|
||||
KeyUtil.destroySecretKeys(sharedSecret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the TLSv1.3 objects, which use the HKDF algorithms.
|
||||
*/
|
||||
private SecretKey t13DeriveKey(String type)
|
||||
throws IOException {
|
||||
SecretKey sharedSecret = null;
|
||||
|
||||
try {
|
||||
if (keyshare != null) {
|
||||
// Using KEM: called by the client after receiving the KEM
|
||||
// ciphertext (keyshare) from the server in ServerHello.
|
||||
// The client decapsulates it using its private key.
|
||||
KEM kem = (provider != null)
|
||||
? KEM.getInstance(algorithmName, provider)
|
||||
: KEM.getInstance(algorithmName);
|
||||
var decapsulator = kem.newDecapsulator(localPrivateKey);
|
||||
sharedSecret = decapsulator.decapsulate(
|
||||
keyshare, 0, decapsulator.secretSize(),
|
||||
"TlsPremasterSecret");
|
||||
} else {
|
||||
// Using traditional DH-style Key Agreement
|
||||
KeyAgreement ka = KeyAgreement.getInstance(algorithmName);
|
||||
ka.init(localPrivateKey);
|
||||
ka.doPhase(peerPublicKey, true);
|
||||
sharedSecret = ka.generateSecret("TlsPremasterSecret");
|
||||
}
|
||||
|
||||
return deriveHandshakeSecret(type, sharedSecret);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new SSLHandshakeException("Could not generate secret", gse);
|
||||
} finally {
|
||||
KeyUtil.destroySecretKeys(sharedSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
223
src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java
Normal file
223
src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.ssl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.ProviderException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.NamedParameterSpec;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import sun.security.ssl.NamedGroup.NamedGroupSpec;
|
||||
import sun.security.x509.X509Key;
|
||||
|
||||
/**
|
||||
* Specifics for single or hybrid Key exchanges based on KEM
|
||||
*/
|
||||
final class KEMKeyExchange {
|
||||
|
||||
static final SSLKeyAgreementGenerator kemKAGenerator
|
||||
= new KEMKAGenerator();
|
||||
|
||||
static final class KEMCredentials implements NamedGroupCredentials {
|
||||
|
||||
final NamedGroup namedGroup;
|
||||
// Unlike other credentials, we directly store the key share
|
||||
// value here, no need to convert to a key
|
||||
private final byte[] keyshare;
|
||||
|
||||
KEMCredentials(byte[] keyshare, NamedGroup namedGroup) {
|
||||
this.keyshare = keyshare;
|
||||
this.namedGroup = namedGroup;
|
||||
}
|
||||
|
||||
// For KEM, server performs encapsulation and the resulting
|
||||
// encapsulated message becomes the key_share value sent to
|
||||
// the client. It is not a public key, so no PublicKey object
|
||||
// to return.
|
||||
@Override
|
||||
public PublicKey getPublicKey() {
|
||||
throw new UnsupportedOperationException(
|
||||
"KEMCredentials stores raw keyshare, not a PublicKey");
|
||||
}
|
||||
|
||||
public byte[] getKeyShare() {
|
||||
return keyshare;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NamedGroup getNamedGroup() {
|
||||
return namedGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a KEMCredentials object
|
||||
*/
|
||||
static KEMCredentials valueOf(NamedGroup namedGroup,
|
||||
byte[] encodedPoint) {
|
||||
|
||||
if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_KEM) {
|
||||
throw new RuntimeException(
|
||||
"Credentials decoding: Not KEM named group");
|
||||
}
|
||||
|
||||
if (encodedPoint == null || encodedPoint.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new KEMCredentials(encodedPoint, namedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private static class KEMPossession implements SSLPossession {
|
||||
private final NamedGroup namedGroup;
|
||||
|
||||
public KEMPossession(NamedGroup ng) {
|
||||
this.namedGroup = ng;
|
||||
}
|
||||
public NamedGroup getNamedGroup() {
|
||||
return namedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
static final class KEMReceiverPossession extends KEMPossession {
|
||||
|
||||
private final PrivateKey privateKey;
|
||||
private final PublicKey publicKey;
|
||||
|
||||
KEMReceiverPossession(NamedGroup namedGroup, SecureRandom random) {
|
||||
super(namedGroup);
|
||||
String algName = null;
|
||||
try {
|
||||
// For KEM: This receiver side (client) generates a key pair.
|
||||
algName = ((NamedParameterSpec)namedGroup.keAlgParamSpec).
|
||||
getName();
|
||||
Provider provider = namedGroup.getProvider();
|
||||
KeyPairGenerator kpg = (provider != null) ?
|
||||
KeyPairGenerator.getInstance(algName, provider) :
|
||||
KeyPairGenerator.getInstance(algName);
|
||||
|
||||
kpg.initialize(namedGroup.keAlgParamSpec, random);
|
||||
KeyPair kp = kpg.generateKeyPair();
|
||||
privateKey = kp.getPrivate();
|
||||
publicKey = kp.getPublic();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(
|
||||
"Could not generate keypair for algorithm: " +
|
||||
algName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
if (publicKey instanceof X509Key xk) {
|
||||
return xk.getKeyAsBytes();
|
||||
} else if (publicKey instanceof Hybrid.PublicKeyImpl hk) {
|
||||
return hk.getEncoded();
|
||||
}
|
||||
throw new ProviderException("Unsupported key type: " + publicKey);
|
||||
}
|
||||
|
||||
// Package-private
|
||||
PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
// Package-private
|
||||
PrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
}
|
||||
|
||||
static final class KEMSenderPossession extends KEMPossession {
|
||||
|
||||
private SecretKey key;
|
||||
private final SecureRandom random;
|
||||
|
||||
KEMSenderPossession(NamedGroup namedGroup, SecureRandom random) {
|
||||
super(namedGroup);
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
// Package-private
|
||||
SecureRandom getRandom() {
|
||||
return random;
|
||||
}
|
||||
|
||||
// Package-private
|
||||
SecretKey getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Package-private
|
||||
void setKey(SecretKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
throw new UnsupportedOperationException("encode() not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private static final class KEMKAGenerator
|
||||
implements SSLKeyAgreementGenerator {
|
||||
|
||||
// Prevent instantiation of this class.
|
||||
private KEMKAGenerator() {
|
||||
// blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLKeyDerivation createKeyDerivation(
|
||||
HandshakeContext context) throws IOException {
|
||||
for (SSLPossession poss : context.handshakePossessions) {
|
||||
if (poss instanceof KEMReceiverPossession kposs) {
|
||||
NamedGroup ng = kposs.getNamedGroup();
|
||||
for (SSLCredentials cred : context.handshakeCredentials) {
|
||||
if (cred instanceof KEMCredentials kcred &&
|
||||
ng.equals(kcred.namedGroup)) {
|
||||
String name = ((NamedParameterSpec)
|
||||
ng.keAlgParamSpec).getName();
|
||||
return new KAKeyDerivation(name, ng, context,
|
||||
kposs.getPrivateKey(), null,
|
||||
kcred.getKeyShare());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
||||
"No suitable KEM key agreement "
|
||||
+ "parameters negotiated");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,8 +27,11 @@ package sun.security.ssl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.AlgorithmConstraints;
|
||||
import java.security.CryptoPrimitive;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.NamedParameterSpec;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import javax.net.ssl.SSLProtocolException;
|
||||
@ -297,7 +300,9 @@ final class KeyShareExtension {
|
||||
// update the context
|
||||
chc.handshakePossessions.add(pos);
|
||||
// May need more possession types in the future.
|
||||
if (pos instanceof NamedGroupPossession) {
|
||||
if (pos instanceof NamedGroupPossession ||
|
||||
pos instanceof
|
||||
KEMKeyExchange.KEMReceiverPossession) {
|
||||
return pos.encode();
|
||||
}
|
||||
}
|
||||
@ -358,24 +363,16 @@ final class KeyShareExtension {
|
||||
try {
|
||||
SSLCredentials kaCred =
|
||||
ng.decodeCredentials(entry.keyExchange);
|
||||
if (shc.algorithmConstraints != null &&
|
||||
kaCred instanceof
|
||||
NamedGroupCredentials namedGroupCredentials) {
|
||||
if (!shc.algorithmConstraints.permits(
|
||||
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
|
||||
namedGroupCredentials.getPublicKey())) {
|
||||
if (SSLLogger.isOn() &&
|
||||
SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.warning(
|
||||
|
||||
if (!isCredentialPermitted(shc.algorithmConstraints,
|
||||
kaCred)) {
|
||||
if (SSLLogger.isOn() &&
|
||||
SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.warning(
|
||||
"key share entry of " + ng + " does not " +
|
||||
" comply with algorithm constraints");
|
||||
}
|
||||
|
||||
kaCred = null;
|
||||
"comply with algorithm constraints");
|
||||
}
|
||||
}
|
||||
|
||||
if (kaCred != null) {
|
||||
} else {
|
||||
credentials.add(kaCred);
|
||||
}
|
||||
} catch (GeneralSecurityException ex) {
|
||||
@ -513,7 +510,8 @@ final class KeyShareExtension {
|
||||
@Override
|
||||
public byte[] produce(ConnectionContext context,
|
||||
HandshakeMessage message) throws IOException {
|
||||
// The producing happens in client side only.
|
||||
// The producing happens in server side only.
|
||||
|
||||
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
||||
|
||||
// In response to key_share request only
|
||||
@ -571,7 +569,9 @@ final class KeyShareExtension {
|
||||
|
||||
SSLPossession[] poses = ke.createPossessions(shc);
|
||||
for (SSLPossession pos : poses) {
|
||||
if (!(pos instanceof NamedGroupPossession)) {
|
||||
if (!(pos instanceof NamedGroupPossession ||
|
||||
pos instanceof
|
||||
KEMKeyExchange.KEMSenderPossession)) {
|
||||
// May need more possession types in the future.
|
||||
continue;
|
||||
}
|
||||
@ -579,7 +579,34 @@ final class KeyShareExtension {
|
||||
// update the context
|
||||
shc.handshakeKeyExchange = ke;
|
||||
shc.handshakePossessions.add(pos);
|
||||
keyShare = new KeyShareEntry(ng.id, pos.encode());
|
||||
|
||||
// For KEM, perform encapsulation using the client’s public
|
||||
// key (KEMCredentials). The resulting encapsulated message
|
||||
// becomes the key_share value sent to the client. The
|
||||
// shared secret derived from encapsulation is stored in
|
||||
// the KEMSenderPossession for later use in the TLS key
|
||||
// schedule.
|
||||
|
||||
// SSLKeyExchange.createPossessions() returns at most one
|
||||
// key-agreement possession or one KEMSenderPossession
|
||||
// per handshake.
|
||||
if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) {
|
||||
if (cd instanceof KEMKeyExchange.KEMCredentials kcred
|
||||
&& ng.equals(kcred.namedGroup)) {
|
||||
String name = ((NamedParameterSpec)
|
||||
ng.keAlgParamSpec).getName();
|
||||
KAKeyDerivation handshakeKD = new KAKeyDerivation(
|
||||
name, ng, shc, null, null,
|
||||
kcred.getKeyShare());
|
||||
var encaped = handshakeKD.encapsulate(
|
||||
"TlsHandshakeSecret", xp.getRandom());
|
||||
xp.setKey(encaped.key());
|
||||
keyShare = new KeyShareEntry(ng.id,
|
||||
encaped.encapsulation());
|
||||
}
|
||||
} else {
|
||||
keyShare = new KeyShareEntry(ng.id, pos.encode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -663,19 +690,13 @@ final class KeyShareExtension {
|
||||
try {
|
||||
SSLCredentials kaCred =
|
||||
ng.decodeCredentials(keyShare.keyExchange);
|
||||
if (chc.algorithmConstraints != null &&
|
||||
kaCred instanceof
|
||||
NamedGroupCredentials namedGroupCredentials) {
|
||||
if (!chc.algorithmConstraints.permits(
|
||||
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
|
||||
namedGroupCredentials.getPublicKey())) {
|
||||
chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
|
||||
"key share entry of " + ng + " does not " +
|
||||
" comply with algorithm constraints");
|
||||
}
|
||||
}
|
||||
|
||||
if (kaCred != null) {
|
||||
if (!isCredentialPermitted(chc.algorithmConstraints,
|
||||
kaCred)) {
|
||||
chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
|
||||
"key share entry of " + ng + " does not " +
|
||||
"comply with algorithm constraints");
|
||||
} else {
|
||||
credentials = kaCred;
|
||||
}
|
||||
} catch (GeneralSecurityException ex) {
|
||||
@ -696,6 +717,34 @@ final class KeyShareExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCredentialPermitted(
|
||||
AlgorithmConstraints constraints,
|
||||
SSLCredentials cred) {
|
||||
|
||||
if (constraints == null) return true;
|
||||
if (cred == null) return false;
|
||||
|
||||
if (cred instanceof NamedGroupCredentials namedGroupCred) {
|
||||
if (namedGroupCred instanceof KEMKeyExchange.KEMCredentials
|
||||
kemCred) {
|
||||
AlgorithmParameterSpec paramSpec = kemCred.getNamedGroup().
|
||||
keAlgParamSpec;
|
||||
String algName = (paramSpec instanceof NamedParameterSpec nps) ?
|
||||
nps.getName() : null;
|
||||
return algName != null && constraints.permits(
|
||||
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
|
||||
algName,
|
||||
null);
|
||||
} else {
|
||||
return constraints.permits(
|
||||
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
|
||||
namedGroupCred.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The absence processing if the extension is not present in
|
||||
* the ServerHello handshake message.
|
||||
|
||||
@ -214,6 +214,39 @@ enum NamedGroup {
|
||||
ProtocolVersion.PROTOCOLS_TO_13,
|
||||
PredefinedDHParameterSpecs.ffdheParams.get(8192)),
|
||||
|
||||
ML_KEM_512(0x0200, "MLKEM512",
|
||||
NamedGroupSpec.NAMED_GROUP_KEM,
|
||||
ProtocolVersion.PROTOCOLS_OF_13,
|
||||
null),
|
||||
|
||||
ML_KEM_768(0x0201, "MLKEM768",
|
||||
NamedGroupSpec.NAMED_GROUP_KEM,
|
||||
ProtocolVersion.PROTOCOLS_OF_13,
|
||||
null),
|
||||
|
||||
ML_KEM_1024(0x0202, "MLKEM1024",
|
||||
NamedGroupSpec.NAMED_GROUP_KEM,
|
||||
ProtocolVersion.PROTOCOLS_OF_13,
|
||||
null),
|
||||
|
||||
X25519MLKEM768(0x11ec, "X25519MLKEM768",
|
||||
NamedGroupSpec.NAMED_GROUP_KEM,
|
||||
ProtocolVersion.PROTOCOLS_OF_13,
|
||||
Hybrid.X25519_MLKEM768,
|
||||
HybridProvider.PROVIDER),
|
||||
|
||||
SECP256R1MLKEM768(0x11eb, "SecP256r1MLKEM768",
|
||||
NamedGroupSpec.NAMED_GROUP_KEM,
|
||||
ProtocolVersion.PROTOCOLS_OF_13,
|
||||
Hybrid.SECP256R1_MLKEM768,
|
||||
HybridProvider.PROVIDER),
|
||||
|
||||
SECP384R1MLKEM1024(0x11ed, "SecP384r1MLKEM1024",
|
||||
NamedGroupSpec.NAMED_GROUP_KEM,
|
||||
ProtocolVersion.PROTOCOLS_OF_13,
|
||||
Hybrid.SECP384R1_MLKEM1024,
|
||||
HybridProvider.PROVIDER),
|
||||
|
||||
// Elliptic Curves (RFC 4492)
|
||||
//
|
||||
// arbitrary prime and characteristic-2 curves
|
||||
@ -234,22 +267,33 @@ enum NamedGroup {
|
||||
final AlgorithmParameterSpec keAlgParamSpec;
|
||||
final AlgorithmParameters keAlgParams;
|
||||
final boolean isAvailable;
|
||||
final Provider defaultProvider;
|
||||
|
||||
// performance optimization
|
||||
private static final Set<CryptoPrimitive> KEY_AGREEMENT_PRIMITIVE_SET =
|
||||
Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT));
|
||||
|
||||
// Constructor used for all NamedGroup types
|
||||
NamedGroup(int id, String name,
|
||||
NamedGroupSpec namedGroupSpec,
|
||||
ProtocolVersion[] supportedProtocols,
|
||||
AlgorithmParameterSpec keAlgParamSpec) {
|
||||
this(id, name, namedGroupSpec, supportedProtocols, keAlgParamSpec,
|
||||
null);
|
||||
}
|
||||
|
||||
// Constructor used for all NamedGroup types
|
||||
NamedGroup(int id, String name,
|
||||
NamedGroupSpec namedGroupSpec,
|
||||
ProtocolVersion[] supportedProtocols,
|
||||
AlgorithmParameterSpec keAlgParamSpec,
|
||||
Provider defaultProvider) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.spec = namedGroupSpec;
|
||||
this.algorithm = namedGroupSpec.algorithm;
|
||||
this.supportedProtocols = supportedProtocols;
|
||||
this.keAlgParamSpec = keAlgParamSpec;
|
||||
this.defaultProvider = defaultProvider;
|
||||
|
||||
// Check if it is a supported named group.
|
||||
AlgorithmParameters algParams = null;
|
||||
@ -266,16 +310,28 @@ enum NamedGroup {
|
||||
// Check the specific algorithm parameters.
|
||||
if (mediator) {
|
||||
try {
|
||||
algParams =
|
||||
AlgorithmParameters.getInstance(namedGroupSpec.algorithm);
|
||||
algParams.init(keAlgParamSpec);
|
||||
// Skip AlgorithmParameters for KEMs (not supported)
|
||||
// Check KEM's availability via KeyFactory
|
||||
if (namedGroupSpec == NamedGroupSpec.NAMED_GROUP_KEM) {
|
||||
if (defaultProvider == null) {
|
||||
KeyFactory.getInstance(name);
|
||||
} else {
|
||||
KeyFactory.getInstance(name, defaultProvider);
|
||||
}
|
||||
} else {
|
||||
// ECDHE or others: use AlgorithmParameters as before
|
||||
algParams = AlgorithmParameters.getInstance(
|
||||
namedGroupSpec.algorithm);
|
||||
algParams.init(keAlgParamSpec);
|
||||
}
|
||||
} catch (InvalidParameterSpecException
|
||||
| NoSuchAlgorithmException exp) {
|
||||
if (namedGroupSpec != NamedGroupSpec.NAMED_GROUP_XDH) {
|
||||
mediator = false;
|
||||
if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.warning(
|
||||
"No AlgorithmParameters for " + name, exp);
|
||||
"No AlgorithmParameters or KeyFactory for " + name,
|
||||
exp);
|
||||
}
|
||||
} else {
|
||||
// Please remove the following code if the XDH/X25519/X448
|
||||
@ -307,6 +363,10 @@ enum NamedGroup {
|
||||
this.keAlgParams = mediator ? algParams : null;
|
||||
}
|
||||
|
||||
Provider getProvider() {
|
||||
return defaultProvider;
|
||||
}
|
||||
|
||||
//
|
||||
// The next set of methods search & retrieve NamedGroups.
|
||||
//
|
||||
@ -545,6 +605,10 @@ enum NamedGroup {
|
||||
return spec.decodeCredentials(this, encoded);
|
||||
}
|
||||
|
||||
SSLPossession createPossession(boolean isClient, SecureRandom random) {
|
||||
return spec.createPossession(this, isClient, random);
|
||||
}
|
||||
|
||||
SSLPossession createPossession(SecureRandom random) {
|
||||
return spec.createPossession(this, random);
|
||||
}
|
||||
@ -566,6 +630,11 @@ enum NamedGroup {
|
||||
|
||||
SSLKeyDerivation createKeyDerivation(
|
||||
HandshakeContext hc) throws IOException;
|
||||
|
||||
default SSLPossession createPossession(NamedGroup ng, boolean isClient,
|
||||
SecureRandom random) {
|
||||
return createPossession(ng, random);
|
||||
}
|
||||
}
|
||||
|
||||
enum NamedGroupSpec implements NamedGroupScheme {
|
||||
@ -578,6 +647,10 @@ enum NamedGroup {
|
||||
// Finite Field Groups (XDH)
|
||||
NAMED_GROUP_XDH("XDH", XDHScheme.instance),
|
||||
|
||||
// Post-Quantum Cryptography (PQC) KEM groups
|
||||
// Currently used for hybrid named groups
|
||||
NAMED_GROUP_KEM("KEM", KEMScheme.instance),
|
||||
|
||||
// arbitrary prime and curves (ECDHE)
|
||||
NAMED_GROUP_ARBITRARY("EC", null),
|
||||
|
||||
@ -634,6 +707,15 @@ enum NamedGroup {
|
||||
return null;
|
||||
}
|
||||
|
||||
public SSLPossession createPossession(
|
||||
NamedGroup ng, boolean isClient, SecureRandom random) {
|
||||
if (scheme != null) {
|
||||
return scheme.createPossession(ng, isClient, random);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLPossession createPossession(
|
||||
NamedGroup ng, SecureRandom random) {
|
||||
@ -739,6 +821,42 @@ enum NamedGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private static class KEMScheme implements NamedGroupScheme {
|
||||
private static final KEMScheme instance = new KEMScheme();
|
||||
|
||||
@Override
|
||||
public byte[] encodePossessionPublicKey(NamedGroupPossession poss) {
|
||||
return poss.encode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLCredentials decodeCredentials(NamedGroup ng,
|
||||
byte[] encoded) throws IOException, GeneralSecurityException {
|
||||
return KEMKeyExchange.KEMCredentials.valueOf(ng, encoded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLPossession createPossession(NamedGroup ng,
|
||||
SecureRandom random) {
|
||||
// Must call createPossession with isClient
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLPossession createPossession(
|
||||
NamedGroup ng, boolean isClient, SecureRandom random) {
|
||||
return isClient
|
||||
? new KEMKeyExchange.KEMReceiverPossession(ng, random)
|
||||
: new KEMKeyExchange.KEMSenderPossession(ng, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLKeyDerivation createKeyDerivation(
|
||||
HandshakeContext hc) throws IOException {
|
||||
return KEMKeyExchange.kemKAGenerator.createKeyDerivation(hc);
|
||||
}
|
||||
}
|
||||
|
||||
static final class SupportedGroups {
|
||||
// the supported named groups, non-null immutable list
|
||||
static final String[] namedGroups;
|
||||
@ -784,6 +902,9 @@ enum NamedGroup {
|
||||
} else { // default groups
|
||||
NamedGroup[] groups = new NamedGroup[] {
|
||||
|
||||
// Hybrid key agreement
|
||||
X25519MLKEM768,
|
||||
|
||||
// Primary XDH (RFC 7748) curves
|
||||
X25519,
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 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
|
||||
@ -570,7 +570,9 @@ final class SSLKeyExchange implements SSLKeyAgreementGenerator,
|
||||
|
||||
@Override
|
||||
public SSLPossession createPossession(HandshakeContext hc) {
|
||||
return namedGroup.createPossession(hc.sslContext.getSecureRandom());
|
||||
return namedGroup.createPossession(
|
||||
hc instanceof ClientHandshakeContext,
|
||||
hc.sslContext.getSecureRandom());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -565,6 +565,34 @@ final class ServerHello {
|
||||
clientHello);
|
||||
shc.serverHelloRandom = shm.serverRandom;
|
||||
|
||||
// For key derivation, we will either use the traditional Key
|
||||
// Agreement (KA) model or the Key Encapsulation Mechanism (KEM)
|
||||
// model, depending on what key exchange group is used.
|
||||
//
|
||||
// For KA flows, the server first receives the client's share,
|
||||
// then generates its key share, and finally comes here.
|
||||
// However, this is changed for KEM: the server
|
||||
// must perform both actions — derive the secret and generate
|
||||
// the key encapsulation message at the same time during
|
||||
// encapsulation in SHKeyShareProducer.
|
||||
//
|
||||
// Traditional Key Agreement (KA):
|
||||
// - Both peers generate a key share and exchange it.
|
||||
// - Each peer computes a shared secret sometime after
|
||||
// receiving the other's key share.
|
||||
//
|
||||
// Key Encapsulation Mechanism (KEM):
|
||||
// The client publishes a public key via a KeyShareExtension,
|
||||
// which the server uses to:
|
||||
//
|
||||
// - generate the shared secret
|
||||
// - encapsulate the message which is sent to the client in
|
||||
// another KeyShareExtension
|
||||
//
|
||||
// The derived shared secret must be stored in a
|
||||
// KEMSenderPossession so it can be retrieved for handshake
|
||||
// traffic secret derivation later.
|
||||
|
||||
// Produce extensions for ServerHello handshake message.
|
||||
SSLExtension[] serverHelloExtensions =
|
||||
shc.sslConfig.getEnabledExtensions(
|
||||
@ -590,9 +618,26 @@ final class ServerHello {
|
||||
"Not negotiated key shares");
|
||||
}
|
||||
|
||||
SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc);
|
||||
SecretKey handshakeSecret = handshakeKD.deriveKey(
|
||||
"TlsHandshakeSecret");
|
||||
SecretKey handshakeSecret = null;
|
||||
|
||||
// For KEM, the shared secret has already been generated and
|
||||
// stored in the server’s possession (KEMSenderPossession)
|
||||
// during encapsulation in SHKeyShareProducer.
|
||||
//
|
||||
// Only one key share is selected by the server, so at most one
|
||||
// possession will contain the pre-derived shared secret.
|
||||
for (var pos : shc.handshakePossessions) {
|
||||
if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) {
|
||||
handshakeSecret = xp.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (handshakeSecret == null) {
|
||||
SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc);
|
||||
handshakeSecret = handshakeKD.deriveKey(
|
||||
"TlsHandshakeSecret");
|
||||
}
|
||||
|
||||
SSLTrafficKeyDerivation kdg =
|
||||
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
|
||||
|
||||
@ -104,6 +104,10 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
return (BitArray)bitStringKey.clone();
|
||||
}
|
||||
|
||||
public byte[] getKeyAsBytes() {
|
||||
return bitStringKey.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct X.509 subject public key from a DER value. If
|
||||
* the runtime environment is configured with a specific class for
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (C) 2022, Tencent. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
@ -26,7 +27,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8281236
|
||||
* @bug 8281236 8314323
|
||||
* @summary Check TLS connection behaviors for named groups configuration
|
||||
* @library /javax/net/ssl/templates
|
||||
* @run main/othervm NamedGroups
|
||||
@ -136,6 +137,60 @@ public class NamedGroups extends SSLSocketTemplate {
|
||||
"secp256r1"
|
||||
},
|
||||
true);
|
||||
|
||||
runTest(new String[] {
|
||||
"X25519MLKEM768"
|
||||
},
|
||||
new String[] {
|
||||
"X25519MLKEM768"
|
||||
},
|
||||
false);
|
||||
|
||||
runTest(new String[] {
|
||||
"SecP256r1MLKEM768"
|
||||
},
|
||||
new String[] {
|
||||
"SecP256r1MLKEM768"
|
||||
},
|
||||
false);
|
||||
|
||||
runTest(new String[] {
|
||||
"SecP384r1MLKEM1024"
|
||||
},
|
||||
new String[] {
|
||||
"SecP384r1MLKEM1024"
|
||||
},
|
||||
false);
|
||||
|
||||
runTest(new String[] {
|
||||
"X25519MLKEM768"
|
||||
},
|
||||
new String[] {
|
||||
"SecP256r1MLKEM768"
|
||||
},
|
||||
true);
|
||||
|
||||
runTest(new String[] {
|
||||
"X25519MLKEM768"
|
||||
},
|
||||
new String[0],
|
||||
true);
|
||||
|
||||
runTest(new String[] {
|
||||
"SecP256r1MLKEM768"
|
||||
},
|
||||
null,
|
||||
true);
|
||||
|
||||
runTest(new String[] {
|
||||
"X25519MLKEM768",
|
||||
"x25519"
|
||||
},
|
||||
new String[] {
|
||||
"X25519MLKEM768",
|
||||
"x25519"
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
private static void runTest(String[] serverNamedGroups,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 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
|
||||
@ -37,7 +37,11 @@ public enum NamedGroup {
|
||||
FFDHE3072("ffdhe3072"),
|
||||
FFDHE4096("ffdhe4096"),
|
||||
FFDHE6144("ffdhe6144"),
|
||||
FFDHE8192("ffdhe8192");
|
||||
FFDHE8192("ffdhe8192"),
|
||||
|
||||
X25519MLKEM768("X25519MLKEM768"),
|
||||
SECP256R1MLKEM768("SecP256r1MLKEM768"),
|
||||
SECP384R1MLKEM1024("SecP384r1MLKEM1024");
|
||||
|
||||
public final String name;
|
||||
|
||||
|
||||
@ -26,16 +26,21 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8247630
|
||||
* @bug 8247630 8314323
|
||||
* @summary Use two key share entries
|
||||
* @run main/othervm ClientHelloKeyShares 29 23
|
||||
* @run main/othervm ClientHelloKeyShares 4588 29
|
||||
* @run main/othervm -Djdk.tls.namedGroups=secp384r1,secp521r1,x448,ffdhe2048 ClientHelloKeyShares 24 30
|
||||
* @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,x25519 ClientHelloKeyShares 29
|
||||
* @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,secp256r1 ClientHelloKeyShares 23
|
||||
* @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,ffdhe2048,ffdhe3072,ffdhe4096 ClientHelloKeyShares 256
|
||||
* @run main/othervm -Djdk.tls.namedGroups=sect163k1,ffdhe2048,x25519,secp256r1 ClientHelloKeyShares 256 29
|
||||
* @run main/othervm -Djdk.tls.namedGroups=secp256r1,secp384r1,ffdhe2048,x25519 ClientHelloKeyShares 23 256
|
||||
*/
|
||||
* @run main/othervm -Djdk.tls.namedGroups=X25519MLKEM768 ClientHelloKeyShares 4588
|
||||
* @run main/othervm -Djdk.tls.namedGroups=x25519,X25519MLKEM768 ClientHelloKeyShares 29 4588
|
||||
* @run main/othervm -Djdk.tls.namedGroups=SecP256r1MLKEM768,x25519 ClientHelloKeyShares 4587 29
|
||||
* @run main/othervm -Djdk.tls.namedGroups=SecP384r1MLKEM1024,secp256r1 ClientHelloKeyShares 4589 23
|
||||
* @run main/othervm -Djdk.tls.namedGroups=X25519MLKEM768,SecP256r1MLKEM768,X25519,secp256r1 ClientHelloKeyShares 4588 29
|
||||
*/
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import javax.net.ssl.SSLEngineResult.*;
|
||||
@ -62,10 +67,6 @@ public class ClientHelloKeyShares {
|
||||
private static final int HELLO_EXT_SUPP_VERS = 43;
|
||||
private static final int HELLO_EXT_KEY_SHARE = 51;
|
||||
private static final int TLS_PROT_VER_13 = 0x0304;
|
||||
private static final int NG_SECP256R1 = 0x0017;
|
||||
private static final int NG_SECP384R1 = 0x0018;
|
||||
private static final int NG_X25519 = 0x001D;
|
||||
private static final int NG_X448 = 0x001E;
|
||||
|
||||
public static void main(String args[]) throws Exception {
|
||||
if (debug) {
|
||||
|
||||
@ -26,10 +26,12 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8247630
|
||||
* @bug 8247630 8314323
|
||||
* @summary Use two key share entries
|
||||
* @library /test/lib
|
||||
* @run main/othervm -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1 HRRKeyShares
|
||||
* @run main/othervm
|
||||
* -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,X25519MLKEM768,SecP256r1MLKEM768,SecP384r1MLKEM1024
|
||||
* HRRKeyShares
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -72,6 +74,10 @@ public class HRRKeyShares {
|
||||
private static final int NG_SECP384R1 = 0x0018;
|
||||
private static final int NG_X25519 = 0x001D;
|
||||
private static final int NG_X448 = 0x001E;
|
||||
private static final int NG_X25519_MLKEM768 = 0x11EC;
|
||||
private static final int NG_SECP256R1_MLKEM768 = 0x11EB;
|
||||
private static final int NG_SECP384R1_MLKEM1024 = 0x11ED;
|
||||
|
||||
private static final int NG_GC512A = 0x0026;
|
||||
private static final int COMP_NONE = 0;
|
||||
private static final int ALERT_TYPE_FATAL = 2;
|
||||
@ -238,6 +244,18 @@ public class HRRKeyShares {
|
||||
System.out.println("Test 4: Bad HRR using known / unasserted x448");
|
||||
hrrKeyShareTest(NG_X448, false);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("Test 5: Good HRR exchange using X25519MLKEM768");
|
||||
hrrKeyShareTest(NG_X25519_MLKEM768, true);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("Test 6: Good HRR exchange using SecP256r1MLKEM768");
|
||||
hrrKeyShareTest(NG_SECP256R1_MLKEM768, true);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("Test 7: Good HRR exchange using SecP384r1MLKEM1024");
|
||||
hrrKeyShareTest(NG_SECP384R1_MLKEM1024, true);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private static void logResult(String str, SSLEngineResult result) {
|
||||
@ -348,7 +366,8 @@ public class HRRKeyShares {
|
||||
|
||||
try {
|
||||
// Now we're expecting to reissue the ClientHello, this time
|
||||
// with a secp384r1 share.
|
||||
// with a key share for the HRR requested named
|
||||
// group (hrrNamedGroup).
|
||||
cTOs.compact();
|
||||
clientResult = engine.wrap(clientOut, cTOs);
|
||||
logResult("client wrap: ", clientResult);
|
||||
|
||||
@ -34,8 +34,12 @@
|
||||
* -Djdk.tls.useExtendedMasterSecret=false
|
||||
* -Djdk.tls.client.enableSessionTicketExtension=false FipsModeTLS
|
||||
* @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet.
|
||||
* Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669
|
||||
* @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.3 FipsModeTLS
|
||||
* Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669.
|
||||
* NSS-FIPS does not support ML-KEM, so configures the list of named groups.
|
||||
* @run main/othervm/timeout=120
|
||||
* -Djdk.tls.client.protocols=TLSv1.3
|
||||
* -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,secp521r1,x448,ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192
|
||||
* FipsModeTLS
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,12 +23,24 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8246330
|
||||
* @bug 8246330 8314323
|
||||
* @library /javax/net/ssl/templates /test/lib
|
||||
* @run main/othervm -Djdk.tls.namedGroups="secp384r1"
|
||||
DisabledCurve DISABLE_NONE PASS
|
||||
* @run main/othervm -Djdk.tls.namedGroups="secp384r1"
|
||||
DisabledCurve secp384r1 FAIL
|
||||
* @run main/othervm -Djdk.tls.namedGroups="X25519MLKEM768"
|
||||
DisabledCurve DISABLE_NONE PASS
|
||||
* @run main/othervm -Djdk.tls.namedGroups="X25519MLKEM768"
|
||||
DisabledCurve X25519MLKEM768 FAIL
|
||||
* @run main/othervm -Djdk.tls.namedGroups="SecP256r1MLKEM768"
|
||||
DisabledCurve DISABLE_NONE PASS
|
||||
* @run main/othervm -Djdk.tls.namedGroups="SecP256r1MLKEM768"
|
||||
DisabledCurve SecP256r1MLKEM768 FAIL
|
||||
* @run main/othervm -Djdk.tls.namedGroups="SecP384r1MLKEM1024"
|
||||
DisabledCurve DISABLE_NONE PASS
|
||||
* @run main/othervm -Djdk.tls.namedGroups="SecP384r1MLKEM1024"
|
||||
DisabledCurve SecP384r1MLKEM1024 FAIL
|
||||
*/
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
@ -45,8 +57,10 @@ public class DisabledCurve extends SSLSocketTemplate {
|
||||
private static final String[][][] protocols = {
|
||||
{ { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" }, { "TLSv1.2" } },
|
||||
{ { "TLSv1.2" }, { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" } },
|
||||
{ { "TLSv1.2" }, { "TLSv1.2" } }, { { "TLSv1.1" }, { "TLSv1.1" } },
|
||||
{ { "TLSv1" }, { "TLSv1" } } };
|
||||
{ { "TLSv1.2" }, { "TLSv1.2" } },
|
||||
{ { "TLSv1.1" }, { "TLSv1.1" } },
|
||||
{ { "TLSv1" }, { "TLSv1" } },
|
||||
{ { "TLSv1.3" }, { "TLSv1.3" } } };
|
||||
|
||||
@Override
|
||||
protected SSLContext createClientSSLContext() throws Exception {
|
||||
@ -94,17 +108,36 @@ public class DisabledCurve extends SSLSocketTemplate {
|
||||
String expected = args[1];
|
||||
String disabledName = ("DISABLE_NONE".equals(args[0]) ? "" : args[0]);
|
||||
boolean disabled = false;
|
||||
if (disabledName.equals("")) {
|
||||
|
||||
if (disabledName.isEmpty()) {
|
||||
Security.setProperty("jdk.disabled.namedCurves", "");
|
||||
Security.setProperty("jdk.certpath.disabledAlgorithms", "");
|
||||
} else {
|
||||
disabled = true;
|
||||
Security.setProperty("jdk.certpath.disabledAlgorithms", "secp384r1");
|
||||
Security.setProperty("jdk.certpath.disabledAlgorithms", disabledName);
|
||||
if (!disabledName.contains("MLKEM")) {
|
||||
Security.setProperty("jdk.disabled.namedCurves", disabledName);
|
||||
} else {
|
||||
Security.setProperty("jdk.disabled.namedCurves", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable TLSv1 and TLSv1.1 since test depends on it.
|
||||
SecurityUtils.removeFromDisabledTlsAlgs("TLSv1", "TLSv1.1");
|
||||
|
||||
String namedGroups = System.getProperty("jdk.tls.namedGroups", "");
|
||||
boolean hybridGroup = namedGroups.contains("MLKEM");
|
||||
|
||||
for (index = 0; index < protocols.length; index++) {
|
||||
if (hybridGroup) {
|
||||
String[] clientProtos = protocols[index][0];
|
||||
String[] serverProtos = protocols[index][1];
|
||||
|
||||
if (!(isTLS13(clientProtos) && isTLS13(serverProtos))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
(new DisabledCurve()).run();
|
||||
if (expected.equals("FAIL")) {
|
||||
@ -123,4 +156,8 @@ public class DisabledCurve extends SSLSocketTemplate {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTLS13(String[] protocols) {
|
||||
return protocols.length == 1 && "TLSv1.3".equals(protocols[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 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
|
||||
@ -21,6 +21,8 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
@ -29,7 +31,7 @@ import jdk.test.lib.security.SecurityUtils;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8224650 8242929
|
||||
* @bug 8224650 8242929 8314323
|
||||
* @library /javax/net/ssl/templates
|
||||
* /javax/net/ssl/TLSCommon
|
||||
* /test/lib
|
||||
@ -44,17 +46,20 @@ import jdk.test.lib.security.SecurityUtils;
|
||||
* @run main/othervm NamedGroupsWithCipherSuite ffdhe4096
|
||||
* @run main/othervm NamedGroupsWithCipherSuite ffdhe6144
|
||||
* @run main/othervm NamedGroupsWithCipherSuite ffdhe8192
|
||||
* @run main/othervm NamedGroupsWithCipherSuite X25519MLKEM768
|
||||
* @run main/othervm NamedGroupsWithCipherSuite SecP256r1MLKEM768
|
||||
* @run main/othervm NamedGroupsWithCipherSuite SecP384r1MLKEM1024
|
||||
*/
|
||||
public class NamedGroupsWithCipherSuite extends SSLSocketTemplate {
|
||||
|
||||
private static final Protocol[] PROTOCOLS = new Protocol[] {
|
||||
private static final List<Protocol> PROTOCOLS = List.of(
|
||||
Protocol.TLSV1_3,
|
||||
Protocol.TLSV1_2,
|
||||
Protocol.TLSV1_1,
|
||||
Protocol.TLSV1
|
||||
};
|
||||
);
|
||||
|
||||
private static final CipherSuite[] CIPHER_SUITES = new CipherSuite[] {
|
||||
private static final List<CipherSuite> CIPHER_SUITES = List.of(
|
||||
CipherSuite.TLS_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_AES_256_GCM_SHA384,
|
||||
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
|
||||
@ -75,7 +80,23 @@ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate {
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
};
|
||||
);
|
||||
|
||||
private static final List<String> HYBRID_NAMEDGROUPS = List.of(
|
||||
"X25519MLKEM768",
|
||||
"SecP256r1MLKEM768",
|
||||
"SecP384r1MLKEM1024"
|
||||
);
|
||||
|
||||
private static final List<Protocol> HYBRID_PROTOCOL = List.of(
|
||||
Protocol.TLSV1_3
|
||||
);
|
||||
|
||||
private static final List<CipherSuite> HYBRID_CIPHER_SUITES = List.of(
|
||||
CipherSuite.TLS_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_AES_256_GCM_SHA384,
|
||||
CipherSuite.TLS_CHACHA20_POLY1305_SHA256
|
||||
);
|
||||
|
||||
private String protocol;
|
||||
private String cipher;
|
||||
@ -151,48 +172,59 @@ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate {
|
||||
// Re-enable TLSv1 and TLSv1.1 since test depends on it.
|
||||
SecurityUtils.removeFromDisabledTlsAlgs("TLSv1", "TLSv1.1");
|
||||
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
for (CipherSuite cipherSuite : CIPHER_SUITES) {
|
||||
// Named group converted to lower case just
|
||||
// to satisfy Test condition
|
||||
boolean hybridGroup = HYBRID_NAMEDGROUPS.contains(namedGroup);
|
||||
List<Protocol> protocolList = hybridGroup ?
|
||||
HYBRID_PROTOCOL : PROTOCOLS;
|
||||
List<CipherSuite> cipherList = hybridGroup ?
|
||||
HYBRID_CIPHER_SUITES : CIPHER_SUITES;
|
||||
|
||||
// non-Hybrid named group converted to lower case just
|
||||
// to satisfy Test condition
|
||||
String normalizedGroup = hybridGroup ?
|
||||
namedGroup : namedGroup.toLowerCase();
|
||||
|
||||
for (Protocol protocol : protocolList) {
|
||||
for (CipherSuite cipherSuite : cipherList) {
|
||||
if (cipherSuite.supportedByProtocol(protocol)
|
||||
&& groupSupportdByCipher(namedGroup.toLowerCase(),
|
||||
cipherSuite)) {
|
||||
&& groupSupportedByCipher(normalizedGroup,
|
||||
cipherSuite)) {
|
||||
System.out.printf("Protocol: %s, cipher suite: %s%n",
|
||||
protocol, cipherSuite);
|
||||
// Named group converted to lower case just
|
||||
// to satisfy Test condition
|
||||
new NamedGroupsWithCipherSuite(protocol,
|
||||
cipherSuite, namedGroup.toLowerCase()).run();
|
||||
cipherSuite, normalizedGroup).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean groupSupportdByCipher(String group,
|
||||
private static boolean groupSupportedByCipher(String group,
|
||||
CipherSuite cipherSuite) {
|
||||
if (HYBRID_NAMEDGROUPS.contains(group)) {
|
||||
return cipherSuite.keyExAlgorithm == null;
|
||||
}
|
||||
|
||||
return (group.startsWith("x")
|
||||
&& xdhGroupSupportdByCipher(cipherSuite))
|
||||
&& xdhGroupSupportedByCipher(cipherSuite))
|
||||
|| (group.startsWith("secp")
|
||||
&& ecdhGroupSupportdByCipher(cipherSuite))
|
||||
&& ecdhGroupSupportedByCipher(cipherSuite))
|
||||
|| (group.startsWith("ffdhe")
|
||||
&& ffdhGroupSupportdByCipher(cipherSuite));
|
||||
&& ffdhGroupSupportedByCipher(cipherSuite));
|
||||
}
|
||||
|
||||
private static boolean xdhGroupSupportdByCipher(
|
||||
private static boolean xdhGroupSupportedByCipher(
|
||||
CipherSuite cipherSuite) {
|
||||
return cipherSuite.keyExAlgorithm == null
|
||||
|| cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_RSA;
|
||||
}
|
||||
|
||||
private static boolean ecdhGroupSupportdByCipher(
|
||||
private static boolean ecdhGroupSupportedByCipher(
|
||||
CipherSuite cipherSuite) {
|
||||
return cipherSuite.keyExAlgorithm == null
|
||||
|| cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_RSA
|
||||
|| cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_ECDSA;
|
||||
}
|
||||
|
||||
private static boolean ffdhGroupSupportdByCipher(
|
||||
private static boolean ffdhGroupSupportedByCipher(
|
||||
CipherSuite cipherSuite) {
|
||||
return cipherSuite.keyExAlgorithm == null
|
||||
|| cipherSuite.keyExAlgorithm == KeyExAlgorithm.DHE_DSS
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8226374 8242929
|
||||
* @bug 8226374 8242929 8314323
|
||||
* @library /javax/net/ssl/templates
|
||||
* @summary Restrict signature algorithms and named groups
|
||||
* @run main/othervm RestrictNamedGroup x25519
|
||||
@ -36,6 +36,9 @@
|
||||
* @run main/othervm RestrictNamedGroup ffdhe4096
|
||||
* @run main/othervm RestrictNamedGroup ffdhe6144
|
||||
* @run main/othervm RestrictNamedGroup ffdhe8192
|
||||
* @run main/othervm RestrictNamedGroup X25519MLKEM768
|
||||
* @run main/othervm RestrictNamedGroup SecP256r1MLKEM768
|
||||
* @run main/othervm RestrictNamedGroup SecP384r1MLKEM1024
|
||||
*/
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8171279
|
||||
* @bug 8171279 8314323
|
||||
* @library /javax/net/ssl/templates
|
||||
* @summary Test TLS connection with each individual supported group
|
||||
* @run main/othervm SupportedGroups x25519
|
||||
@ -36,6 +36,9 @@
|
||||
* @run main/othervm SupportedGroups ffdhe4096
|
||||
* @run main/othervm SupportedGroups ffdhe6144
|
||||
* @run main/othervm SupportedGroups ffdhe8192
|
||||
* @run main/othervm SupportedGroups X25519MLKEM768
|
||||
* @run main/othervm SupportedGroups SecP256r1MLKEM768
|
||||
* @run main/othervm SupportedGroups SecP384r1MLKEM1024
|
||||
*/
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
@ -45,15 +48,24 @@ import javax.net.ssl.SSLServerSocket;
|
||||
public class SupportedGroups extends SSLSocketTemplate {
|
||||
|
||||
private static volatile int index;
|
||||
private static final String[][][] protocols = {
|
||||
private static final String[][][] protocolsForClassic = {
|
||||
{{"TLSv1.3"}, {"TLSv1.3"}},
|
||||
{{"TLSv1.3", "TLSv1.2"}, {"TLSv1.2"}},
|
||||
{{"TLSv1.2"}, {"TLSv1.3", "TLSv1.2"}},
|
||||
{{"TLSv1.2"}, {"TLSv1.2"}}
|
||||
};
|
||||
|
||||
public SupportedGroups() {
|
||||
private static final String[][][] protocolsForHybrid = {
|
||||
{{"TLSv1.3"}, {"TLSv1.3"}},
|
||||
{{"TLSv1.3", "TLSv1.2"}, {"TLSv1.3"}},
|
||||
{{"TLSv1.3"}, {"TLSv1.3", "TLSv1.2"}}
|
||||
};
|
||||
|
||||
private final String[][][] protocols;
|
||||
|
||||
public SupportedGroups(String[][][] protocols) {
|
||||
this.serverAddress = InetAddress.getLoopbackAddress();
|
||||
this.protocols = protocols;
|
||||
}
|
||||
|
||||
// Servers are configured before clients, increment test case after.
|
||||
@ -85,8 +97,18 @@ public class SupportedGroups extends SSLSocketTemplate {
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.setProperty("jdk.tls.namedGroups", args[0]);
|
||||
|
||||
boolean hybridGroup = hybridNamedGroup(args[0]);
|
||||
String[][][] protocols = hybridGroup ?
|
||||
protocolsForHybrid : protocolsForClassic;
|
||||
|
||||
for (index = 0; index < protocols.length; index++) {
|
||||
(new SupportedGroups()).run();
|
||||
(new SupportedGroups(protocols)).run();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hybridNamedGroup(String namedGroup) {
|
||||
return namedGroup.equals("X25519MLKEM768") ||
|
||||
namedGroup.equals("SecP256r1MLKEM768") ||
|
||||
namedGroup.equals("SecP384r1MLKEM1024");
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
@ -75,8 +76,15 @@ public class SSLHandshake {
|
||||
@Param({"true", "false"})
|
||||
boolean resume;
|
||||
|
||||
@Param({"TLSv1.2", "TLS"})
|
||||
String tlsVersion;
|
||||
@Param({
|
||||
"TLSv1.2-secp256r1",
|
||||
"TLSv1.3-x25519", "TLSv1.3-secp256r1", "TLSv1.3-secp384r1",
|
||||
"TLSv1.3-X25519MLKEM768", "TLSv1.3-SecP256r1MLKEM768", "TLSv1.3-SecP384r1MLKEM1024"
|
||||
})
|
||||
String versionAndGroup;
|
||||
|
||||
private String tlsVersion;
|
||||
private String namedGroup;
|
||||
|
||||
private static SSLContext getServerContext() {
|
||||
try {
|
||||
@ -96,6 +104,10 @@ public class SSLHandshake {
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void init() throws Exception {
|
||||
String[] components = versionAndGroup.split("-", 2);
|
||||
tlsVersion = components[0];
|
||||
namedGroup = components[1];
|
||||
|
||||
KeyStore ts = TestCertificates.getTrustStore();
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
|
||||
@ -195,5 +207,14 @@ public class SSLHandshake {
|
||||
*/
|
||||
clientEngine = sslClientCtx.createSSLEngine("client", 80);
|
||||
clientEngine.setUseClientMode(true);
|
||||
|
||||
// Set the key exchange named group in client and server engines
|
||||
SSLParameters clientParams = clientEngine.getSSLParameters();
|
||||
clientParams.setNamedGroups(new String[]{namedGroup});
|
||||
clientEngine.setSSLParameters(clientParams);
|
||||
|
||||
SSLParameters serverParams = serverEngine.getSSLParameters();
|
||||
serverParams.setNamedGroups(new String[]{namedGroup});
|
||||
serverEngine.setSSLParameters(serverParams);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,34 +23,69 @@
|
||||
package org.openjdk.bench.javax.crypto.full;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import javax.crypto.DecapsulateException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import javax.crypto.KEM;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
|
||||
public class KEMBench extends CryptoBase {
|
||||
public abstract class KEMBench extends CryptoBase {
|
||||
|
||||
public static final int SET_SIZE = 128;
|
||||
|
||||
@Param({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024" })
|
||||
@Param({})
|
||||
private String algorithm;
|
||||
|
||||
@Param({""}) // Used when the KeyPairGenerator Alg != KEM Alg
|
||||
private String kpgSpec;
|
||||
|
||||
private KeyPair[] keys;
|
||||
private byte[][] messages;
|
||||
|
||||
private KEM kem;
|
||||
|
||||
@Setup
|
||||
public void setup() throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
kem = (prov == null) ? KEM.getInstance(algorithm) : KEM.getInstance(algorithm, prov);
|
||||
KeyPairGenerator generator = (prov == null) ? KeyPairGenerator.getInstance(algorithm) : KeyPairGenerator.getInstance(algorithm, prov);
|
||||
public void setup() throws NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidAlgorithmParameterException {
|
||||
String kpgAlg;
|
||||
String kpgParams;
|
||||
kem = (prov == null) ? KEM.getInstance(algorithm) :
|
||||
KEM.getInstance(algorithm, prov);
|
||||
|
||||
// By default use the same provider for KEM and KPG
|
||||
Provider kpgProv = prov;
|
||||
if (kpgSpec.isEmpty()) {
|
||||
kpgAlg = algorithm;
|
||||
kpgParams = "";
|
||||
} else {
|
||||
// The key pair generation spec is broken down from a colon-
|
||||
// delimited string spec into 3 fields:
|
||||
// [0] - the provider name
|
||||
// [1] - the algorithm name
|
||||
// [2] - the parameters (i.e. the name of the curve)
|
||||
String[] kpgTok = kpgSpec.split(":");
|
||||
kpgProv = Security.getProvider(kpgTok[0]);
|
||||
kpgAlg = kpgTok[1];
|
||||
kpgParams = kpgTok[2];
|
||||
}
|
||||
KeyPairGenerator generator = (kpgProv == null) ?
|
||||
KeyPairGenerator.getInstance(kpgAlg) :
|
||||
KeyPairGenerator.getInstance(kpgAlg, kpgProv);
|
||||
if (kpgParams != null && !kpgParams.isEmpty()) {
|
||||
generator.initialize(new ECGenParameterSpec(kpgParams));
|
||||
}
|
||||
keys = new KeyPair[SET_SIZE];
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
keys[i] = generator.generateKeyPair();
|
||||
@ -63,20 +98,79 @@ public class KEMBench extends CryptoBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static Provider getInternalJce() {
|
||||
try {
|
||||
Class<?> dhClazz = Class.forName("sun.security.ssl.HybridProvider");
|
||||
return (Provider) dhClazz.getField("PROVIDER").get(null);
|
||||
} catch (ReflectiveOperationException exc) {
|
||||
throw new RuntimeException(exc);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(SET_SIZE)
|
||||
public void encapsulate(Blackhole bh) throws InvalidKeyException {
|
||||
for (KeyPair kp : keys) {
|
||||
bh.consume(kem.newEncapsulator(kp.getPublic()).encapsulate().encapsulation());
|
||||
bh.consume(kem.newEncapsulator(kp.getPublic()).encapsulate().
|
||||
encapsulation());
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(SET_SIZE)
|
||||
public void decapsulate(Blackhole bh) throws InvalidKeyException, DecapsulateException {
|
||||
public void decapsulate(Blackhole bh) throws InvalidKeyException,
|
||||
DecapsulateException {
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
bh.consume(kem.newDecapsulator(keys[i].getPrivate()).decapsulate(messages[i]));
|
||||
bh.consume(kem.newDecapsulator(keys[i].getPrivate()).
|
||||
decapsulate(messages[i]));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MLKEM extends KEMBench {
|
||||
@Param({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024" })
|
||||
private String algorithm;
|
||||
|
||||
@Param({""}) // ML-KEM uses the same alg for KPG and KEM
|
||||
private String kpgSpec;
|
||||
}
|
||||
|
||||
@Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens",
|
||||
"java.base/sun.security.ssl=ALL-UNNAMED"})
|
||||
public static class JSSE_DHasKEM extends KEMBench {
|
||||
@Setup
|
||||
public void init() {
|
||||
try {
|
||||
prov = getInternalJce();
|
||||
super.setup();
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new RuntimeException(gse);
|
||||
}
|
||||
}
|
||||
|
||||
@Param({"DH"})
|
||||
private String algorithm;
|
||||
|
||||
@Param({"SunEC:XDH:x25519", "SunEC:EC:secp256r1", "SunEC:EC:secp384r1"})
|
||||
private String kpgSpec;
|
||||
}
|
||||
|
||||
@Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens",
|
||||
"java.base/sun.security.ssl=ALL-UNNAMED"})
|
||||
public static class JSSE_Hybrid extends KEMBench {
|
||||
@Setup
|
||||
public void init() {
|
||||
try {
|
||||
prov = getInternalJce();
|
||||
super.setup();
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new RuntimeException(gse);
|
||||
}
|
||||
}
|
||||
|
||||
@Param({"X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024"})
|
||||
private String algorithm;
|
||||
|
||||
@Param({""}) // ML-KEM uses the same alg for KPG and KEM
|
||||
private String kpgSpec;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,13 +22,16 @@
|
||||
*/
|
||||
package org.openjdk.bench.javax.crypto.full;
|
||||
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
|
||||
public class KeyPairGeneratorBench extends CryptoBase {
|
||||
|
||||
@ -45,11 +48,21 @@ public class KeyPairGeneratorBench extends CryptoBase {
|
||||
setupProvider();
|
||||
generator = (prov == null) ? KeyPairGenerator.getInstance(algorithm)
|
||||
: KeyPairGenerator.getInstance(algorithm, prov);
|
||||
if (keyLength > 0) { // not all key pair generators allow the use of key length
|
||||
// not all key pair generators allow the use of key length
|
||||
if (keyLength > 0) {
|
||||
generator.initialize(keyLength);
|
||||
}
|
||||
}
|
||||
|
||||
private static Provider getInternalJce() {
|
||||
try {
|
||||
Class<?> dhClazz = Class.forName("sun.security.ssl.HybridProvider");
|
||||
return (Provider) dhClazz.getField("PROVIDER").get(null);
|
||||
} catch (ReflectiveOperationException exc) {
|
||||
throw new RuntimeException(exc);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public KeyPair generateKeyPair() {
|
||||
return generator.generateKeyPair();
|
||||
@ -118,4 +131,23 @@ public class KeyPairGeneratorBench extends CryptoBase {
|
||||
private int keyLength;
|
||||
}
|
||||
|
||||
@Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens",
|
||||
"java.base/sun.security.ssl=ALL-UNNAMED"})
|
||||
public static class JSSE_Hybrid extends KeyPairGeneratorBench {
|
||||
@Setup
|
||||
public void init() {
|
||||
try {
|
||||
prov = getInternalJce();
|
||||
super.setup();
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new RuntimeException(gse);
|
||||
}
|
||||
}
|
||||
|
||||
@Param({"X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024"})
|
||||
private String algorithm;
|
||||
|
||||
@Param({"0"}) // Hybrid KPGs don't need key lengths
|
||||
private int keyLength;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user