8358099: PEM spec updates
Reviewed-by: mullan
This commit is contained in:
parent
c793de989f
commit
78158f30ae
@ -81,24 +81,24 @@ import java.util.Objects;
|
||||
* {@link PEMRecord}.
|
||||
*
|
||||
* <p> The {@linkplain #decode(String, Class)} and
|
||||
* {@linkplain #decode(InputStream, Class)} methods take a Class parameter
|
||||
* {@linkplain #decode(InputStream, Class)} methods take a class parameter
|
||||
* which determines the type of {@code DEREncodable} that is returned. These
|
||||
* methods are useful when extracting or changing the return class.
|
||||
* For example, if the PEM contains both public and private keys, the
|
||||
* Class parameter can specify which to return. Use
|
||||
* class parameter can specify which to return. Use
|
||||
* {@code PrivateKey.class} to return only the private key.
|
||||
* If the Class parameter is set to {@code X509EncodedKeySpec.class}, the
|
||||
* If the class parameter is set to {@code X509EncodedKeySpec.class}, the
|
||||
* public key will be returned in that format. Any type of PEM data can be
|
||||
* decoded into a {@code PEMRecord} by specifying {@code PEMRecord.class}.
|
||||
* If the Class parameter doesn't match the PEM content, an
|
||||
* {@code IllegalArgumentException} will be thrown.
|
||||
* If the class parameter doesn't match the PEM content, a
|
||||
* {@linkplain ClassCastException} will be thrown.
|
||||
*
|
||||
* <p> A new {@code PEMDecoder} instance is created when configured
|
||||
* with {@linkplain #withFactory(Provider)} and/or
|
||||
* {@linkplain #withDecryption(char[])}. {@linkplain #withFactory(Provider)}
|
||||
* configures the decoder to use only {@linkplain KeyFactory} and
|
||||
* {@linkplain CertificateFactory} instances from the given {@code Provider}.
|
||||
* {@link#withDecryption(char[])} configures the decoder to decrypt all
|
||||
* {@linkplain #withDecryption(char[])} configures the decoder to decrypt all
|
||||
* encrypted private key PEM data using the given password.
|
||||
* Configuring an instance for decryption does not prevent decoding with
|
||||
* unencrypted PEM. Any encrypted PEM that fails decryption
|
||||
@ -117,15 +117,15 @@ import java.util.Objects;
|
||||
* <p> Here is an example of a {@code PEMDecoder} configured with decryption
|
||||
* and a factory provider:
|
||||
* {@snippet lang = java:
|
||||
* PEMDecoder pe = PEMDecoder.of().withDecryption(password).
|
||||
* PEMDecoder pd = PEMDecoder.of().withDecryption(password).
|
||||
* withFactory(provider);
|
||||
* byte[] pemData = pe.decode(privKey);
|
||||
* byte[] pemData = pd.decode(privKey);
|
||||
* }
|
||||
*
|
||||
* @implNote An implementation may support other PEM types and
|
||||
* {@code DEREncodables}. This implementation additionally supports PEM types:
|
||||
* {@code X509 CERTIFICATE}, {@code X.509 CERTIFICATE}, {@code CRL},
|
||||
* and {@code RSA PRIVATE KEY}.
|
||||
* {@code DEREncodable} objects. This implementation additionally supports
|
||||
* the following PEM types: {@code X509 CERTIFICATE},
|
||||
* {@code X.509 CERTIFICATE}, {@code CRL}, and {@code RSA PRIVATE KEY}.
|
||||
*
|
||||
* @see PEMEncoder
|
||||
* @see PEMRecord
|
||||
@ -179,13 +179,13 @@ public final class PEMDecoder {
|
||||
return switch (pem.type()) {
|
||||
case Pem.PUBLIC_KEY -> {
|
||||
X509EncodedKeySpec spec =
|
||||
new X509EncodedKeySpec(decoder.decode(pem.pem()));
|
||||
new X509EncodedKeySpec(decoder.decode(pem.content()));
|
||||
yield getKeyFactory(
|
||||
KeyUtil.getAlgorithm(spec.getEncoded())).
|
||||
generatePublic(spec);
|
||||
}
|
||||
case Pem.PRIVATE_KEY -> {
|
||||
PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.pem()));
|
||||
PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.content()));
|
||||
String algo = p8key.getAlgorithm();
|
||||
KeyFactory kf = getKeyFactory(algo);
|
||||
DEREncodable d = kf.generatePrivate(
|
||||
@ -216,27 +216,27 @@ public final class PEMDecoder {
|
||||
case Pem.ENCRYPTED_PRIVATE_KEY -> {
|
||||
if (password == null) {
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(
|
||||
pem.pem()));
|
||||
pem.content()));
|
||||
}
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(pem.pem())).
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(pem.content())).
|
||||
getKey(password.getPassword());
|
||||
}
|
||||
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE,
|
||||
Pem.X_509_CERTIFICATE -> {
|
||||
CertificateFactory cf = getCertFactory("X509");
|
||||
yield (X509Certificate) cf.generateCertificate(
|
||||
new ByteArrayInputStream(decoder.decode(pem.pem())));
|
||||
new ByteArrayInputStream(decoder.decode(pem.content())));
|
||||
}
|
||||
case Pem.X509_CRL, Pem.CRL -> {
|
||||
CertificateFactory cf = getCertFactory("X509");
|
||||
yield (X509CRL) cf.generateCRL(
|
||||
new ByteArrayInputStream(decoder.decode(pem.pem())));
|
||||
new ByteArrayInputStream(decoder.decode(pem.content())));
|
||||
}
|
||||
case Pem.RSA_PRIVATE_KEY -> {
|
||||
KeyFactory kf = getKeyFactory("RSA");
|
||||
yield kf.generatePrivate(
|
||||
RSAPrivateCrtKeyImpl.getKeySpec(decoder.decode(
|
||||
pem.pem())));
|
||||
pem.content())));
|
||||
}
|
||||
default -> pem;
|
||||
};
|
||||
@ -271,7 +271,6 @@ public final class PEMDecoder {
|
||||
*/
|
||||
public DEREncodable decode(String str) {
|
||||
Objects.requireNonNull(str);
|
||||
DEREncodable de;
|
||||
try {
|
||||
return decode(new ByteArrayInputStream(
|
||||
str.getBytes(StandardCharsets.UTF_8)));
|
||||
@ -483,9 +482,6 @@ public final class PEMDecoder {
|
||||
* from the specified {@link Provider} to produce cryptographic objects.
|
||||
* Any errors using the {@code Provider} will occur during decoding.
|
||||
*
|
||||
* <p>If {@code provider} is {@code null}, a new instance is returned with
|
||||
* the default provider configuration.
|
||||
*
|
||||
* @param provider the factory provider
|
||||
* @return a new PEMEncoder instance configured to the {@code Provider}.
|
||||
* @throws NullPointerException if {@code provider} is null
|
||||
|
@ -71,7 +71,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
* OneAsymmetricKey structure using the "PRIVATE KEY" type.
|
||||
*
|
||||
* <p> When encoding a {@link PEMRecord}, the API surrounds the
|
||||
* {@linkplain PEMRecord#pem()} with the PEM header and footer
|
||||
* {@linkplain PEMRecord#content()} with the PEM header and footer
|
||||
* from {@linkplain PEMRecord#type()}. {@linkplain PEMRecord#leadingData()} is
|
||||
* not included in the encoding. {@code PEMRecord} will not perform
|
||||
* validity checks on the data.
|
||||
@ -108,7 +108,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
* byte[] pemData = pe.encode(privKey);
|
||||
* }
|
||||
*
|
||||
* @implNote An implementation may support other PEM types and DEREncodables.
|
||||
* @implNote An implementation may support other PEM types and
|
||||
* {@code DEREncodable} objects.
|
||||
*
|
||||
*
|
||||
* @see PEMDecoder
|
||||
@ -287,7 +288,7 @@ public final class PEMEncoder {
|
||||
}
|
||||
|
||||
// If `keySpec` is non-null, then `key` hasn't been established.
|
||||
// Setting a `key' prevents repeated key generations operations.
|
||||
// Setting a `key` prevents repeated key generation operations.
|
||||
// withEncryption() is a configuration method and cannot throw an
|
||||
// exception; therefore generation is delayed.
|
||||
if (keySpec != null) {
|
||||
|
@ -29,7 +29,6 @@ import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import sun.security.util.Pem;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@ -39,20 +38,20 @@ import java.util.Objects;
|
||||
* cryptographic object is not desired or the type has no
|
||||
* {@code DEREncodable}.
|
||||
*
|
||||
* <p> {@code type} and {@code pem} may not be {@code null}.
|
||||
* <p> {@code type} and {@code content} may not be {@code null}.
|
||||
* {@code leadingData} may be null if no non-PEM data preceded PEM header
|
||||
* during decoding. {@code leadingData} may be useful for reading metadata
|
||||
* that accompanies PEM data.
|
||||
*
|
||||
* <p> No validation is performed during instantiation to ensure that
|
||||
* {@code type} conforms to {@code RFC 7468}, that {@code pem} is valid Base64,
|
||||
* or that {@code pem} matches the {@code type}. {@code leadingData} is not
|
||||
* defensively copied and does not return a clone when
|
||||
* {@linkplain #leadingData()} is called.
|
||||
* {@code type} conforms to {@code RFC 7468}, that {@code content} is valid
|
||||
* Base64, or that {@code content} matches the {@code type}.
|
||||
* {@code leadingData} is not defensively copied and does not return a
|
||||
* clone when {@linkplain #leadingData()} is called.
|
||||
*
|
||||
* @param type the type identifier in the PEM header without PEM syntax labels.
|
||||
* For a public key, {@code type} would be "PUBLIC KEY".
|
||||
* @param pem any data between the PEM header and footer.
|
||||
* @param content the Base64-encoded data, excluding the PEM header and footer
|
||||
* @param leadingData any non-PEM data preceding the PEM header when decoding.
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
@ -64,25 +63,25 @@ import java.util.Objects;
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public record PEMRecord(String type, String pem, byte[] leadingData)
|
||||
public record PEMRecord(String type, String content, byte[] leadingData)
|
||||
implements DEREncodable {
|
||||
|
||||
/**
|
||||
* Creates a {@code PEMRecord} instance with the given parameters.
|
||||
*
|
||||
* @param type the type identifier
|
||||
* @param pem the Base64-encoded data encapsulated by the PEM header and
|
||||
* footer.
|
||||
* @param content the Base64-encoded data, excluding the PEM header and
|
||||
* footer
|
||||
* @param leadingData any non-PEM data read during the decoding process
|
||||
* before the PEM header. This value maybe {@code null}.
|
||||
* @throws IllegalArgumentException if the {@code type} is incorrectly
|
||||
* @throws IllegalArgumentException if {@code type} is incorrectly
|
||||
* formatted.
|
||||
* @throws NullPointerException if {@code type} and/or {@code pem} are
|
||||
* @throws NullPointerException if {@code type} and/or {@code content} are
|
||||
* {@code null}.
|
||||
*/
|
||||
public PEMRecord(String type, String pem, byte[] leadingData) {
|
||||
public PEMRecord {
|
||||
Objects.requireNonNull(type, "\"type\" cannot be null.");
|
||||
Objects.requireNonNull(pem, "\"pem\" cannot be null.");
|
||||
Objects.requireNonNull(content, "\"content\" cannot be null.");
|
||||
|
||||
// With no validity checking on `type`, the constructor accept anything
|
||||
// including lowercase. The onus is on the caller.
|
||||
@ -92,37 +91,22 @@ public record PEMRecord(String type, String pem, byte[] leadingData)
|
||||
"Only the PEM type identifier is allowed");
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
this.pem = pem;
|
||||
this.leadingData = leadingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PEMRecord} instance with a given {@code type} and
|
||||
* {@code pem} data in String form. {@code leadingData} is set to null.
|
||||
* {@code content} data in String form. {@code leadingData} is set to null.
|
||||
*
|
||||
* @param type the PEM type identifier
|
||||
* @param pem the Base64-encoded data encapsulated by the PEM header and
|
||||
* footer.
|
||||
* @throws IllegalArgumentException if the {@code type} is incorrectly
|
||||
* @param content the Base64-encoded data, excluding the PEM header and
|
||||
* footer
|
||||
* @throws IllegalArgumentException if {@code type} is incorrectly
|
||||
* formatted.
|
||||
* @throws NullPointerException if {@code type} and/or {@code pem} are
|
||||
* @throws NullPointerException if {@code type} and/or {@code content} are
|
||||
* {@code null}.
|
||||
*/
|
||||
public PEMRecord(String type, String pem) {
|
||||
this(type, pem, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary encoding from the Base64 data contained in
|
||||
* {@code pem}.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code pem} cannot be decoded.
|
||||
* @return a new array of the binary encoding each time this
|
||||
* method is called.
|
||||
*/
|
||||
public byte[] getEncoded() {
|
||||
return Base64.getMimeDecoder().decode(pem);
|
||||
public PEMRecord(String type, String content) {
|
||||
this(type, content, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -565,7 +565,7 @@ public class X509Factory extends CertificateFactorySpi {
|
||||
} catch (EOFException e) {
|
||||
return null;
|
||||
}
|
||||
return Base64.getDecoder().decode(rec.pem());
|
||||
return Base64.getDecoder().decode(rec.content());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ public class Pem {
|
||||
* @return PEM in a string
|
||||
*/
|
||||
public static String pemEncoded(PEMRecord pem) {
|
||||
String p = pem.pem().replaceAll("(.{64})", "$1\r\n");
|
||||
String p = pem.content().replaceAll("(.{64})", "$1\r\n");
|
||||
return pemEncoded(pem.type(), p);
|
||||
}
|
||||
}
|
||||
|
@ -107,8 +107,8 @@ public class PEMDecoderTest {
|
||||
System.err.println("received: " + new String(rec.leadingData()));
|
||||
throw new AssertionError("ecCSRWithData preData wrong");
|
||||
}
|
||||
if (rec.pem().lastIndexOf("F") > rec.pem().length() - 5) {
|
||||
System.err.println("received: " + rec.pem());
|
||||
if (rec.content().lastIndexOf("F") > rec.content().length() - 5) {
|
||||
System.err.println("received: " + rec.content());
|
||||
throw new AssertionError("ecCSRWithData: " +
|
||||
"End of PEM data has an unexpected character");
|
||||
}
|
||||
@ -235,10 +235,10 @@ public class PEMDecoderTest {
|
||||
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
|
||||
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
|
||||
try {
|
||||
PEMData.checkResults(expected, r.pem());
|
||||
PEMData.checkResults(expected, r.content());
|
||||
} catch (AssertionError e) {
|
||||
System.err.println("expected:\n" + expected);
|
||||
System.err.println("received:\n" + r.pem());
|
||||
System.err.println("received:\n" + r.content());
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user