8386681: Remove RawKeySpec

Reviewed-by: hchao, ascarpino
Backport-of: 33a49d1119826f541d78bb702e16790b8c2664eb
This commit is contained in:
Weijun Wang 2026-06-18 18:52:21 +00:00
parent 8f260e6f9b
commit 5d44544186
11 changed files with 228 additions and 192 deletions

View File

@ -827,12 +827,6 @@ public final class HSS extends SignatureSpi {
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
}
} else if (keySpec instanceof RawKeySpec rawSpec) {
try {
return new HSSPublicKey(rawSpec.getKeyArr(), false);
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
}
}
throw new InvalidKeySpecException("Unrecognized KeySpec");
}
@ -866,17 +860,27 @@ public final class HSS extends SignatureSpi {
if (key == null) {
throw new InvalidKeyException("key cannot be null");
}
if (!(key instanceof PublicKey)) {
throw new InvalidKeyException("Only support public key");
}
PublicKey pKey;
try {
// Check if key originates from this factory
if (key instanceof HSSPublicKey) {
return key;
}
// Convert key to spec
X509EncodedKeySpec x509EncodedKeySpec
= engineGetKeySpec(key, X509EncodedKeySpec.class);
// Create key from spec, and return it
pKey = engineGeneratePublic(x509EncodedKeySpec);
String format = key.getFormat();
if ("X.509".equalsIgnoreCase(format)) {
// Convert key to spec
X509EncodedKeySpec x509EncodedKeySpec
= engineGetKeySpec(key, X509EncodedKeySpec.class);
// Create key from spec, and return it
pKey = engineGeneratePublic(x509EncodedKeySpec);
} else if ("RAW".equalsIgnoreCase(format)) {
pKey = new HSSPublicKey(key.getEncoded(), false);
} else {
throw new InvalidKeyException("Unknown format " + format);
}
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2026, 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
@ -26,7 +26,6 @@
package sun.security.provider;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.util.RawKeySpec;
import sun.security.x509.NamedX509Key;
import java.security.AsymmetricKey;
@ -53,7 +52,6 @@ import java.util.Arrays;
/// 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.
@ -98,13 +96,6 @@ public abstract class NamedKeyFactory extends KeyFactorySpi {
throw new InvalidKeySpecException(e);
}
}
case RawKeySpec rks -> {
if (pnames.length == 1) {
yield new NamedX509Key(fname, pnames[0], rks.getKeyArr());
} else {
throw new InvalidKeySpecException("Parameter set name unavailable");
}
}
case EncodedKeySpec espec when espec.getFormat().equalsIgnoreCase("RAW") -> {
if (pnames.length == 1) {
yield new NamedX509Key(fname, pnames[0], espec.getEncoded());
@ -134,18 +125,6 @@ public abstract class NamedKeyFactory extends KeyFactorySpi {
Arrays.fill(bytes, (byte) 0);
}
}
case RawKeySpec rks -> {
if (pnames.length == 1) {
var raw = rks.getKeyArr();
try {
yield fromRaw(pnames[0], raw);
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException("Invalid key input", e);
}
} else {
throw new InvalidKeySpecException("Parameter set name unavailable");
}
}
case EncodedKeySpec espec when espec.getFormat().equalsIgnoreCase("RAW") -> {
if (pnames.length == 1) {
var raw = espec.getEncoded();
@ -212,8 +191,6 @@ public abstract class NamedKeyFactory extends KeyFactorySpi {
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()));
@ -229,8 +206,6 @@ public abstract class NamedKeyFactory extends KeyFactorySpi {
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 {

View File

@ -28,7 +28,7 @@ package sun.security.ssl;
import sun.security.util.ArrayUtil;
import sun.security.util.CurveDB;
import sun.security.util.ECUtil;
import sun.security.util.RawKeySpec;
import sun.security.util.KeyUtil;
import sun.security.x509.X509Key;
import javax.crypto.DecapsulateException;
@ -171,67 +171,19 @@ public class Hybrid {
@Override
protected PublicKey engineGeneratePublic(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec == null) {
throw new InvalidKeySpecException("keySpec must not be null");
}
throw new InvalidKeySpecException("Not supported");
}
if (keySpec instanceof RawKeySpec rks) {
byte[] key = rks.getKeyArr();
if (key == null) {
throw new InvalidKeySpecException(
"RawkeySpec contains null key data");
}
if (key.length <= leftlen) {
throw new InvalidKeySpecException(
"Hybrid key length " + key.length +
" is too short and its left key length is " +
leftlen);
}
@Override
protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws
InvalidKeySpecException {
throw new InvalidKeySpecException("Not supported");
}
byte[] leftKeyBytes = Arrays.copyOfRange(key, 0, leftlen);
byte[] rightKeyBytes = Arrays.copyOfRange(key, leftlen,
key.length);
PublicKey leftKey, rightKey;
try {
if (leftname.startsWith("secp")) {
var curve = CurveDB.lookup(leftname);
var ecSpec = new ECPublicKeySpec(
ECUtil.decodePoint(leftKeyBytes,
curve.getCurve()), curve);
leftKey = left.generatePublic(ecSpec);
} else if (leftname.startsWith("ML-KEM")) {
leftKey = left.generatePublic(new RawKeySpec(
leftKeyBytes));
} else {
throw new InvalidKeySpecException("Unsupported left" +
" algorithm" + leftname);
}
if (rightname.equals("X25519")) {
ArrayUtil.reverse(rightKeyBytes);
var xecSpec = new XECPublicKeySpec(
new NamedParameterSpec(rightname),
new BigInteger(1, rightKeyBytes));
rightKey = right.generatePublic(xecSpec);
} else if (rightname.startsWith("ML-KEM")) {
rightKey = right.generatePublic(new RawKeySpec(
rightKeyBytes));
} else {
throw new InvalidKeySpecException("Unsupported right" +
" algorithm: " + rightname);
}
return new PublicKeyImpl("Hybrid", leftKey, rightKey);
} catch (Exception e) {
throw new InvalidKeySpecException("Failed to decode " +
"hybrid key", e);
}
}
throw new InvalidKeySpecException(
"KeySpec type:" +
keySpec.getClass().getName() + " not supported");
@Override
protected <T extends KeySpec> T engineGetKeySpec(Key key,
Class<T> keySpec) throws InvalidKeySpecException {
throw new InvalidKeySpecException("Not supported");
}
private static int leftPublicLength(String name) {
@ -245,20 +197,83 @@ public class Hybrid {
}
@Override
protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws
InvalidKeySpecException {
throw new UnsupportedOperationException();
}
protected Key engineTranslateKey(Key inKey) throws InvalidKeyException {
if (inKey == null) {
throw new InvalidKeyException("key must not be null");
}
@Override
protected <T extends KeySpec> T engineGetKeySpec(Key key,
Class<T> keySpec) throws InvalidKeySpecException {
throw new UnsupportedOperationException();
}
if (inKey instanceof PublicKey
&& "RAW".equalsIgnoreCase(inKey.getFormat())) {
byte[] key = inKey.getEncoded();
if (key == null) {
throw new InvalidKeyException(
"Key contains null key data");
}
if (key.length <= leftlen) {
throw new InvalidKeyException(
"Hybrid key length " + key.length +
" is too short and its left key length is " +
leftlen);
}
@Override
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
throw new UnsupportedOperationException();
byte[] leftKeyBytes = Arrays.copyOfRange(key, 0, leftlen);
byte[] rightKeyBytes = Arrays.copyOfRange(key, leftlen,
key.length);
PublicKey leftKey, rightKey;
try {
if (leftname.startsWith("secp")) {
var curve = CurveDB.lookup(leftname);
var ecSpec = new ECPublicKeySpec(
ECUtil.decodePoint(leftKeyBytes,
curve.getCurve()), curve);
leftKey = left.generatePublic(ecSpec);
} else if (leftname.startsWith("ML-KEM")) {
try {
leftKey = (PublicKey) left.translateKey(KeyUtil
.newRawPublicKey(leftname, leftKeyBytes));
} catch (InvalidKeyException e) {
// Fallback to X.509 encoding if ML-KEM impl
// does not support translating from RAW
leftKey = left.generatePublic(new X509EncodedKeySpec(
KeyUtil.rawToX509(leftname, leftKeyBytes)));
}
} else {
throw new InvalidKeySpecException("Unsupported left" +
" algorithm" + leftname);
}
if (rightname.equals("X25519")) {
ArrayUtil.reverse(rightKeyBytes);
var xecSpec = new XECPublicKeySpec(
new NamedParameterSpec(rightname),
new BigInteger(1, rightKeyBytes));
rightKey = right.generatePublic(xecSpec);
} else if (rightname.startsWith("ML-KEM")) {
try {
rightKey = (PublicKey) right.translateKey(KeyUtil
.newRawPublicKey(rightname, rightKeyBytes));
} catch (InvalidKeyException e) {
// Fallback to X.509 encoding if ML-KEM impl
// does not support translating from RAW
rightKey = right.generatePublic(new X509EncodedKeySpec(
KeyUtil.rawToX509(rightname, rightKeyBytes)));
}
} else {
throw new InvalidKeySpecException("Unsupported right" +
" algorithm: " + rightname);
}
return new PublicKeyImpl("Hybrid", leftKey, rightKey);
} catch (Exception e) {
throw new InvalidKeyException("Failed to decode " +
"hybrid key", e);
}
} else {
throw new InvalidKeyException("Unknown key "
+ inKey.getClass().getName() + " in "
+ inKey.getFormat());
}
}
}

View File

@ -24,8 +24,6 @@
*/
package sun.security.ssl;
import sun.security.util.RawKeySpec;
import javax.crypto.DecapsulateException;
import javax.crypto.KDF;
import javax.crypto.KEM;
@ -42,6 +40,8 @@ import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import sun.security.util.KeyUtil;
/**
@ -185,7 +185,21 @@ public class KAKeyDerivation implements SSLKeyDerivation {
KeyFactory kf = (provider != null) ?
KeyFactory.getInstance(algorithmName, provider) :
KeyFactory.getInstance(algorithmName);
var pk = kf.generatePublic(new RawKeySpec(keyshare));
PublicKey pk;
try {
pk = (PublicKey) kf.translateKey(
KeyUtil.newRawPublicKey(algorithmName, keyshare));
} catch (InvalidKeyException e) {
// Fallback to X.509 encoding if ML-KEM impl
// does not support translating from RAW
try {
pk = kf.generatePublic(new X509EncodedKeySpec(
KeyUtil.rawToX509(algorithmName, keyshare)));
} catch (GeneralSecurityException e2) {
e2.addSuppressed(e);
throw new InvalidKeyException(e2);
}
}
KEM kem = (provider != null) ?
KEM.getInstance(algorithmName, provider) :

View File

@ -37,6 +37,7 @@ import java.security.spec.NamedParameterSpec;
import javax.crypto.SecretKey;
import sun.security.ssl.NamedGroup.NamedGroupSpec;
import sun.security.util.KeyUtil;
import sun.security.x509.X509Key;
/**
@ -140,10 +141,20 @@ final class KEMKeyExchange {
public byte[] encode() {
if (publicKey instanceof X509Key xk) {
return xk.getKeyAsBytes();
} else if (publicKey instanceof Hybrid.PublicKeyImpl hk) {
return hk.getEncoded();
} else {
String format = publicKey.getFormat();
if ("RAW".equalsIgnoreCase(format)) {
return publicKey.getEncoded();
} else if ("X.509".equalsIgnoreCase(format)) {
try {
return KeyUtil.x509ToRaw(publicKey.getEncoded());
} catch (IOException e) {
throw new ProviderException("Invalid X.509 format");
}
} else {
throw new ProviderException("Unknown format " + format);
}
}
throw new ProviderException("Unsupported key type: " + publicKey);
}
// Package-private

View File

@ -46,6 +46,7 @@ import com.sun.crypto.provider.PBKDF2KeyImpl;
import sun.security.jca.JCAUtil;
import sun.security.pkcs.PKCS8Key;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X509Key;
/**
* A utility class to get key length, validate keys, etc.
@ -589,5 +590,66 @@ public final class KeyUtil {
}
}
}
public static PublicKey newRawPublicKey(String algorithm, byte[] key) {
return newRawPublicKey(algorithm, null, key);
}
public static PublicKey newRawPublicKey(String algorithm,
AlgorithmParameterSpec params, byte[] key) {
return new RawPublicKey(algorithm, params, key);
}
private record RawPublicKey(String algorithm, AlgorithmParameterSpec params,
byte[] data) implements PublicKey {
RawPublicKey {
data = data.clone();
}
@Override
public String getAlgorithm() {
return algorithm;
}
@Override
public String getFormat() {
return "RAW";
}
@Override
public byte[] getEncoded() {
return data.clone();
}
@Override
public AlgorithmParameterSpec getParams() {
return params;
}
}
// Convert RAW encoding to X.509 encoding of a public key.
// The AlgorithmId will be a single OID from `pname`, so this
// cannot be used by EC or RSASSA-PSS.
static public byte[] rawToX509(String pname, byte[] bytes)
throws NoSuchAlgorithmException {
return new X509Key(AlgorithmId.get(pname),
new BitArray(bytes.length * 8, bytes)).getEncoded();
}
// Convert X.509 encoding to RAW encoding of a public key.
// AlgorithmId is ignored. No check for trailing data after key.
static public byte[] x509ToRaw(byte[] bytes) throws IOException {
DerValue in = new DerValue(bytes);
if (in.tag != DerValue.tag_Sequence) {
throw new IOException("corrupt subject key");
}
AlgorithmId.parse(in.data.getDerValue());
BitArray keyMaterial = in.data.getUnalignedBitString();
if (keyMaterial.length() % 8 != 0) {
throw new IOException("Unaligned bits in public key");
}
return keyMaterial.toByteArray();
}
}

View File

@ -1,52 +0,0 @@
/*
* 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 sun.security.util;
import java.security.spec.KeySpec;
/**
* This is a KeySpec that is used to specify a key by its byte array implementation.
* It is intended to be used in testing algorithms where the algorithm specification
* describes the key in this form.
*/
public class RawKeySpec implements KeySpec {
private final byte[] keyArr;
/**
* The sole constructor.
* @param key contains the key as a byte array
*/
public RawKeySpec(byte[] key) {
keyArr = key.clone();
}
/**
* Getter function.
* @return a copy of the key bits
*/
public byte[] getKeyArr() {
return keyArr.clone();
}
}

View File

@ -22,7 +22,7 @@
*/
import jdk.test.lib.Asserts;
import jdk.test.lib.json.JSONValue;
import sun.security.util.RawKeySpec;
import sun.security.util.KeyUtil;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
@ -62,11 +62,11 @@ public class LMS_Test {
// Convert to HSS key by prepending height of tree (1)
// to the LMS public key.
RawKeySpec rks = new RawKeySpec(toByteArray(
"00000001" + t.get("publicKey").asString()));
KeyFactory kf = p == null ? KeyFactory.getInstance("HSS/LMS") :
KeyFactory.getInstance("HSS/LMS", p);
PublicKey pk1 = kf.generatePublic(rks);
PublicKey pk1 = (PublicKey) kf.translateKey(KeyUtil
.newRawPublicKey("HSS/LMS", toByteArray(
"00000001" + t.get("publicKey").asString())));
try {
s.initVerify(pk1);

View File

@ -167,9 +167,9 @@ public class TestHSS {
PublicKey pk1;
// build public key
RawKeySpec rks = new RawKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance(ALG, provider);
pk1 = kf.generatePublic(rks);
pk1 = (PublicKey) kf.translateKey(KeyUtil
.newRawPublicKey(ALG, pk));
var v = Signature.getInstance(ALG);
v.initVerify(pk1);

View File

@ -36,7 +36,6 @@ import jdk.test.lib.security.SeededSecureRandom;
import sun.security.pkcs.NamedPKCS8Key;
import sun.security.provider.NamedKeyFactory;
import sun.security.provider.NamedKeyPairGenerator;
import sun.security.util.RawKeySpec;
import sun.security.x509.NamedX509Key;
import java.security.*;
@ -119,30 +118,22 @@ public class NamedKeyFactoryTest {
Utils.runAndCheckException(() -> kf5.generatePublic(skSpec),
InvalidKeySpecException.class);
// The private RawKeySpec and unnamed RAW EncodedKeySpec
var prk = kf.getKeySpec(pk, RawKeySpec.class);
Asserts.assertEqualsByteArray(prk.getKeyArr(), pk.getRawBytes());
var prk2 = kf.getKeySpec(pk, EncodedKeySpec.class);
Asserts.assertEquals("RAW", prk2.getFormat());
Asserts.assertEqualsByteArray(prk.getKeyArr(), prk2.getEncoded());
// The unnamed RAW EncodedKeySpec
var prk = kf.getKeySpec(pk, EncodedKeySpec.class);
Asserts.assertEquals("RAW", prk.getFormat());
Asserts.assertEqualsByteArray(pk.getRawBytes(), prk.getEncoded());
Asserts.assertEqualsByteArray(kf2.generatePublic(prk).getEncoded(), pk.getEncoded());
Utils.runAndCheckException(() -> kf.generatePublic(prk), InvalidKeySpecException.class); // no pname
Asserts.assertEqualsByteArray(kf2.generatePublic(prk2).getEncoded(), pk.getEncoded());
Utils.runAndCheckException(() -> kf.generatePublic(prk2), InvalidKeySpecException.class); // no pname
var srk = kf.getKeySpec(sk, RawKeySpec.class);
Asserts.assertEqualsByteArray(srk.getKeyArr(), sk.getRawBytes());
var srk2 = kf.getKeySpec(sk, EncodedKeySpec.class);
Asserts.assertEquals("RAW", srk2.getFormat());
Asserts.assertEqualsByteArray(srk2.getEncoded(), sk.getRawBytes());
var srk = kf.getKeySpec(sk, EncodedKeySpec.class);
Asserts.assertEquals("RAW", srk.getFormat());
Asserts.assertEqualsByteArray(srk.getEncoded(), sk.getRawBytes());
checkKey(kf2.generatePrivate(srk), "SHA", "SHA-256");
Asserts.assertEqualsByteArray(kf2.generatePrivate(srk).getEncoded(), sk.getEncoded());
Utils.runAndCheckException(() -> kf.generatePrivate(srk), InvalidKeySpecException.class); // no pname
checkKey(kf2.generatePrivate(srk), "SHA", "SHA-256");
Asserts.assertEqualsByteArray(kf2.generatePrivate(srk2).getEncoded(), sk.getEncoded());
Utils.runAndCheckException(() -> kf.generatePrivate(srk2), InvalidKeySpecException.class); // no pname
var pk1 = new PublicKey() {
public String getAlgorithm() { return "SHA"; }

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2026, 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
@ -34,9 +34,9 @@ import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import sun.security.util.RawKeySpec;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.util.HexFormat;
@ -64,7 +64,23 @@ public class HSSBench {
public static Signature getVerifier(byte[] pk) throws Exception {
var kf = KeyFactory.getInstance("HSS/LMS", Security.getProvider("SUN"));
var pk1 = kf.generatePublic(new RawKeySpec(pk));
var pk1 = (PublicKey) kf.translateKey(new PublicKey() {
@Override
public String getAlgorithm() {
return "HSS/LMS";
}
@Override
public String getFormat() {
return "RAW";
}
@Override
public byte[] getEncoded() {
return pk.clone();
}
});
var vv = Signature.getInstance("HSS/LMS");
vv.initVerify(pk1);