diff --git a/src/java.base/share/classes/sun/security/util/ECUtil.java b/src/java.base/share/classes/sun/security/util/ECUtil.java index 8fc44bb029a..739f3648dfd 100644 --- a/src/java.base/share/classes/sun/security/util/ECUtil.java +++ b/src/java.base/share/classes/sun/security/util/ECUtil.java @@ -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. * * 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 { + // 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. public static ECPoint decodePoint(byte[] data, EllipticCurve curve) throws IOException { diff --git a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSAOperations.java b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSAOperations.java index 3c2f9eb524e..2bce5784002 100644 --- a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSAOperations.java +++ b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSAOperations.java @@ -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. * * 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.spec.*; +import java.util.Arrays; import java.util.Optional; public class ECDSAOperations { @@ -164,10 +165,7 @@ public class ECDSAOperations { IntegerModuloP r = R.asAffine().getX(); // put r into the correct field by fully reducing to an array byte[] temp = new byte[length]; - r.asByteArray(temp); - r = orderField.getElement(temp); - // store r in result - r.asByteArray(temp); + r = b2a(r, orderField, temp); byte[] result = new byte[2 * length]; ArrayUtil.reverse(temp); System.arraycopy(temp, 0, result, 0, length); @@ -198,5 +196,68 @@ public class ECDSAOperations { 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; + } } diff --git a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSASignature.java b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSASignature.java index 695427d7452..b8f1c5e1708 100644 --- a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSASignature.java +++ b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECDSASignature.java @@ -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. * * 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); } - private byte[] signDigestImpl(ECDSAOperations ops, int seedBits, - byte[] digest, ECPrivateKeyImpl privImpl, SecureRandom random) + byte[] digest, ECPrivateKey priv, SecureRandom random) throws SignatureException { 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 // each time. The chance of failure is very small assuming the @@ -401,13 +402,9 @@ abstract class ECDSASignature extends SignatureSpi { } - private Optional signDigestImpl(ECPrivateKey privateKey, + private Optional signDigestAvailable(ECPrivateKey privateKey, byte[] digest, SecureRandom random) throws SignatureException { - if (! (privateKey instanceof ECPrivateKeyImpl)) { - return Optional.empty(); - } - ECPrivateKeyImpl privImpl = (ECPrivateKeyImpl) privateKey; ECParameterSpec params = privateKey.getParams(); // seed is the key size + 64 bits @@ -418,7 +415,7 @@ abstract class ECDSASignature extends SignatureSpi { return Optional.empty(); } else { byte[] sig = signDigestImpl(opsOpt.get(), seedBits, digest, - privImpl, random); + privateKey, random); return Optional.of(sig); } } @@ -461,7 +458,7 @@ abstract class ECDSASignature extends SignatureSpi { } byte[] digest = getDigestValue(); - Optional sigOpt = signDigestImpl(privateKey, digest, random); + Optional sigOpt = signDigestAvailable(privateKey, digest, random); byte[] sig; if (sigOpt.isPresent()) { sig = sigOpt.get(); @@ -480,17 +477,6 @@ abstract class ECDSASignature extends SignatureSpi { @Override protected boolean engineVerify(byte[] signature) throws SignatureException { - byte[] w; - ECParameterSpec params = publicKey.getParams(); - // DER OID - byte[] encodedParams = ECUtil.encodeECParameterSpec(null, params); - - if (publicKey instanceof ECPublicKeyImpl) { - w = ((ECPublicKeyImpl) publicKey).getEncodedPublicValue(); - } else { // instanceof ECPublicKey - w = ECUtil.encodePoint(publicKey.getW(), params.getCurve()); - } - byte[] sig; if (p1363Format) { sig = signature; @@ -498,13 +484,53 @@ abstract class ECDSASignature extends SignatureSpi { sig = ECUtil.decodeSignature(signature); } - try { - return verifySignedDigest(sig, getDigestValue(), w, encodedParams); - } catch (GeneralSecurityException e) { - throw new SignatureException("Could not verify signature", e); + byte[] digest = getDigestValue(); + Optional verifyOpt + = verifySignedDigestAvailable(publicKey, sig, digest); + + if (verifyOpt.isPresent()) { + return verifyOpt.get(); + } else { + byte[] w; + ECParameterSpec params = publicKey.getParams(); + // DER OID + byte[] encodedParams = ECUtil.encodeECParameterSpec(null, params); + + if (publicKey instanceof ECPublicKeyImpl) { + w = ((ECPublicKeyImpl) publicKey).getEncodedPublicValue(); + } else { // instanceof ECPublicKey + w = ECUtil.encodePoint(publicKey.getW(), params.getCurve()); + } + + try { + return verifySignedDigest(sig, digest, w, encodedParams); + } catch (GeneralSecurityException e) { + throw new SignatureException("Could not verify signature", e); + } } } + private Optional verifySignedDigestAvailable( + ECPublicKey publicKey, byte[] sig, byte[] digestValue) { + + ECParameterSpec params = publicKey.getParams(); + + Optional 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 @Override @Deprecated diff --git a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECPrivateKeyImpl.java b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECPrivateKeyImpl.java index 81c992d62d3..5f6edbefc78 100644 --- a/src/jdk.crypto.ec/share/classes/sun/security/ec/ECPrivateKeyImpl.java +++ b/src/jdk.crypto.ec/share/classes/sun/security/ec/ECPrivateKeyImpl.java @@ -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. * * 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() { if (arrayS == null) { - byte[] arr = getS().toByteArray(); - 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); + arrayS = ECUtil.sArray(getS(), params); } return arrayS.clone(); } diff --git a/test/jdk/sun/security/ec/ECDSAJavaVerify.java b/test/jdk/sun/security/ec/ECDSAJavaVerify.java new file mode 100644 index 00000000000..cb87fea4ae0 --- /dev/null +++ b/test/jdk/sun/security/ec/ECDSAJavaVerify.java @@ -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 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 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; + } + } +}