8297878: KEM: Implementation

Reviewed-by: ascarpino, mullan
This commit is contained in:
Weijun Wang 2023-05-30 16:29:19 +00:00
parent 21af8bae38
commit 6b90b0519e
12 changed files with 2324 additions and 21 deletions

View File

@ -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();
}
}

View File

@ -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
*

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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.
* <p>
* The {@code getInstance} method creates a new {@code KEM} object that
* implements the specified algorithm.
* <p>
* 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.
* <p>
* 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.
* <p>
* {@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.
* <p>
*
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* This method is equivalent to
* {@code encapsulate(0, secretSize(), "Generic")}. This combination
* of arguments must be supported by every implementation.
* <p>
* 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.
* <p>
* Each invocation of this method generates a new secret key and key
* encapsulation message that is returned in an {@link Encapsulated} object.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* This method is equivalent to
* {@code decapsulate(encapsulation, 0, secretSize(), "Generic")}. This
* combination of arguments must be supported by every implementation.
* <p>
* 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.
* <p>
* An invocation of this method recovers the secret key from the key
* encapsulation message.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <T extends Exception> T merge(T e1, T e2) {
if (e1 == null) {
return e2;
} else {
e1.addSuppressed(e2);
return e1;
}
}
private Decapsulator newDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (privateKey == 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(privateKey)) {
continue;
}
try {
KEMSpi spi = (KEMSpi) service.newInstance(null);
return new Decapsulator(
spi.engineNewDecapsulator(privateKey, spec),
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: "
+ privateKey.getClass().getName(), re);
}
}
// If delayed provider selection is needed
private final DelayedKEM delayed;
// otherwise
private final KEMSpi spi;
private final Provider provider;
private final String algorithm;
private KEM(String algorithm, KEMSpi spi, Provider provider) {
assert spi != null;
assert provider != null;
this.delayed = null;
this.spi = spi;
this.provider = provider;
this.algorithm = algorithm;
}
private KEM(String algorithm, DelayedKEM delayed) {
assert delayed != null;
this.delayed = delayed;
this.spi = null;
this.provider = null;
this.algorithm = algorithm;
}
/**
* Returns a {@code KEM} object that implements the specified algorithm.
*
* @param algorithm the name of the KEM algorithm.
* See the {@code KEM} section in the <a href=
* "{@docRoot}/../specs/security/standard-names.html#kem-algorithms">
* Java Security Standard Algorithm Names Specification</a>
* for information about standard KEM algorithm names.
* @return the new {@code KEM} object
* @throws NoSuchAlgorithmException if no {@code Provider} supports a
* {@code KEM} implementation for the specified algorithm
* @throws NullPointerException if {@code algorithm} is {@code null}
*/
public static KEM getInstance(String algorithm)
throws NoSuchAlgorithmException {
List<Provider.Service> list = GetInstance.getServices(
"KEM",
Objects.requireNonNull(algorithm, "null algorithm name"));
if (list.isEmpty()) {
throw new NoSuchAlgorithmException(algorithm + " KEM not available");
}
return new KEM(algorithm, new DelayedKEM(list.toArray(new Provider.Service[0])));
}
/**
* Returns a {@code KEM} object that implements the specified algorithm
* from the specified security provider.
*
* @param algorithm the name of the KEM algorithm.
* See the {@code KEM} section in the <a href=
* "{@docRoot}/../specs/security/standard-names.html#kem-algorithms">
* Java Security Standard Algorithm Names Specification</a>
* for information about standard KEM algorithm names.
* @param provider the provider. If {@code null}, this method is equivalent
* to {@link #getInstance(String)}.
* @return the new {@code KEM} object
* @throws NoSuchAlgorithmException if a {@code provider} is specified and
* it does not support the specified KEM algorithm,
* or if {@code provider} is {@code null} and there is no provider
* that supports a KEM implementation of the specified algorithm
* @throws NullPointerException if {@code algorithm} is {@code null}
*/
public static KEM getInstance(String algorithm, Provider provider)
throws NoSuchAlgorithmException {
if (provider == null) {
return getInstance(algorithm);
}
GetInstance.Instance instance = GetInstance.getInstance(
"KEM",
KEMSpi.class,
Objects.requireNonNull(algorithm, "null algorithm name"),
provider);
return new KEM(algorithm, (KEMSpi) instance.impl, instance.provider);
}
/**
* Returns a {@code KEM} object that implements the specified algorithm
* from the specified security provider.
*
* @param algorithm the name of the KEM algorithm.
* See the {@code KEM} section in the <a href=
* "{@docRoot}/../specs/security/standard-names.html#kem-algorithms">
* Java Security Standard Algorithm Names Specification</a>
* for information about standard KEM algorithm names.
* @param provider the provider. If {@code null}, this method is equivalent
* to {@link #getInstance(String)}.
* @return the new {@code KEM} object
* @throws NoSuchAlgorithmException if a {@code provider} is specified and
* it does not support the specified KEM algorithm,
* or if {@code provider} is {@code null} and there is no provider
* that supports a KEM implementation of the specified algorithm
* @throws NoSuchProviderException if the specified provider is not
* registered in the security provider list
* @throws NullPointerException if {@code algorithm} is {@code null}
*/
public static KEM getInstance(String algorithm, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException {
if (provider == null) {
return getInstance(algorithm);
}
GetInstance.Instance instance = GetInstance.getInstance(
"KEM",
KEMSpi.class,
Objects.requireNonNull(algorithm, "null algorithm name"),
provider);
return new KEM(algorithm, (KEMSpi) instance.impl, instance.provider);
}
/**
* Creates a KEM encapsulator on the KEM sender side.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* A {@code KEMSpi} implementation must be immutable. It must be safe to
* call multiple {@code engineNewEncapsulator} and {@code engineNewDecapsulator}
* methods at the same time.
* <p>
* {@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.
* <p>
* 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.
* <p>
* Each invocation of this method must generate a new secret key and key
* encapsulation message that is returned in an {@link KEM.Encapsulated} object.
* <p>
* 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.
* <p>
* An invocation of this method recovers the secret key from the key
* encapsulation message.
* <p>
* 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;
}

View File

@ -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;

View File

@ -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",

View File

@ -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();

View File

@ -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 {

View File

@ -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<Throwable> {
ExChecker {
Objects.requireNonNull(ex);
}
static ExChecker of(Class<? extends Throwable> ex) {
return new ExChecker(ex, null);
}
ExChecker by(String caller) {
return new ExChecker(ex(), caller);
}
ExChecker by(Class<?> caller) {
return new ExChecker(ex(), caller.getName());
}
@Override
public void accept(Throwable t) {
if (t == null) {
throw new AssertionError("no exception thrown");
} else if (!ex.isAssignableFrom(t.getClass())) {
throw new AssertionError("exception thrown is " + t.getClass());
} else if (caller == null) {
return;
} else if (t.getStackTrace()[0].getClassName().equals(caller)) {
return;
} else {
throw new AssertionError("thrown by " + t.getStackTrace()[0].getClassName());
}
}
}
}

View File

@ -0,0 +1,501 @@
/*
* 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 RSA_KEM example
* @modules java.base/sun.security.jca
* java.base/sun.security.rsa
* java.base/sun.security.util
*/
import sun.security.jca.JCAUtil;
import sun.security.rsa.RSACore;
import sun.security.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
// This test implements RSA-KEM as described in RFC 5990. In this KEM, the
// sender configures the encapsulator with an RSAKEMParameterSpec object.
// This object is encoded as a byte array and included in the Encapsulated
// output. The receiver is then able to recover the same RSAKEMParameterSpec
// object from the encoding using an AlgorithmParameters implementation
// and use the object to configure the decapsulator.
public class RSA_KEM {
public static void main(String[] args) throws Exception {
Provider p = new ProviderImpl();
RSAKEMParameterSpec[] kspecs = new RSAKEMParameterSpec[] {
RSAKEMParameterSpec.kdf1("SHA-256", "AES_128/KW/NoPadding"),
RSAKEMParameterSpec.kdf1("SHA-512", "AES_256/KW/NoPadding"),
RSAKEMParameterSpec.kdf2("SHA-256", "AES_128/KW/NoPadding"),
RSAKEMParameterSpec.kdf2("SHA-512", "AES_256/KW/NoPadding"),
RSAKEMParameterSpec.kdf3("SHA-256", new byte[10], "AES_128/KW/NoPadding"),
RSAKEMParameterSpec.kdf3("SHA-256", new byte[0], "AES_128/KW/NoPadding"),
RSAKEMParameterSpec.kdf3("SHA-512", new byte[0], "AES_128/KW/NoPadding"),
};
for (RSAKEMParameterSpec kspec : kspecs) {
System.err.println("---------");
System.err.println(kspec);
AlgorithmParameters d = AlgorithmParameters.getInstance("RSA-KEM", p);
d.init(kspec);
AlgorithmParameters s = AlgorithmParameters.getInstance("RSA-KEM", p);
s.init(d.getEncoded());
AlgorithmParameterSpec spec = s.getParameterSpec(AlgorithmParameterSpec.class);
if (!spec.toString().equals(kspec.toString())) {
throw new RuntimeException(spec.toString());
}
}
byte[] msg = "hello".getBytes(StandardCharsets.UTF_8);
byte[] iv = new byte[16];
for (int size : List.of(1024, 2048)) {
KeyPairGenerator g = KeyPairGenerator.getInstance("RSA");
g.initialize(size);
KeyPair kp = g.generateKeyPair();
for (RSAKEMParameterSpec kspec : kspecs) {
SecretKey cek = KeyGenerator.getInstance("AES").generateKey();
KEM kem1 = KEM.getInstance("RSA-KEM", p);
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, cek, new IvParameterSpec(iv));
byte[] ciphertext = c.doFinal(msg);
KEM.Encapsulator e = kem1.newEncapsulator(kp.getPublic(), kspec, null);
KEM.Encapsulated enc = e.encapsulate(0, e.secretSize(), "AES");
Cipher c2 = Cipher.getInstance(kspec.encAlg);
c2.init(Cipher.WRAP_MODE, enc.key());
byte[] ek = c2.wrap(cek);
AlgorithmParameters a = AlgorithmParameters.getInstance("RSA-KEM", p);
a.init(enc.params());
KEM kem2 = KEM.getInstance("RSA-KEM", p);
KEM.Decapsulator d = kem2.newDecapsulator(kp.getPrivate(), a.getParameterSpec(AlgorithmParameterSpec.class));
SecretKey k = d.decapsulate(enc.encapsulation(), 0, d.secretSize(), "AES");
Cipher c3 = Cipher.getInstance(kspec.encAlg);
c3.init(Cipher.UNWRAP_MODE, k);
cek = (SecretKey) c3.unwrap(ek, "AES", Cipher.SECRET_KEY);
Cipher c4 = Cipher.getInstance("AES/CBC/PKCS5Padding");
c4.init(Cipher.DECRYPT_MODE, cek, new IvParameterSpec(iv));
byte[] cleartext = c4.doFinal(ciphertext);
if (!Arrays.equals(cleartext, msg)) {
throw new RuntimeException();
}
System.out.printf("%4d %20s - %11d %11d %11d %11d %s\n",
size, kspec,
e.secretSize(), e.encapsulationSize(),
d.secretSize(), d.encapsulationSize(), k.getAlgorithm());
}
}
}
static final String RSA_KEM = "1.2.840.113549.1.9.16.3.14";
static final String KEM_RSA = "1.0.18033.2.2.4";
public static class ProviderImpl extends Provider {
public ProviderImpl() {
super("MYKEM", "1", "RSA-KEM");
List<String> alias = List.of(RSA_KEM, "OID." + RSA_KEM);
Map<String, String> attrs = Map.of(
"SupportedKeyClasses", "java.security.interfaces.RSAKey");
putService(new Service(this, "KEM", "RSA-KEM",
"RSA_KEM$KEMImpl", alias, attrs));
putService(new Service(this, "AlgorithmParameters", "RSA-KEM",
"RSA_KEM$AlgorithmParametersImpl", alias, attrs));
}
}
public static class AlgorithmParametersImpl extends AlgorithmParametersSpi {
RSAKEMParameterSpec spec;
@Override
protected void engineInit(AlgorithmParameterSpec paramSpec)
throws InvalidParameterSpecException {
if (paramSpec instanceof RSAKEMParameterSpec rspec) {
spec = rspec;
} else {
throw new InvalidParameterSpecException();
}
}
@Override
protected void engineInit(byte[] params) throws IOException {
spec = decode(params);
}
@Override
protected void engineInit(byte[] params, String format) throws IOException {
spec = decode(params);
}
@Override
protected <T extends AlgorithmParameterSpec> T engineGetParameterSpec(
Class<T> paramSpec) throws InvalidParameterSpecException {
if (paramSpec.isAssignableFrom(RSAKEMParameterSpec.class)) {
return paramSpec.cast(spec);
} else {
throw new InvalidParameterSpecException();
}
}
@Override
protected byte[] engineGetEncoded() {
return encode(spec);
}
@Override
protected byte[] engineGetEncoded(String format) {
return encode(spec);
}
@Override
protected String engineToString() {
return spec == null ? "<null>" : spec.toString();
}
static final ObjectIdentifier id_rsa_kem;
static final ObjectIdentifier id_kem_rsa;
static final ObjectIdentifier id_kdf1;
static final ObjectIdentifier id_kdf2;
static final ObjectIdentifier id_kdf3;
static {
try {
id_rsa_kem = ObjectIdentifier.of("1.2.840.113549.1.9.16.3.14");
id_kem_rsa = ObjectIdentifier.of("1.0.18033.2.2.4");
id_kdf1 = ObjectIdentifier.of("1.3.133.16.840.9.44.1.0"); // fake
id_kdf2 = ObjectIdentifier.of("1.3.133.16.840.9.44.1.1");
id_kdf3 = ObjectIdentifier.of("1.3.133.16.840.9.44.1.2");
} catch (IOException e) {
throw new AssertionError(e);
}
}
static byte[] encode(RSAKEMParameterSpec spec) {
DerOutputStream kdf = new DerOutputStream()
.write(DerValue.tag_Sequence, new DerOutputStream()
.putOID(oid4(spec.kdfAlg))
.write(DerValue.tag_Sequence, new DerOutputStream()
.putOID(oid4(spec.hashAlg))))
.putInteger(spec.kdfLen());
// The next line is not in RFC 5990
if (spec.fixedInfo != null) {
kdf.putOctetString(spec.fixedInfo);
}
return new DerOutputStream()
.write(DerValue.tag_Sequence, new DerOutputStream()
.write(DerValue.tag_Sequence, new DerOutputStream()
.putOID(id_kem_rsa)
.write(DerValue.tag_Sequence, kdf))
.write(DerValue.tag_Sequence, new DerOutputStream()
.putOID(oid4(spec.encAlg)))).toByteArray();
}
static RSAKEMParameterSpec decode(byte[] der) throws IOException {
String kdfAlg, encAlg, hashAlg;
int kdfLen;
byte[] fixedInfo;
DerInputStream d2 = new DerValue(der).toDerInputStream();
DerInputStream d3 = d2.getDerValue().toDerInputStream();
if (!d3.getOID().equals(id_kem_rsa)) {
throw new IOException("not id_kem_rsa");
}
DerInputStream d4 = d3.getDerValue().toDerInputStream();
DerInputStream d5 = d4.getDerValue().toDerInputStream();
kdfLen = d4.getInteger();
fixedInfo = d4.available() > 0 ? d4.getOctetString() : null;
d4.atEnd();
ObjectIdentifier kdfOid = d5.getOID();
if (kdfOid.equals(id_kdf1)) {
kdfAlg = "kdf1";
} else if (kdfOid.equals(id_kdf2)) {
kdfAlg = "kdf2";
} else if (kdfOid.equals(id_kdf3)) {
kdfAlg = "kdf3";
} else {
throw new IOException("unknown kdf");
}
DerInputStream d6 = d5.getDerValue().toDerInputStream();
String hashOID = d6.getOID().toString();
KnownOIDs k = KnownOIDs.findMatch(hashOID);
hashAlg = k == null ? hashOID : k.stdName();
d6.atEnd();
d5.atEnd();
d3.atEnd();
DerInputStream d7 = d2.getDerValue().toDerInputStream();
String encOID = d7.getOID().toString();
KnownOIDs e = KnownOIDs.findMatch(encOID);
encAlg = e == null ? encOID : e.stdName();
d7.atEnd();
d2.atEnd();
if (kdfLen != RSAKEMParameterSpec.kdfLen(encAlg)) {
throw new IOException("kdfLen does not match encAlg");
}
return new RSAKEMParameterSpec(kdfAlg, hashAlg, fixedInfo, encAlg);
}
static ObjectIdentifier oid4(String s) {
return switch (s) {
case "kdf1" -> id_kdf1;
case "kdf2" -> id_kdf2;
case "kdf3" -> id_kdf3;
default -> {
KnownOIDs k = KnownOIDs.findMatch(s);
if (k == null) throw new UnsupportedOperationException();
yield ObjectIdentifier.of(k);
}
};
}
}
public static class RSAKEMParameterSpec implements AlgorithmParameterSpec {
private final String kdfAlg;
private final String hashAlg;
private final byte[] fixedInfo;
private final String encAlg;
private RSAKEMParameterSpec(String kdfAlg, String hashAlg, byte[] fixedInfo, String encAlg) {
this.hashAlg = hashAlg;
this.kdfAlg = kdfAlg;
this.fixedInfo = fixedInfo == null ? null : fixedInfo.clone();
this.encAlg = encAlg;
}
public static RSAKEMParameterSpec kdf1(String hashAlg, String encAlg) {
return new RSAKEMParameterSpec("kdf1", hashAlg, null, encAlg);
}
public static RSAKEMParameterSpec kdf2(String hashAlg, String encAlg) {
return new RSAKEMParameterSpec("kdf2", hashAlg, null, encAlg);
}
public static RSAKEMParameterSpec kdf3(String hashAlg, byte[] fixedInfo, String encAlg) {
return new RSAKEMParameterSpec("kdf3", hashAlg, fixedInfo, encAlg);
}
public int kdfLen() {
return RSAKEMParameterSpec.kdfLen(encAlg);
}
public static int kdfLen(String encAlg) {
return Integer.parseInt(encAlg, 4, 7, 10) / 8;
}
public String hashAlgorithm() {
return hashAlg;
}
public String kdfAlgorithm() {
return kdfAlg;
}
public byte[] fixedInfo() {
return fixedInfo == null ? null : fixedInfo.clone();
}
public String getEncAlg() {
return encAlg;
}
@Override
public String toString() {
return String.format("[%s,%s,%s]", kdfAlg, hashAlg, encAlg);
}
}
public static class KEMImpl implements KEMSpi {
@Override
public KEMSpi.EncapsulatorSpi engineNewEncapsulator(
PublicKey pk, AlgorithmParameterSpec spec, SecureRandom secureRandom)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (!(pk instanceof RSAPublicKey rpk)) {
throw new InvalidKeyException("Not an RSA key");
}
return Handler.newEncapsulator(spec, rpk, secureRandom);
}
@Override
public KEMSpi.DecapsulatorSpi engineNewDecapsulator(
PrivateKey sk, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (!(sk instanceof RSAPrivateCrtKey rsk)) {
throw new InvalidKeyException("Not an RSA key");
}
return Handler.newDecapsulator(spec, rsk);
}
static class Handler implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi {
private final RSAPublicKey rpk; // not null for encapsulator
private final RSAPrivateKey rsk; // not null for decapsulator
private final RSAKEMParameterSpec kspec; // not null
private final SecureRandom sr; // not null for encapsulator
Handler(AlgorithmParameterSpec spec, RSAPublicKey rpk, RSAPrivateCrtKey rsk, SecureRandom sr)
throws InvalidAlgorithmParameterException {
this.rpk = rpk;
this.rsk = rsk;
this.sr = sr;
if (spec != null) {
if (spec instanceof RSAKEMParameterSpec rs) {
this.kspec = rs;
} else {
throw new InvalidAlgorithmParameterException();
}
} else {
this.kspec = RSAKEMParameterSpec
.kdf2("SHA-256", "AES_256/KW/NoPadding");
}
}
static Handler newEncapsulator(AlgorithmParameterSpec spec, RSAPublicKey rpk, SecureRandom sr)
throws InvalidAlgorithmParameterException {
if (sr == null) {
sr = JCAUtil.getDefSecureRandom();
}
return new Handler(spec, rpk, null, sr);
}
static Handler newDecapsulator(AlgorithmParameterSpec spec, RSAPrivateCrtKey rsk)
throws InvalidAlgorithmParameterException {
return new Handler(spec, null, rsk, null);
}
@Override
public SecretKey engineDecapsulate(byte[] encapsulation,
int from, int to, String algorithm)
throws DecapsulateException {
Objects.checkFromToIndex(from, to, kspec.kdfLen());
Objects.requireNonNull(algorithm, "null algorithm");
Objects.requireNonNull(encapsulation, "null encapsulation");
if (encapsulation.length != KeyUtil.getKeySize(rsk) / 8) {
throw new DecapsulateException("incorrect encapsulation size");
}
try {
byte[] Z = RSACore.rsa(encapsulation, rsk, false);
return new SecretKeySpec(kdf(Z), from, to - from, algorithm);
} catch (BadPaddingException e) {
throw new DecapsulateException("cannot decrypt", e);
}
}
@Override
public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) {
Objects.checkFromToIndex(from, to, kspec.kdfLen());
Objects.requireNonNull(algorithm, "null algorithm");
int nLen = rpk.getModulus().bitLength();
int nSize = (nLen + 7) / 8;
BigInteger z;
int tried = 0;
while (true) {
z = new BigInteger(nLen, sr);
if (z.compareTo(rpk.getModulus()) < 0) {
break;
}
if (tried++ > 20) {
throw new ProviderException("Cannot get good random number");
}
}
byte[] Z = z.toByteArray();
if (Z.length > nSize) {
Z = Arrays.copyOfRange(Z, Z.length - nSize, Z.length);
} else if (Z.length < nSize) {
byte[] tmp = new byte[nSize];
System.arraycopy(Z, 0, tmp, nSize - Z.length, Z.length);
Z = tmp;
}
byte[] c;
try {
c = RSACore.rsa(Z, rpk);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
return new KEM.Encapsulated(
new SecretKeySpec(kdf(Z), from, to - from, algorithm),
c, AlgorithmParametersImpl.encode(kspec));
}
byte[] kdf(byte[] input) {
String hashAlg = kspec.hashAlgorithm();
MessageDigest md;
try {
md = MessageDigest.getInstance(hashAlg);
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(e);
}
String kdfAlg = kspec.kdfAlgorithm();
byte[] fixedInput = kspec.fixedInfo();
int length = kspec.kdfLen();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int n = kdfAlg.equals("kdf1") ? 0 : 1;
while (true) {
switch (kdfAlg) {
case "kdf1", "kdf2" -> {
md.update(input);
md.update(u32str(n));
}
case "kdf3" -> {
md.update(u32str(n));
md.update(input);
md.update(fixedInput);
}
default -> throw new ProviderException();
}
bout.writeBytes(md.digest());
if (bout.size() > length) break;
n++;
}
byte[] result = bout.toByteArray();
return result.length == length
? result
: Arrays.copyOf(result, length);
}
@Override
public int engineSecretSize() {
return kspec.kdfLen();
}
@Override
public int engineEncapsulationSize() {
return KeyUtil.getKeySize(rsk == null ? rpk : rsk) / 8;
}
}
}
static byte[] u32str(int i) {
return new byte[] {
(byte)(i >> 24), (byte)(i >> 16), (byte)(i >> 8), (byte)i };
}
}