Skip to content

Commit df89039

Browse files
JinhangZhangjohnpeck-us-ibm
authored andcommitted
Tolerate the optional fields in the ECPrivateKey sequence (IBM#1057)
The ECPrivateKey sequence defines two optional fields: parameters and publicKey. When parsing an ECPrivateKey, the code must allow cases where only one of these fields is present, or where both are absent or where both are presented. In addition, it seems that java does not automatically concatenate the cause’s message with the outer exception’s message, the detailed message associated with the cause is not merged into the current exception’s detail message. Therefore, getMessage() is explicitly included when throwing the exception in order to preserve and expose the underlying error details. Signed-off-by: JinhangZhang <[email protected]>
1 parent 8ebebab commit df89039

File tree

2 files changed

+126
-21
lines changed

2 files changed

+126
-21
lines changed

src/main/java/com/ibm/crypto/plus/provider/ECPrivateKey.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2023, 2025
2+
* Copyright IBM Corp. 2023, 2026
33
*
44
* This code is free software; you can redistribute it and/or modify it
55
* under the terms provided by IBM in the LICENSE file that accompanied
@@ -87,7 +87,7 @@ final class ECPrivateKey extends PKCS8Key implements java.security.interfaces.EC
8787
this.ecKey = ECKey.createPrivateKey(provider.getOCKContext(), privateKeyBytes,
8888
paramBytes, provider);
8989
} catch (Exception exception) {
90-
throw new InvalidKeyException("Failed to create EC private key", exception);
90+
throw new InvalidKeyException("Failed to create EC private key, " + exception.getMessage(), exception);
9191
}
9292

9393
}
@@ -122,7 +122,7 @@ final class ECPrivateKey extends PKCS8Key implements java.security.interfaces.EC
122122
this.ecKey = ECKey.createPrivateKey(provider.getOCKContext(), privateKeyBytes,
123123
paramBytes, provider);
124124
} catch (Exception exception) {
125-
throw new InvalidKeyException("Failed to create EC private key", exception);
125+
throw new InvalidKeyException("Failed to create EC private key, " + exception.getMessage(), exception);
126126
}
127127
}
128128

@@ -194,16 +194,18 @@ private byte[] createEncodedPrivateKeyWithParams() throws IOException {
194194
outEncodedStream.putOctetString(privateKeyBytes);
195195

196196
byte[] encodedParams = this.getAlgorithmId().getEncodedParams();
197-
if (inputDerValue.length > 2) {
198-
if (!inputDerValue[2].isContextSpecific(TAG_PARAMETERS_ATTRS)) {
199-
throw new IOException("Decoding EC private key failed. Third element is not tagged as parameters");
200-
}
201-
DerInputStream paramDerInputStream = inputDerValue[2].getData();
202-
byte[] privateKeyParams = paramDerInputStream.toByteArray();
203-
204-
// Check against the existing parameters created by PKCS8Key.
205-
if (!Arrays.equals(privateKeyParams, encodedParams)) {
206-
throw new IOException("Decoding EC private key failed. The params are not the same as PKCS8Key's");
197+
if (inputDerValue.length > 2) {
198+
if (inputDerValue[2].isContextSpecific(TAG_PARAMETERS_ATTRS)) {
199+
DerInputStream paramDerInputStream = inputDerValue[2].getData();
200+
byte[] privateKeyParams = paramDerInputStream.toByteArray();
201+
// Check against the existing parameters created by PKCS8Key.
202+
if (!Arrays.equals(privateKeyParams, encodedParams)) {
203+
throw new IOException("Decoding EC private key failed. The params are not the same as PKCS8Key's");
204+
}
205+
} else if (!inputDerValue[2].isContextSpecific(TAG_PUBLIC_KEY_ATTRS)) {
206+
// Unknown third+ element: we can throw, or ignore.
207+
// Keeping old behavior would be to throw; but RFC allows only [0]/[1] here.
208+
throw new IOException("Decoding EC private key failed. Unexpected tagged field in ECPrivateKey");
207209
}
208210
}
209211
// The native library needs the ASN.1 DER decoding of the private key to contain the parameters (i.e., the OID).
@@ -230,13 +232,12 @@ private void parsePrivateKeyEncoding() throws IOException {
230232
byte[] privateKeyBytes = inputDerValue[1].getOctetString();
231233
this.s = new BigInteger(1, privateKeyBytes);
232234

233-
if (inputDerValue.length == 4) {
234-
if (!inputDerValue[3].isContextSpecific(TAG_PUBLIC_KEY_ATTRS)) {
235-
throw new IOException("Decoding EC private key failed. Last element is not tagged as public key");
235+
for (int i = 2; i < inputDerValue.length; i++) {
236+
DerValue v = inputDerValue[i];
237+
if (v.isContextSpecific(TAG_PUBLIC_KEY_ATTRS)) {
238+
DerValue bits = v.withTag(DerValue.tag_BitString);
239+
this.pubKeyEncoded = new X509Key(this.algid, bits.data.getUnalignedBitString()).getEncoded();
236240
}
237-
DerValue bits = inputDerValue[3].withTag(DerValue.tag_BitString);
238-
this.pubKeyEncoded = new X509Key(this.algid,
239-
bits.data.getUnalignedBitString()).getEncoded();
240241
}
241242
}
242243

src/test/java/ibm/jceplus/junit/base/BaseTestECKeyImport.java

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2023, 2025
2+
* Copyright IBM Corp. 2023, 2026
33
*
44
* This code is free software; you can redistribute it and/or modify it
55
* under the terms provided by IBM in the LICENSE file that accompanied
@@ -9,11 +9,13 @@
99
package ibm.jceplus.junit.base;
1010

1111
import java.math.BigInteger;
12+
import java.nio.charset.StandardCharsets;
1213
import java.security.KeyFactory;
1314
import java.security.KeyPair;
1415
import java.security.KeyPairGenerator;
1516
import java.security.PrivateKey;
1617
import java.security.PublicKey;
18+
import java.security.Signature;
1719
import java.security.spec.ECField;
1820
import java.security.spec.ECFieldFp;
1921
import java.security.spec.ECGenParameterSpec;
@@ -24,11 +26,13 @@
2426
import java.security.spec.PKCS8EncodedKeySpec;
2527
import java.security.spec.X509EncodedKeySpec;
2628
import java.util.Arrays;
29+
import java.util.Base64;
2730
import java.util.HexFormat;
2831
import org.junit.jupiter.api.Test;
2932
import sun.security.pkcs.PKCS8Key;
3033
import sun.security.x509.X509Key;
3134
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
35+
import static org.junit.jupiter.api.Assertions.assertNotNull;
3236
import static org.junit.jupiter.api.Assertions.assertTrue;
3337

3438
public class BaseTestECKeyImport extends BaseTestJunit5 {
@@ -82,7 +86,37 @@ public class BaseTestECKeyImport extends BaseTestJunit5 {
8286
+ "9C57C30FAA2DEF09DDDAD4E8748C442325B8EDB94EF7AA9"
8387
+ "78D4A56F0B601448B0DDFA4CC4B0555EAE67354C442A3AC"
8488
+ "E9D04BE186765A1921962FC08D1A58C53A";
85-
89+
90+
private static final String private_secp256r1_parameters_publickey =
91+
"MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg15U0PERqsupn6Hy5" +
92+
"Jlk6HKBiJlBvMWVSxEWYNipV2SOgCgYIKoZIzj0DAQehRANCAARFcF00hBK8Es2M" +
93+
"H29DmA3fsYf4qWFSloVWoFct4CxffJ7hG0O4TXkMaPrAjgXc42SPdKRb7FcO0Lhz" +
94+
"EVpYquVY";
95+
private static final String public_secp256r1_parameters_publickey =
96+
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERXBdNIQSvBLNjB9vQ5gN37GH+Klh" +
97+
"UpaFVqBXLeAsX3ye4RtDuE15DGj6wI4F3ONkj3SkW+xXDtC4cxFaWKrlWA==";
98+
99+
private static final String private_secp256r1_publickey_only =
100+
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgn5K03bpTLjEtFQRa" +
101+
"JUtx22gtmGEvvSUSQdimhGthdtihRANCAARv72fTmp9ed8dRvTG1Ak1Lgl5KLoiM" +
102+
"59bk2pyG8qd8l7L1WQnNHtAcu44RJ1/GVHurxghaCKHeJYsZ8H7DEeI6";
103+
private static final String public_secp256r1_publickey_only =
104+
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb+9n05qfXnfHUb0xtQJNS4JeSi6I" +
105+
"jOfW5NqchvKnfJey9VkJzR7QHLuOESdfxlR7q8YIWgih3iWLGfB+wxHiOg==";
106+
107+
private static final String private_secp256r1_parameters_only =
108+
"ME0CAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEMzAxAgEBBCDQG6sBj2l7SbIwjb4f" +
109+
"4AVQdtE717IaMXiRBzGboI7IWaAKBggqhkjOPQMBBw==";
110+
private static final String public_secp256r1_parameters_only =
111+
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESaogWgFBKmRm5SUKilBVhNx/l/60" +
112+
"H/OTNDAz1OUi+Gy1MkrKMktPmpRw6gq26jxRXhRYFJhMuU0R1fktCXcG3g==";
113+
114+
private static final String private_secp256r1_no_parameters_no_public =
115+
"MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDlySzNwwln6W+zDiDk" +
116+
"K7OvyaalUUdkOd6CrThHqenrfg==";
117+
private static final String public_secp256r1_no_parameters_no_public =
118+
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG9JA6mkSGkIKBSsKxDDtmhHMEo+P" +
119+
"MNHwWo60TKvsYpEWYqGLvaqmhwqIyf+qVQD5/9hU4WFiB/t3jV+sO2RuHA==";
86120

87121
/**
88122
* Generate a KeyPair using ECGEenParam and then import the key pair
@@ -305,5 +339,75 @@ public void testImportHardcoded() throws Exception {
305339

306340
assertTrue(((X509Key) importPublicKey).getAlgorithmId().toString().contains("secp521r1"), "Curve is not what is expected.");
307341
}
342+
343+
@Test
344+
public void testECPrivateKeyWithVariousOptionalFields() throws Exception {
345+
KeyFactory kf = KeyFactory.getInstance("EC", getProviderName());
346+
Signature signature = Signature.getInstance("SHA256withECDSA", getProviderName());
347+
348+
// 1) parameters + publicKey
349+
testSignAndVerify(
350+
"parameters and publicKey",
351+
kf,
352+
signature,
353+
private_secp256r1_parameters_publickey,
354+
public_secp256r1_parameters_publickey
355+
);
356+
357+
// 2) publicKey only
358+
testSignAndVerify(
359+
"publicKey only",
360+
kf,
361+
signature,
362+
private_secp256r1_publickey_only,
363+
public_secp256r1_publickey_only
364+
);
365+
366+
// 3) parameters only
367+
testSignAndVerify(
368+
"parameters only",
369+
kf,
370+
signature,
371+
private_secp256r1_parameters_only,
372+
public_secp256r1_parameters_only
373+
);
374+
375+
// 4) no parameters + no publicKey
376+
testSignAndVerify(
377+
"no parameters and no publickey",
378+
kf,
379+
signature,
380+
private_secp256r1_no_parameters_no_public,
381+
public_secp256r1_no_parameters_no_public
382+
);
383+
System.out.println("ALL tests completed.");
384+
}
385+
386+
private static void testSignAndVerify(
387+
String label,
388+
KeyFactory kf,
389+
Signature sig,
390+
String pkcs8Base64,
391+
String x509PublicBase64
392+
) throws Exception {
393+
PrivateKey priv = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pkcs8Base64)));
394+
395+
// Sign
396+
byte[] msg = ("msg-" + label).getBytes(StandardCharsets.UTF_8);
397+
sig.initSign(priv);
398+
sig.update(msg);
399+
byte[] signed = sig.sign();
400+
assertNotNull(signed, "Signature is null for: " + label);
401+
assertTrue(signed.length > 0, "Signature should not be empty for: " + label);
402+
System.out.println("sign OK");
403+
404+
// Verify
405+
PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(x509PublicBase64)));
406+
sig.initVerify(pub);
407+
sig.update(msg);
408+
boolean verified = sig.verify(signed);
409+
assertTrue(verified, "Signature verification failed for: " + label);
410+
System.out.println("verify OK");
411+
}
308412
}
309413

0 commit comments

Comments
 (0)