-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
8298420: PEM API: Implementation (Preview) #17543
base: master
Are you sure you want to change the base?
Conversation
👋 Welcome back ascarpino! A progress list of the required criteria for merging this PR into |
@ascarpino The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
❗ This change is not yet ready to be integrated. |
@ascarpino this pull request can not be integrated into git checkout pem
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
Fixed P8v2 0xA0 Test: Use EKPI with PBEParameters to match EPK8 base64 cleanup
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.
Update copyright year.
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.
Update copyright year, if needed.
* AlgorithmParameterSpec of that provider. | ||
* | ||
* @param key The PrivateKey object to encrypt. | ||
* @param password the password used in the PBE encryption. |
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.
Consider indicating that a clone happens.
@@ -81,6 +81,9 @@ public enum Feature { | |||
STREAM_GATHERERS, | |||
@JEP(number=476, title="Module Import Declarations", status="Preview") | |||
MODULE_IMPORTS, | |||
//XXX Number will change when assigned | |||
@JEP(number=999, title="PEM API", status="Preview") |
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.
No JEP # assigned yet?
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 just checked, and there isn't one yet, so maybe you can use this comment as a reminder to update later. :)
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.
Update copyright year, if required.
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.
Update copyright year, if required.
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.
date is correct
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.
Consider undoing the diff/change to remove the newline, or technically, you'll need to update the copyright year, I suppose.
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'll restore the line
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.
Update copyright year, if required.
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.
has correct year
This JEP is misnamed. The RFC clearly says
So this RFC is clearly not PEM and this JEP shouldn't be named as such, hence class names neither. |
PEM has evolved over time as the RFC states, but that doesn't change that PEM is the established term for this textual format. RFC1421 was not added to the JEP because it does not need to explain the history. To quote the whole paragraph:
The JEP is clear that PKCS#8 and X.509 are supported. Other variations could be added to the PEM API in the future or by a different API. OpenSSL use the "PEM" for |
// KeyType not relevant here. We just want KeyFactory | ||
if ((PKCS8EncodedKeySpec.class).isAssignableFrom(tClass)) { | ||
getKeyFactory(key.getAlgorithm()). | ||
getKeySpec(key, PKCS8EncodedKeySpec.class); |
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.
Did you forget to assign above to so
?
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.
Ah yes, and that may address a bug found by testing a few days ago.
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.
Maybe rename to PEMData
. There are non-cert here.
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.
Yeah, makes sense
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.
Some comments on EncryptedPrivateKeyInfo
.
* PKCS#8 ASN.1 encoding. | ||
* @param encoded the ASN.1 encoding to be parsed. | ||
* @throws NullPointerException if {@code encoded} is {@code null}. | ||
* @throws IOException if error occurs when parsing the ASN.1 encoding. |
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.
Why change the old spec? There seems to be no problem. Especially, why remove the "array are copied" sentence?
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 can re-add the cloning. As far as the others, parses
was implied by from an ASN.1 encoding.
and I thought being more specific about what type of ASN.1 encoding was better
* @return an EncryptedPrivateKeyInfo. | ||
* @throws IllegalArgumentException when an argument causes an | ||
* initialization error. | ||
* @throws SecurityException on a cryptographic errors. |
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.
Oh, I didn't notice this before. Have we decided to repurpose SecurityException
for this usage now that there will be no more Security Manager?
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'm not sure if it was exclusively a Security Manager exception, but @seanjmullan had said previously that SecurityException
was used elsewhere.
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 was used when JarInputStream::read
encountered a wrong hash or signature. But, it was mainly used for permission violation.
} | ||
|
||
/** | ||
* Return a PrivateKey from the object's encrypted data with a KeyFactory |
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.
Should be SecretKeyFactory
. Same in @param provider
below.
Also, "Return a key" is not clear. Existing getKeySpec
methods use "Extract". You maybe also use "Recover".
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 "Return" is very clear and I'm not a fan of "Extract" or "Recover". The other methods are overly descriptive of the method's internals rather than the just stating the purpose. Even with that, the other methods end with "returns it."
} | ||
|
||
/** | ||
* Creates and encrypts an `EncryptedPrivateKeyInfo` from a given PrivateKey |
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.
Oh, you should use {@code}
here. Same in @impNote
below. Also, there are some other class names that should be in enclosed in {@code}
.
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.
ok
* @exception NoSuchAlgorithmException if cannot find appropriate | ||
* cipher to decrypt the encrypted data. | ||
* @exception NullPointerException if {@code decryptKey} is {@code null}. | ||
* @exception NoSuchAlgorithmException Cannot find appropriate cipher to |
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.
Use "if".
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.
ok
* from the given Provider. | ||
* | ||
* @param password the password | ||
* @param provider the KeyFactory provider used to generate the key. |
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.
Since you allow it to be null, mention it here.
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.
ok
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.
New encrypt and decrypt methods are all password-based and work on keys directly. Old methods uses a decryption key and works on key specs. For completeness; have you thought about more combinations? Maybe at least encryption with a key? I assume an EncryptedPrivateKeyInfo
is not only encrypted with a password.
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 could add some Key
related methods.
Can you please support the read-public-key-from-pkcs8 feature in
BTW, I see in your other |
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.
For PEMEncoder
.
Also, do you want to update the PKCS8EncodedKeySpec
class with a new ASN.1 grammar and a link to version 2.0?
* on a PEMEncoder instance returned by {@link #withEncryption(char[])} or | ||
* by passing an {@link EncryptedPrivateKeyInfo} object into the encode methods. | ||
* <p> | ||
* PKCS8 v2.0 allows OneAsymmetric encoding, which is a private and public |
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 link to PKCS 8 2.0?
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 an external link? I don't believe that is allowed
* | ||
* @apiNote | ||
* Here is an example of encoding a PrivateKey object: | ||
* <pre> |
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.
Change to code snippet.
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.
ok
* @param de a cryptographic object to be PEM encoded that implements | ||
* DEREncodable. | ||
* @return PEM encoding in a String | ||
* @throws IllegalArgumentException when the passed object returns a null |
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.
What does "returns a null binary encoding" mean? There is no other method talking about this. I think we can just say "if configured for encryption but object does not support" since this looks like the only reason.
Also, how about IllegalStateException
?
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.
getEncoded() returns null.
I think your suggestion would still need me to explain why the object doesn't support encryption.
I don't think IllegalStateException
makes sense when object passed does not provide the needed data.
yield pemEncoded(new PEMRecord( | ||
PEMRecord.ENCRYPTED_PRIVATE_KEY, epki.getEncoded())); | ||
} catch (IOException e) { | ||
throw new SecurityException(e); |
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.
Do you really want to use SecurityException
? This only happens when the AlgorithmParameters
inside EPKI is not initialized. I would say this is a very good candidate for an IllegalStateException
.
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.
For consistency, InvalidArgumentException
is better than SecurityException
* {@link #encode(DEREncodable)}. | ||
* | ||
* @param password sets the encryption password. The array is cloned and | ||
* stored in the new instance. |
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.
Can password be empty? I vaguely remember some algorithms might not like it, or, is it just that SecretKeySpec
does not like an empty key?
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.
PBEKeySpec
allows null and empty passwords and I hope the provider/algorithm would throw an error if that was a problem.
I changed this to allow null. I realized EKPI
allowed null, but PEM didn't. It would be consistent to just allow what PBEKeySpec
allows.
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.
Comments for PEMDecoder
.
* is useful for algorithm-agnostic methods, {@code ECPublicKey} for | ||
* algorithm-specific operations, or {@code X509EncodedKeySpec} if the | ||
* X.509 binary encoding is desired instead of a Key object. An IOException | ||
* will be thrown if the class is incorrect for the given PEM data. |
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.
There is no IOE in this method.
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.
Yep
} | ||
|
||
/** | ||
* Configures and returns a new {@code PEMDecoder} instance from the |
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.
Are you going to be more specific on what kind of factories will be involved?
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.
ok
* Configures and returns a new {@code PEMDecoder} instance from the | ||
* current instance that will use Factory classes from the specified | ||
* {@link Provider}. Any errors using the {@code provider} will occur | ||
* during decoding. |
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.
Do you mean errors will happen during decoding? Do you want to be clear on what exceptions will be thrown?
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.
Errors will be thrown during decoding, when decode()
is called. This method sets the provider in the PEMDecoder instance. This method throws no exceptions.
* the default provider configuration. | ||
* | ||
* @param provider the Factory provider. | ||
* @return a new PEM decoder instance. |
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.
The return spec for this method and the next one should be using a consistent wording.
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.
Yep
throw new IllegalArgumentException("No PEM data found."); | ||
} | ||
|
||
DEREncodable so = decode(pem); |
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.
The line above could throw IOE. Shall we wrap it in an IAE? I mean the same error in the other decode-from-string method is an IAE.
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.
Yes, that would be consistent
if (next.isContextSpecific((byte)0)) { | ||
|
||
// OPTIONAL Context tag 0 for Attributes for PKCS8 v1 & v2 | ||
// Uses 0xA0 constructed define-length or 0x80 constructed |
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.
Minor nit: 0xA0 = context-specific/constructed, 0x80 = context-specific/primitive. Definite length vs. indefinite length is not defined by the tag itself.
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.
ok
|
||
} | ||
|
||
if (pubKeyEncoded != null) { |
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.
Looking back at an earlier conversation between you and Weijun, I think I read that pubKeyEncoded will be set/overwritten if the private key encoding holds a public key. So when consuming a PKCS#8 EC key, where the private key is itself a SEC1-v2 formatted key encoding with a pubkey, wouldn't the version be set to 0 (v1), but the pubKeyEncoded is also non-null?
I ask only because upon running this method, wouldn't you end up making the output a v2 OneAsymmetricKey, still with the SEC1-v2 private key (with pub key) and also have it in the public key section?
Hi all,
I need a code review of the PEM API. Privacy-Enhanced Mail (PEM) is a format for encoding and decoding cryptographic keys and certificates. It will be integrated into JDK24 as a Preview Feature. Preview features does not permanently define the API and it is subject to change in future releases until it is finalized.
Details about this change can be seen at PEM API JEP.
Thanks
Tony
Progress
Integration blocker
Warnings
Issues
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/17543/head:pull/17543
$ git checkout pull/17543
Update a local copy of the PR:
$ git checkout pull/17543
$ git pull https://git.openjdk.org/jdk.git pull/17543/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 17543
View PR using the GUI difftool:
$ git pr show -t 17543
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/17543.diff
Using Webrev
Link to Webrev Comment