diff --git a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java new file mode 100644 index 00000000000..01a6e38d2aa --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2023, 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 com.sun.crypto.provider; + +import sun.security.jca.JCAUtil; +import sun.security.ssl.HKDF; +import sun.security.util.*; + +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.interfaces.*; +import java.security.spec.*; +import java.util.Arrays; +import java.util.Objects; + +// Implementing DHKEM defined inside https://www.rfc-editor.org/rfc/rfc9180.html, +// without the AuthEncap and AuthDecap functions +public class DHKEM implements KEMSpi { + + private static final byte[] KEM = new byte[] + {'K', 'E', 'M'}; + private static final byte[] EAE_PRK = new byte[] + {'e', 'a', 'e', '_', 'p', 'r', 'k'}; + private static final byte[] SHARED_SECRET = new byte[] + {'s', 'h', 'a', 'r', 'e', 'd', '_', 's', 'e', 'c', 'r', 'e', 't'}; + private static final byte[] DKP_PRK = new byte[] + {'d', 'k', 'p', '_', 'p', 'r', 'k'}; + private static final byte[] CANDIDATE = new byte[] + {'c', 'a', 'n', 'd', 'i', 'd', 'a', 't', 'e'}; + private static final byte[] SK = new byte[] + {'s', 'k'}; + private static final byte[] HPKE_V1 = new byte[] + {'H', 'P', 'K', 'E', '-', 'v', '1'}; + private static final byte[] EMPTY = new byte[0]; + + private record Handler(Params params, SecureRandom secureRandom, + PrivateKey skR, PublicKey pkR) + implements EncapsulatorSpi, DecapsulatorSpi { + + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { + Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.requireNonNull(algorithm, "null algorithm"); + KeyPair kpE = params.generateKeyPair(secureRandom); + PrivateKey skE = kpE.getPrivate(); + PublicKey pkE = kpE.getPublic(); + byte[] pkEm = params.SerializePublicKey(pkE); + byte[] pkRm = params.SerializePublicKey(pkR); + byte[] kem_context = concat(pkEm, pkRm); + try { + byte[] dh = params.DH(skE, pkR); + byte[] key = params.ExtractAndExpand(dh, kem_context); + return new KEM.Encapsulated( + new SecretKeySpec(key, from, to - from, algorithm), + pkEm, null); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, + int from, int to, String algorithm) throws DecapsulateException { + Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.requireNonNull(algorithm, "null algorithm"); + Objects.requireNonNull(encapsulation, "null encapsulation"); + if (encapsulation.length != params.Npk) { + throw new DecapsulateException("incorrect encapsulation size"); + } + try { + PublicKey pkE = params.DeserializePublicKey(encapsulation); + byte[] dh = params.DH(skR, pkE); + byte[] pkRm = params.SerializePublicKey(pkR); + byte[] kem_context = concat(encapsulation, pkRm); + byte[] key = params.ExtractAndExpand(dh, kem_context); + return new SecretKeySpec(key, from, to - from, algorithm); + } catch (IOException | InvalidKeyException e) { + throw new DecapsulateException("Cannot decapsulate", e); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + @Override + public int engineSecretSize() { + return params.Nsecret; + } + + @Override + public int engineEncapsulationSize() { + return params.Npk; + } + } + + // Not really a random. For KAT test only. It generates key pair from ikm. + public static class RFC9180DeriveKeyPairSR extends SecureRandom { + + static final long serialVersionUID = 0L; + + private final byte[] ikm; + + public RFC9180DeriveKeyPairSR(byte[] ikm) { + super(null, null); // lightest constructor + this.ikm = ikm; + } + + public KeyPair derive(Params params) { + try { + return params.deriveKeyPair(ikm); + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + } + + public KeyPair derive(int kem_id) { + Params params = Arrays.stream(Params.values()) + .filter(p -> p.kem_id == kem_id) + .findFirst() + .orElseThrow(); + return derive(params); + } + } + + private enum Params { + + P256(0x10, 32, 32, 2 * 32 + 1, + "ECDH", "EC", CurveDB.P_256, "SHA-256"), + + P384(0x11, 48, 48, 2 * 48 + 1, + "ECDH", "EC", CurveDB.P_384, "SHA-384"), + + P521(0x12, 64, 66, 2 * 66 + 1, + "ECDH", "EC", CurveDB.P_521, "SHA-512"), + + X25519(0x20, 32, 32, 32, + "XDH", "XDH", NamedParameterSpec.X25519, "SHA-256"), + + X448(0x21, 64, 56, 56, + "XDH", "XDH", NamedParameterSpec.X448, "SHA-512"), + ; + + private final int kem_id; + private final int Nsecret; + private final int Nsk; + private final int Npk; + private final String kaAlgorithm; + private final String keyAlgorithm; + private final AlgorithmParameterSpec spec; + private final String hkdfAlgorithm; + + private final byte[] suiteId; + + Params(int kem_id, int Nsecret, int Nsk, int Npk, + String kaAlgorithm, String keyAlgorithm, AlgorithmParameterSpec spec, + String hkdfAlgorithm) { + this.kem_id = kem_id; + this.spec = spec; + this.Nsecret = Nsecret; + this.Nsk = Nsk; + this.Npk = Npk; + this.kaAlgorithm = kaAlgorithm; + this.keyAlgorithm = keyAlgorithm; + this.hkdfAlgorithm = hkdfAlgorithm; + suiteId = concat(KEM, I2OSP(kem_id, 2)); + } + + private boolean isEC() { + return this == P256 || this == P384 || this == P521; + } + + private KeyPair generateKeyPair(SecureRandom sr) { + if (sr instanceof RFC9180DeriveKeyPairSR r9) { + return r9.derive(this); + } + 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, Npk); + } + } + + 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 byte[] DH(PrivateKey skE, PublicKey pkR) + throws NoSuchAlgorithmException, InvalidKeyException { + KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); + ka.init(skE); + ka.doPhase(pkR, true); + return ka.generateSecret(); + } + + private byte[] ExtractAndExpand(byte[] dh, byte[] kem_context) + throws NoSuchAlgorithmException, InvalidKeyException { + HKDF kdf = new HKDF(hkdfAlgorithm); + SecretKey eae_prk = LabeledExtract(kdf, suiteId, null, EAE_PRK, dh); + return LabeledExpand(kdf, suiteId, eae_prk, SHARED_SECRET, + kem_context, Nsecret); + } + + private PublicKey getPublicKey(PrivateKey sk) + throws InvalidKeyException { + if (!(sk instanceof InternalPrivateKey)) { + try { + KeyFactory kf = KeyFactory.getInstance(keyAlgorithm, "SunEC"); + sk = (PrivateKey) kf.translateKey(sk); + } catch (Exception e) { + throw new InvalidKeyException("Error translating key", e); + } + } + if (sk instanceof InternalPrivateKey ik) { + try { + return ik.calculatePublicKey(); + } catch (UnsupportedOperationException e) { + throw new InvalidKeyException("Error retrieving key", e); + } + } else { + // Should not happen, unless SunEC goes wrong + throw new ProviderException("Unknown key"); + } + } + + // For KAT tests only. See RFC9180DeriveKeyPairSR. + public KeyPair deriveKeyPair(byte[] ikm) throws Exception { + HKDF kdf = new HKDF(hkdfAlgorithm); + SecretKey dkp_prk = LabeledExtract(kdf, suiteId, null, DKP_PRK, ikm); + if (isEC()) { + NamedCurve curve = (NamedCurve) spec; + BigInteger sk = BigInteger.ZERO; + int counter = 0; + while (sk.signum() == 0 || sk.compareTo(curve.getOrder()) >= 0) { + if (counter > 255) { + throw new RuntimeException(); + } + byte[] bytes = LabeledExpand(kdf, suiteId, dkp_prk, + CANDIDATE, I2OSP(counter, 1), Nsk); + // bitmask is defined to be 0xFF for P-256 and P-384, and 0x01 for P-521 + if (this == Params.P521) { + bytes[0] = (byte) (bytes[0] & 0x01); + } + sk = new BigInteger(1, (bytes)); + counter = counter + 1; + } + PrivateKey k = DeserializePrivateKey(sk.toByteArray()); + return new KeyPair(getPublicKey(k), k); + } else { + byte[] sk = LabeledExpand(kdf, suiteId, dkp_prk, SK, EMPTY, Nsk); + PrivateKey k = DeserializePrivateKey(sk); + return new KeyPair(getPublicKey(k), k); + } + } + + private PrivateKey DeserializePrivateKey(byte[] data) throws Exception { + KeySpec keySpec = isEC() + ? new ECPrivateKeySpec(new BigInteger(1, (data)), (NamedCurve) spec) + : new XECPrivateKeySpec(spec, data); + return KeyFactory.getInstance(keyAlgorithm).generatePrivate(keySpec); + } + } + + private static SecureRandom getSecureRandom(SecureRandom userSR) { + return userSR != null ? userSR : JCAUtil.getSecureRandom(); + } + + @Override + public EncapsulatorSpi engineNewEncapsulator( + PublicKey pk, AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (pk == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(pk); + return new Handler(params, getSecureRandom(secureRandom), null, pk); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (sk == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(sk); + return new Handler(params, null, sk, params.getPublicKey(sk)); + } + + 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().equals("X25519")) { + return Params.X25519; + } else if (ns.getName().equals("X448")) { + return Params.X448; + } + } + throw new InvalidKeyException("Unsupported key"); + } + + private static byte[] concat(byte[]... inputs) { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + Arrays.stream(inputs).forEach(o::writeBytes); + return o.toByteArray(); + } + + private static byte[] I2OSP(int n, int w) { + assert n < 256; + assert w == 1 || w == 2; + if (w == 1) { + return new byte[] { (byte) n }; + } else { + return new byte[] { (byte) (n >> 8), (byte) n }; + } + } + + private static SecretKey LabeledExtract(HKDF kdf, byte[] suite_id, + byte[] salt, byte[] label, byte[] ikm) throws InvalidKeyException { + return kdf.extract(salt, + new SecretKeySpec(concat(HPKE_V1, suite_id, label, ikm), "IKM"), + "HKDF-PRK"); + } + + private static byte[] LabeledExpand(HKDF kdf, byte[] suite_id, + SecretKey prk, byte[] label, byte[] info, int L) + throws InvalidKeyException { + byte[] labeled_info = concat(I2OSP(L, 2), HPKE_V1, + suite_id, label, info); + return kdf.expand(prk, labeled_info, L, "NONE").getEncoded(); + } +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 622434cfbaf..10093137151 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2023, 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 @@ -74,6 +74,10 @@ import static sun.security.util.SecurityProviderConstants.*; * * - HMAC-MD5, HMAC-SHA1, HMAC with SHA2 family and SHA3 family of digests * + * - JCEKS KeyStore + * + * - DHKEM + * */ public final class SunJCE extends Provider { @@ -743,6 +747,15 @@ public final class SunJCE extends Provider { ps("KeyStore", "JCEKS", "com.sun.crypto.provider.JceKeyStore"); + /* + * KEMs + */ + attrs.clear(); + attrs.put("ImplementedIn", "Software"); + attrs.put("SupportedKeyClasses", "java.security.interfaces.ECKey" + + "|java.security.interfaces.XECKey"); + ps("KEM", "DHKEM", "com.sun.crypto.provider.DHKEM", null, attrs); + /* * SSL/TLS mechanisms * diff --git a/src/java.base/share/classes/java/security/Provider.java b/src/java.base/share/classes/java/security/Provider.java index fa8c0bd5912..7246285b349 100644 --- a/src/java.base/share/classes/java/security/Provider.java +++ b/src/java.base/share/classes/java/security/Provider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2023, 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 @@ -1599,6 +1599,7 @@ public abstract class Provider extends Properties { addEngine("KeyAgreement", true, null); addEngine("KeyGenerator", false, null); addEngine("SecretKeyFactory", false, null); + addEngine("KEM", true, null); // JSSE addEngine("KeyManagerFactory", false, null); addEngine("SSLContext", false, null); diff --git a/src/java.base/share/classes/javax/crypto/DecapsulateException.java b/src/java.base/share/classes/javax/crypto/DecapsulateException.java new file mode 100644 index 00000000000..89efa9b6e48 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/DecapsulateException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, 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 javax.crypto; + +import java.security.GeneralSecurityException; + +/** + * An exception that is thrown by the + * {@link javax.crypto.KEM.Decapsulator#decapsulate} method to denote an + * error during decapsulation. + * + * @since 21 + */ +public class DecapsulateException extends GeneralSecurityException { + + @java.io.Serial + private static final long serialVersionUID = 21L; + + /** + * Creates a {@code DecapsulateException} with the specified + * detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + */ + public DecapsulateException(String message) { + super(message); + } + + /** + * Creates a {@code DecapsulateException} with the specified + * detail message and cause. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + */ + public DecapsulateException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/java.base/share/classes/javax/crypto/KEM.java b/src/java.base/share/classes/javax/crypto/KEM.java new file mode 100644 index 00000000000..e05027a7abc --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/KEM.java @@ -0,0 +1,737 @@ +/* + * Copyright (c) 2023, 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 javax.crypto; + +import sun.security.jca.GetInstance; + +import java.security.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.List; +import java.util.Objects; + +/** + * This class provides the functionality of a Key Encapsulation Mechanism (KEM). + * A KEM can be used to secure symmetric keys using asymmetric or public key + * cryptography between two parties. The sender calls the encapsulate method + * to generate a secret key and a key encapsulation message, and the receiver + * calls the decapsulate method to recover the same secret key from + * the key encapsulation message. + *
+ * The {@code getInstance} method creates a new {@code KEM} object that + * implements the specified algorithm. + *
+ * A {@code KEM} object is immutable. It is safe to call multiple + * {@code newEncapsulator} and {@code newDecapsulator} methods on the + * same {@code KEM} object at the same time. + *
+ * If a provider is not specified in the {@code getInstance} method when + * instantiating a {@code KEM} object, the {@code newEncapsulator} and + * {@code newDecapsulator} methods may return encapsulators or decapsulators + * from different providers. The provider selected is based on the parameters + * passed to the {@code newEncapsulator} or {@code newDecapsulator} methods: + * the private or public key and the optional {@code AlgorithmParameterSpec}. + * The {@link Encapsulator#providerName} and {@link Decapsulator#providerName} + * methods return the name of the selected provider. + *
+ * {@code Encapsulator} and {@code Decapsulator} objects are also immutable. + * It is safe to invoke multiple {@code encapsulate} and {@code decapsulate} + * methods on the same {@code Encapsulator} or {@code Decapsulator} object + * at the same time. Each invocation of {@code encapsulate} will generate a + * new shared secret and key encapsulation message. + *
+ * + * Example: + * {@snippet lang = java: + * // Receiver side + * var kpg = KeyPairGenerator.getInstance("X25519"); + * var kp = kpg.generateKeyPair(); + * + * // Sender side + * var kem1 = KEM.getInstance("DHKEM"); + * var sender = kem1.newEncapsulator(kp.getPublic()); + * var encapsulated = sender.encapsulate(); + * var k1 = encapsulated.key(); + * + * // Receiver side + * var kem2 = KEM.getInstance("DHKEM"); + * var receiver = kem2.newDecapsulator(kp.getPrivate()); + * var k2 = receiver.decapsulate(encapsulated.encapsulation()); + * + * assert Arrays.equals(k1.getEncoded(), k2.getEncoded()); + * } + * + * @since 21 + */ +public final class KEM { + + /** + * This class specifies the return value of the encapsulate method of + * a Key Encapsulation Mechanism (KEM), which includes the shared secret + * (as a {@code SecretKey}), the key encapsulation message, + * and optional parameters. + *
+ * Note: the key encapsulation message can be also referred to as ciphertext. + * + * @see #newEncapsulator(PublicKey, AlgorithmParameterSpec, SecureRandom) + * @see Encapsulator#encapsulate(int, int, String) + * + * @since 21 + */ + public static final class Encapsulated { + private final SecretKey key; + private final byte[] encapsulation; + private final byte[] params; + + /** + * Constructs an {@code Encapsulated} object. + * + * @param key the shared secret as a key, must not be {@code null}. + * @param encapsulation the key encapsulation message, must not + * be {@code null}. The contents of the array are copied + * to protect against subsequent modification. + * @param params optional parameters, can be {@code null}. + * The contents of the array are copied to protect + * against subsequent modification. + * @throws NullPointerException if {@code key} or {@code encapsulation} + * is {@code null} + */ + public Encapsulated(SecretKey key, byte[] encapsulation, byte[] params) { + Objects.requireNonNull(key); + Objects.requireNonNull(encapsulation); + this.key = key; + this.encapsulation = encapsulation.clone(); + this.params = params == null ? null : params.clone(); + } + + /** + * Returns the {@code SecretKey}. + * + * @return the secret key + */ + public SecretKey key() { + return key; + } + + /** + * Returns the key encapsulation message. + * + * @return the key encapsulation message. A new copy of the byte array + * is returned. + */ + public byte[] encapsulation() { + return encapsulation.clone(); + } + + /** + * Returns the optional parameters in a byte array. + * + * @return the optional parameters in a byte array or {@code null} + * if not specified. A new copy of the byte array is returned. + */ + public byte[] params() { + return params == null ? null : params.clone(); + } + } + + /** + * An encapsulator, generated by {@link #newEncapsulator} on the KEM + * sender side. + *
+ * This class represents the key encapsulation function of a KEM. + * Each invocation of the {@code encapsulate} method generates a + * new secret key and key encapsulation message that is returned + * in an {@link Encapsulated} object. + * + * @since 21 + */ + public static final class Encapsulator { + + private final KEMSpi.EncapsulatorSpi e; + private final Provider p; + + private Encapsulator(KEMSpi.EncapsulatorSpi e, Provider p) { + assert e != null; + assert p != null; + this.e = e; + this.p = p; + } + + /** + * Returns the name of the provider. + * + * @return the name of the provider + */ + public String providerName() { + return p.getName(); + } + + /** + * The key encapsulation function. + *
+ * This method is equivalent to + * {@code encapsulate(0, secretSize(), "Generic")}. This combination + * of arguments must be supported by every implementation. + *
+ * The generated secret key is usually passed to a key derivation + * function (KDF) as the input keying material. + * + * @return a {@link Encapsulated} object containing the shared + * secret, key encapsulation message, and optional parameters. + * The shared secret is a {@code SecretKey} containing all of + * the bytes of the secret, and an algorithm name of "Generic". + */ + public Encapsulated encapsulate() { + return encapsulate(0, secretSize(), "Generic"); + } + + /** + * The key encapsulation function. + *
+ * Each invocation of this method generates a new secret key and key + * encapsulation message that is returned in an {@link Encapsulated} object. + *
+ * An implementation may choose to not support arbitrary combinations + * of {@code from}, {@code to}, and {@code algorithm}. + * + * @param from the initial index of the shared secret byte array + * to be returned, inclusive + * @param to the final index of the shared secret byte array + * to be returned, exclusive + * @param algorithm the algorithm name for the secret key that is returned + * @return a {@link Encapsulated} object containing a portion of + * the shared secret, key encapsulation message, and optional + * parameters. The portion of the shared secret is a + * {@code SecretKey} containing the bytes of the secret + * ranging from {@code from} to {@code to}, exclusive, + * and an algorithm name as specified. For example, + * {@code encapsulate(0, 16, "AES")} uses the first 16 bytes + * of the shared secret as a 128-bit AES key. + * @throws IndexOutOfBoundsException if {@code from < 0}, + * {@code from > to}, or {@code to > secretSize()} + * @throws NullPointerException if {@code algorithm} is {@code null} + * @throws UnsupportedOperationException if the combination of + * {@code from}, {@code to}, and {@code algorithm} + * is not supported by the encapsulator + */ + public Encapsulated encapsulate(int from, int to, String algorithm) { + return e.engineEncapsulate(from, to, algorithm); + } + + /** + * Returns the size of the shared secret. + *
+ * This method can be called to find out the length of the shared secret + * before {@code encapsulate} is called or if the obtained + * {@code SecretKey} is not extractable. + * + * @return the size of the shared secret + */ + public int secretSize() { + int result = e.engineSecretSize(); + assert result >= 0 && result != Integer.MAX_VALUE + : "invalid engineSecretSize result"; + return result; + } + + /** + * Returns the size of the key encapsulation message. + *
+ * This method can be called to find out the length of the encapsulation + * message before {@code encapsulate} is called. + * + * @return the size of the key encapsulation message + */ + public int encapsulationSize() { + int result = e.engineEncapsulationSize(); + assert result >= 0 && result != Integer.MAX_VALUE + : "invalid engineEncapsulationSize result"; + return result; + } + } + + /** + * A decapsulator, generated by {@link #newDecapsulator} on the KEM + * receiver side. + *
+ * This class represents the key decapsulation function of a KEM. + * An invocation of the {@code decapsulate} method recovers the + * secret key from the key encapsulation message. + * + * @since 21 + */ + public static final class Decapsulator { + private final KEMSpi.DecapsulatorSpi d; + private final Provider p; + + private Decapsulator(KEMSpi.DecapsulatorSpi d, Provider p) { + assert d != null; + assert p != null; + this.d = d; + this.p = p; + } + + /** + * Returns the name of the provider. + * + * @return the name of the provider + */ + public String providerName() { + return p.getName(); + } + + /** + * The key decapsulation function. + *
+ * This method is equivalent to + * {@code decapsulate(encapsulation, 0, secretSize(), "Generic")}. This + * combination of arguments must be supported by every implementation. + *
+ * The generated secret key is usually passed to a key derivation + * function (KDF) as the input keying material. + * + * @param encapsulation the key encapsulation message from the sender. + * The size must be equal to the value returned by + * {@link #encapsulationSize()}, or a {@code DecapsulateException} + * will be thrown. + * @return the shared secret as a {@code SecretKey} with + * an algorithm name of "Generic" + * @throws DecapsulateException if an error occurs during the + * decapsulation process + * @throws NullPointerException if {@code encapsulation} is {@code null} + */ + public SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException { + return decapsulate(encapsulation, 0, secretSize(), "Generic"); + } + + /** + * The key decapsulation function. + *
+ * An invocation of this method recovers the secret key from the key + * encapsulation message. + *
+ * An implementation may choose to not support arbitrary combinations + * of {@code from}, {@code to}, and {@code algorithm}. + * + * @param encapsulation the key encapsulation message from the sender. + * The size must be equal to the value returned by + * {@link #encapsulationSize()}, or a {@code DecapsulateException} + * will be thrown. + * @param from the initial index of the shared secret byte array + * to be returned, inclusive + * @param to the final index of the shared secret byte array + * to be returned, exclusive + * @param algorithm the algorithm name for the secret key that is returned + * @return a portion of the shared secret as a {@code SecretKey} + * containing the bytes of the secret ranging from {@code from} + * to {@code to}, exclusive, and an algorithm name as specified. + * For example, {@code decapsulate(encapsulation, secretSize() + * - 16, secretSize(), "AES")} uses the last 16 bytes + * of the shared secret as a 128-bit AES key. + * @throws DecapsulateException if an error occurs during the + * decapsulation process + * @throws IndexOutOfBoundsException if {@code from < 0}, + * {@code from > to}, or {@code to > secretSize()} + * @throws NullPointerException if {@code encapsulation} or + * {@code algorithm} is {@code null} + * @throws UnsupportedOperationException if the combination of + * {@code from}, {@code to}, and {@code algorithm} + * is not supported by the decapsulator + */ + public SecretKey decapsulate(byte[] encapsulation, + int from, int to, String algorithm) + throws DecapsulateException { + return d.engineDecapsulate( + encapsulation, + from, to, + algorithm); + } + + /** + * Returns the size of the shared secret. + *
+ * This method can be called to find out the length of the shared secret + * before {@code decapsulate} is called or if the obtained + * {@code SecretKey} is not extractable. + * + * @return the size of the shared secret + */ + public int secretSize() { + int result = d.engineSecretSize(); + assert result >= 0 && result != Integer.MAX_VALUE + : "invalid engineSecretSize result"; + return result; + } + + /** + * Returns the size of the key encapsulation message. + *
+ * This method can be used to extract the encapsulation message
+ * from a longer byte array if no length information is provided
+ * by a higher level protocol.
+ *
+ * @return the size of the key encapsulation message
+ */
+ public int encapsulationSize() {
+ int result = d.engineEncapsulationSize();
+ assert result >= 0 && result != Integer.MAX_VALUE
+ : "invalid engineEncapsulationSize result";
+ return result;
+ }
+ }
+
+ private static final class DelayedKEM {
+
+ private final Provider.Service[] list; // non empty array
+
+ private DelayedKEM(Provider.Service[] list) {
+ this.list = list;
+ }
+
+ private Encapsulator newEncapsulator(PublicKey publicKey,
+ AlgorithmParameterSpec spec, SecureRandom secureRandom)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ if (publicKey == null) {
+ throw new InvalidKeyException("input key is null");
+ }
+ RuntimeException re = null;
+ InvalidAlgorithmParameterException iape = null;
+ InvalidKeyException ike = null;
+ NoSuchAlgorithmException nsae = null;
+ for (Provider.Service service : list) {
+ if (!service.supportsParameter(publicKey)) {
+ continue;
+ }
+ try {
+ KEMSpi spi = (KEMSpi) service.newInstance(null);
+ return new Encapsulator(
+ spi.engineNewEncapsulator(publicKey, spec, secureRandom),
+ service.getProvider());
+ } catch (NoSuchAlgorithmException e) {
+ nsae = merge(nsae, e);
+ } catch (InvalidAlgorithmParameterException e) {
+ iape = merge(iape, e);
+ } catch (InvalidKeyException e) {
+ ike = merge(ike, e);
+ } catch (RuntimeException e) {
+ re = merge(re, e);
+ }
+ }
+ if (iape != null) throw iape;
+ if (ike != null) throw ike;
+ if (nsae != null) {
+ throw new InvalidKeyException("No installed provider found", nsae);
+ }
+ throw new InvalidKeyException("No installed provider supports this key: "
+ + publicKey.getClass().getName(), re);
+ }
+
+ private static
+ * This method is equivalent to {@code newEncapsulator(publicKey, null, null)}.
+ *
+ * @param publicKey the receiver's public key, must not be {@code null}
+ * @return the encapsulator for this key
+ * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
+ * @throws UnsupportedOperationException if this method is not supported
+ * because an {@code AlgorithmParameterSpec} must be provided
+ */
+ public Encapsulator newEncapsulator(PublicKey publicKey)
+ throws InvalidKeyException {
+ try {
+ return newEncapsulator(publicKey, null, null);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new UnsupportedOperationException(
+ "AlgorithmParameterSpec must be provided", e);
+ }
+ }
+
+ /**
+ * Creates a KEM encapsulator on the KEM sender side.
+ *
+ * This method is equivalent to {@code newEncapsulator(publicKey, null, secureRandom)}.
+ *
+ * @param publicKey the receiver's public key, must not be {@code null}
+ * @param secureRandom the source of randomness for encapsulation.
+ * If {@code} null, a default one from the
+ * implementation will be used.
+ * @return the encapsulator for this key
+ * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
+ * @throws UnsupportedOperationException if this method is not supported
+ * because an {@code AlgorithmParameterSpec} must be provided
+ */
+ public Encapsulator newEncapsulator(PublicKey publicKey, SecureRandom secureRandom)
+ throws InvalidKeyException {
+ try {
+ return newEncapsulator(publicKey, null, secureRandom);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new UnsupportedOperationException(
+ "AlgorithmParameterSpec must be provided", e);
+ }
+ }
+
+ /**
+ * Creates a KEM encapsulator on the KEM sender side.
+ *
+ * An algorithm can define an {@code AlgorithmParameterSpec} child class to
+ * provide extra information in this method. This is especially useful if
+ * the same key can be used to derive shared secrets in different ways.
+ * If any extra information inside this object needs to be transmitted along
+ * with the key encapsulation message so that the receiver is able to create
+ * a matching decapsulator, it will be included as a byte array in the
+ * {@link Encapsulated#params} field inside the encapsulation output.
+ * In this case, the security provider should provide an
+ * {@code AlgorithmParameters} implementation using the same algorithm name
+ * as the KEM. The receiver can initiate such an {@code AlgorithmParameters}
+ * instance with the {@code params} byte array received and recover
+ * an {@code AlgorithmParameterSpec} object to be used in its
+ * {@link #newDecapsulator(PrivateKey, AlgorithmParameterSpec)} call.
+ *
+ * @param publicKey the receiver's public key, must not be {@code null}
+ * @param spec the optional parameter, can be {@code null}
+ * @param secureRandom the source of randomness for encapsulation.
+ * If {@code} null, a default one from the
+ * implementation will be used.
+ * @return the encapsulator for this key
+ * @throws InvalidAlgorithmParameterException if {@code spec} is invalid
+ * or one is required but {@code spec} is {@code null}
+ * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
+ */
+ public Encapsulator newEncapsulator(PublicKey publicKey,
+ AlgorithmParameterSpec spec, SecureRandom secureRandom)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ return delayed != null
+ ? delayed.newEncapsulator(publicKey, spec, secureRandom)
+ : new Encapsulator(spi.engineNewEncapsulator(publicKey, spec, secureRandom), provider);
+ }
+
+ /**
+ * Creates a KEM decapsulator on the KEM receiver side.
+ *
+ * This method is equivalent to {@code newDecapsulator(privateKey, null)}.
+ *
+ * @param privateKey the receiver's private key, must not be {@code null}
+ * @return the decapsulator for this key
+ * @throws InvalidKeyException if {@code privateKey} is {@code null} or invalid
+ * @throws UnsupportedOperationException if this method is not supported
+ * because an {@code AlgorithmParameterSpec} must be provided
+ */
+ public Decapsulator newDecapsulator(PrivateKey privateKey)
+ throws InvalidKeyException {
+ try {
+ return newDecapsulator(privateKey, null);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ /**
+ * Creates a KEM decapsulator on the KEM receiver side.
+ *
+ * @param privateKey the receiver's private key, must not be {@code null}
+ * @param spec the parameter, can be {@code null}
+ * @return the decapsulator for this key
+ * @throws InvalidAlgorithmParameterException if {@code spec} is invalid
+ * or one is required but {@code spec} is {@code null}
+ * @throws InvalidKeyException if {@code privateKey} is {@code null} or invalid
+ */
+ public Decapsulator newDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ return delayed != null
+ ? delayed.newDecapsulator(privateKey, spec)
+ : new Decapsulator(spi.engineNewDecapsulator(privateKey, spec), provider);
+ }
+
+ /**
+ * Returns the name of the algorithm for this {@code KEM} object.
+ *
+ * @return the name of the algorithm for this {@code KEM} object.
+ */
+ public String getAlgorithm() {
+ return this.algorithm;
+ }
+}
diff --git a/src/java.base/share/classes/javax/crypto/KEMSpi.java b/src/java.base/share/classes/javax/crypto/KEMSpi.java
new file mode 100644
index 00000000000..61d13aeb024
--- /dev/null
+++ b/src/java.base/share/classes/javax/crypto/KEMSpi.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2023, 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 javax.crypto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class defines the Service Provider Interface (SPI) for the {@link KEM}
+ * class. A security provider implements this interface to provide an
+ * implementation of a Key Encapsulation Mechanism (KEM) algorithm.
+ *
+ * A KEM algorithm may support a family of configurations. Each configuration
+ * may accept different types of keys, cryptographic primitives, and sizes of
+ * shared secrets and key encapsulation messages. A configuration is defined
+ * by the KEM algorithm name, the key it uses, and an optional
+ * {@code AlgorithmParameterSpec} argument that is specified when creating
+ * an encapsulator or decapsulator. The result of calling
+ * {@link #engineNewEncapsulator} or {@link #engineNewDecapsulator} must return
+ * an encapsulator or decapsulator that maps to a single configuration,
+ * where its {@code engineSecretSize()} and {@code engineEncapsulationSize()}
+ * methods return constant values.
+ *
+ * A {@code KEMSpi} implementation must be immutable. It must be safe to
+ * call multiple {@code engineNewEncapsulator} and {@code engineNewDecapsulator}
+ * methods at the same time.
+ *
+ * {@code EncapsulatorSpi} and {@code DecapsulatorSpi} implementations must also
+ * be immutable. It must be safe to invoke multiple {@code encapsulate} and
+ * {@code decapsulate} methods at the same time. Each invocation of
+ * {@code encapsulate} should generate a new shared secret and key
+ * encapsulation message.
+ *
+ * For example,
+ * {@snippet lang = java:
+ * public static class MyKEMImpl implements KEMSpi {
+ *
+ * @Override
+ * public KEMSpi.EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey,
+ * AlgorithmParameterSpec spec, SecureRandom secureRandom)
+ * throws InvalidAlgorithmParameterException, InvalidKeyException {
+ * if (!checkPublicKey(publicKey)) {
+ * throw new InvalidKeyException("unsupported key");
+ * }
+ * if (!checkParameters(spec)) {
+ * throw new InvalidAlgorithmParameterException("unsupported params");
+ * }
+ * return new MyEncapsulator(publicKey, spec, secureRandom);
+ * }
+ *
+ * class MyEncapsulator implements KEMSpi.EncapsulatorSpi {
+ * MyEncapsulator(PublicKey publicKey, AlgorithmParameterSpec spec,
+ * SecureRandom secureRandom){
+ * this.spec = spec != null ? spec : getDefaultParameters();
+ * this.secureRandom = secureRandom != null
+ * ? secureRandom
+ * : getDefaultSecureRandom();
+ * this.publicKey = publicKey;
+ * }
+ *
+ * @Override
+ * public KEM.Encapsulated encapsulate(int from, int to, String algorithm) {
+ * byte[] encapsulation;
+ * byte[] secret;
+ * // calculating...
+ * return new KEM.Encapsulated(
+ * new SecretKeySpec(secret, from, to - from, algorithm),
+ * encapsulation, null);
+ * }
+ *
+ * // ...
+ * }
+ *
+ * // ...
+ * }
+ * }
+ *
+ * @see KEM
+ * @since 21
+ */
+public interface KEMSpi {
+
+ /**
+ * The KEM encapsulator implementation, generated by
+ * {@link #engineNewEncapsulator} on the KEM sender side.
+ *
+ * @see KEM.Encapsulator
+ *
+ * @since 21
+ */
+ interface EncapsulatorSpi {
+ /**
+ * The key encapsulation function.
+ *
+ * Each invocation of this method must generate a new secret key and key
+ * encapsulation message that is returned in an {@link KEM.Encapsulated} object.
+ *
+ * An implementation must support the case where {@code from} is 0,
+ * {@code to} is the same as the return value of {@code secretSize()},
+ * and {@code algorithm} is "Generic".
+ *
+ * @param from the initial index of the shared secret byte array
+ * to be returned, inclusive
+ * @param to the final index of the shared secret byte array
+ * to be returned, exclusive
+ * @param algorithm the algorithm name for the secret key that is returned
+ * @return an {@link KEM.Encapsulated} object containing a portion of
+ * the shared secret as a key with the specified algorithm,
+ * key encapsulation message, and optional parameters.
+ * @throws IndexOutOfBoundsException if {@code from < 0},
+ * {@code from > to}, or {@code to > secretSize()}
+ * @throws NullPointerException if {@code algorithm} is {@code null}
+ * @throws UnsupportedOperationException if the combination of
+ * {@code from}, {@code to}, and {@code algorithm}
+ * is not supported by the encapsulator
+ * @see KEM.Encapsulated
+ * @see KEM.Encapsulator#encapsulate(int, int, String)
+ */
+ KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm);
+
+ /**
+ * Returns the size of the shared secret.
+ *
+ * @return the size of the shared secret as a finite non-negative integer
+ * @see KEM.Encapsulator#secretSize()
+ */
+ int engineSecretSize();
+
+ /**
+ * Returns the size of the key encapsulation message.
+ *
+ * @return the size of the key encapsulation message as a finite non-negative integer
+ * @see KEM.Encapsulator#encapsulationSize()
+ */
+ int engineEncapsulationSize();
+ }
+
+ /**
+ * The KEM decapsulator implementation, generated by
+ * {@link #engineNewDecapsulator} on the KEM receiver side.
+ *
+ * @see KEM.Decapsulator
+ *
+ * @since 21
+ */
+ interface DecapsulatorSpi {
+ /**
+ * The key decapsulation function.
+ *
+ * An invocation of this method recovers the secret key from the key
+ * encapsulation message.
+ *
+ * An implementation must support the case where {@code from} is 0,
+ * {@code to} is the same as the return value of {@code secretSize()},
+ * and {@code algorithm} is "Generic".
+ *
+ * @param encapsulation the key encapsulation message from the sender.
+ * The size must be equal to the value returned by
+ * {@link #engineEncapsulationSize()} ()}, or a
+ * {@code DecapsulateException} must be thrown.
+ * @param from the initial index of the shared secret byte array
+ * to be returned, inclusive
+ * @param to the final index of the shared secret byte array
+ * to be returned, exclusive
+ * @param algorithm the algorithm name for the secret key that is returned
+ * @return a portion of the shared secret as a {@code SecretKey} with
+ * the specified algorithm
+ * @throws DecapsulateException if an error occurs during the
+ * decapsulation process
+ * @throws IndexOutOfBoundsException if {@code from < 0},
+ * {@code from > to}, or {@code to > secretSize()}
+ * @throws NullPointerException if {@code encapsulation} or
+ * {@code algorithm} is {@code null}
+ * @throws UnsupportedOperationException if the combination of
+ * {@code from}, {@code to}, and {@code algorithm}
+ * is not supported by the decapsulator
+ * @see KEM.Decapsulator#decapsulate(byte[], int, int, String)
+ */
+ SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm)
+ throws DecapsulateException;
+
+ /**
+ * Returns the size of the shared secret.
+ *
+ * @return the size of the shared secret as a finite non-negative integer
+ * @see KEM.Decapsulator#secretSize()
+ */
+ int engineSecretSize();
+
+ /**
+ * Returns the size of the key encapsulation message.
+ *
+ * @return the size of the key encapsulation message as a finite non-negative integer
+ * @see KEM.Decapsulator#encapsulationSize()
+ */
+ int engineEncapsulationSize();
+ }
+
+ /**
+ * Creates a KEM encapsulator on the KEM sender side.
+ *
+ * @param publicKey the receiver's public key, must not be {@code null}
+ * @param spec the optional parameter, can be {@code null}
+ * @param secureRandom the source of randomness for encapsulation.
+ * If {@code null}, the implementation must provide
+ * a default one.
+ * @return the encapsulator for this key
+ * @throws InvalidAlgorithmParameterException if {@code spec} is invalid
+ * or one is required but {@code spec} is {@code null}
+ * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
+ * @see KEM#newEncapsulator(PublicKey, AlgorithmParameterSpec, SecureRandom)
+ */
+ EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey,
+ AlgorithmParameterSpec spec, SecureRandom secureRandom)
+ throws InvalidAlgorithmParameterException, InvalidKeyException;
+
+ /**
+ * Creates a KEM decapsulator on the KEM receiver side.
+ *
+ * @param privateKey the receiver's private key, must not be {@code null}
+ * @param spec the optional parameter, can be {@code null}
+ * @return the decapsulator for this key
+ * @throws InvalidAlgorithmParameterException if {@code spec} is invalid
+ * or one is required but {@code spec} is {@code null}
+ * @throws InvalidKeyException if {@code privateKey} is {@code null} or invalid
+ * @see KEM#newDecapsulator(PrivateKey, AlgorithmParameterSpec)
+ */
+ DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
+ throws InvalidAlgorithmParameterException, InvalidKeyException;
+}
diff --git a/src/java.base/share/classes/sun/security/ssl/HKDF.java b/src/java.base/share/classes/sun/security/ssl/HKDF.java
index 1ea46807d75..5e5cd144305 100644
--- a/src/java.base/share/classes/sun/security/ssl/HKDF.java
+++ b/src/java.base/share/classes/sun/security/ssl/HKDF.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2023, 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
@@ -43,7 +43,7 @@ import java.util.Objects;
* digest algorithm will be used by the HMAC function as part of the HKDF
* derivation process.
*/
-final class HKDF {
+public final class HKDF {
private final Mac hmacObj;
private final int hmacLen;
@@ -57,7 +57,7 @@ final class HKDF {
* @throws NoSuchAlgorithmException if that message digest algorithm does
* not have an HMAC variant supported on any available provider.
*/
- HKDF(String hashAlg) throws NoSuchAlgorithmException {
+ public HKDF(String hashAlg) throws NoSuchAlgorithmException {
Objects.requireNonNull(hashAlg,
"Must provide underlying HKDF Digest algorithm.");
String hmacAlg = "Hmac" + hashAlg.replace("-", "");
@@ -82,7 +82,7 @@ final class HKDF {
* @throws InvalidKeyException if the {@code salt} parameter cannot be
* used to initialize the underlying HMAC.
*/
- SecretKey extract(SecretKey salt, SecretKey inputKey, String keyAlg)
+ public SecretKey extract(SecretKey salt, SecretKey inputKey, String keyAlg)
throws InvalidKeyException {
if (salt == null) {
salt = new SecretKeySpec(new byte[hmacLen], "HKDF-Salt");
@@ -110,7 +110,7 @@ final class HKDF {
* @throws InvalidKeyException if the {@code salt} parameter cannot be
* used to initialize the underlying HMAC.
*/
- SecretKey extract(byte[] salt, SecretKey inputKey, String keyAlg)
+ public SecretKey extract(byte[] salt, SecretKey inputKey, String keyAlg)
throws InvalidKeyException {
if (salt == null) {
salt = new byte[hmacLen];
@@ -133,7 +133,7 @@ final class HKDF {
* @throws InvalidKeyException if the underlying HMAC operation cannot
* be initialized using the provided {@code pseudoRandKey} object.
*/
- SecretKey expand(SecretKey pseudoRandKey, byte[] info, int outLen,
+ public SecretKey expand(SecretKey pseudoRandKey, byte[] info, int outLen,
String keyAlg) throws InvalidKeyException {
byte[] kdfOutput;
diff --git a/src/java.base/share/classes/sun/security/util/CurveDB.java b/src/java.base/share/classes/sun/security/util/CurveDB.java
index 37578c157bd..b3b44ff0684 100644
--- a/src/java.base/share/classes/sun/security/util/CurveDB.java
+++ b/src/java.base/share/classes/sun/security/util/CurveDB.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2023, 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
@@ -39,6 +39,11 @@ import java.util.*;
* @author Andreas Sterbenz
*/
public class CurveDB {
+
+ public static final NamedCurve P_256;
+ public static final NamedCurve P_384;
+ public static final NamedCurve P_521;
+
private static final int P = 1; // prime curve
private static final int B = 2; // binary curve
private static final int PD = 5; // prime curve, mark as default
@@ -109,7 +114,7 @@ public class CurveDB {
return new BigInteger(s, 16);
}
- private static void add(KnownOIDs o, int type, String sfield,
+ private static NamedCurve add(KnownOIDs o, int type, String sfield,
String a, String b, String x, String y, String n, int h) {
BigInteger p = bi(sfield);
ECField field;
@@ -143,6 +148,8 @@ public class CurveDB {
// the curve is marked as a default curve.
lengthMap.put(len, params);
}
+
+ return params;
}
static {
@@ -255,7 +262,7 @@ public class CurveDB {
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
1);
- add(KnownOIDs.secp256r1, PD,
+ P_256 = add(KnownOIDs.secp256r1, PD,
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
@@ -264,7 +271,7 @@ public class CurveDB {
"FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
1);
- add(KnownOIDs.secp384r1, PD,
+ P_384 = add(KnownOIDs.secp384r1, PD,
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
"B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
@@ -273,7 +280,7 @@ public class CurveDB {
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
1);
- add(KnownOIDs.secp521r1, PD,
+ P_521 = add(KnownOIDs.secp521r1, PD,
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
"0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
diff --git a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECOperations.java b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECOperations.java
index c750e510fc7..d4959aed463 100644
--- a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECOperations.java
+++ b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2023, 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
@@ -541,7 +541,7 @@ public class ECOperations {
final class Secp256R1GeneratorMultiplier implements PointMultiplier {
private static final ECPoint generator =
- CurveDB.lookup("secp256r1").getGenerator();
+ CurveDB.P_256.getGenerator();
private static final PointMultiplier multiplier =
new Secp256R1GeneratorMultiplier();
diff --git a/src/jdk.crypto.ec/share/classes/sun/security/ec/SunEC.java b/src/jdk.crypto.ec/share/classes/sun/security/ec/SunEC.java
index 3cfb74c8115..7f8c4dba002 100644
--- a/src/jdk.crypto.ec/share/classes/sun/security/ec/SunEC.java
+++ b/src/jdk.crypto.ec/share/classes/sun/security/ec/SunEC.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2023, 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
@@ -212,11 +212,8 @@ public final class SunEC extends Provider {
boolean firstCurve = true;
StringBuilder names = new StringBuilder();
- for (NamedCurve namedCurve :
- List.of(
- CurveDB.lookup("secp256r1"),
- CurveDB.lookup("secp384r1"),
- CurveDB.lookup("secp521r1"))) {
+ for (NamedCurve namedCurve : List.of(
+ CurveDB.P_256, CurveDB.P_384, CurveDB.P_521)) {
if (!firstCurve) {
names.append("|");
} else {
diff --git a/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java b/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java
new file mode 100644
index 00000000000..27b72b5cf7c
--- /dev/null
+++ b/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8297878
+ * @summary Key Encapsulation Mechanism API
+ * @library /test/lib
+ * @modules java.base/com.sun.crypto.provider
+ */
+import jdk.test.lib.Asserts;
+import jdk.test.lib.Utils;
+
+import javax.crypto.DecapsulateException;
+import javax.crypto.KEM;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.*;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.*;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.Consumer;
+
+import com.sun.crypto.provider.DHKEM;
+
+public class Compliance {
+
+ public static void main(String[] args) throws Exception {
+ basic();
+ conform();
+ determined();
+ try {
+ Security.insertProviderAt(new ProviderImpl(), 1);
+ delayed();
+ } finally {
+ Security.removeProvider("XP");
+ }
+ }
+
+ // Encapsulated conformance checks
+ private static void conform() {
+ new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), new byte[0], new byte[0]);
+ new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), new byte[0], null);
+ Utils.runAndCheckException(
+ () -> new KEM.Encapsulated(null, new byte[0], null),
+ NullPointerException.class);
+ Utils.runAndCheckException(
+ () -> new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), null, null),
+ NullPointerException.class);
+ }
+
+ // basic should and shouldn't behaviors
+ static void basic() throws Exception {
+ KeyPair kpRSA = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+ KeyPair kpX = KeyPairGenerator.getInstance("X25519").generateKeyPair();
+
+ KeyPairGenerator ecg = KeyPairGenerator.getInstance("EC");
+ ecg.initialize(new ECGenParameterSpec("secp256r1"));
+ KeyPair kpEC = ecg.generateKeyPair();
+
+ KEM.getInstance("DHKEM", (String) null);
+ KEM.getInstance("DHKEM", (Provider) null);
+ KEM kem = KEM.getInstance("DHKEM");
+ Utils.runAndCheckException(
+ () -> KEM.getInstance("OLALA"),
+ NoSuchAlgorithmException.class);
+ Utils.runAndCheckException(
+ () -> KEM.getInstance("DHKEM", "NoWhere"),
+ NoSuchProviderException.class);
+ Utils.runAndCheckException(
+ () -> KEM.getInstance("DHKEM", "SunRsaSign"),
+ NoSuchAlgorithmException.class);
+
+ Utils.runAndCheckException(
+ () -> kem.newEncapsulator(null),
+ InvalidKeyException.class);
+ Utils.runAndCheckException(
+ () -> kem.newDecapsulator(null),
+ InvalidKeyException.class);
+
+ // Still an EC key, rejected by implementation
+ Utils.runAndCheckException(
+ () -> kem.newEncapsulator(badECKey()),
+ ExChecker.of(InvalidKeyException.class).by(DHKEM.class));
+
+ // Not an EC key at all, rejected by framework coz it's not
+ // listed in "SupportedKeyClasses" in SunJCE.java.
+ Utils.runAndCheckException(
+ () -> kem.newEncapsulator(kpRSA.getPublic()),
+ ExChecker.of(InvalidKeyException.class).by(KEM.class.getName() + "$DelayedKEM"));
+
+ Utils.runAndCheckException(
+ () -> kem.newDecapsulator(kpRSA.getPrivate()),
+ InvalidKeyException.class);
+
+ kem.newEncapsulator(kpX.getPublic(), null);
+ kem.newEncapsulator(kpX.getPublic(), null, null);
+ KEM.Encapsulator e2 = kem.newEncapsulator(kpX.getPublic());
+ KEM.Encapsulated enc1 = e2.encapsulate(0, e2.secretSize(), "AES");
+ Asserts.assertEQ(enc1.key().getEncoded().length, e2.secretSize());
+ Asserts.assertEQ(enc1.key().getAlgorithm(), "AES");
+
+ Utils.runAndCheckException(
+ () -> e2.encapsulate(-1, 12, "AES"),
+ IndexOutOfBoundsException.class);
+ Utils.runAndCheckException(
+ () -> e2.encapsulate(0, e2.secretSize() + 1, "AES"),
+ IndexOutOfBoundsException.class);
+ Utils.runAndCheckException(
+ () -> e2.encapsulate(0, e2.secretSize(), null),
+ NullPointerException.class);
+
+ KEM.Encapsulated enc = e2.encapsulate();
+ Asserts.assertEQ(enc.key().getEncoded().length, e2.secretSize());
+ Asserts.assertEQ(enc.key().getAlgorithm(), "Generic");
+
+ kem.newDecapsulator(kpX.getPrivate(), null);
+ KEM.Decapsulator d = kem.newDecapsulator(kpX.getPrivate());
+ d.decapsulate(enc.encapsulation());
+ SecretKey dec = d.decapsulate(enc.encapsulation());
+ Asserts.assertTrue(Arrays.equals(enc.key().getEncoded(), dec.getEncoded()));
+
+ dec = d.decapsulate(enc.encapsulation(), 0, d.secretSize(), "AES");
+ Asserts.assertTrue(Arrays.equals(enc.key().getEncoded(), dec.getEncoded()));
+
+ KEM.Encapsulated encHead = e2.encapsulate(0, 16, "AES");
+ Asserts.assertEQ(encHead.key().getEncoded().length, 16);
+ Asserts.assertEQ(encHead.key().getAlgorithm(), "AES");
+ SecretKey decHead = d.decapsulate(encHead.encapsulation(), 0, 16, "AES");
+ Asserts.assertEQ(encHead.key(), decHead);
+
+ KEM.Encapsulated encTail = e2.encapsulate(
+ e2.secretSize() - 16, e2.secretSize(), "AES");
+ Asserts.assertEQ(encTail.key().getEncoded().length, 16);
+ Asserts.assertEQ(encTail.key().getAlgorithm(), "AES");
+ SecretKey decTail = d.decapsulate(encTail.encapsulation(),
+ d.secretSize() - 16, d.secretSize(), "AES");
+ Asserts.assertEQ(encTail.key(), decTail);
+
+ Utils.runAndCheckException(
+ () -> d.decapsulate(null),
+ NullPointerException.class);
+ Utils.runAndCheckException(
+ () -> d.decapsulate(enc.encapsulation(), -1, 12, "AES"),
+ IndexOutOfBoundsException.class);
+ Utils.runAndCheckException(
+ () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize() + 1, "AES"),
+ IndexOutOfBoundsException.class);
+ Utils.runAndCheckException(
+ () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize(), null),
+ NullPointerException.class);
+
+ KEM.Encapsulator e3 = kem.newEncapsulator(kpEC.getPublic());
+ KEM.Encapsulated enc2 = e3.encapsulate();
+ KEM.Decapsulator d3 = kem.newDecapsulator(kpX.getPrivate());
+ Utils.runAndCheckException(
+ () -> d3.decapsulate(enc2.encapsulation()),
+ DecapsulateException.class);
+
+ Utils.runAndCheckException(
+ () -> d3.decapsulate(new byte[100]),
+ DecapsulateException.class);
+ }
+
+ static class MySecureRandom extends SecureRandom {
+ final Random ran;
+
+ MySecureRandom(long seed) {
+ ran = new Random(seed);
+ }
+
+ @Override
+ public void nextBytes(byte[] bytes) {
+ ran.nextBytes(bytes);
+ }
+ }
+
+ // Same random should generate same key encapsulation messages
+ static void determined() throws Exception {
+ long seed = new Random().nextLong();
+ byte[] enc1 = calcDetermined(seed);
+ byte[] enc2 = calcDetermined(seed);
+ Asserts.assertTrue(Arrays.equals(enc1, enc2),
+ "Undetermined for " + seed);
+ }
+
+ static byte[] calcDetermined(long seed) throws Exception {
+ SecureRandom random = new MySecureRandom(seed);
+ KeyPairGenerator g = KeyPairGenerator.getInstance("XDH");
+ g.initialize(NamedParameterSpec.X25519, random);
+ PublicKey pk = g.generateKeyPair().getPublic();
+ KEM kem = KEM.getInstance("DHKEM");
+ kem.newEncapsulator(pk, random); // skip one
+ KEM.Encapsulator e = kem.newEncapsulator(pk, random);
+ byte[] enc1 = e.encapsulate().encapsulation();
+ byte[] enc2 = e.encapsulate().encapsulation();
+ Asserts.assertFalse(Arrays.equals(enc1, enc2));
+ return enc2;
+ }
+
+ public static class ProviderImpl extends Provider {
+ ProviderImpl() {
+ super("XP", "1", "XP");
+ put("KEM.DHKEM", "Compliance$KEMImpl");
+ }
+ }
+
+ static boolean isEven(Key k) {
+ return Arrays.hashCode(k.getEncoded()) % 2 == 0;
+ }
+
+ public static class KEMImpl extends DHKEM {
+
+ @Override
+ public EncapsulatorSpi engineNewEncapsulator(PublicKey pk, AlgorithmParameterSpec spec, SecureRandom secureRandom)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ if (!isEven(pk)) throw new InvalidKeyException("Only accept even keys");
+ return super.engineNewEncapsulator(pk, spec, secureRandom);
+ }
+
+ @Override
+ public DecapsulatorSpi engineNewDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ if (!isEven(sk)) throw new InvalidKeyException("Only accept even keys");
+ return super.engineNewDecapsulator(sk, spec);
+ }
+ }
+
+ // Ensure delayed provider selection
+ static void delayed() throws Exception {
+ KeyPairGenerator g = KeyPairGenerator.getInstance("X25519");
+ PublicKey even = null, odd = null;
+ while (even == null || odd == null) {
+ KeyPair kp = g.generateKeyPair();
+ if (isEven(kp.getPublic())) {
+ even = kp.getPublic();
+ }
+ if (!isEven(kp.getPublic())) {
+ odd = kp.getPublic();
+ }
+ }
+ KEM kem = KEM.getInstance("DHKEM");
+
+ KEM.Encapsulator eodd = kem.newEncapsulator(odd);
+ KEM.Encapsulator eeven = kem.newEncapsulator(even);
+ Asserts.assertEQ(eodd.providerName(), "SunJCE");
+ Asserts.assertEQ(eeven.providerName(), "XP");
+ }
+
+ static ECPublicKey badECKey() {
+ return new ECPublicKey() {
+ @Override
+ public ECPoint getW() {
+ return null;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return null;
+ }
+
+ @Override
+ public String getFormat() {
+ return null;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return new byte[0];
+ }
+
+ @Override
+ public ECParameterSpec getParams() {
+ return null;
+ }
+ };
+ }
+
+ // Used by Utils.runAndCheckException. Checks for type and final thrower.
+ record ExChecker(Class extends Throwable> ex, String caller)
+ implements Consumer