Skip to content
This repository has been archived by the owner on Sep 2, 2022. It is now read-only.

Commit

Permalink
8023980: JCE doesn't provide any class to handle RSA private key in P…
Browse files Browse the repository at this point in the history
…KCS#1

Backport-of: 68cf65d284a73f5c5229d30ca642bba9585095f3
  • Loading branch information
Prajwal Kumaraswamy authored and Rob McKenna committed Apr 22, 2021
1 parent 32eb4f8 commit cce99e5
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 159 deletions.
49 changes: 18 additions & 31 deletions src/java.base/share/classes/sun/security/rsa/RSAKeyFactory.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2021, 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
Expand Down Expand Up @@ -43,13 +43,15 @@
* between the following:
*
* For public keys:
* . PublicKey with an X.509 encoding
* . RSA PublicKey with an X.509 encoding
* . RSA PublicKey with an PKCS#1 encoding
* . RSAPublicKey
* . RSAPublicKeySpec
* . X509EncodedKeySpec
*
* For private keys:
* . PrivateKey with a PKCS#8 encoding
* . RSA PrivateKey with a PKCS#8 encoding
* . RSA PrivateKey with a PKCS#1 encoding
* . RSAPrivateKey
* . RSAPrivateCrtKey
* . RSAPrivateKeySpec
Expand Down Expand Up @@ -95,8 +97,8 @@ static RSAKeyFactory getInstance(KeyType type) {
return new RSAKeyFactory(type);
}

// Internal utility method for checking key algorithm
private static void checkKeyAlgo(Key key, String expectedAlg)
// pkg-private utility method for checking key algorithm
static void checkKeyAlgo(Key key, String expectedAlg)
throws InvalidKeyException {
String keyAlg = key.getAlgorithm();
if (keyAlg == null || !(keyAlg.equalsIgnoreCase(expectedAlg))) {
Expand Down Expand Up @@ -265,14 +267,10 @@ private PublicKey translatePublicKey(PublicKey key)
// catch providers that incorrectly implement RSAPublicKey
throw new InvalidKeyException("Invalid key", e);
}
} else if ("X.509".equals(key.getFormat())) {
RSAPublicKey translated = new RSAPublicKeyImpl(key.getEncoded());
// ensure the key algorithm matches the current KeyFactory instance
checkKeyAlgo(translated, type.keyAlgo);
return translated;
} else {
throw new InvalidKeyException("Public keys must be instance "
+ "of RSAPublicKey or have X.509 encoding");
// create new key based on the format and encoding of current 'key'
return RSAPublicKeyImpl.newKey(type, key.getFormat(),
key.getEncoded());
}
}

Expand Down Expand Up @@ -309,27 +307,18 @@ private PrivateKey translatePrivateKey(PrivateKey key)
// catch providers that incorrectly implement RSAPrivateKey
throw new InvalidKeyException("Invalid key", e);
}
} else if ("PKCS#8".equals(key.getFormat())) {
RSAPrivateKey translated =
RSAPrivateCrtKeyImpl.newKey(key.getEncoded());
// ensure the key algorithm matches the current KeyFactory instance
checkKeyAlgo(translated, type.keyAlgo);
return translated;
} else {
throw new InvalidKeyException("Private keys must be instance "
+ "of RSAPrivate(Crt)Key or have PKCS#8 encoding");
return RSAPrivateCrtKeyImpl.newKey(type, key.getFormat(),
key.getEncoded());
}
}

// internal implementation of generatePublic. See JCA doc
private PublicKey generatePublic(KeySpec keySpec)
throws GeneralSecurityException {
if (keySpec instanceof X509EncodedKeySpec) {
X509EncodedKeySpec x509Spec = (X509EncodedKeySpec)keySpec;
RSAPublicKey generated = new RSAPublicKeyImpl(x509Spec.getEncoded());
// ensure the key algorithm matches the current KeyFactory instance
checkKeyAlgo(generated, type.keyAlgo);
return generated;
return RSAPublicKeyImpl.newKey(type, "X.509",
((X509EncodedKeySpec)keySpec).getEncoded());
} else if (keySpec instanceof RSAPublicKeySpec) {
RSAPublicKeySpec rsaSpec = (RSAPublicKeySpec)keySpec;
try {
Expand All @@ -351,11 +340,8 @@ private PublicKey generatePublic(KeySpec keySpec)
private PrivateKey generatePrivate(KeySpec keySpec)
throws GeneralSecurityException {
if (keySpec instanceof PKCS8EncodedKeySpec) {
PKCS8EncodedKeySpec pkcsSpec = (PKCS8EncodedKeySpec)keySpec;
RSAPrivateKey generated = RSAPrivateCrtKeyImpl.newKey(pkcsSpec.getEncoded());
// ensure the key algorithm matches the current KeyFactory instance
checkKeyAlgo(generated, type.keyAlgo);
return generated;
return RSAPrivateCrtKeyImpl.newKey(type, "PKCS#8",
((PKCS8EncodedKeySpec)keySpec).getEncoded());
} else if (keySpec instanceof RSAPrivateCrtKeySpec) {
RSAPrivateCrtKeySpec rsaSpec = (RSAPrivateCrtKeySpec)keySpec;
try {
Expand Down Expand Up @@ -395,7 +381,8 @@ protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
try {
// convert key to one of our keys
// this also verifies that the key is a valid RSA key and ensures
// that the encoding is X.509/PKCS#8 for public/private keys
// that the encoding is X.509/PKCS#8 or PKCS#1 for public/private
// keys
key = engineTranslateKey(key);
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
Expand Down
134 changes: 83 additions & 51 deletions src/java.base/share/classes/sun/security/rsa/RSAPrivateCrtKeyImpl.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2021, 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
Expand Down Expand Up @@ -74,30 +74,52 @@ public final class RSAPrivateCrtKeyImpl
private transient AlgorithmParameterSpec keyParams;

/**
* Generate a new key from its encoding. Returns a CRT key if possible
* and a non-CRT key otherwise. Used by RSAKeyFactory.
* Generate a new RSAPrivate(Crt)Key from the specified type,
* format and encoding. Returns a CRT key if possible and a non-CRT
* key otherwise.
* Also used by SunPKCS11 provider.
*/
public static RSAPrivateKey newKey(byte[] encoded)
throws InvalidKeyException {
public static RSAPrivateKey newKey(KeyType type, String format,
byte[] encoded) throws InvalidKeyException {
if (encoded == null || encoded.length == 0) {
throw new InvalidKeyException("Missing key encoding");
}
RSAPrivateCrtKeyImpl key = new RSAPrivateCrtKeyImpl(encoded);
// check all CRT-specific components are available, if any one
// missing, return a non-CRT key instead
if ((key.getPublicExponent().signum() == 0) ||
(key.getPrimeExponentP().signum() == 0) ||
(key.getPrimeExponentQ().signum() == 0) ||
(key.getPrimeP().signum() == 0) ||
(key.getPrimeQ().signum() == 0) ||
(key.getCrtCoefficient().signum() == 0)) {
return new RSAPrivateKeyImpl(
key.type, key.keyParams,
key.getModulus(),
key.getPrivateExponent()
);
} else {
return key;
switch (format) {
case "PKCS#8":
RSAPrivateCrtKeyImpl key = new RSAPrivateCrtKeyImpl(encoded);
RSAKeyFactory.checkKeyAlgo(key, type.keyAlgo);
// check all CRT-specific components are available, if any one
// missing, return a non-CRT key instead
if ((key.getPublicExponent().signum() == 0) ||
(key.getPrimeExponentP().signum() == 0) ||
(key.getPrimeExponentQ().signum() == 0) ||
(key.getPrimeP().signum() == 0) ||
(key.getPrimeQ().signum() == 0) ||
(key.getCrtCoefficient().signum() == 0)) {
return new RSAPrivateKeyImpl(key.type, key.keyParams,
key.getModulus(), key.getPrivateExponent());
} else {
return key;
}
case "PKCS#1":
try {
BigInteger[] comps = parseASN1(encoded);
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
(comps[6].signum() == 0) || (comps[7].signum() == 0)) {
return new RSAPrivateKeyImpl(type, null, comps[0],
comps[2]);
} else {
return new RSAPrivateCrtKeyImpl(type, null, comps[0],
comps[1], comps[2], comps[3], comps[4], comps[5],
comps[6], comps[7]);
}
} catch (IOException ioe) {
throw new InvalidKeyException("Invalid PKCS#1 encoding", ioe);
}
default:
throw new InvalidKeyException("Unsupported RSA Private(Crt)Key "
+ "format: " + format);
}
}

Expand Down Expand Up @@ -126,7 +148,7 @@ public static RSAPrivateKey newKey(KeyType type,
/**
* Construct a key from its encoding. Called from newKey above.
*/
RSAPrivateCrtKeyImpl(byte[] encoded) throws InvalidKeyException {
private RSAPrivateCrtKeyImpl(byte[] encoded) throws InvalidKeyException {
super(encoded);
parseKeyBits();
RSAKeyFactory.checkRSAProviderKeyLengths(n.bitLength(), e);
Expand Down Expand Up @@ -258,37 +280,47 @@ public String toString() {
+ "\n modulus: " + n + "\n private exponent: " + d;
}

// utility method for parsing DER encoding of RSA private keys in PKCS#1
// format as defined in RFC 8017 Appendix A.1.2, i.e. SEQ of version, n,
// e, d, p, q, pe, qe, and coeff, and return the parsed components.
private static BigInteger[] parseASN1(byte[] raw) throws IOException {
DerValue derValue = new DerValue(raw);
if (derValue.tag != DerValue.tag_Sequence) {
throw new IOException("Not a SEQUENCE");
}
int version = derValue.data.getInteger();
if (version != 0) {
throw new IOException("Version must be 0");
}

BigInteger[] result = new BigInteger[8]; // n, e, d, p, q, pe, qe, coeff
/*
* Some implementations do not correctly encode ASN.1 INTEGER values
* in 2's complement format, resulting in a negative integer when
* decoded. Correct the error by converting it to a positive integer.
*
* See CR 6255949
*/
for (int i = 0; i < result.length; i++) {
result[i] = derValue.data.getPositiveBigInteger();
}
if (derValue.data.available() != 0) {
throw new IOException("Extra data available");
}
return result;
}

private void parseKeyBits() throws InvalidKeyException {
try {
DerInputStream in = new DerInputStream(key);
DerValue derValue = in.getDerValue();
if (derValue.tag != DerValue.tag_Sequence) {
throw new IOException("Not a SEQUENCE");
}
DerInputStream data = derValue.data;
int version = data.getInteger();
if (version != 0) {
throw new IOException("Version must be 0");
}

/*
* Some implementations do not correctly encode ASN.1 INTEGER values
* in 2's complement format, resulting in a negative integer when
* decoded. Correct the error by converting it to a positive integer.
*
* See CR 6255949
*/
n = data.getPositiveBigInteger();
e = data.getPositiveBigInteger();
d = data.getPositiveBigInteger();
p = data.getPositiveBigInteger();
q = data.getPositiveBigInteger();
pe = data.getPositiveBigInteger();
qe = data.getPositiveBigInteger();
coeff = data.getPositiveBigInteger();
if (derValue.data.available() != 0) {
throw new IOException("Extra data available");
}
BigInteger[] comps = parseASN1(key);
n = comps[0];
e = comps[1];
d = comps[2];
p = comps[3];
q = comps[4];
pe = comps[5];
qe = comps[6];
coeff = comps[7];
} catch (IOException e) {
throw new InvalidKeyException("Invalid RSA private key", e);
}
Expand Down
67 changes: 47 additions & 20 deletions src/java.base/share/classes/sun/security/rsa/RSAPublicKeyImpl.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2021, 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
Expand Down Expand Up @@ -66,17 +66,36 @@ public final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
private transient AlgorithmParameterSpec keyParams;

/**
* Generate a new RSAPublicKey from the specified encoding.
* Used by SunPKCS11 provider.
* Generate a new RSAPublicKey from the specified type, format, and
* encoding.
* Also used by SunPKCS11 provider.
*/
public static RSAPublicKey newKey(byte[] encoded)
throws InvalidKeyException {
return new RSAPublicKeyImpl(encoded);
public static RSAPublicKey newKey(KeyType type, String format,
byte[] encoded) throws InvalidKeyException {
RSAPublicKey key;
switch (format) {
case "X.509":
key = new RSAPublicKeyImpl(encoded);
RSAKeyFactory.checkKeyAlgo(key, type.keyAlgo);
break;
case "PKCS#1":
try {
BigInteger[] comps = parseASN1(encoded);
key = new RSAPublicKeyImpl(type, null, comps[0], comps[1]);
} catch (IOException ioe) {
throw new InvalidKeyException("Invalid PKCS#1 encoding", ioe);
}
break;
default:
throw new InvalidKeyException("Unsupported RSA PublicKey format: " +
format);
}
return key;
}

/**
* Generate a new RSAPublicKey from the specified type and components.
* Used by SunPKCS11 provider.
* Also used by SunPKCS11 provider.
*/
public static RSAPublicKey newKey(KeyType type,
AlgorithmParameterSpec params, BigInteger n, BigInteger e)
Expand Down Expand Up @@ -123,9 +142,9 @@ public static RSAPublicKey newKey(KeyType type,
}

/**
* Construct a key from its encoding. Used by RSAKeyFactory.
* Construct a key from its encoding.
*/
RSAPublicKeyImpl(byte[] encoded) throws InvalidKeyException {
private RSAPublicKeyImpl(byte[] encoded) throws InvalidKeyException {
if (encoded == null || encoded.length == 0) {
throw new InvalidKeyException("Missing key encoding");
}
Expand Down Expand Up @@ -181,22 +200,30 @@ public AlgorithmParameterSpec getParams() {
return keyParams;
}

// utility method for parsing DER encoding of RSA public keys in PKCS#1
// format as defined in RFC 8017 Appendix A.1.1, i.e. SEQ of n and e.
private static BigInteger[] parseASN1(byte[] raw) throws IOException {
DerValue derValue = new DerValue(raw);
if (derValue.tag != DerValue.tag_Sequence) {
throw new IOException("Not a SEQUENCE");
}
BigInteger[] result = new BigInteger[2]; // n, e
result[0] = derValue.data.getPositiveBigInteger();
result[1] = derValue.data.getPositiveBigInteger();
if (derValue.data.available() != 0) {
throw new IOException("Extra data available");
}
return result;
}

/**
* Parse the key. Called by X509Key.
*/
protected void parseKeyBits() throws InvalidKeyException {
try {
DerInputStream in = new DerInputStream(getKey().toByteArray());
DerValue derValue = in.getDerValue();
if (derValue.tag != DerValue.tag_Sequence) {
throw new IOException("Not a SEQUENCE");
}
DerInputStream data = derValue.data;
n = data.getPositiveBigInteger();
e = data.getPositiveBigInteger();
if (derValue.data.available() != 0) {
throw new IOException("Extra data available");
}
BigInteger[] comps = parseASN1(getKey().toByteArray());
n = comps[0];
e = comps[1];
} catch (IOException e) {
throw new InvalidKeyException("Invalid RSA public key", e);
}
Expand Down

1 comment on commit cce99e5

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.