Hai-May Chao 21dc41f744 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
2026-01-20 16:16:38 +00:00

255 lines
9.4 KiB
Java

/*
* 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);
}
}
}