Skip to content
Merged
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
39 changes: 20 additions & 19 deletions src/main/java/com/ibm/crypto/plus/provider/ECPrivateKey.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright IBM Corp. 2023, 2025
* Copyright IBM Corp. 2023, 2026
*
* This code is free software; you can redistribute it and/or modify it
* under the terms provided by IBM in the LICENSE file that accompanied
Expand Down Expand Up @@ -87,7 +87,7 @@ final class ECPrivateKey extends PKCS8Key implements java.security.interfaces.EC
this.ecKey = ECKey.createPrivateKey(provider.getOCKContext(), privateKeyBytes,
paramBytes, provider);
} catch (Exception exception) {
throw new InvalidKeyException("Failed to create EC private key", exception);
throw new InvalidKeyException("Failed to create EC private key, " + exception.getMessage(), exception);
}

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

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

byte[] encodedParams = this.getAlgorithmId().getEncodedParams();
if (inputDerValue.length > 2) {
if (!inputDerValue[2].isContextSpecific(TAG_PARAMETERS_ATTRS)) {
throw new IOException("Decoding EC private key failed. Third element is not tagged as parameters");
}
DerInputStream paramDerInputStream = inputDerValue[2].getData();
byte[] privateKeyParams = paramDerInputStream.toByteArray();

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

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

Expand Down
108 changes: 106 additions & 2 deletions src/test/java/ibm/jceplus/junit/base/BaseTestECKeyImport.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright IBM Corp. 2023, 2025
* Copyright IBM Corp. 2023, 2026
*
* This code is free software; you can redistribute it and/or modify it
* under the terms provided by IBM in the LICENSE file that accompanied
Expand All @@ -9,11 +9,13 @@
package ibm.jceplus.junit.base;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
Expand All @@ -24,11 +26,13 @@
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HexFormat;
import org.junit.jupiter.api.Test;
import sun.security.pkcs.PKCS8Key;
import sun.security.x509.X509Key;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class BaseTestECKeyImport extends BaseTestJunit5 {
Expand Down Expand Up @@ -82,7 +86,37 @@ public class BaseTestECKeyImport extends BaseTestJunit5 {
+ "9C57C30FAA2DEF09DDDAD4E8748C442325B8EDB94EF7AA9"
+ "78D4A56F0B601448B0DDFA4CC4B0555EAE67354C442A3AC"
+ "E9D04BE186765A1921962FC08D1A58C53A";


private static final String private_secp256r1_parameters_publickey =
"MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg15U0PERqsupn6Hy5" +
"Jlk6HKBiJlBvMWVSxEWYNipV2SOgCgYIKoZIzj0DAQehRANCAARFcF00hBK8Es2M" +
"H29DmA3fsYf4qWFSloVWoFct4CxffJ7hG0O4TXkMaPrAjgXc42SPdKRb7FcO0Lhz" +
"EVpYquVY";
private static final String public_secp256r1_parameters_publickey =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERXBdNIQSvBLNjB9vQ5gN37GH+Klh" +
"UpaFVqBXLeAsX3ye4RtDuE15DGj6wI4F3ONkj3SkW+xXDtC4cxFaWKrlWA==";

private static final String private_secp256r1_publickey_only =
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgn5K03bpTLjEtFQRa" +
"JUtx22gtmGEvvSUSQdimhGthdtihRANCAARv72fTmp9ed8dRvTG1Ak1Lgl5KLoiM" +
"59bk2pyG8qd8l7L1WQnNHtAcu44RJ1/GVHurxghaCKHeJYsZ8H7DEeI6";
private static final String public_secp256r1_publickey_only =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb+9n05qfXnfHUb0xtQJNS4JeSi6I" +
"jOfW5NqchvKnfJey9VkJzR7QHLuOESdfxlR7q8YIWgih3iWLGfB+wxHiOg==";

private static final String private_secp256r1_parameters_only =
"ME0CAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEMzAxAgEBBCDQG6sBj2l7SbIwjb4f" +
"4AVQdtE717IaMXiRBzGboI7IWaAKBggqhkjOPQMBBw==";
private static final String public_secp256r1_parameters_only =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESaogWgFBKmRm5SUKilBVhNx/l/60" +
"H/OTNDAz1OUi+Gy1MkrKMktPmpRw6gq26jxRXhRYFJhMuU0R1fktCXcG3g==";

private static final String private_secp256r1_no_parameters_no_public =
"MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDlySzNwwln6W+zDiDk" +
"K7OvyaalUUdkOd6CrThHqenrfg==";
private static final String public_secp256r1_no_parameters_no_public =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG9JA6mkSGkIKBSsKxDDtmhHMEo+P" +
"MNHwWo60TKvsYpEWYqGLvaqmhwqIyf+qVQD5/9hU4WFiB/t3jV+sO2RuHA==";

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

assertTrue(((X509Key) importPublicKey).getAlgorithmId().toString().contains("secp521r1"), "Curve is not what is expected.");
}

@Test
public void testECPrivateKeyWithVariousOptionalFields() throws Exception {
KeyFactory kf = KeyFactory.getInstance("EC", getProviderName());
Signature signature = Signature.getInstance("SHA256withECDSA", getProviderName());

// 1) parameters + publicKey
testSignAndVerify(
"parameters and publicKey",
kf,
signature,
private_secp256r1_parameters_publickey,
public_secp256r1_parameters_publickey
);

// 2) publicKey only
testSignAndVerify(
"publicKey only",
kf,
signature,
private_secp256r1_publickey_only,
public_secp256r1_publickey_only
);

// 3) parameters only
testSignAndVerify(
"parameters only",
kf,
signature,
private_secp256r1_parameters_only,
public_secp256r1_parameters_only
);

// 4) no parameters + no publicKey
testSignAndVerify(
"no parameters and no publickey",
kf,
signature,
private_secp256r1_no_parameters_no_public,
public_secp256r1_no_parameters_no_public
);
System.out.println("ALL tests completed.");
}

private static void testSignAndVerify(
String label,
KeyFactory kf,
Signature sig,
String pkcs8Base64,
String x509PublicBase64
) throws Exception {
PrivateKey priv = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pkcs8Base64)));

// Sign
byte[] msg = ("msg-" + label).getBytes(StandardCharsets.UTF_8);
sig.initSign(priv);
sig.update(msg);
byte[] signed = sig.sign();
assertNotNull(signed, "Signature is null for: " + label);
assertTrue(signed.length > 0, "Signature should not be empty for: " + label);
System.out.println("sign OK");

// Verify
PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(x509PublicBase64)));
sig.initVerify(pub);
sig.update(msg);
boolean verified = sig.verify(signed);
assertTrue(verified, "Signature verification failed for: " + label);
System.out.println("verify OK");
}
}