Skip to content

Commit b2e7cda

Browse files
committed
8358171: Additional code coverage for PEM API
Reviewed-by: ascarpino
1 parent 65fda5c commit b2e7cda

File tree

10 files changed

+533
-43
lines changed

10 files changed

+533
-43
lines changed

test/jdk/java/security/PEM/PEMData.java

Lines changed: 193 additions & 28 deletions
Large diffs are not rendered by default.

test/jdk/java/security/PEM/PEMDecoderTest.java

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
/*
2727
* @test
2828
* @bug 8298420
29+
* @library /test/lib
2930
* @modules java.base/sun.security.pkcs
3031
* java.base/sun.security.util
3132
* @summary Testing basic PEM API decoding
@@ -37,23 +38,27 @@
3738
import java.lang.Class;
3839
import java.nio.charset.StandardCharsets;
3940
import java.security.*;
41+
import java.security.cert.CertificateEncodingException;
4042
import java.security.cert.X509CRL;
4143
import java.security.cert.X509Certificate;
4244
import java.security.interfaces.*;
43-
import java.security.spec.PKCS8EncodedKeySpec;
44-
import java.security.spec.X509EncodedKeySpec;
45+
import java.security.spec.*;
4546
import java.util.*;
4647
import java.util.Arrays;
4748

49+
import jdk.test.lib.Asserts;
50+
import sun.security.pkcs.PKCS8Key;
4851
import sun.security.util.Pem;
4952

5053
public class PEMDecoderTest {
5154

5255
static HexFormat hex = HexFormat.of();
5356

54-
public static void main(String[] args) throws IOException {
57+
public static void main(String[] args) throws Exception {
5558
System.out.println("Decoder test:");
56-
PEMData.entryList.forEach(PEMDecoderTest::test);
59+
PEMData.entryList.forEach(entry -> test(entry, false));
60+
System.out.println("Decoder test withFactory:");
61+
PEMData.entryList.forEach(entry -> test(entry, true));
5762
System.out.println("Decoder test returning DEREncodable class:");
5863
PEMData.entryList.forEach(entry -> test(entry, DEREncodable.class));
5964
System.out.println("Decoder test with encrypted PEM:");
@@ -95,7 +100,11 @@ public static void main(String[] args) throws IOException {
95100

96101
System.out.println("Check a Signature/Verify op is successful:");
97102
PEMData.privList.forEach(PEMDecoderTest::testSignature);
98-
PEMData.oasList.forEach(PEMDecoderTest::testSignature);
103+
PEMData.oasList.stream().filter(e -> !e.name().endsWith("xdh"))
104+
.forEach(PEMDecoderTest::testSignature);
105+
106+
System.out.println("Checking if decode() returns a PKCS8Key and can generate a pub");
107+
PEMData.oasList.forEach(PEMDecoderTest::testPKCS8Key);
99108

100109
System.out.println("Checking if ecCSR:");
101110
test(PEMData.ecCSR);
@@ -182,6 +191,10 @@ public static void main(String[] args) throws IOException {
182191
} catch (Exception e) {
183192
throw new AssertionError("error getting key", e);
184193
}
194+
testCertTypeConverter(PEMData.ecCert);
195+
196+
System.out.println("Decoder test testCoefZero:");
197+
testCoefZero(PEMData.rsaCrtCoefZeroPriv);
185198
}
186199

187200
static void testInputStream() throws IOException {
@@ -231,6 +244,24 @@ static void testInputStream() throws IOException {
231244
throw new AssertionError("Failed");
232245
}
233246

247+
// test that X509 CERTIFICATE is converted to CERTIFICATE in PEM
248+
static void testCertTypeConverter(PEMData.Entry entry) throws CertificateEncodingException {
249+
String certPem = entry.pem().replace("CERTIFICATE", "X509 CERTIFICATE");
250+
Asserts.assertEqualsByteArray(entry.der(),
251+
PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded());
252+
253+
certPem = entry.pem().replace("CERTIFICATE", "X.509 CERTIFICATE");
254+
Asserts.assertEqualsByteArray(entry.der(),
255+
PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded());
256+
}
257+
258+
// test that when the crtCoeff is zero, the key is decoded but only the modulus and private
259+
// exponent are used resulting in a different der
260+
static void testCoefZero(PEMData.Entry entry) {
261+
RSAPrivateKey decoded = PEMDecoder.of().decode(entry.pem(), RSAPrivateKey.class);
262+
Asserts.assertNotEqualsByteArray(decoded.getEncoded(), entry.der());
263+
}
264+
234265
static void testPEMRecord(PEMData.Entry entry) {
235266
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
236267
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
@@ -333,13 +364,26 @@ static DEREncodable testEncrypted(PEMData.Entry entry) {
333364

334365
// Change the Entry to use the given class as the expected class returned
335366
static DEREncodable test(PEMData.Entry entry, Class c) {
336-
return test(entry.newClass(c));
367+
return test(entry.newClass(c), false);
337368
}
338369

339370
// Run test with a given Entry
340371
static DEREncodable test(PEMData.Entry entry) {
372+
return test(entry, false);
373+
}
374+
375+
// Run test with a given Entry
376+
static DEREncodable test(PEMData.Entry entry, boolean withFactory) {
377+
System.out.printf("Testing %s %s%n", entry.name(), entry.provider());
341378
try {
342-
DEREncodable r = test(entry.pem(), entry.clazz(), PEMDecoder.of());
379+
PEMDecoder pemDecoder;
380+
if (withFactory) {
381+
Provider provider = Security.getProvider(entry.provider());
382+
pemDecoder = PEMDecoder.of().withFactory(provider);
383+
} else {
384+
pemDecoder = PEMDecoder.of();
385+
}
386+
DEREncodable r = test(entry.pem(), entry.clazz(), pemDecoder);
343387
System.out.println("PASS (" + entry.name() + ")");
344388
return r;
345389
} catch (Exception | AssertionError e) {
@@ -412,6 +456,19 @@ static void testTwoKeys() throws IOException {
412456
}
413457
}
414458

459+
private static void testPKCS8Key(PEMData.Entry entry) {
460+
try {
461+
PKCS8Key key = PEMDecoder.of().decode(entry.pem(), PKCS8Key.class);
462+
PKCS8EncodedKeySpec spec =
463+
new PKCS8EncodedKeySpec(key.getEncoded());
464+
465+
KeyFactory kf = KeyFactory.getInstance(key.getAlgorithm());
466+
kf.generatePublic(spec);
467+
} catch (Exception ex) {
468+
throw new RuntimeException(ex);
469+
}
470+
}
471+
415472
static void testClass(PEMData.Entry entry, Class clazz) throws IOException {
416473
var pk = PEMDecoder.of().decode(entry.pem(), clazz);
417474
}
@@ -472,9 +529,15 @@ static void testSignature(PEMData.Entry entry) {
472529
"should not be null");
473530
}
474531

532+
AlgorithmParameterSpec spec = null;
475533
String algorithm = switch(privateKey.getAlgorithm()) {
476534
case "EC" -> "SHA256withECDSA";
477535
case "EdDSA" -> "EdDSA";
536+
case "RSASSA-PSS" -> {
537+
spec = new PSSParameterSpec(
538+
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
539+
yield "RSASSA-PSS";
540+
}
478541
case null -> {
479542
System.out.println("Algorithm is null " +
480543
entry.name());
@@ -487,6 +550,9 @@ static void testSignature(PEMData.Entry entry) {
487550
try {
488551
if (d instanceof PrivateKey) {
489552
s = Signature.getInstance(algorithm);
553+
if (spec != null) {
554+
s.setParameter(spec);
555+
}
490556
s.initSign(privateKey);
491557
s.update(data);
492558
s.sign();

test/jdk/java/security/PEM/PEMEncoderTest.java

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@
2626
/*
2727
* @test
2828
* @bug 8298420
29+
* @library /test/lib
2930
* @summary Testing basic PEM API encoding
3031
* @enablePreview
3132
* @modules java.base/sun.security.util
33+
* @run main PEMEncoderTest PBEWithHmacSHA256AndAES_128
34+
* @run main/othervm -Djava.security.properties=${test.src}/java.security-anotherAlgo
35+
* PEMEncoderTest PBEWithHmacSHA512AndAES_256
36+
* @run main/othervm -Djava.security.properties=${test.src}/java.security-emptyAlgo
37+
* PEMEncoderTest PBEWithHmacSHA256AndAES_128
3238
*/
3339

3440
import sun.security.util.Pem;
@@ -39,13 +45,22 @@
3945
import java.nio.charset.StandardCharsets;
4046
import java.security.*;
4147
import java.security.spec.InvalidParameterSpecException;
48+
import java.security.spec.PKCS8EncodedKeySpec;
49+
import java.security.spec.X509EncodedKeySpec;
4250
import java.util.*;
4351

52+
import jdk.test.lib.security.SecurityUtils;
53+
54+
import static jdk.test.lib.Asserts.assertEquals;
55+
import static jdk.test.lib.Asserts.assertThrows;
56+
4457
public class PEMEncoderTest {
4558

4659
static Map<String, DEREncodable> keymap;
60+
static String pkcs8DefaultAlgExpect;
4761

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

5166
// These entries are removed
@@ -63,7 +78,12 @@ public static void main(String[] args) throws Exception {
6378
System.out.println("New instance re-encode testToString:");
6479
keymap.keySet().stream().forEach(key -> testToString(key,
6580
PEMEncoder.of()));
66-
81+
System.out.println("Same instance Encoder testEncodedKeySpec:");
82+
testEncodedKeySpec(encoder);
83+
System.out.println("New instance Encoder testEncodedKeySpec:");
84+
testEncodedKeySpec(PEMEncoder.of());
85+
System.out.println("Same instance Encoder testEmptyKey:");
86+
testEmptyAndNullKey(encoder);
6787
keymap = generateObjKeyMap(PEMData.encryptedList);
6888
System.out.println("Same instance Encoder match test:");
6989
keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder));
@@ -86,6 +106,13 @@ public static void main(String[] args) throws Exception {
86106
PEMRecord pemRecord =
87107
d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class);
88108
PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString());
109+
110+
// test PemRecord is encapsulated with PEM header and footer on encoding
111+
String[] pemLines = PEMData.ed25519ep8.pem().split("\n");
112+
String[] pemNoHeaderFooter = Arrays.copyOfRange(pemLines, 1, pemLines.length - 1);
113+
PEMRecord pemR = new PEMRecord("ENCRYPTED PRIVATE KEY", String.join("\n",
114+
pemNoHeaderFooter));
115+
PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pemR));
89116
}
90117

91118
static Map generateObjKeyMap(List<PEMData.Entry> list) {
@@ -145,10 +172,12 @@ static void testToString(String key, PEMEncoder encoder) {
145172
static void testEncrypted(String key, PEMEncoder encoder) {
146173
PEMData.Entry entry = PEMData.getEntry(key);
147174
try {
148-
encoder.withEncryption(
175+
String pem = encoder.withEncryption(
149176
(entry.password() != null ? entry.password() :
150177
"fish".toCharArray()))
151178
.encodeToString(keymap.get(key));
179+
180+
verifyEncriptionAlg(pem);
152181
} catch (RuntimeException e) {
153182
throw new AssertionError("Encrypted encoder failed with " +
154183
entry.name(), e);
@@ -157,6 +186,11 @@ static void testEncrypted(String key, PEMEncoder encoder) {
157186
System.out.println("PASS: " + entry.name());
158187
}
159188

189+
private static void verifyEncriptionAlg(String pem) {
190+
var epki = PEMDecoder.of().decode(pem, EncryptedPrivateKeyInfo.class);
191+
assertEquals(epki.getAlgName(), pkcs8DefaultAlgExpect);
192+
}
193+
160194
/*
161195
Test cannot verify PEM was the same as known PEM because we have no
162196
public access to the AlgoritmID.params and PBES2Parameters.
@@ -195,5 +229,42 @@ static void testEncryptedMatch(String key, PEMEncoder encoder) {
195229
PEMData.checkResults(entry, result);
196230
System.out.println("PASS: " + entry.name());
197231
}
198-
}
199232

233+
static void testEncodedKeySpec(PEMEncoder encoder) throws NoSuchAlgorithmException {
234+
KeyPair kp = getKeyPair();
235+
encoder.encodeToString(new X509EncodedKeySpec(kp.getPublic().getEncoded()));
236+
encoder.encodeToString(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));
237+
System.out.println("PASS: testEncodedKeySpec");
238+
}
239+
private static void testEmptyAndNullKey(PEMEncoder encoder) throws NoSuchAlgorithmException {
240+
KeyPair kp = getKeyPair();
241+
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
242+
new KeyPair(kp.getPublic(), new EmptyKey())));
243+
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
244+
new KeyPair(kp.getPublic(), null)));
245+
246+
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
247+
new KeyPair(new EmptyKey(), kp.getPrivate())));
248+
assertThrows(IllegalArgumentException.class, () -> encoder.encode(
249+
new KeyPair(null, kp.getPrivate())));
250+
System.out.println("PASS: testEmptyKey");
251+
}
252+
253+
private static KeyPair getKeyPair() throws NoSuchAlgorithmException {
254+
Provider provider = Security.getProvider("SunRsaSign");
255+
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", provider);
256+
kpg.initialize(SecurityUtils.getTestKeySize("RSA"));
257+
return kpg.generateKeyPair();
258+
}
259+
260+
private static class EmptyKey implements PublicKey, PrivateKey {
261+
@Override
262+
public String getAlgorithm() { return "Test"; }
263+
264+
@Override
265+
public String getFormat() { return "Test"; }
266+
267+
@Override
268+
public byte[] getEncoded() { return new byte[0]; }
269+
}
270+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
/*
27+
* @test
28+
* @bug 8298420
29+
* @library /test/lib
30+
* @summary Testing PEM API is thread safe
31+
* @enablePreview
32+
* @modules java.base/sun.security.util
33+
*/
34+
35+
import java.security.*;
36+
import java.util.*;
37+
import java.util.concurrent.CountDownLatch;
38+
import java.util.concurrent.ExecutorService;
39+
import java.util.concurrent.Executors;
40+
41+
import jdk.test.lib.security.SecurityUtils;
42+
43+
public class PEMMultiThreadTest {
44+
static final int THREAD_COUNT = 5;
45+
static final int KEYS_COUNT = 50;
46+
47+
public static void main(String[] args) throws Exception {
48+
PEMEncoder encoder = PEMEncoder.of();
49+
try (ExecutorService ex = Executors.newFixedThreadPool(THREAD_COUNT)) {
50+
Map<Integer, PublicKey> keys = new HashMap<>();
51+
Map<Integer, String> encoded = Collections.synchronizedMap(new HashMap<>());
52+
Map<Integer, String> decoded = Collections.synchronizedMap(new HashMap<>());
53+
final CountDownLatch encodingComplete = new CountDownLatch(KEYS_COUNT);
54+
final CountDownLatch decodingComplete = new CountDownLatch(KEYS_COUNT);
55+
56+
// Generate keys and encode them in parallel
57+
for (int i = 0; i < KEYS_COUNT; i++) {
58+
final int finalI = i;
59+
KeyPair kp = getKeyPair();
60+
keys.put(finalI, kp.getPublic());
61+
62+
ex.submit(() -> {
63+
encoded.put(finalI, encoder.encodeToString(kp.getPublic()));
64+
encodingComplete.countDown();
65+
});
66+
}
67+
encodingComplete.await();
68+
69+
// Decode keys in parallel
70+
PEMDecoder decoder = PEMDecoder.of();
71+
for (Map.Entry<Integer, String> entry : encoded.entrySet()) {
72+
ex.submit(() -> {
73+
decoded.put(entry.getKey(), decoder.decode(entry.getValue(), PublicKey.class)
74+
.toString());
75+
decodingComplete.countDown();
76+
});
77+
}
78+
decodingComplete.await();
79+
80+
// verify all keys were properly encoded and decoded comparing with the original key map
81+
for (Map.Entry<Integer, PublicKey> kp : keys.entrySet()) {
82+
if (!decoded.get(kp.getKey()).equals(kp.getValue().toString())) {
83+
throw new RuntimeException("a key was not properly encoded and decoded: " + decoded);
84+
}
85+
}
86+
}
87+
88+
System.out.println("PASS: testThreadSafety");
89+
}
90+
91+
private static KeyPair getKeyPair() throws NoSuchAlgorithmException {
92+
String alg = "EC";
93+
KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg);
94+
kpg.initialize(SecurityUtils.getTestKeySize(alg));
95+
return kpg.generateKeyPair();
96+
}
97+
}

0 commit comments

Comments
 (0)