8340327: A common framework to support public key algorithms with standard parameter sets

Reviewed-by: ascarpino, mullan
This commit is contained in:
Weijun Wang 2024-10-11 21:16:41 +00:00
parent 0a57fe1df6
commit 3f53d57134
10 changed files with 1693 additions and 9 deletions

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.pkcs;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import sun.security.x509.AlgorithmId;
import javax.security.auth.DestroyFailedException;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serial;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.NamedParameterSpec;
import java.util.Arrays;
/// Represents a private key from an algorithm family that is specialized
/// with a named parameter set.
///
/// This key is generated by either a [sun.security.provider.NamedKeyPairGenerator]
/// or [sun.security.provider.NamedKeyFactory]. Its [#getAlgorithm] method
/// returns the algorithm family name, while its [#getParams()] method returns
/// the parameter set name as a [NamedParameterSpec] object. The algorithm
/// identifier in the PKCS #8 encoding of the key is always a single OID derived
/// from the parameter set name.
///
/// @see sun.security.provider.NamedKeyPairGenerator
public final class NamedPKCS8Key extends PKCS8Key {
@Serial
private static final long serialVersionUID = 1L;
private final String fname;
private final transient NamedParameterSpec paramSpec;
private final byte[] rawBytes;
private transient boolean destroyed = false;
/// Ctor from family name, parameter set name, raw key bytes.
/// Key bytes won't be cloned, caller must relinquish ownership
public NamedPKCS8Key(String fname, String pname, byte[] rawBytes) {
this.fname = fname;
this.paramSpec = new NamedParameterSpec(pname);
try {
this.algid = AlgorithmId.get(pname);
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(e);
}
this.rawBytes = rawBytes;
DerValue val = new DerValue(DerValue.tag_OctetString, rawBytes);
try {
this.key = val.toByteArray();
} finally {
val.clear();
}
}
/// Ctor from family name, and PKCS #8 bytes
public NamedPKCS8Key(String fname, byte[] encoded) throws InvalidKeyException {
super(encoded);
this.fname = fname;
try {
paramSpec = new NamedParameterSpec(algid.getName());
if (algid.getEncodedParams() != null) {
throw new InvalidKeyException("algorithm identifier has params");
}
rawBytes = new DerInputStream(key).getOctetString();
} catch (IOException e) {
throw new InvalidKeyException("Cannot parse input", e);
}
}
@Override
public String toString() {
// Do not modify: this can be used by earlier JDKs that
// do not have the getParams() method
return paramSpec.getName() + " private key";
}
/// Returns the reference to the internal key. Caller must not modify
/// the content or keep a reference.
public byte[] getRawBytes() {
return rawBytes;
}
@Override
public NamedParameterSpec getParams() {
return paramSpec;
}
@Override
public String getAlgorithm() {
return fname;
}
@java.io.Serial
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
throw new InvalidObjectException(
"NamedPKCS8Key keys are not directly deserializable");
}
@Override
public void destroy() throws DestroyFailedException {
Arrays.fill(rawBytes, (byte)0);
Arrays.fill(key, (byte)0);
if (encodedKey != null) {
Arrays.fill(encodedKey, (byte)0);
}
destroyed = true;
}
@Override
public boolean isDestroyed() {
return destroyed;
}
}

View file

@ -0,0 +1,223 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.x509.NamedX509Key;
import javax.crypto.DecapsulateException;
import javax.crypto.KEM;
import javax.crypto.KEMSpi;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.NamedParameterSpec;
import java.util.Arrays;
import java.util.Objects;
/// A base class for all `KEM` implementations that can be
/// configured with a named parameter set. See [NamedKeyPairGenerator]
/// for more details.
public abstract class NamedKEM implements KEMSpi {
private final String fname; // family name
private final String[] pnames; // allowed parameter set name (at least one)
/// Creates a new `NamedKEM` object.
///
/// @param fname the family name
/// @param pnames the standard parameter set names, at least one is needed.
protected NamedKEM(String fname, String... pnames) {
if (fname == null) {
throw new AssertionError("fname cannot be null");
}
if (pnames == null || pnames.length == 0) {
throw new AssertionError("pnames cannot be null or empty");
}
this.fname = fname;
this.pnames = pnames;
}
@Override
public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey,
AlgorithmParameterSpec spec, SecureRandom secureRandom)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (spec != null) {
throw new InvalidAlgorithmParameterException(
"The " + fname + " algorithm does not take any parameters");
}
// translate also check the key
var nk = (NamedX509Key) new NamedKeyFactory(fname, pnames)
.engineTranslateKey(publicKey);
var pk = nk.getRawBytes();
return getKeyConsumerImpl(this, nk.getParams(), pk,
implCheckPublicKey(nk.getParams().getName(), pk), secureRandom);
}
@Override
public DecapsulatorSpi engineNewDecapsulator(
PrivateKey privateKey, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (spec != null) {
throw new InvalidAlgorithmParameterException(
"The " + fname + " algorithm does not take any parameters");
}
// translate also check the key
var nk = (NamedPKCS8Key) new NamedKeyFactory(fname, pnames)
.engineTranslateKey(privateKey);
var sk = nk.getRawBytes();
return getKeyConsumerImpl(this, nk.getParams(), sk,
implCheckPrivateKey(nk.getParams().getName(), sk), null);
}
// We don't have a flag on whether key is public key or private key.
// The correct method should always be called.
private record KeyConsumerImpl(NamedKEM kem, String name, int sslen,
int clen, byte[] key, Object k2, SecureRandom sr)
implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi {
@Override
public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to,
String algorithm) throws DecapsulateException {
if (encapsulation.length != clen) {
throw new DecapsulateException("Invalid key encapsulation message length");
}
var ss = kem.implDecapsulate(name, key, k2, encapsulation);
try {
return new SecretKeySpec(ss,
from, to - from, algorithm);
} finally {
Arrays.fill(ss, (byte)0);
}
}
@Override
public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) {
var enc = kem.implEncapsulate(name, key, k2, sr);
try {
return new KEM.Encapsulated(
new SecretKeySpec(enc[1],
from, to - from, algorithm),
enc[0],
null);
} finally {
Arrays.fill(enc[1], (byte)0);
}
}
@Override
public int engineSecretSize() {
return sslen;
}
@Override
public int engineEncapsulationSize() {
return clen;
}
}
private static KeyConsumerImpl getKeyConsumerImpl(NamedKEM kem,
NamedParameterSpec nps, byte[] key, Object k2, SecureRandom sr) {
String name = nps.getName();
return new KeyConsumerImpl(kem, name, kem.implSecretSize(name), kem.implEncapsulationSize(name),
key, k2, sr);
}
/// User-defined encap function.
///
/// @param name parameter name
/// @param pk public key in raw bytes
/// @param pk2 parsed public key, `null` if none. See [#implCheckPublicKey].
/// @param sr SecureRandom object, `null` if not initialized
/// @return the key encapsulation message and the shared key (in this order)
/// @throws ProviderException if there is an internal error
protected abstract byte[][] implEncapsulate(String name, byte[] pk, Object pk2, SecureRandom sr);
/// User-defined decap function.
///
/// @param name parameter name
/// @param sk private key in raw bytes
/// @param sk2 parsed private key, `null` if none. See [#implCheckPrivateKey].
/// @param encap the key encapsulation message
/// @return the shared key
/// @throws ProviderException if there is an internal error
/// @throws DecapsulateException if there is another error
protected abstract byte[] implDecapsulate(String name, byte[] sk, Object sk2, byte[] encap)
throws DecapsulateException;
/// User-defined function returning shared secret key length.
///
/// @param name parameter name
/// @return shared secret key length
/// @throws ProviderException if there is an internal error
protected abstract int implSecretSize(String name);
/// User-defined function returning key encapsulation message length.
///
/// @param name parameter name
/// @return key encapsulation message length
/// @throws ProviderException if there is an internal error
protected abstract int implEncapsulationSize(String name);
/// User-defined function to validate a public key.
///
/// This method will be called in `newEncapsulator`. This gives the provider a chance to
/// reject the key so an `InvalidKeyException` can be thrown earlier.
/// An implementation can optionally return a "parsed key" as an `Object` value.
/// This object will be passed into the [#implEncapsulate] method along with the raw key.
///
/// The default implementation returns `null`.
///
/// @param name parameter name
/// @param pk public key in raw bytes
/// @return a parsed key, `null` if none.
/// @throws InvalidKeyException if the key is invalid
protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException {
return null;
}
/// User-defined function to validate a private key.
///
/// This method will be called in `newDecapsulator`. This gives the provider a chance to
/// reject the key so an `InvalidKeyException` can be thrown earlier.
/// An implementation can optionally return a "parsed key" as an `Object` value.
/// This object will be passed into the [#implDecapsulate] method along with the raw key.
///
/// The default implementation returns `null`.
///
/// @param name parameter name
/// @param sk private key in raw bytes
/// @return a parsed key, `null` if none.
/// @throws InvalidKeyException if the key is invalid
protected Object implCheckPrivateKey(String name, byte[] sk) throws InvalidKeyException {
return null;
}
}

View file

@ -0,0 +1,288 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.util.RawKeySpec;
import sun.security.x509.NamedX509Key;
import java.security.AsymmetricKey;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactorySpi;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.NamedParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Objects;
/// A base class for all `KeyFactory` implementations that can be
/// configured with a named parameter set. See [NamedKeyPairGenerator]
/// for more details.
///
/// This factory supports reading and writing to RAW formats:
///
/// 1. It reads from a RAW key using `translateKey` if `key.getFormat` is "RAW".
/// 2. It writes to a RAW [EncodedKeySpec] if `getKeySpec(key, EncodedKeySpec.class)`
/// is called. The format of the output is "RAW" and the algorithm is
/// intentionally left unspecified.
/// 3. It reads from and writes to the internal type [RawKeySpec].
///
/// When reading from a RAW format, it needs enough info to derive the
/// parameter set name.
public class NamedKeyFactory extends KeyFactorySpi {
private final String fname; // family name
private final String[] pnames; // allowed parameter set name (at least one)
/// Creates a new `NamedKeyFactory` object.
///
/// @param fname the family name
/// @param pnames the standard parameter set names, at least one is needed.
protected NamedKeyFactory(String fname, String... pnames) {
if (fname == null) {
throw new AssertionError("fname cannot be null");
}
if (pnames == null || pnames.length == 0) {
throw new AssertionError("pnames cannot be null or empty");
}
this.fname = fname;
this.pnames = pnames;
}
private String checkName(String name) throws InvalidKeyException {
for (var pname : pnames) {
if (pname.equalsIgnoreCase(name)) {
// return the stored standard name
return pname;
}
}
throw new InvalidKeyException("Unsupported parameter set name: " + name);
}
@Override
protected PublicKey engineGeneratePublic(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec instanceof X509EncodedKeySpec xspec) {
try {
return fromX509(xspec.getEncoded());
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
}
} else if (keySpec instanceof RawKeySpec rks) {
if (pnames.length == 1) {
return new NamedX509Key(fname, pnames[0], rks.getKeyArr());
} else {
throw new InvalidKeySpecException("Parameter set name unavailable");
}
} else if (keySpec instanceof EncodedKeySpec espec
&& espec.getFormat().equalsIgnoreCase("RAW")) {
if (pnames.length == 1) {
return new NamedX509Key(fname, pnames[0], espec.getEncoded());
} else {
throw new InvalidKeySpecException("Parameter set name unavailable");
}
} else {
throw new InvalidKeySpecException("Unsupported keyspec: " + keySpec);
}
}
@Override
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec instanceof PKCS8EncodedKeySpec pspec) {
var bytes = pspec.getEncoded();
try {
return fromPKCS8(bytes);
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
} finally {
Arrays.fill(bytes, (byte) 0);
}
} else if (keySpec instanceof RawKeySpec rks) {
if (pnames.length == 1) {
var bytes = rks.getKeyArr();
try {
return new NamedPKCS8Key(fname, pnames[0], bytes);
} finally {
Arrays.fill(bytes, (byte) 0);
}
} else {
throw new InvalidKeySpecException("Parameter set name unavailable");
}
} else if (keySpec instanceof EncodedKeySpec espec
&& espec.getFormat().equalsIgnoreCase("RAW")) {
if (pnames.length == 1) {
var bytes = espec.getEncoded();
try {
return new NamedPKCS8Key(fname, pnames[0], bytes);
} finally {
Arrays.fill(bytes, (byte) 0);
}
} else {
throw new InvalidKeySpecException("Parameter set name unavailable");
}
} else {
throw new InvalidKeySpecException("Unsupported keyspec: " + keySpec);
}
}
private PrivateKey fromPKCS8(byte[] bytes)
throws InvalidKeyException, InvalidKeySpecException {
var k = new NamedPKCS8Key(fname, bytes);
checkName(k.getParams().getName());
return k;
}
private PublicKey fromX509(byte[] bytes)
throws InvalidKeyException, InvalidKeySpecException {
var k = new NamedX509Key(fname, bytes);
checkName(k.getParams().getName());
return k;
}
private static class RawEncodedKeySpec extends EncodedKeySpec {
public RawEncodedKeySpec(byte[] encodedKey) {
super(encodedKey);
}
@Override
public String getFormat() {
return "RAW";
}
}
@Override
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
throws InvalidKeySpecException {
try {
key = engineTranslateKey(key);
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
}
// key is now either NamedPKCS8Key or NamedX509Key of permitted param set
if (key instanceof NamedPKCS8Key nk) {
byte[] bytes = null;
try {
if (keySpec == PKCS8EncodedKeySpec.class) {
return keySpec.cast(
new PKCS8EncodedKeySpec(bytes = key.getEncoded()));
} else if (keySpec == RawKeySpec.class) {
return keySpec.cast(new RawKeySpec(nk.getRawBytes()));
} else if (keySpec.isAssignableFrom(EncodedKeySpec.class)) {
return keySpec.cast(
new RawEncodedKeySpec(nk.getRawBytes()));
} else {
throw new InvalidKeySpecException("Unsupported type: " + keySpec);
}
} finally {
if (bytes != null) {
Arrays.fill(bytes, (byte)0);
}
}
} else if (key instanceof NamedX509Key nk) {
if (keySpec == X509EncodedKeySpec.class
&& key.getFormat().equalsIgnoreCase("X.509")) {
return keySpec.cast(new X509EncodedKeySpec(key.getEncoded()));
} else if (keySpec == RawKeySpec.class) {
return keySpec.cast(new RawKeySpec(nk.getRawBytes()));
} else if (keySpec.isAssignableFrom(EncodedKeySpec.class)) {
return keySpec.cast(new RawEncodedKeySpec(nk.getRawBytes()));
} else {
throw new InvalidKeySpecException("Unsupported type: " + keySpec);
}
}
throw new AssertionError("No " + keySpec.getName() + " for " + key.getClass());
}
@Override
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("Key must not be null");
}
if (key instanceof NamedX509Key nk) {
checkName(nk.getParams().getName());
return key;
}
if (key instanceof NamedPKCS8Key nk) {
checkName(nk.getParams().getName());
return key;
}
var format = key.getFormat();
if (format == null) {
throw new InvalidKeyException("Unextractable key");
} else if (format.equalsIgnoreCase("RAW")) {
var kAlg = key.getAlgorithm();
if (key instanceof AsymmetricKey pk) {
String name;
// Three cases that we can find the parameter set name from a RAW key:
// 1. getParams() returns one
// 2. getAlgorithm() returns param set name (some provider does this)
// 3. getAlgorithm() returns family name but this KF is for param set name
if (pk.getParams() instanceof NamedParameterSpec nps) {
name = checkName(nps.getName());
} else {
if (kAlg.equalsIgnoreCase(fname)) {
if (pnames.length == 1) {
name = pnames[0];
} else {
throw new InvalidKeyException("No parameter set info");
}
} else {
name = checkName(kAlg);
}
}
return key instanceof PrivateKey
? new NamedPKCS8Key(fname, name, key.getEncoded())
: new NamedX509Key(fname, name, key.getEncoded());
} else {
throw new InvalidKeyException("Unsupported key type: " + key.getClass());
}
} else if (format.equalsIgnoreCase("PKCS#8") && key instanceof PrivateKey) {
var bytes = key.getEncoded();
try {
return fromPKCS8(bytes);
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException("Invalid PKCS#8 key", e);
} finally {
Arrays.fill(bytes, (byte) 0);
}
} else if (format.equalsIgnoreCase("X.509") && key instanceof PublicKey) {
try {
return fromX509(key.getEncoded());
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException("Invalid X.509 key", e);
}
} else {
throw new InvalidKeyException("Unsupported key format: " + key.getFormat());
}
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.x509.NamedX509Key;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidParameterException;
import java.security.KeyPair;
import java.security.KeyPairGeneratorSpi;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.NamedParameterSpec;
import java.util.Objects;
/// A base class for all `KeyPairGenerator` implementations that can be
/// configured with a named parameter set.
///
/// Together with [NamedKeyFactory], [NamedKEM], and [NamedSignature], these
/// classes form a compact framework designed to support any public key
/// algorithm standardized with named parameter sets. In this scenario,
/// the algorithm name is the "family name" and each standardized parameter
/// set has a "parameter set name". Implementations of these classes are able
/// to instantiate a `KeyPairGenerator`, `KeyFactory`, or `KEM` or `Signature`
/// object using either the family name or a parameter set name. All keys used
/// in this context will be of the type [NamedPKCS8Key] or [NamedX509Key],
/// with `getAlgorithm` returning the family name, and `getParams` returning
/// the parameter set name as a [NamedParameterSpec] object.
///
/// An implementation must include a zero-argument public constructor that
/// calls `super(fname, pnames)`, where `fname` is the family name of the
/// algorithm and `pnames` are its supported parameter set names. `pnames`
/// must contain at least one element. For an implementation of
/// `NamedKeyPairGenerator`, the first element becomes its default parameter
/// set, i.e. the parameter set to be used in key pair generation unless
/// [#initialize(AlgorithmParameterSpec, java.security.SecureRandom)]
/// is called on a different parameter set.
///
/// An implementation must implement all abstract methods. For all these
/// methods, the implementation must relinquish any "ownership" of any input
/// and output array argument. Precisely, the implementation must not retain
/// any reference to a returning array so that it won't be able to modify its
/// content later. Similarly, the implementation must not modify any input
/// array argument and must not retain any reference to an input array argument
/// after the call.
///
/// Also, an implementation must not keep any extra copy of a private key.
/// For key generation, the only copy is the one returned in the
/// [#implGenerateKeyPair] call. For all other methods, it must not make
/// a copy of the input private key. A `KEM` implementation also must not
/// keep a copy of the shared secret key, no matter if it's an encapsulator
/// or a decapsulator. Only the code that owns these sensitive data can
/// choose to perform cleanup when it determines they are no longer needed.
///
/// The `NamedSignature` and `NamedKEM` classes provide `implCheckPublicKey`
/// and `implCheckPrivateKey` methods that allow an implementation to validate
/// a key before using it. An implementation may return a parsed key in
/// a local type, and this parsed key will be passed to an operational method
/// (For example, `implSign`) later. An implementation must not retain
/// a reference of the parsed key.
///
/// When constructing a [NamedX509Key] or [NamedPKCS8Key] object from raw key
/// bytes, the key bytes are directly referenced within the object, so the
/// caller must not modify them afterward. Similarly, the key's `getRawBytes`
/// method returns direct references to the underlying raw key bytes, meaning
/// the caller must not alter the contents of the returned value.
///
/// Together, these measures ensure the classes are as efficient as possible,
/// preventing unnecessary array cloning and potential data leaks. While these
/// classes should not be considered immutable, strictly adhering to the rules
/// above will ensure data integrity is maintained.
///
/// Note: A limitation of `NamedKeyPairGenerator` and `NamedKeyFactory` is
/// that the keys generated by their implementations will always be of type
/// `NamedX509Key` or `NamedPKCS8Key`. Existing implementations of algorithms
/// like EdDSA and XDH have been generating keys implementing `EdECKey` or
/// `XECKey` interfaces, and they are not rewritten with this framework.
/// `NamedParameterSpec` fields not implemented with this framework include
/// Ed25519, Ed448, X25519, and X448.
public abstract class NamedKeyPairGenerator extends KeyPairGeneratorSpi {
private final String fname; // family name
private final String[] pnames; // allowed parameter set name (at least one)
protected String name; // init as
private SecureRandom secureRandom;
/// Creates a new `NamedKeyPairGenerator` object.
///
/// @param fname the family name
/// @param pnames supported parameter set names, at least one is needed.
/// If multiple, the first one becomes the default parameter set name.
protected NamedKeyPairGenerator(String fname, String... pnames) {
if (fname == null) {
throw new AssertionError("fname cannot be null");
}
if (pnames == null || pnames.length == 0) {
throw new AssertionError("pnames cannot be null or empty");
}
this.fname = fname;
this.pnames = pnames;
}
private String checkName(String name) throws InvalidAlgorithmParameterException {
for (var pname : pnames) {
if (pname.equalsIgnoreCase(name)) {
// return the stored standard name
return pname;
}
}
throw new InvalidAlgorithmParameterException(
"Unsupported parameter set name: " + name);
}
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
if (params instanceof NamedParameterSpec spec) {
name = checkName(spec.getName());
} else {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameterSpec: " + params);
}
this.secureRandom = random;
}
@Override
public void initialize(int keysize, SecureRandom random) {
if (keysize != -1) {
// User can call initialize(-1, sr) to provide a SecureRandom
// without touching the parameter set currently used
throw new InvalidParameterException("keysize not supported");
}
this.secureRandom = random;
}
@Override
public KeyPair generateKeyPair() {
String pname = name != null ? name : pnames[0];
var keys = implGenerateKeyPair(pname, secureRandom);
return new KeyPair(new NamedX509Key(fname, pname, keys[0]),
new NamedPKCS8Key(fname, pname, keys[1]));
}
/// User-defined key pair generator.
///
/// @param pname parameter set name
/// @param sr `SecureRandom` object, `null` if not initialized
/// @return public key and private key (in this order) in raw bytes
/// @throws ProviderException if there is an internal error
protected abstract byte[][] implGenerateKeyPair(String pname, SecureRandom sr);
}

View file

@ -0,0 +1,222 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.x509.NamedX509Key;
import java.io.ByteArrayOutputStream;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.SignatureSpi;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Objects;
/// A base class for all `Signature` implementations that can be
/// configured with a named parameter set. See [NamedKeyPairGenerator]
/// for more details.
///
/// This class does not work with preHash signatures.
public abstract class NamedSignature extends SignatureSpi {
private final String fname; // family name
private final String[] pnames; // allowed parameter set name (at least one)
private final ByteArrayOutputStream bout = new ByteArrayOutputStream();
// init with...
private String name;
private byte[] secKey;
private byte[] pubKey;
private Object sk2;
private Object pk2;
/// Creates a new `NamedSignature` object.
///
/// @param fname the family name
/// @param pnames the standard parameter set names, at least one is needed.
protected NamedSignature(String fname, String... pnames) {
if (fname == null) {
throw new AssertionError("fname cannot be null");
}
if (pnames == null || pnames.length == 0) {
throw new AssertionError("pnames cannot be null or empty");
}
this.fname = fname;
this.pnames = pnames;
}
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
// translate also check the key
var nk = (NamedX509Key) new NamedKeyFactory(fname, pnames)
.engineTranslateKey(publicKey);
name = nk.getParams().getName();
pubKey = nk.getRawBytes();
pk2 = implCheckPublicKey(name, pubKey);
secKey = null;
bout.reset();
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
// translate also check the key
var nk = (NamedPKCS8Key) new NamedKeyFactory(fname, pnames)
.engineTranslateKey(privateKey);
name = nk.getParams().getName();
secKey = nk.getRawBytes();
sk2 = implCheckPrivateKey(name, secKey);
pubKey = null;
bout.reset();
}
@Override
protected void engineUpdate(byte b) throws SignatureException {
bout.write(b);
}
@Override
protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {
bout.write(b, off, len);
}
@Override
protected byte[] engineSign() throws SignatureException {
if (secKey != null) {
var msg = bout.toByteArray();
bout.reset();
return implSign(name, secKey, sk2, msg, appRandom);
} else {
throw new SignatureException("No private key");
}
}
@Override
protected boolean engineVerify(byte[] sig) throws SignatureException {
if (pubKey != null) {
var msg = bout.toByteArray();
bout.reset();
return implVerify(name, pubKey, pk2, msg, sig);
} else {
throw new SignatureException("No public key");
}
}
@Override
@SuppressWarnings("deprecation")
protected void engineSetParameter(String param, Object value)
throws InvalidParameterException {
throw new InvalidParameterException("setParameter() not supported");
}
@Override
@SuppressWarnings("deprecation")
protected Object engineGetParameter(String param) throws InvalidParameterException {
throw new InvalidParameterException("getParameter() not supported");
}
@Override
protected void engineSetParameter(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"The " + fname + " algorithm does not take any parameters");
}
}
@Override
protected AlgorithmParameters engineGetParameters() {
return null;
}
/// User-defined sign function.
///
/// @param name parameter name
/// @param sk private key in raw bytes
/// @param sk2 parsed private key, `null` if none. See [#implCheckPrivateKey].
/// @param msg the message
/// @param sr SecureRandom object, `null` if not initialized
/// @return the signature
/// @throws ProviderException if there is an internal error
/// @throws SignatureException if there is another error
protected abstract byte[] implSign(String name, byte[] sk, Object sk2,
byte[] msg, SecureRandom sr) throws SignatureException;
/// User-defined verify function.
///
/// @param name parameter name
/// @param pk public key in raw bytes
/// @param pk2 parsed public key, `null` if none. See [#implCheckPublicKey].
/// @param msg the message
/// @param sig the signature
/// @return true if verified
/// @throws ProviderException if there is an internal error
/// @throws SignatureException if there is another error
protected abstract boolean implVerify(String name, byte[] pk, Object pk2,
byte[] msg, byte[] sig) throws SignatureException;
/// User-defined function to validate a public key.
///
/// This method will be called in `initVerify`. This gives the provider a chance to
/// reject the key so an `InvalidKeyException` can be thrown earlier.
/// An implementation can optionally return a "parsed key" as an `Object` value.
/// This object will be passed into the [#implVerify] method along with the raw key.
///
/// The default implementation returns `null`.
///
/// @param name parameter name
/// @param pk public key in raw bytes
/// @return a parsed key, `null` if none.
/// @throws InvalidKeyException if the key is invalid
protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException {
return null;
}
/// User-defined function to validate a private key.
///
/// This method will be called in `initSign`. This gives the provider a chance to
/// reject the key so an `InvalidKeyException` can be thrown earlier.
/// An implementation can optionally return a "parsed key" as an `Object` value.
/// This object will be passed into the [#implSign] method along with the raw key.
///
/// The default implementation returns `null`.
///
/// @param name parameter name
/// @param sk private key in raw bytes
/// @return a parsed key, `null` if none.
/// @throws InvalidKeyException if the key is invalid
protected Object implCheckPrivateKey(String name, byte[] sk) throws InvalidKeyException {
return null;
}
}

View file

@ -184,13 +184,13 @@ public final class KeyUtil {
*/
public static final String fullDisplayAlgName(Key key) {
String result = key.getAlgorithm();
if (key instanceof ECKey) {
ECParameterSpec paramSpec = ((ECKey) key).getParams();
if (key instanceof AsymmetricKey ak) {
AlgorithmParameterSpec paramSpec = ak.getParams();
if (paramSpec instanceof NamedCurve nc) {
result += " (" + nc.getNameAndAliases()[0] + ")";
} else if (paramSpec instanceof NamedParameterSpec nps) {
result = nps.getName();
}
} else if (key instanceof EdECKey) {
result = ((EdECKey) key).getParams().getName();
}
return result;
}

View file

@ -33,6 +33,7 @@ import java.security.interfaces.RSAKey;
import java.security.spec.*;
import java.util.Locale;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.rsa.RSAUtil;
import jdk.internal.access.SharedSecrets;
import sun.security.x509.AlgorithmId;
@ -274,7 +275,7 @@ public class SignatureUtil {
return signatureAlgorithm.substring(0, with);
} else {
throw new IllegalArgumentException(
"Unknown algorithm: " + signatureAlgorithm);
"Cannot extract digest algorithm from " + signatureAlgorithm);
}
}
@ -390,8 +391,8 @@ public class SignatureUtil {
public static AlgorithmId fromSignature(Signature sigEngine, PrivateKey key)
throws SignatureException {
try {
if (key instanceof EdECKey) {
return AlgorithmId.get(((EdECKey) key).getParams().getName());
if (key.getParams() instanceof NamedParameterSpec nps) {
return AlgorithmId.get(nps.getName());
}
AlgorithmParameters params = null;
@ -431,6 +432,14 @@ public class SignatureUtil {
public static void checkKeyAndSigAlgMatch(PrivateKey key, String sAlg) {
String kAlg = key.getAlgorithm().toUpperCase(Locale.ENGLISH);
sAlg = checkName(sAlg);
if (key instanceof NamedPKCS8Key n8k) {
if (!sAlg.equalsIgnoreCase(n8k.getAlgorithm())
&& !sAlg.equalsIgnoreCase(n8k.getParams().getName())) {
throw new IllegalArgumentException(
"key algorithm not compatible with signature algorithm");
}
return;
}
switch (sAlg) {
case "RSASSA-PSS" -> {
if (!kAlg.equals("RSASSA-PSS")
@ -495,8 +504,10 @@ public class SignatureUtil {
case "EDDSA" -> k instanceof EdECPrivateKey
? ((EdECPrivateKey) k).getParams().getName()
: kAlg;
default -> kAlg; // All modern signature algorithms,
// RSASSA-PSS, ED25519, ED448, HSS/LMS, etc
default -> kAlg.contains("KEM") ? null : kAlg;
// All modern signature algorithms use the same name across
// key algorithms and signature algorithms, for example,
// RSASSA-PSS, ED25519, ED448, HSS/LMS, etc
};
}

View file

@ -0,0 +1,120 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.x509;
import sun.security.util.BitArray;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serial;
import java.security.InvalidKeyException;
import java.security.KeyRep;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.NamedParameterSpec;
/// Represents a public key from an algorithm family that is specialized
/// with a named parameter set.
///
/// This key is generated by either a [sun.security.provider.NamedKeyPairGenerator]
/// or [sun.security.provider.NamedKeyFactory]. Its [#getAlgorithm] method
/// returns the algorithm family name, while its [#getParams()] method returns
/// the parameter set name as a [NamedParameterSpec] object. The algorithm
/// identifier in the X.509 encoding of the key is always a single OID derived
/// from the parameter set name.
///
/// @see sun.security.provider.NamedKeyPairGenerator
public final class NamedX509Key extends X509Key {
@Serial
private static final long serialVersionUID = 1L;
private final String fname;
private final transient NamedParameterSpec paramSpec;
private final byte[] rawBytes;
/// Ctor from family name, parameter set name, raw key bytes.
/// Key bytes won't be cloned, caller must relinquish ownership
public NamedX509Key(String fname, String pname, byte[] rawBytes) {
this.fname = fname;
this.paramSpec = new NamedParameterSpec(pname);
try {
this.algid = AlgorithmId.get(pname);
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(e);
}
this.rawBytes = rawBytes;
setKey(new BitArray(rawBytes.length * 8, rawBytes));
}
/// Ctor from family name, and X.509 bytes
public NamedX509Key(String fname, byte[] encoded) throws InvalidKeyException {
this.fname = fname;
decode(encoded);
this.paramSpec = new NamedParameterSpec(algid.getName());
if (algid.encodedParams != null) {
throw new InvalidKeyException("algorithm identifier has params");
}
this.rawBytes = getKey().toByteArray();
}
@Override
public String toString() {
// Do not modify: this can be used by earlier JDKs that
// do not have the getParams() method
return paramSpec.getName() + " public key";
}
/// Returns the reference to the internal key. Caller must not modify
/// the content or keep a reference.
public byte[] getRawBytes() {
return rawBytes;
}
@Override
public NamedParameterSpec getParams() {
return paramSpec;
}
@Override
public String getAlgorithm() {
return fname;
}
@java.io.Serial
private Object writeReplace() throws java.io.ObjectStreamException {
return new KeyRep(KeyRep.Type.PUBLIC, getAlgorithm(), getFormat(),
getEncoded());
}
@java.io.Serial
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
throw new InvalidObjectException(
"NamedX509Key keys are not directly deserializable");
}
}