Skip to content

8358171: Additional code coverage for PEM API #25588

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

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 193 additions & 28 deletions test/jdk/java/security/PEM/PEMData.java

Large diffs are not rendered by default.

80 changes: 73 additions & 7 deletions test/jdk/java/security/PEM/PEMDecoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
/*
* @test
* @bug 8298420
* @library /test/lib
* @modules java.base/sun.security.pkcs
* java.base/sun.security.util
* @summary Testing basic PEM API decoding
Expand All @@ -37,23 +38,27 @@
import java.lang.Class;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.interfaces.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.*;
import java.util.*;
import java.util.Arrays;

import jdk.test.lib.Asserts;
import sun.security.pkcs.PKCS8Key;
import sun.security.util.Pem;

public class PEMDecoderTest {

static HexFormat hex = HexFormat.of();

public static void main(String[] args) throws IOException {
public static void main(String[] args) throws Exception {
System.out.println("Decoder test:");
PEMData.entryList.forEach(PEMDecoderTest::test);
PEMData.entryList.forEach(entry -> test(entry, false));
System.out.println("Decoder test withFactory:");
PEMData.entryList.forEach(entry -> test(entry, true));
System.out.println("Decoder test returning DEREncodable class:");
PEMData.entryList.forEach(entry -> test(entry, DEREncodable.class));
System.out.println("Decoder test with encrypted PEM:");
Expand Down Expand Up @@ -95,7 +100,11 @@ public static void main(String[] args) throws IOException {

System.out.println("Check a Signature/Verify op is successful:");
PEMData.privList.forEach(PEMDecoderTest::testSignature);
PEMData.oasList.forEach(PEMDecoderTest::testSignature);
PEMData.oasList.stream().filter(e -> !e.name().endsWith("xdh"))
.forEach(PEMDecoderTest::testSignature);

System.out.println("Checking if decode() returns a PKCS8Key and can generate a pub");
PEMData.oasList.forEach(PEMDecoderTest::testPKCS8Key);

System.out.println("Checking if ecCSR:");
test(PEMData.ecCSR);
Expand Down Expand Up @@ -182,6 +191,10 @@ public static void main(String[] args) throws IOException {
} catch (Exception e) {
throw new AssertionError("error getting key", e);
}
testCertTypeConverter(PEMData.ecCert);

System.out.println("Decoder test testCoefZero:");
testCoefZero(PEMData.rsaCrtCoefZeroPriv);
}

static void testInputStream() throws IOException {
Expand Down Expand Up @@ -231,6 +244,24 @@ static void testInputStream() throws IOException {
throw new AssertionError("Failed");
}

// test that X509 CERTIFICATE is converted to CERTIFICATE in PEM
static void testCertTypeConverter(PEMData.Entry entry) throws CertificateEncodingException {
String certPem = entry.pem().replace("CERTIFICATE", "X509 CERTIFICATE");
Asserts.assertEqualsByteArray(entry.der(),
PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded());

certPem = entry.pem().replace("CERTIFICATE", "X.509 CERTIFICATE");
Asserts.assertEqualsByteArray(entry.der(),
PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded());
}

// test that when the crtCoeff is zero, the key is decoded but only the modulus and private
// exponent are used resulting in a different der
static void testCoefZero(PEMData.Entry entry) {
RSAPrivateKey decoded = PEMDecoder.of().decode(entry.pem(), RSAPrivateKey.class);
Asserts.assertNotEqualsByteArray(decoded.getEncoded(), entry.der());
}

static void testPEMRecord(PEMData.Entry entry) {
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
Expand Down Expand Up @@ -333,13 +364,26 @@ static DEREncodable testEncrypted(PEMData.Entry entry) {

// Change the Entry to use the given class as the expected class returned
static DEREncodable test(PEMData.Entry entry, Class c) {
return test(entry.newClass(c));
return test(entry.newClass(c), false);
}

// Run test with a given Entry
static DEREncodable test(PEMData.Entry entry) {
return test(entry, false);
}

// Run test with a given Entry
static DEREncodable test(PEMData.Entry entry, boolean withFactory) {
System.out.printf("Testing %s %s%n", entry.name(), entry.provider());
try {
DEREncodable r = test(entry.pem(), entry.clazz(), PEMDecoder.of());
PEMDecoder pemDecoder;
if (withFactory) {
Provider provider = Security.getProvider(entry.provider());
pemDecoder = PEMDecoder.of().withFactory(provider);
} else {
pemDecoder = PEMDecoder.of();
}
DEREncodable r = test(entry.pem(), entry.clazz(), pemDecoder);
System.out.println("PASS (" + entry.name() + ")");
return r;
} catch (Exception | AssertionError e) {
Expand Down Expand Up @@ -412,6 +456,19 @@ static void testTwoKeys() throws IOException {
}
}

private static void testPKCS8Key(PEMData.Entry entry) {
try {
PKCS8Key key = PEMDecoder.of().decode(entry.pem(), PKCS8Key.class);
PKCS8EncodedKeySpec spec =
new PKCS8EncodedKeySpec(key.getEncoded());

KeyFactory kf = KeyFactory.getInstance(key.getAlgorithm());
kf.generatePublic(spec);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

static void testClass(PEMData.Entry entry, Class clazz) throws IOException {
var pk = PEMDecoder.of().decode(entry.pem(), clazz);
}
Expand Down Expand Up @@ -472,9 +529,15 @@ static void testSignature(PEMData.Entry entry) {
"should not be null");
}

AlgorithmParameterSpec spec = null;
String algorithm = switch(privateKey.getAlgorithm()) {
case "EC" -> "SHA256withECDSA";
case "EdDSA" -> "EdDSA";
case "RSASSA-PSS" -> {
spec = new PSSParameterSpec(
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
yield "RSASSA-PSS";
}
case null -> {
System.out.println("Algorithm is null " +
entry.name());
Expand All @@ -487,6 +550,9 @@ static void testSignature(PEMData.Entry entry) {
try {
if (d instanceof PrivateKey) {
s = Signature.getInstance(algorithm);
if (spec != null) {
s.setParameter(spec);
}
s.initSign(privateKey);
s.update(data);
s.sign();
Expand Down
77 changes: 74 additions & 3 deletions test/jdk/java/security/PEM/PEMEncoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@
/*
* @test
* @bug 8298420
* @library /test/lib
* @summary Testing basic PEM API encoding
* @enablePreview
* @modules java.base/sun.security.util
* @run main PEMEncoderTest PBEWithHmacSHA256AndAES_128
* @run main/othervm -Djava.security.properties=${test.src}/java.security-anotherAlgo
* PEMEncoderTest PBEWithHmacSHA512AndAES_256
* @run main/othervm -Djava.security.properties=${test.src}/java.security-emptyAlgo
* PEMEncoderTest PBEWithHmacSHA256AndAES_128
*/

import sun.security.util.Pem;
Expand All @@ -39,13 +45,22 @@
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

import jdk.test.lib.security.SecurityUtils;

import static jdk.test.lib.Asserts.assertEquals;
import static jdk.test.lib.Asserts.assertThrows;

public class PEMEncoderTest {

static Map<String, DEREncodable> keymap;
static String pkcs8DefaultAlgExpect;

public static void main(String[] args) throws Exception {
pkcs8DefaultAlgExpect = args[0];
PEMEncoder encoder = PEMEncoder.of();

// These entries are removed
Expand All @@ -63,7 +78,12 @@ public static void main(String[] args) throws Exception {
System.out.println("New instance re-encode testToString:");
keymap.keySet().stream().forEach(key -> testToString(key,
PEMEncoder.of()));

System.out.println("Same instance Encoder testEncodedKeySpec:");
testEncodedKeySpec(encoder);
System.out.println("New instance Encoder testEncodedKeySpec:");
testEncodedKeySpec(PEMEncoder.of());
System.out.println("Same instance Encoder testEmptyKey:");
testEmptyAndNullKey(encoder);
keymap = generateObjKeyMap(PEMData.encryptedList);
System.out.println("Same instance Encoder match test:");
keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder));
Expand All @@ -86,6 +106,13 @@ public static void main(String[] args) throws Exception {
PEMRecord pemRecord =
d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class);
PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString());

// test PemRecord is encapsulated with PEM header and footer on encoding
String[] pemLines = PEMData.ed25519ep8.pem().split("\n");
String[] pemNoHeaderFooter = Arrays.copyOfRange(pemLines, 1, pemLines.length - 1);
PEMRecord pemR = new PEMRecord("ENCRYPTED PRIVATE KEY", String.join("\n",
pemNoHeaderFooter));
PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pemR));
}

static Map generateObjKeyMap(List<PEMData.Entry> list) {
Expand Down Expand Up @@ -145,10 +172,12 @@ static void testToString(String key, PEMEncoder encoder) {
static void testEncrypted(String key, PEMEncoder encoder) {
PEMData.Entry entry = PEMData.getEntry(key);
try {
encoder.withEncryption(
String pem = encoder.withEncryption(
(entry.password() != null ? entry.password() :
"fish".toCharArray()))
.encodeToString(keymap.get(key));

verifyEncriptionAlg(pem);
} catch (RuntimeException e) {
throw new AssertionError("Encrypted encoder failed with " +
entry.name(), e);
Expand All @@ -157,6 +186,11 @@ static void testEncrypted(String key, PEMEncoder encoder) {
System.out.println("PASS: " + entry.name());
}

private static void verifyEncriptionAlg(String pem) {
var epki = PEMDecoder.of().decode(pem, EncryptedPrivateKeyInfo.class);
assertEquals(epki.getAlgName(), pkcs8DefaultAlgExpect);
}

/*
Test cannot verify PEM was the same as known PEM because we have no
public access to the AlgoritmID.params and PBES2Parameters.
Expand Down Expand Up @@ -195,5 +229,42 @@ static void testEncryptedMatch(String key, PEMEncoder encoder) {
PEMData.checkResults(entry, result);
System.out.println("PASS: " + entry.name());
}
}

static void testEncodedKeySpec(PEMEncoder encoder) throws NoSuchAlgorithmException {
KeyPair kp = getKeyPair();
encoder.encodeToString(new X509EncodedKeySpec(kp.getPublic().getEncoded()));
encoder.encodeToString(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));
System.out.println("PASS: testEncodedKeySpec");
}
private static void testEmptyAndNullKey(PEMEncoder encoder) throws NoSuchAlgorithmException {
KeyPair kp = getKeyPair();
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
new KeyPair(kp.getPublic(), new EmptyKey())));
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
new KeyPair(kp.getPublic(), null)));

assertThrows(IllegalArgumentException.class, () -> encoder.encode(
new KeyPair(new EmptyKey(), kp.getPrivate())));
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
new KeyPair(null, kp.getPrivate())));
System.out.println("PASS: testEmptyKey");
}

private static KeyPair getKeyPair() throws NoSuchAlgorithmException {
Provider provider = Security.getProvider("SunRsaSign");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", provider);
kpg.initialize(SecurityUtils.getTestKeySize("RSA"));
return kpg.generateKeyPair();
}

private static class EmptyKey implements PublicKey, PrivateKey {
@Override
public String getAlgorithm() { return "Test"; }

@Override
public String getFormat() { return "Test"; }

@Override
public byte[] getEncoded() { return new byte[0]; }
}
}
97 changes: 97 additions & 0 deletions test/jdk/java/security/PEM/PEMMultiThreadTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2025, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8298420
* @library /test/lib
* @summary Testing PEM API is thread safe
* @enablePreview
* @modules java.base/sun.security.util
*/

import java.security.*;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import jdk.test.lib.security.SecurityUtils;

public class PEMMultiThreadTest {
static final int THREAD_COUNT = 5;
static final int KEYS_COUNT = 50;

public static void main(String[] args) throws Exception {
PEMEncoder encoder = PEMEncoder.of();
try (ExecutorService ex = Executors.newFixedThreadPool(THREAD_COUNT)) {
Map<Integer, PublicKey> keys = new HashMap<>();
Map<Integer, String> encoded = Collections.synchronizedMap(new HashMap<>());
Map<Integer, String> decoded = Collections.synchronizedMap(new HashMap<>());
final CountDownLatch encodingComplete = new CountDownLatch(KEYS_COUNT);
final CountDownLatch decodingComplete = new CountDownLatch(KEYS_COUNT);

// Generate keys and encode them in parallel
for (int i = 0; i < KEYS_COUNT; i++) {
final int finalI = i;
KeyPair kp = getKeyPair();
keys.put(finalI, kp.getPublic());

ex.submit(() -> {
encoded.put(finalI, encoder.encodeToString(kp.getPublic()));
encodingComplete.countDown();
});
}
encodingComplete.await();

// Decode keys in parallel
PEMDecoder decoder = PEMDecoder.of();
for (Map.Entry<Integer, String> entry : encoded.entrySet()) {
ex.submit(() -> {
decoded.put(entry.getKey(), decoder.decode(entry.getValue(), PublicKey.class)
.toString());
decodingComplete.countDown();
});
}
decodingComplete.await();

// verify all keys were properly encoded and decoded comparing with the original key map
for (Map.Entry<Integer, PublicKey> kp : keys.entrySet()) {
if (!decoded.get(kp.getKey()).equals(kp.getValue().toString())) {
throw new RuntimeException("a key was not properly encoded and decoded: " + decoded);
}
}
}

System.out.println("PASS: testThreadSafety");
}

private static KeyPair getKeyPair() throws NoSuchAlgorithmException {
String alg = "EC";
KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg);
kpg.initialize(SecurityUtils.getTestKeySize(alg));
return kpg.generateKeyPair();
}
}
Loading