8237218: Support NIST Curves verification in java implementation

Reviewed-by: ascarpino
This commit is contained in:
Weijun Wang 2020-02-22 08:10:21 +08:00
parent 2596e83a34
commit 533649b8ca
5 changed files with 378 additions and 39 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -34,6 +34,17 @@ import java.util.Arrays;
public final class ECUtil { public final class ECUtil {
// Used by SunEC
public static byte[] sArray(BigInteger s, ECParameterSpec params) {
byte[] arr = s.toByteArray();
ArrayUtil.reverse(arr);
int byteLength = (params.getOrder().bitLength() + 7) / 8;
byte[] arrayS = new byte[byteLength];
int length = Math.min(byteLength, arr.length);
System.arraycopy(arr, 0, arrayS, 0, length);
return arrayS;
}
// Used by SunPKCS11 and SunJSSE. // Used by SunPKCS11 and SunJSSE.
public static ECPoint decodePoint(byte[] data, EllipticCurve curve) public static ECPoint decodePoint(byte[] data, EllipticCurve curve)
throws IOException { throws IOException {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -32,6 +32,7 @@ import static sun.security.ec.ECOperations.IntermediateValueException;
import java.security.ProviderException; import java.security.ProviderException;
import java.security.spec.*; import java.security.spec.*;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
public class ECDSAOperations { public class ECDSAOperations {
@ -164,10 +165,7 @@ public class ECDSAOperations {
IntegerModuloP r = R.asAffine().getX(); IntegerModuloP r = R.asAffine().getX();
// put r into the correct field by fully reducing to an array // put r into the correct field by fully reducing to an array
byte[] temp = new byte[length]; byte[] temp = new byte[length];
r.asByteArray(temp); r = b2a(r, orderField, temp);
r = orderField.getElement(temp);
// store r in result
r.asByteArray(temp);
byte[] result = new byte[2 * length]; byte[] result = new byte[2 * length];
ArrayUtil.reverse(temp); ArrayUtil.reverse(temp);
System.arraycopy(temp, 0, result, 0, length); System.arraycopy(temp, 0, result, 0, length);
@ -198,5 +196,68 @@ public class ECDSAOperations {
return result; return result;
} }
public boolean verifySignedDigest(byte[] digest, byte[] sig, ECPoint pp) {
IntegerFieldModuloP field = ecOps.getField();
IntegerFieldModuloP orderField = ecOps.getOrderField();
int length = (orderField.getSize().bitLength() + 7) / 8;
byte[] r;
byte[] s;
int encodeLength = sig.length / 2;
if (sig.length %2 != 0 || encodeLength > length) {
return false;
} else if (encodeLength == length) {
r = Arrays.copyOf(sig, length);
s = Arrays.copyOfRange(sig, length, length * 2);
} else {
r = new byte[length];
s = new byte[length];
System.arraycopy(sig, 0, r, length - encodeLength, encodeLength);
System.arraycopy(sig, encodeLength, s, length - encodeLength, encodeLength);
}
ArrayUtil.reverse(r);
ArrayUtil.reverse(s);
IntegerModuloP ri = orderField.getElement(r);
IntegerModuloP si = orderField.getElement(s);
// z
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);
IntegerModuloP sInv = si.multiplicativeInverse();
ImmutableIntegerModuloP u1 = e.multiply(sInv);
ImmutableIntegerModuloP u2 = ri.multiply(sInv);
AffinePoint pub = new AffinePoint(field.getElement(pp.getAffineX()),
field.getElement(pp.getAffineY()));
byte[] temp1 = new byte[length];
b2a(u1, orderField, temp1);
byte[] temp2 = new byte[length];
b2a(u2, orderField, temp2);
MutablePoint p1 = ecOps.multiply(basePoint, temp1);
MutablePoint p2 = ecOps.multiply(pub, temp2);
ecOps.setSum(p1, p2.asAffine());
IntegerModuloP result = p1.asAffine().getX();
result = result.additiveInverse().add(ri);
b2a(result, orderField, temp1);
return ECOperations.allZero(temp1);
}
static public ImmutableIntegerModuloP b2a(IntegerModuloP b,
IntegerFieldModuloP orderField, byte[] temp1) {
b.asByteArray(temp1);
ImmutableIntegerModuloP b2 = orderField.getElement(temp1);
b2.asByteArray(temp1);
return b2;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -374,13 +374,14 @@ abstract class ECDSASignature extends SignatureSpi {
return ECUtil.equals(sigParams, keyParams); return ECUtil.equals(sigParams, keyParams);
} }
private byte[] signDigestImpl(ECDSAOperations ops, int seedBits, private byte[] signDigestImpl(ECDSAOperations ops, int seedBits,
byte[] digest, ECPrivateKeyImpl privImpl, SecureRandom random) byte[] digest, ECPrivateKey priv, SecureRandom random)
throws SignatureException { throws SignatureException {
byte[] seedBytes = new byte[(seedBits + 7) / 8]; byte[] seedBytes = new byte[(seedBits + 7) / 8];
byte[] s = privImpl.getArrayS(); byte[] s = priv instanceof ECPrivateKeyImpl
? ((ECPrivateKeyImpl)priv).getArrayS()
: ECUtil.sArray(priv.getS(), priv.getParams());
// Attempt to create the signature in a loop that uses new random input // Attempt to create the signature in a loop that uses new random input
// each time. The chance of failure is very small assuming the // each time. The chance of failure is very small assuming the
@ -401,13 +402,9 @@ abstract class ECDSASignature extends SignatureSpi {
} }
private Optional<byte[]> signDigestImpl(ECPrivateKey privateKey, private Optional<byte[]> signDigestAvailable(ECPrivateKey privateKey,
byte[] digest, SecureRandom random) throws SignatureException { byte[] digest, SecureRandom random) throws SignatureException {
if (! (privateKey instanceof ECPrivateKeyImpl)) {
return Optional.empty();
}
ECPrivateKeyImpl privImpl = (ECPrivateKeyImpl) privateKey;
ECParameterSpec params = privateKey.getParams(); ECParameterSpec params = privateKey.getParams();
// seed is the key size + 64 bits // seed is the key size + 64 bits
@ -418,7 +415,7 @@ abstract class ECDSASignature extends SignatureSpi {
return Optional.empty(); return Optional.empty();
} else { } else {
byte[] sig = signDigestImpl(opsOpt.get(), seedBits, digest, byte[] sig = signDigestImpl(opsOpt.get(), seedBits, digest,
privImpl, random); privateKey, random);
return Optional.of(sig); return Optional.of(sig);
} }
} }
@ -461,7 +458,7 @@ abstract class ECDSASignature extends SignatureSpi {
} }
byte[] digest = getDigestValue(); byte[] digest = getDigestValue();
Optional<byte[]> sigOpt = signDigestImpl(privateKey, digest, random); Optional<byte[]> sigOpt = signDigestAvailable(privateKey, digest, random);
byte[] sig; byte[] sig;
if (sigOpt.isPresent()) { if (sigOpt.isPresent()) {
sig = sigOpt.get(); sig = sigOpt.get();
@ -480,6 +477,20 @@ abstract class ECDSASignature extends SignatureSpi {
@Override @Override
protected boolean engineVerify(byte[] signature) throws SignatureException { protected boolean engineVerify(byte[] signature) throws SignatureException {
byte[] sig;
if (p1363Format) {
sig = signature;
} else {
sig = ECUtil.decodeSignature(signature);
}
byte[] digest = getDigestValue();
Optional<Boolean> verifyOpt
= verifySignedDigestAvailable(publicKey, sig, digest);
if (verifyOpt.isPresent()) {
return verifyOpt.get();
} else {
byte[] w; byte[] w;
ECParameterSpec params = publicKey.getParams(); ECParameterSpec params = publicKey.getParams();
// DER OID // DER OID
@ -491,19 +502,34 @@ abstract class ECDSASignature extends SignatureSpi {
w = ECUtil.encodePoint(publicKey.getW(), params.getCurve()); w = ECUtil.encodePoint(publicKey.getW(), params.getCurve());
} }
byte[] sig;
if (p1363Format) {
sig = signature;
} else {
sig = ECUtil.decodeSignature(signature);
}
try { try {
return verifySignedDigest(sig, getDigestValue(), w, encodedParams); return verifySignedDigest(sig, digest, w, encodedParams);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new SignatureException("Could not verify signature", e); throw new SignatureException("Could not verify signature", e);
} }
} }
}
private Optional<Boolean> verifySignedDigestAvailable(
ECPublicKey publicKey, byte[] sig, byte[] digestValue) {
ECParameterSpec params = publicKey.getParams();
Optional<ECDSAOperations> opsOpt =
ECDSAOperations.forParameters(params);
if (opsOpt.isEmpty()) {
return Optional.empty();
} else {
boolean result = verifySignedDigestImpl(opsOpt.get(), digestValue,
publicKey, sig);
return Optional.of(result);
}
}
private boolean verifySignedDigestImpl(ECDSAOperations ops,
byte[] digest, ECPublicKey pub, byte[] sig) {
return ops.verifySignedDigest(digest, sig, pub.getW());
}
// set parameter, not supported. See JCA doc // set parameter, not supported. See JCA doc
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -153,12 +153,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
public byte[] getArrayS() { public byte[] getArrayS() {
if (arrayS == null) { if (arrayS == null) {
byte[] arr = getS().toByteArray(); arrayS = ECUtil.sArray(getS(), params);
ArrayUtil.reverse(arr);
int byteLength = (params.getOrder().bitLength() + 7) / 8;
arrayS = new byte[byteLength];
int length = Math.min(byteLength, arr.length);
System.arraycopy(arr, 0, arrayS, 0, length);
} }
return arrayS.clone(); return arrayS.clone();
} }

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) 2020, 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 com.sun.jdi.Bootstrap;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.request.MethodEntryRequest;
import java.security.AlgorithmParameters;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
/*
* @test
* @bug 8237218
* @summary Support NIST Curves verification in java implementation
* @modules jdk.crypto.ec
* jdk.jdi
* @run main ECDSAJavaVerify debug
*/
// ATTENTION: This test depends on method names inside the non-exported
// class sun.security.ec.ECDSASignature.
public class ECDSAJavaVerify {
static final String[] ALL_ALGS = new String[] {
"SHA1withECDSA", "SHA256withECDSA", "SHA384withECDSA", "SHA512withECDSA"};
static final String[] ALL_CURVES = new String[] {
"secp128r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1"};
static final List<String> ALL_JAVA_CURVES
= List.of("secp256r1", "secp384r1", "secp521r1");
public static void main(String[] args) throws Exception {
if (args.length == 1) {
// Debugging a new process with no arg
debug();
} else if (args.length == 3) {
// If one test case fail, re-run it with first 3 columns
new Test().run(Integer.parseInt(args[0]), args[1], args[2]);
} else {
// Run all test cases
Test t = new Test();
Random r = new Random();
for (String sigAlg : ALL_ALGS) {
for (String curve : ALL_CURVES) {
t.run(r.nextInt(1000000), sigAlg, curve);
}
}
}
}
static void debug() throws Exception {
LaunchingConnector launchingConnector = Bootstrap
.virtualMachineManager().defaultConnector();
Map<String, Connector.Argument> arguments
= launchingConnector.defaultArguments();
arguments.get("main").setValue(ECDSAJavaVerify.class.getName());
arguments.get("options").setValue(
"-cp " + System.getProperty("test.classes"));
VirtualMachine vm = launchingConnector.launch(arguments);
MethodEntryRequest req = vm.eventRequestManager()
.createMethodEntryRequest();
req.addClassFilter("sun.security.ec.ECDSASignature");
req.enable();
int numberOfTests = ALL_ALGS.length * ALL_CURVES.length * 2;
// Expected methods to call. 'J' for java impl, 'N' for native impl
char[] expected = new char[numberOfTests];
int pos = 0;
for (String dummy : ALL_ALGS) {
for (String curve : ALL_CURVES) {
char caller = ALL_JAVA_CURVES.contains(curve) ? 'J' : 'N';
// For each case, Signature::verify is called twice
expected[pos++] = caller;
expected[pos++] = caller;
}
}
// Test result, init as ' ', '-' if run, 'x' for unexpected.
char[] result = new char[numberOfTests];
Arrays.fill(result, ' ');
String stdout, stderr;
try {
EventSet eventSet;
pos = -1; // will become 0 when entering 'engineVerify'
while ((eventSet = vm.eventQueue().remove()) != null) {
for (Event event : eventSet) {
if (event instanceof MethodEntryEvent) {
MethodEntryEvent e = (MethodEntryEvent)event;
switch (e.method().name()) {
case "engineVerify":
pos++;
result[pos] = '-';
break;
case "verifySignedDigestImpl": // the java impl
if (expected[pos] != 'J') {
result[pos] = 'x';
}
break;
case "verifySignedDigest":
if (expected[pos] != 'N') { // the native impl
result[pos] = 'x';
}
break;
}
}
vm.resume();
}
}
} catch (VMDisconnectedException e) {
System.out.println("Virtual Machine is disconnected.");
} finally {
stderr = new String(vm.process().getErrorStream().readAllBytes());
stdout = new String(vm.process().getInputStream().readAllBytes());
}
System.out.println("stderr:\n" + stderr);
System.out.println("stdout:\n" + stdout);
String sResult = new String(result);
System.out.println("Expected: " + new String(expected));
System.out.println(" Actual: " + sResult);
if (pos != numberOfTests - 1 || sResult.contains("x")) {
throw new Exception("Unexpected result");
}
if (stdout.contains("fail") || !stderr.isEmpty()) {
throw new Exception("Test failed");
}
}
static class Test {
public boolean run(int seed, String sigAlg, String curve)
throws Exception {
// A determined SecureRandom based on seed. If there is anything
// wrong, we can reproduce the problem using the seed.
Random r = new Random(seed);
SecureRandom rand = new SecureRandom() {
@Override
public void nextBytes(byte[] bytes) {
r.nextBytes(bytes);
}
};
AlgorithmParameters ap = AlgorithmParameters.getInstance("EC", "SunEC");
ap.init(new ECGenParameterSpec(curve));
ECParameterSpec spec = ap.getParameterSpec(ECParameterSpec.class);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "SunEC");
kpg.initialize(spec, rand);
KeyPair kp = kpg.generateKeyPair();
ECPrivateKey ecPrivateKey = (ECPrivateKey) kp.getPrivate();
ECPublicKey ecPublicKey = (ECPublicKey) kp.getPublic();
Signature s1 = Signature.getInstance(sigAlg, "SunEC");
s1.initSign(ecPrivateKey, rand);
byte[] msg = new byte[1234];
rand.nextBytes(msg);
s1.update(msg);
byte[] sig = s1.sign();
Signature s2 = Signature.getInstance(sigAlg, "SunEC");
s2.initVerify(ecPublicKey);
s2.update(msg);
boolean result1 = s2.verify(sig);
s2.initVerify(ecPublicKey);
// modify the signature in some random manner
if (rand.nextInt(10) < 8) {
sig[rand.nextInt(10000) % sig.length]
= (byte) rand.nextInt(10000);
} else {
int newLength = rand.nextInt(100);
if (newLength == sig.length) {
newLength += 1 + rand.nextInt(2);
}
sig = Arrays.copyOf(sig, newLength);
}
boolean result2;
try {
result2 = s2.verify(sig);
} catch (SignatureException se) {
result2 = false;
}
boolean finalResult = result1 && !result2;
System.out.printf("%10d %20s %20s -- %5s %5s -- %s\n",
seed, sigAlg, curve, result1, result2,
finalResult ? "succeed" : "fail");
return finalResult;
}
}
}