-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8263404: RsaPrivateKeySpec is always recognized as RSAPrivateCrtKeySpec in RSAKeyFactory.engineGetKeySpec #2949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
👋 Welcome back luoziyi! A progress list of the required criteria for merging this PR into |
Webrevs
|
|
My understanding is that the problem here is the 2 BTW, please add a regression test to the fix. Thanks. |
|
If we check the parent class |
|
Hi @wangweij Thanks for your review. As @SalusaSecondus commented, RSAPrivateKeyCrtSpec should be favored over RSAPrivateKeySpec when the PrivateKey is a Crt Key. I just modified our JTreg test to include all four cases described in the PR description. |
|
I still cannot understand why CRT is always preferred. The original implementation also hadn't done that. |
|
I believe that the original implementation intended to do this but made a mistake. This is why the original implementation (with the backwards RSA CRT keys are much more efficient than normal RSA private keys and also result in more a more standard compliant output when serialized to PKCS#8 format (which technically requires the CRT parameters to be present). Thus, I believe we should try to preserve the CRT parameters whenever possible for our users. Now users who request an |
|
P11RSAKeyFactory.java should also be included into this fix as it's subject to the same RSAPrivateKeySpec vs RSAPrivateCrtKeySpec issue. My personal take is that the isAssignableFrom() call may be more for checking if both keyspecs are the same. If the original impl (pre-8254717 code) really intend to return RSAPrivateCrtKeySpec for RSAPrivateCrtKey objs, then it should just check key type to be CRT and no need to call isAssignableFrom() with both RSAPrivateCrtKeySpec AND RSAPrivateKeySpec, just check the later would suffice. Thus, I'd argue the original intention is to return the keyspec matching the requested keyspec class. I can see the potential performance benefit of piggybacking the Crt info and generating Crt keys when RSAPrivateKeySpec is specified. However, the way it is coded in this PR seems a bit unclear. The InvalidKeySpecException definitely must be fixed. As for the RSAPrivateKeySpec->RSAPrivateCrtKeySpec change, it's a change of behavior comparing to pre-8254717 code which I am ok with if we can be sure that there is no undesirable side-effects. |
test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java
Outdated
Show resolved
Hide resolved
test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java
Outdated
Show resolved
Hide resolved
test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java
Outdated
Show resolved
Hide resolved
Thank you. We'll fix this.
I wondered this too, but if that were the case, I'd expect to see
I'm definitely up for ways to make this clearer. I'll ensure that the next version has some better comments to explain the logic flow so that future us doesn't need to guess about original intent in the way we are now. I agree that this should be a safe change. Outside of some extremely sensitive unit tests (which is how I discovered this bug in the first place), I don't expect much code to depend on the exact class returned by this method. (Any code doing so would be wrong as well, but we still need to pay attention to it.) We can see similar behavior elsewhere in the JDK where a more specialized class is returned by a method similar to this. AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
params.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec spec = params.getParameterSpec(ECParameterSpec.class);
System.out.println(spec);
System.out.println(spec.getClass());In this case we can see that EcParameters returns an instance of the private class |
SalusaSecondus
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks to cover the cases and fixes we talked about.
|
@valeriepeng Sorry for the delay. There were unknown Windows build failure during the pre-submit tests that I have to rebase my commits on top of the master tip. This new revision should cover all comments you left before. Thank you! |
| } else if (keySpec.isAssignableFrom(RSA_PRIVCRT_KEYSPEC_CLS) && key instanceof RSAPrivateCrtKey) { | ||
| RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey)key; | ||
| return keySpec.cast(new RSAPrivateCrtKeySpec( | ||
| crtKey.getModulus(), | ||
| crtKey.getPublicExponent(), | ||
| crtKey.getPrivateExponent(), | ||
| crtKey.getPrimeP(), | ||
| crtKey.getPrimeQ(), | ||
| crtKey.getPrimeExponentP(), | ||
| crtKey.getPrimeExponentQ(), | ||
| crtKey.getCrtCoefficient(), | ||
| crtKey.getParams() | ||
| )); | ||
| } else if (keySpec.isAssignableFrom(RSA_PRIV_KEYSPEC_CLS)) { | ||
| RSAPrivateKey rsaKey = (RSAPrivateKey)key; | ||
| return keySpec.cast(new RSAPrivateKeySpec( | ||
| rsaKey.getModulus(), | ||
| rsaKey.getPrivateExponent(), | ||
| rsaKey.getParams() | ||
| )); | ||
| } else if (keySpec.isAssignableFrom(RSA_PRIVCRT_KEYSPEC_CLS)) { | ||
| throw new InvalidKeySpecException | ||
| ("RSAPrivateCrtKeySpec can only be used with CRT keys"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are basing the decision on type of key, it may be clearer to do something like below:
} else if (key instanceof RSAPrivateKey) {
if (keySpec.isAssignableFrom(PKCS8_KEYSPEC_CLS)) {
return keySpec.cast(new PKCS8EncodedKeySpec(key.getEncoded()));
} else if (keySpec.isAssignableFrom(RSA_PRIVCRT_KEYSPEC_CLS)) {
if (key instanceof RSAPrivateCrtKey) {
RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey)key;
return keySpec.cast(new RSAPrivateCrtKeySpec(
crtKey.getModulus(),
crtKey.getPublicExponent(),
crtKey.getPrivateExponent(),
crtKey.getPrimeP(),
crtKey.getPrimeQ(),
crtKey.getPrimeExponentP(),
crtKey.getPrimeExponentQ(),
crtKey.getCrtCoefficient(),
crtKey.getParams()
));
} else { // RSAPrivateKey (non-CRT)
if (!keySpec.isAssignableFrom(RSA_PRIV_KEYSPEC_CLS)) {
throw new InvalidKeySpecException
("RSAPrivateCrtKeySpec can only be used with CRT keys");
}
// fall through to RSAPrivateKey (non-CRT)
RSAPrivateKey rsaKey = (RSAPrivateKey) key;
return keySpec.cast(new RSAPrivateKeySpec(
rsaKey.getModulus(),
rsaKey.getPrivateExponent(),
rsaKey.getParams()
));
}
} else {
throw new InvalidKeySpecException
("KeySpec must be RSAPrivate(Crt)KeySpec or "
+ "PKCS8EncodedKeySpec for RSA private keys");
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this flow and think that it's clearer.
One question: Do you think we could use this code almost character for character in the P11RSAKeyFactory? The keys handled should be descendants of RSAPrivateCrtKey, RSAPrivateKey, or P11PrivateKey (which would need some special handling). I didn't want to refactor it that much even though I think that it would work because I assume it must have been written to use the underlying PKCS#11 properties for some reason (even if I cannot figure out why).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, for P11RSAKeyFactory, I think some minor modification may be needed given the additional P11PrivateKey type.
I'd expect it to be something like:
// must be either RSAPrivateKeySpec or RSAPrivateCrtKeySpec
if (keySpec.isAssignableFrom(RSAPrivateCrtKeySpec.class)) {
session[0] = token.getObjSession();
CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
new CK_ATTRIBUTE(CKA_MODULUS),
new CK_ATTRIBUTE(CKA_PUBLIC_EXPONENT),
new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT),
new CK_ATTRIBUTE(CKA_PRIME_1),
new CK_ATTRIBUTE(CKA_PRIME_2),
new CK_ATTRIBUTE(CKA_EXPONENT_1),
new CK_ATTRIBUTE(CKA_EXPONENT_2),
new CK_ATTRIBUTE(CKA_COEFFICIENT),
};
long keyID = key.getKeyID();
try {
token.p11.C_GetAttributeValue(session[0].id(), keyID, attributes);
KeySpec spec = new RSAPrivateCrtKeySpec(
attributes[0].getBigInteger(),
attributes[1].getBigInteger(),
attributes[2].getBigInteger(),
attributes[3].getBigInteger(),
attributes[4].getBigInteger(),
attributes[5].getBigInteger(),
attributes[6].getBigInteger(),
attributes[7].getBigInteger()
);
return keySpec.cast(spec);
} catch (final PKCS11Exception ex) {
// bubble this up if RSAPrivateCrtKeySpec is specified
// otherwise fall through to RSAPrivateKeySpec
if (!keySpec.isAssignableFrom(RSAPrivateKeySpec.class)) {
throw ex;
}
} finally {
key.releaseKeyID();
}
attributes = new CK_ATTRIBUTE[] {
new CK_ATTRIBUTE(CKA_MODULUS),
new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT),
};
keyID = key.getKeyID();
try {
token.p11.C_GetAttributeValue(session[0].id(), keyID, attributes);
} finally {
key.releaseKeyID();
}
KeySpec spec = new RSAPrivateKeySpec(
attributes[0].getBigInteger(),
attributes[1].getBigInteger()
);
return keySpec.cast(spec);
} else { // PKCS#8 handled in superclass
throw new InvalidKeySpecException("Only RSAPrivate(Crt)KeySpec "
+ "and PKCS8EncodedKeySpec supported for RSA private keys");
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this code will work, but since there already is P11RSAPrivateKey and P11RSAPrivateNonCRTKey (both of which implement the standard RSAPrivateKey and RSAPrivateCrtKey interfaces respectively), can we just depend on and simplify our code? (My concern is that there is some unknown reason for calling C_GetAttributeValue directly here.)
For example (based on the non-P11 code above)
} else if (key instanceof RSAPrivateKey) {
if (keySpec.isAssignableFrom(PKCS8_KEYSPEC_CLS)) {
return keySpec.cast(new PKCS8EncodedKeySpec(key.getEncoded()));
} else if (keySpec.isAssignableFrom(RSA_PRIVCRT_KEYSPEC_CLS)) {
if (key instanceof RSAPrivateCrtKey) {
RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey)key;
return keySpec.cast(new RSAPrivateCrtKeySpec(
crtKey.getModulus(),
crtKey.getPublicExponent(),
crtKey.getPrivateExponent(),
crtKey.getPrimeP(),
crtKey.getPrimeQ(),
crtKey.getPrimeExponentP(),
crtKey.getPrimeExponentQ(),
crtKey.getCrtCoefficient(),
crtKey.getParams()
));
} else if (key instanceof RSAPrivateKey) {
if (!keySpec.isAssignableFrom(RSA_PRIV_KEYSPEC_CLS)) {
throw new InvalidKeySpecException
("RSAPrivateCrtKeySpec can only be used with CRT keys");
}
// fall through to RSAPrivateKey (non-CRT)
RSAPrivateKey rsaKey = (RSAPrivateKey) key;
return keySpec.cast(new RSAPrivateKeySpec(
rsaKey.getModulus(),
rsaKey.getPrivateExponent(),
rsaKey.getParams()
));
} else { // New P11 code starts here and handles the P11PrivateKey case
throw new InvalidKeySpecException("RSA Private key is not extractable");
} // End new P11 code
} else {
throw new InvalidKeySpecException
("KeySpec must be RSAPrivate(Crt)KeySpec or "
+ "PKCS8EncodedKeySpec for RSA private keys");
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used your code for both because it looks like a good solution.
One thing which puzzles me is that the following implementation for the P11 code passes all tests and avoids any interaction with P11 library at all. I didn't want to use it because I'm concerned that there is an untested edge-case we'd bump into.
<T extends KeySpec> T implGetPrivateKeySpec(P11Key key, Class<T> keySpec,
Session[] session) throws PKCS11Exception, InvalidKeySpecException {
if (key instanceof RSAPrivateKey) {
try {
return implGetSoftwareFactory().getKeySpec(key, keySpec);
} catch (final InvalidKeySpecException e) {
throw e;
} catch (final GeneralSecurityException e) {
throw new InvalidKeySpecException("Could not generate KeySpec", e);
}
} else {
throw new InvalidKeySpecException("Key is not extractable");
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, when the key object has all the attribute values available, things will often work regardless which path you took.
|
Mailing list message from Michael StJohns on security-dev: On 3/19/2021 2:24 PM, Valerie Peng wrote:
If the PKCS11 private key has the CKA_SENSITIVE attribute set to true or Mike |
|
Mike, From what I can find, if you try to get a spec from a non-extractable key you'll get an
|
|
Mailing list message from Michael StJohns on security-dev: On 3/20/2021 1:54 PM, SalusaSecondus wrote: Given that, I'd refactor the code to pull the CKA_SENSITIVE and Either that or fail early by checking the error code of the first thrown
if (ex.getErrorCode() == PKCS11Constants.CKR_ATTRIBUTE_SENSITIVE) {
Later, Mike |
|
We seem to have a choice and I'm not sure the best way to approach this.
We should probably reduce calls to [1] https://github.com/openjdk/jdk/blob/master/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java#L92 |
|
Mailing list message from Michael StJohns on security-dev: On 3/20/2021 2:46 PM, SalusaSecondus wrote: Actually, the important lines are 63-66 and 365-373: ?* If the components are not accessible, we use a generic class that
If the key is non-extractable, then the only attributes will be these Simple check at the top. Mike |
|
P11PrivateKey is private so we cannot check that. Our options to figure out if something is sensitive are:
The smallest change would be to keep our strategy as 2. While I like it the least (my favorite is number 1) it has the smallest risk of changing some undocumented behavior on a PKCS#11 device that we're unfamiliar with (and not testing because we may not have the hardware costing tens of thousands of dollars). Option 3 somewhat spits the difference between 1 and 2. |
| } | ||
| // If the key is both extractable and not sensitive, then when it was converted into a P11Key | ||
| // it was also converted into subclass of RSAPrivateKey which encapsulates all of the logic | ||
| // necessary to retrive the attributes we need. This sub-class will also cache these attributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: retrieve
| // Expected exception so swallow it | ||
| ex.printStackTrace(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a System.out.println() to clarify that this exception stack trace is expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting it in System.err.println() so that it goes to the same output as the stack trace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, you know what I mean.
| /** | ||
| * @test | ||
| * @bug 8263404 | ||
| * @summary RsaPrivateKeySpec is always recognized as RSAPrivateCrtKeySpec in RSAKeyFactory.engineGetKeySpec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be clearer to re-word the summary to clear things up, e.g. return RSAPrivateCrtKeySpec for CRT Keys even when RSAPrivateKeySpec is specified for KeyFactory.getKeySpec() calls.
| if (!(spec instanceof RSAPrivateCrtKeySpec)) { | ||
| throw new Exception("Spec should be an instance of RSAPrivateCrtKeySpec"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that SunPKCS11 provider does not really generate the keys unlike SunRsaSign provider, thus for correctness, you should check it's a CRT key before you impose the instanceof RSAPrivateCrtKeySpec check.
| * @bug 8263404 | ||
| * @summary RsaPrivateKeySpec is always recognized as RSAPrivateCrtKeySpec in RSAKeyFactory.engineGetKeySpec | ||
| * @summary Also checks to ensure that sensitive RSA keys are correctly not exposed | ||
| * @author Greg Rubin, Ziyi Luo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest policy is to not include @author tag.
| disabledMechanisms = { | ||
| CKM_DSA_SHA224 | ||
| CKM_DSA_SHA256 | ||
| CKM_DSA_SHA384 | ||
| CKM_DSA_SHA512 | ||
| CKM_DSA_SHA3_224 | ||
| CKM_DSA_SHA3_256 | ||
| CKM_DSA_SHA3_384 | ||
| CKM_DSA_SHA3_512 | ||
| CKM_ECDSA_SHA224 | ||
| CKM_ECDSA_SHA256 | ||
| CKM_ECDSA_SHA384 | ||
| CKM_ECDSA_SHA512 | ||
| CKM_ECDSA_SHA3_224 | ||
| CKM_ECDSA_SHA3_256 | ||
| CKM_ECDSA_SHA3_384 | ||
| CKM_ECDSA_SHA3_512 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this is to avoid using this special provider for non-RSA impls? Add a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is copy/pasted from p11-nss.txt with only the sensitive modifications at the end. I do not know why those disabled mechanisms are present in the source file.
|
Rest of changes look good. Thanks for the update. |
|
@ziyiluo This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 161 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@valeriepeng) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
|
@valeriepeng Thanks for the review. Do we need a second reviewer for this PR? Besides, are you willing to sponsor this? |
|
/contributor add Greg Rubin rubin@amazon.com |
|
@ziyiluo |
|
I can sponsor this. It looks like you need to do the "/integrate" comment first? |
|
/integrate |
|
/sponsor |
|
@valeriepeng @ziyiluo Since your change was applied there have been 182 commits pushed to the
Your commit was automatically rebased without conflicts. Pushed as commit a5d7de2. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This is a P2 regression introduced by JDK-8254717.
In
RSAKeyFactory.engineGetKeySpec, when the key is a RSA key and the KeySpec is RSAPrivateKeySpec or RSAPrivateCrtKeySpec. The method behavior is described as follow:X-axis: type of
keySpecY-axis: type of
keyBefore JDK-8254717:
InvalidKeySpecExceptionAfter JDK-8254717 (Green check is what we want to fix, red cross is the regression):
InvalidKeySpecException❌InvalidKeySpecExceptionThis commit fixes the regression.
Tests
java/security,sun/security,javax/cryptopassedapi/java_securitypassedProgress
Issue
Reviewers
Contributors
<rubin@amazon.com>Download
To checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/2949/head:pull/2949$ git checkout pull/2949To update a local copy of the PR:
$ git checkout pull/2949$ git pull https://git.openjdk.java.net/jdk pull/2949/head