On Thu, 24 Apr 2025 18:29:08 GMT, Weijun Wang <wei...@openjdk.org> wrote:
>> Anthony Scarpino has updated the pull request incrementally with two >> additional commits since the last revision: >> >> - javadoc updates >> - code review comments > > src/java.base/share/classes/java/security/PEMDecoder.java line 60: > >> 58: * A specified return class must implement {@link DEREncodable} and be an >> 59: * appropriate JCE object class for the PEM; otherwise an >> 60: * {@link IllegalArgumentException} is thrown. > > Do we need to document somewhere what "appropriate" JCE classes are for each > PEM type? I view this as an advanced feature for experienced users. The list is large and algorithm-dependent. For example an EC private key PEM could be PrivateKey.class, ECPrivateKey.class, PEMRecord.class, PKCS8EncodedKeySpec.class. I don't think it's realistic to list everything. > src/java.base/share/classes/java/security/PEMDecoder.java line 398: > >> 396: * Returns a new {@code PEMDecoder} instance from the current >> instance >> 397: * configured to decrypt encrypted PEM data with given password. >> 398: * Non-encrypted PEM may still be decoded from this instance. > > I assume that if users want to use a non-default cipher provider they should > decode as `EncryptedPrivateKeyInfo` first and it has more sophisticated > methods there. Do we need to document about this here? I think that's would be too rare to specify in the javadoc. > src/java.base/share/classes/java/security/PEMEncoder.java line 120: > >> 118: >> 119: /** >> 120: * Returns an instance of PEMEncoder. > > Do we need to say "simple" or "normal" or "raw" `PEMEncoder` here? Just to be > clear that it does not do any encryption. I'll just say "new" > src/java.base/share/classes/java/security/PEMEncoder.java line 134: > >> 132: private String pemEncoded(PEMRecord pem) { >> 133: StringBuilder sb = new StringBuilder(1024); >> 134: sb.append("-----BEGIN ").append(pem.type()).append("-----"); > > So you throw away the leading data? Shall we document this somewhere? This is a private method that just converts the `pem` and `type` into a properly formatted PEM. Leading data is not applicable here. > src/java.base/share/classes/java/security/PEMEncoder.java line 175: > >> 173: throw new IllegalArgumentException("KeyPair does >> not " + >> 174: "contain PrivateKey."); >> 175: } > > Do we really need to care about whether the private part or the public part > is null? Previous discussions determined that a null value in a KeyPair was undefined and should not be done. > src/java.base/share/classes/java/security/PEMEncoder.java line 235: > >> 233: */ >> 234: public byte[] encode(DEREncodable de) { >> 235: return encodeToString(de).getBytes(StandardCharsets.ISO_8859_1); > > Which operation is lighter? Have you considered letting `pemEncoded` to > return a byte array and converting it into a string in `encodeToString()`? It's easier to construct using StringBuilder. Either direction some data is converting String to bytes or bytes to String. > src/java.base/share/classes/java/security/PEMEncoder.java line 279: > >> 277: if (keySpec != null) { >> 278: // For thread safety >> 279: lock.lock(); > > How much does this lock buy? If someone provides a password, I assume they > will use it anyway. key generation was delayed because we don't want `withEncryption()` to throw an exception as it's a config/builder method. The lock was to prevent multiple versions of the same key being generated in a threaded situation. > src/java.base/share/classes/java/security/PEMEncoder.java line 287: > >> 285: keySpec = null; >> 286: } catch (GeneralSecurityException e) { >> 287: throw new SecurityException("Security property " + > > Let's discuss whether to use `SecurityException` here. I would use > `ProviderException`. This is catching any errors that may occur that may not a result of the Provider. ProviderException is for errors/problems with the Provider. > src/java.base/share/classes/java/security/PEMEncoder.java line 300: > >> 298: // If `key` is non-null, this is an encoder ready to encrypt. >> 299: if (key != null) { >> 300: if (privateBytes == null || publicBytes != null) { > > `publicBytes` cannot be null here. You rejected both being null at the > beginning of this method. The check at the top is AND, while this is OR. > src/java.base/share/classes/java/security/PEMRecord.java line 61: > >> 59: * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures >> 60: */ >> 61: public record PEMRecord(String type, String pem, byte[] leadingData) > > How about using the raw byte data instead of the `pem` string? This would > automatically reject all format problems, like extra newline at the end, too > wide string, and invalid Base64 chars. Also, two `PEMRecord` should equal to > each other even if they are encoded differently (for example, different > width). > > Also, how about forcing all fields to be non null? If there is no leading > bytes, use an empty array. I cannot imagine how data could be empty. This record was built for two reasons: 1) For PEM that we do not have a cryptographic representation for (ECPrivateKey, X509Certificate, etc). 2) An early comment about reading a private key in PEM from a file, but not creating a PrivateKey object with it. The classes are only supposeed to include Base64. In fact, I found an inconsistency with `PEMEncoder` using `PEMRecord` with binary data that I've fixed but yet to push. It checking two records are equal, then overriding the `equals()` makes more sense than changing how the data is stored. Also, storing the PEM solves the simplest consistency test where PEM read from a file may be different when Base64 decoded and encoded. Some of the values have to be null because of `leadingData`. You were a proponent of storing the non-PEM data before the PEM data was found when reading from an `InputStream`. The null situation happens if at the end of the stream there is only non-PEM data. Throwing an exception or ignoring the data was inconsistent. So PEMRecord has to allow for `leadingData` with no `type` and `pem`. > src/java.base/share/classes/java/security/PEMRecord.java line 79: > >> 77: * @param pem The data between the PEM header and footer. >> 78: * @param leadingData Any non-PEM data read during the decoding >> process >> 79: * before the PEM header. This value maybe >> {@code null}. > > Do we need to say if `leadingData` contains the final newline char? I'm not sure what you mean by "final" as it contains whatever the stream contains before the dashes. The point is not to parse the non-PEM data and store it as is. > src/java.base/share/classes/java/security/PEMRecord.java line 95: > >> 93: // including lowercase. The onus is on the caller. >> 94: if (type != null && type.startsWith("-----")) { >> 95: // Remove PEM headers syntax if present. > > I don't think we need to be so tolerant at the beginning. Just reject it. ok > src/java.base/share/classes/java/security/PEMRecord.java line 135: > >> 133: /** >> 134: * Returns the binary encoding from the Base64 data contained in >> 135: * {@code pem}. > > The name does not sound correct to me. PEM encodes binary data to an ASCII > string, so "encoding" is the ASCII string. How about just `getData`? `PEMRecord` implements `DEREncodable` which all the classes return DER through `getEncoded()`. I is consistent with the other implementing classes. > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line 81: > >> 79: >> 80: /** >> 81: * Constructs an {@code EncryptedPrivateKeyInfo} from a given >> Encrypted > > Why is "E" capitalized? Is it a special term? yep > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line 83: > >> 81: * Constructs an {@code EncryptedPrivateKeyInfo} from a given >> Encrypted >> 82: * PKCS#8 ASN.1 encoding. >> 83: * @param encoded the ASN.1 encoding which is cloned and then parsed. > > Somehow I prefer the original "The contents of the array...". We've used this > style in a lot of places. I didn't change the rest of them because I didn't want to make too many unnecessary edits. I understand you like it, but it is not a concise description. The one word "cloned" is a lot simpler and means the same as: "The contents of {@code encryptedData} are copied to protect against subsequent modification when constructing this object." > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line > 324: > >> 322: >> 323: /** >> 324: * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a >> given > > Can we say "encrypt" an "EncryptedPrivateKeyInfo"? It sounds like an already > encrypted thing. ok > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line > 338: > >> 336: * >> 337: * @param key the PrivateKey object to encrypt. >> 338: * @param password the password used for generating the PBE key. > > Do we need to mention the "PBE key"? It's just the password to encrypt the > private key. ok > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line > 341: > >> 339: * @param algorithm the PBE encryption algorithm. >> 340: * @param params the parameters used with the PBE encryption. >> 341: * @param provider the Provider that will perform the encryption. > > I prefer moving (if not copying) the nullable description of `params` and > `provider` here. > > Also, in your implementation, the provider is used to choose both > `SecretkeyFactory` and `Cipher`. I hope for each provider there always exists > a pair of `SecretkeyFactory` and `Cipher` with a given algorithm name. I'll move the descriptions Yes this method needs both on the same provider. If they are on separate providers, they can use the other method that takes the `Key` instead of a `password`. > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line > 351: > >> 349: * @implNote The encryption uses the algorithm set by >> 350: * `jdk.epkcs8.defaultAlgorithm` Security Property >> 351: * and default the {@code AlgorithmParameterSpec} of that provider. > > I think this `implNote` is not necessary here. You already required > `algorithm` to be non-null. Since I changed to allow null, it's needed now. (see below) > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line > 416: > >> 414: * {@link PrivateKey} using the {@code encKey} and given parameters. >> 415: * >> 416: * If {@code algorithm} is {@code null} the default algorithm will >> be used. > > In the other `encryptKey` method using password, `algorithm` must be > provided. Why the inconsistency? > > In fact, since you have `encKey`, doesn't it already have an algorithm name? It maybe good for both `encryptKey` methods do not allow null algorithms when APS is non-null. I think `PBEParameterSpec` is currently the only APS used, but if in the future a new APS is used, a default algorithm with a specified APS could lead to applications breaking. There is no guarantee the key's algorithm will be properly formatted to use as an algorithm name. I wouldn't trust it. > src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java line > 422: > >> 420: * >> 421: * @param key the {@code PrivateKey} object to encrypt. >> 422: * @param encKey the encryption {@code Key} > > It will be more precise if we say `key` is the key to be encrypted and > `encKey` is the key used to encrypt `key`. ok ------------- PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062695427 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062723080 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062725966 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062726982 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062727349 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062730381 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062733556 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062734449 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062734838 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2061221714 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2061224179 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062747907 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2061217873 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2061226526 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2061225921 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062750755 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062750997 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062873270 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2061226193 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062846705 PR Review Comment: https://git.openjdk.org/jdk/pull/17543#discussion_r2062876630