-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
8333754: Add a Test against ECDSA and ECDH NIST Test vector
Reviewed-by: mbaesken Backport-of: fad6644eabbad6b6d3472206d9db946408aca612
Showing
4 changed files
with
9,497 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright (c) 2018, 2024, 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. | ||
* | ||
* 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. | ||
*/ | ||
|
||
import java.io.*; | ||
import java.math.BigInteger; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.security.*; | ||
import java.security.spec.*; | ||
import java.util.*; | ||
import javax.crypto.*; | ||
|
||
import jdk.test.lib.Asserts; | ||
|
||
/* | ||
* @test | ||
* @bug 8189189 | ||
* @summary Test ECDH primitive operations | ||
* @library /test/lib | ||
* @run main ECDHPrimitive | ||
*/ | ||
public class ECDHPrimitive { | ||
|
||
|
||
private static final Map<String, String> NAME_MAP = Map.of( | ||
"P-256", "secp256r1", | ||
"P-384", "secp384r1", | ||
"P-521", "secp521r1" | ||
); | ||
|
||
public static void main(String[] args) throws Exception { | ||
Path testFile = Path.of(System.getProperty("test.src"), "KAS_ECC_CDH_PrimitiveTest.txt"); | ||
|
||
ECParameterSpec ecParams = null; | ||
|
||
try (BufferedReader in = Files.newBufferedReader(testFile)) { | ||
Map<String, byte[]> values = new HashMap<>(); | ||
String line = in.readLine(); | ||
while (line != null) { | ||
line = line.trim(); | ||
if (line.startsWith("#") || line.length() == 0) { | ||
// ignore | ||
} else if (line.startsWith("[")) { | ||
// change curve name | ||
StringTokenizer tok = new StringTokenizer(line, "[]"); | ||
String name = tok.nextToken(); | ||
String curveName = lookupName(name); | ||
|
||
if (curveName == null) { | ||
System.out.println("Unknown curve: " + name | ||
+ ". Skipping test"); | ||
ecParams = null; | ||
} else { | ||
AlgorithmParameters params | ||
= AlgorithmParameters.getInstance("EC"); | ||
|
||
params.init(new ECGenParameterSpec(curveName)); | ||
ecParams = params.getParameterSpec( | ||
ECParameterSpec.class); | ||
System.out.println("Testing curve: " + curveName); | ||
} | ||
|
||
} else if (line.startsWith("ZIUT")) { | ||
addKeyValue(line, values); | ||
if (ecParams != null) { | ||
runTest(ecParams, values); | ||
} | ||
} else { | ||
addKeyValue(line, values); | ||
} | ||
|
||
line = in.readLine(); | ||
} | ||
} | ||
} | ||
|
||
private static void runTest(ECParameterSpec ecParams, | ||
Map<String, byte[]> values) throws Exception { | ||
|
||
byte[] xArr = values.get("QCAVSx"); | ||
BigInteger x = new BigInteger(1, xArr); | ||
byte[] yArr = values.get("QCAVSy"); | ||
BigInteger y = new BigInteger(1, yArr); | ||
ECPoint w = new ECPoint(x, y); | ||
ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, ecParams); | ||
|
||
byte[] dArr = values.get("dIUT"); | ||
BigInteger d = new BigInteger(1, dArr); | ||
ECPrivateKeySpec priSpec = new ECPrivateKeySpec(d, ecParams); | ||
|
||
KeyFactory kf = KeyFactory.getInstance("EC"); | ||
PublicKey pub = kf.generatePublic(pubSpec); | ||
PrivateKey pri = kf.generatePrivate(priSpec); | ||
|
||
KeyAgreement ka = KeyAgreement.getInstance("ECDH"); | ||
ka.init(pri); | ||
ka.doPhase(pub, true); | ||
byte[] secret = ka.generateSecret(); | ||
|
||
byte[] expectedSecret = values.get("ZIUT"); | ||
Asserts.assertEqualsByteArray(secret, expectedSecret, "Incorrect secret value"); | ||
int testIndex = values.get("COUNT")[0]; | ||
System.out.println("Test " + testIndex + " passed."); | ||
} | ||
|
||
private static void addKeyValue(String line, Map<String, byte[]> values) { | ||
StringTokenizer tok = new StringTokenizer(line, " ="); | ||
String key = tok.nextToken(); | ||
String value = tok.nextToken(); | ||
byte[] valueArr; | ||
if (value.length() <= 2) { | ||
valueArr = new byte[1]; | ||
valueArr[0] = Byte.parseByte(value, 10); | ||
} else { | ||
valueArr = HexFormat.of().parseHex(value); | ||
} | ||
|
||
values.put(key, valueArr); | ||
} | ||
|
||
private static String lookupName(String name) { | ||
return NAME_MAP.get(name); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,425 @@ | ||
/* | ||
* Copyright (c) 2018, 2024, 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. | ||
* | ||
* 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. | ||
*/ | ||
|
||
import java.io.*; | ||
import java.math.BigInteger; | ||
import java.nio.file.Path; | ||
import java.security.*; | ||
import java.security.spec.*; | ||
import java.util.*; | ||
|
||
import sun.security.ec.*; | ||
import sun.security.ec.point.*; | ||
import sun.security.util.ArrayUtil; | ||
import sun.security.util.math.*; | ||
import jdk.test.lib.Asserts; | ||
|
||
/* | ||
* @test | ||
* @bug 8189189 8147502 8295010 | ||
* @summary Test ECDSA primitive operations | ||
* @library /test/lib | ||
* @modules jdk.crypto.ec/sun.security.ec jdk.crypto.ec/sun.security.ec.point | ||
* java.base/sun.security.util java.base/sun.security.util.math | ||
* @run main ECDSAPrimitive | ||
*/ | ||
public class ECDSAPrimitive { | ||
|
||
private static final Map<String, String> CURVE_NAME_MAP = Map.ofEntries( | ||
Map.entry("P-256", "secp256r1"), | ||
Map.entry("P-384", "secp384r1"), | ||
Map.entry("P-521", "secp521r1") | ||
); | ||
private static final Set<String> DIGEST_NAME_SET = Set.of( | ||
"SHA-224", | ||
"SHA-256", | ||
"SHA-384", | ||
"SHA-512" | ||
); | ||
|
||
public static void main(String[] args) throws Exception { | ||
Path siggenFile = Path.of(System.getProperty("test.src"), "SigGen-1.txt"); | ||
|
||
ECParameterSpec ecParams = null; | ||
String digestAlg = null; | ||
|
||
try (BufferedReader in = new BufferedReader(new FileReader( | ||
siggenFile.toFile()))) { | ||
Map<String, byte[]> values = new HashMap<>(); | ||
String line = in.readLine(); | ||
while (line != null) { | ||
line = line.trim(); | ||
if (line.startsWith("#") || line.length() == 0) { | ||
// ignore | ||
} else if (line.startsWith("[")) { | ||
// change curve and hash | ||
StringTokenizer tok = new StringTokenizer(line, "[,]"); | ||
String name = tok.nextToken(); | ||
String curveName = lookUpCurveName(name); | ||
|
||
String digestName = tok.nextToken(); | ||
digestAlg = lookUpDigestName(digestName); | ||
|
||
if (curveName == null) { | ||
System.out.println("Unknown curve: " + name | ||
+ ". Skipping test"); | ||
ecParams = null; | ||
digestAlg = null; | ||
} | ||
if (digestAlg == null) { | ||
System.out.println("Unknown digest: " + digestName | ||
+ ". Skipping test"); | ||
ecParams = null; | ||
digestAlg = null; | ||
} else { | ||
AlgorithmParameters params = | ||
AlgorithmParameters.getInstance("EC", "SunEC"); | ||
params.init(new ECGenParameterSpec(curveName)); | ||
ecParams = params.getParameterSpec( | ||
ECParameterSpec.class); | ||
System.out.println("Testing curve/digest: " | ||
+ curveName + "/" + digestAlg); | ||
} | ||
|
||
} else if (line.startsWith("S")) { | ||
addKeyValue(line, values); | ||
if (ecParams != null) { | ||
runTest(ecParams, digestAlg, values); | ||
} | ||
} else { | ||
addKeyValue(line, values); | ||
} | ||
|
||
line = in.readLine(); | ||
} | ||
} | ||
} | ||
|
||
private static void runTest(ECParameterSpec ecParams, String digestAlg, | ||
Map<String, byte[]> values) throws Exception { | ||
|
||
Optional<ECDSAOperations> opsOpt = | ||
ECDSAOperations.forParameters(ecParams); | ||
Optional<Signer> signerOpt = opsOpt.map(OpsSigner::new); | ||
Signer signer = signerOpt.orElseGet(() -> new JCASigner(ecParams)); | ||
|
||
byte[] msg = values.get("Msg"); | ||
MessageDigest md = MessageDigest.getInstance(digestAlg); | ||
byte[] digest = md.digest(msg); | ||
|
||
// all operations accept little endian private key and nonce | ||
byte[] privateKey = values.get("d"); | ||
byte[] k = values.get("k"); | ||
|
||
byte[] computedSig = signer.sign(privateKey, digest, k); | ||
|
||
int valueLength = computedSig.length / 2; | ||
byte[] computedR = Arrays.copyOf(computedSig, valueLength); | ||
byte[] expectedR = values.get("R"); | ||
Asserts.assertEquals(new BigInteger(1, expectedR), new BigInteger(1, computedR), "R"); | ||
|
||
byte[] computedS = Arrays.copyOfRange(computedSig, valueLength, | ||
2 * valueLength); | ||
byte[] expectedS = values.get("S"); | ||
Asserts.assertEquals(new BigInteger(1, expectedS), new BigInteger(1, computedS), "S"); | ||
|
||
// ensure public key is correct | ||
byte[] expectedQx = values.get("Qx"); | ||
byte[] expectedQy = values.get("Qy"); | ||
ECPoint ecPublicKey = | ||
signer.checkPublicKey(privateKey, expectedQx, expectedQy); | ||
|
||
// ensure the verification works | ||
if (!signer.verify(ecPublicKey, digest, computedSig)) { | ||
throw new RuntimeException("Signature did not verify"); | ||
} | ||
|
||
// ensure incorrect signature does not verify | ||
int length = k.length; | ||
computedSig[length / 2] ^= (byte) 1; | ||
if (signer.verify(ecPublicKey, digest, computedSig)) { | ||
throw new RuntimeException("Incorrect signature verified"); | ||
} | ||
computedSig[length / 2] ^= (byte) 1; | ||
computedSig[length + length / 2] ^= (byte) 1; | ||
if (signer.verify(ecPublicKey, digest, computedSig)) { | ||
throw new RuntimeException("Incorrect signature verified"); | ||
} | ||
|
||
System.out.println("Test case passed"); | ||
} | ||
|
||
private static void addKeyValue(String line, Map<String, byte[]> values) { | ||
StringTokenizer tok = new StringTokenizer(line, " ="); | ||
String key = tok.nextToken(); | ||
String value = tok.nextToken(); | ||
byte[] valueArr; | ||
if (value.length() <= 2) { | ||
valueArr = new byte[1]; | ||
valueArr[0] = Byte.parseByte(value, 10); | ||
} else { | ||
// some values are odd-length big-endian integers | ||
if (value.length() % 2 == 1) { | ||
if (key.equals("Msg")) { | ||
throw new RuntimeException("message length may not be odd"); | ||
} | ||
value = "0" + value; | ||
} | ||
valueArr = HexFormat.of().parseHex(value); | ||
} | ||
|
||
values.put(key, valueArr); | ||
} | ||
|
||
private static String lookUpCurveName(String name) { | ||
return CURVE_NAME_MAP.get(name); | ||
} | ||
|
||
private static String lookUpDigestName(String name) { | ||
return DIGEST_NAME_SET.contains(name) ? name : null; | ||
} | ||
|
||
public static boolean verifySignedDigest(ECDSAOperations ops, ECPoint publicKey, | ||
byte[] digest, byte[] signature) { | ||
|
||
try { | ||
return verifySignedDigestImpl(ops, publicKey, digest, signature); | ||
} catch (ImproperSignatureException ex) { | ||
return false; | ||
} | ||
} | ||
|
||
private static boolean verifySignedDigestImpl(ECDSAOperations ops, ECPoint publicKey, | ||
byte[] digest, byte[] signature) | ||
throws ImproperSignatureException { | ||
|
||
ECOperations ecOps = ops.getEcOperations(); | ||
IntegerFieldModuloP orderField = ecOps.getOrderField(); | ||
int orderBits = orderField.getSize().bitLength(); | ||
if (orderBits % 8 != 0 && orderBits < digest.length * 8) { | ||
// This implementation does not support truncating digests to | ||
// a length that is not a multiple of 8. | ||
throw new ProviderException("Invalid digest length"); | ||
} | ||
// decode signature as (r, s) | ||
byte[] rBytes = Arrays.copyOf(signature, signature.length / 2); | ||
ArrayUtil.reverse(rBytes); | ||
byte[] sBytes = Arrays.copyOfRange(signature, signature.length / 2, | ||
signature.length); | ||
ArrayUtil.reverse(sBytes); | ||
|
||
// convert r and s to field elements | ||
// TODO: reject non-canonical values | ||
IntegerModuloP s = orderField.getElement(sBytes); | ||
IntegerModuloP r = orderField.getElement(rBytes); | ||
|
||
// truncate the digest and interpret as a field element | ||
int length = (orderBits + 7) / 8; | ||
int lengthE = Math.min(length, digest.length); | ||
byte[] E = new byte[lengthE]; | ||
System.arraycopy(digest, 0, E, 0, lengthE); | ||
ArrayUtil.reverse(E); | ||
IntegerModuloP e = orderField.getElement(E); | ||
|
||
// perform the calculation | ||
IntegerModuloP sInverse = s.multiplicativeInverse(); | ||
IntegerModuloP u1 = e.multiply(sInverse); | ||
IntegerModuloP u2 = r.multiply(sInverse); | ||
|
||
byte[] u1Bytes = u1.asByteArray(length); | ||
byte[] u2Bytes = u2.asByteArray(length); | ||
AffinePoint publicKeyPoint = ECDSAOperations.toAffinePoint(publicKey, | ||
ecOps.getField()); | ||
MutablePoint R = ecOps.multiply(publicKeyPoint, u2Bytes); | ||
AffinePoint a1 = ops.basePointMultiply(u1Bytes); | ||
ecOps.setSum(R, a1); | ||
|
||
// can't continue if R is neutral | ||
if (ecOps.isNeutral(R)) { | ||
throw new ImproperSignatureException(); | ||
} | ||
|
||
IntegerModuloP xr = R.asAffine().getX(); | ||
byte[] temp = new byte[length]; | ||
xr.asByteArray(temp); | ||
IntegerModuloP v = orderField.getElement(temp); | ||
|
||
// Check that v==r by subtracting and comparing result to 0 | ||
v.subtract(r).mutable().asByteArray(temp); | ||
return ECOperations.allZero(temp); | ||
} | ||
|
||
private interface Signer { | ||
byte[] sign(byte[] privateKey, byte[] digest, byte[] k); | ||
|
||
ECPoint checkPublicKey(byte[] privateKey, byte[] expectedQx, | ||
byte[] expectedQy); | ||
|
||
boolean verify(ECPoint ecPublicKey, byte[] digest, byte[] sig); | ||
} | ||
|
||
private static class FixedRandom extends SecureRandom { | ||
|
||
private final byte[] val; | ||
|
||
public FixedRandom(byte[] val) { | ||
BigInteger biVal = new BigInteger(1, val); | ||
biVal = biVal.subtract(BigInteger.ONE); | ||
byte[] temp = biVal.toByteArray(); | ||
this.val = new byte[val.length]; | ||
int inStartPos = Math.max(0, temp.length - val.length); | ||
int outStartPos = Math.max(0, val.length - temp.length); | ||
System.arraycopy(temp, inStartPos, this.val, outStartPos, | ||
temp.length - inStartPos); | ||
} | ||
|
||
@Override | ||
public void nextBytes(byte[] bytes) { | ||
Arrays.fill(bytes, (byte) 0); | ||
int copyLength = Math.min(val.length, bytes.length - 2); | ||
System.arraycopy(val, 0, bytes, bytes.length - copyLength - 2, | ||
copyLength); | ||
} | ||
} | ||
|
||
// The signature verification function lives here. It is not used in the | ||
// JDK, but it is working, and the performance is roughly as good as the | ||
// native implementation in the JDK. | ||
|
||
private static class JCASigner implements Signer { | ||
|
||
private static final String SIG_ALG = "NONEwithECDSAinP1363Format"; | ||
private final ECParameterSpec ecParams; | ||
|
||
private JCASigner(ECParameterSpec ecParams) { | ||
this.ecParams = ecParams; | ||
} | ||
|
||
@Override | ||
public byte[] sign(byte[] privateKey, byte[] digest, byte[] k) { | ||
|
||
try { | ||
|
||
KeyFactory kf = KeyFactory.getInstance("EC", "SunEC"); | ||
BigInteger s = new BigInteger(1, privateKey); | ||
ECPrivateKeySpec privKeySpec = | ||
new ECPrivateKeySpec(s, ecParams); | ||
PrivateKey privKey = kf.generatePrivate(privKeySpec); | ||
|
||
Signature sig = Signature.getInstance(SIG_ALG, "SunEC"); | ||
sig.initSign(privKey, new FixedRandom(k)); | ||
sig.update(digest); | ||
return sig.sign(); | ||
} catch (Exception ex) { | ||
throw new RuntimeException(ex); | ||
} | ||
} | ||
|
||
@Override | ||
public ECPoint checkPublicKey(byte[] privateKey, byte[] expectedQx, | ||
byte[] expectedQy) { | ||
// no way to compute the public key using the API | ||
BigInteger x = new BigInteger(1, expectedQx); | ||
BigInteger y = new BigInteger(1, expectedQy); | ||
return new ECPoint(x, y); | ||
} | ||
|
||
@Override | ||
public boolean verify(ECPoint ecPublicKey, byte[] digest, | ||
byte[] providedSig) { | ||
|
||
try { | ||
KeyFactory kf = KeyFactory.getInstance("EC", "SunEC"); | ||
ECPublicKeySpec pubKeySpec = | ||
new ECPublicKeySpec(ecPublicKey, ecParams); | ||
PublicKey pubKey = kf.generatePublic(pubKeySpec); | ||
|
||
Signature sig = Signature.getInstance(SIG_ALG, "SunEC"); | ||
sig.initVerify(pubKey); | ||
sig.update(digest); | ||
return sig.verify(providedSig); | ||
} catch (Exception ex) { | ||
throw new RuntimeException(ex); | ||
} | ||
} | ||
} | ||
|
||
private static class OpsSigner implements Signer { | ||
|
||
private final ECDSAOperations ops; | ||
|
||
public OpsSigner(ECDSAOperations ops) { | ||
this.ops = ops; | ||
} | ||
|
||
@Override | ||
public byte[] sign(byte[] privateKey, byte[] digest, byte[] k) { | ||
|
||
privateKey = privateKey.clone(); | ||
ArrayUtil.reverse(privateKey); | ||
k = k.clone(); | ||
ArrayUtil.reverse(k); | ||
ECDSAOperations.Nonce nonce = new ECDSAOperations.Nonce(k); | ||
try { | ||
return ops.signDigest(privateKey, digest, nonce); | ||
} catch (Exception ex) { | ||
throw new RuntimeException(ex); | ||
} | ||
} | ||
|
||
@Override | ||
public ECPoint checkPublicKey(byte[] privateKey, byte[] expectedQx, | ||
byte[] expectedQy) { | ||
|
||
privateKey = privateKey.clone(); | ||
ArrayUtil.reverse(privateKey); | ||
AffinePoint publicKey = ops.basePointMultiply(privateKey); | ||
int length = privateKey.length; | ||
byte[] computedQx = new byte[length]; | ||
byte[] computedQy = new byte[length]; | ||
publicKey.getX().asByteArray(computedQx); | ||
ArrayUtil.reverse(computedQx); | ||
Asserts.assertEqualsByteArray(expectedQx, computedQx, "Qx"); | ||
publicKey.getY().asByteArray(computedQy); | ||
ArrayUtil.reverse(computedQy); | ||
Asserts.assertEqualsByteArray(expectedQy, computedQy, "Qy"); | ||
BigInteger bigX = publicKey.getX().asBigInteger(); | ||
BigInteger bigY = publicKey.getY().asBigInteger(); | ||
return new ECPoint(bigX, bigY); | ||
} | ||
|
||
@Override | ||
public boolean verify(ECPoint publicKey, byte[] digest, byte[] sig) { | ||
return verifySignedDigest(ops, publicKey, digest, sig); | ||
} | ||
} | ||
|
||
/* | ||
* An exception indicating that a signature is not formed correctly. | ||
*/ | ||
private static class ImproperSignatureException extends Exception { | ||
|
||
private static final long serialVersionUID = 1; | ||
} | ||
|
||
} |
3,049 changes: 3,049 additions & 0 deletions
3,049
test/jdk/sun/security/ec/KAS_ECC_CDH_PrimitiveTest.txt
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
93dc591
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review
Issues