1 /** 2 * Licensed under the Apache License, Version 2.0 (the "License"); 3 * you may not use this file except in compliance with the License. 4 * You may obtain a copy of the License at 5 * 6 * http://www.apache.org/licenses/LICENSE-2.0 7 * 8 * Unless required by applicable law or agreed to in writing, software 9 * distributed under the License is distributed on an "AS IS" BASIS, 10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 * See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 package com.google.security.wycheproof; 15 16 import static org.junit.Assert.assertTrue; 17 import static org.junit.Assert.fail; 18 19 import java.math.BigInteger; 20 import java.nio.ByteBuffer; 21 import java.security.InvalidAlgorithmParameterException; 22 import java.security.KeyPair; 23 import java.security.KeyPairGenerator; 24 import java.security.KeyStore; 25 import java.security.MessageDigest; 26 import java.security.NoSuchAlgorithmException; 27 import java.security.Signature; 28 import java.security.PrivateKey; 29 import java.security.PublicKey; 30 import java.security.interfaces.ECPrivateKey; 31 import java.security.interfaces.ECPublicKey; 32 import java.security.spec.ECGenParameterSpec; 33 import java.security.spec.ECParameterSpec; 34 import java.util.Arrays; 35 import java.util.HashSet; 36 import org.junit.After; 37 import org.junit.Test; 38 import org.junit.Ignore; 39 import android.security.keystore.KeyProtection; 40 import android.security.keystore.KeyProperties; 41 import android.keystore.cts.util.KeyStoreUtil; 42 43 /** 44 * Tests ECDSA signatures. 45 * 46 * <p>Tests for signature verification with test vectors are in JsonSignatureTest.java toghether 47 * with other signature schemes. 48 * 49 * @author bleichen@google.com (Daniel Bleichenbacher) 50 */ 51 public class EcdsaTest { 52 private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; 53 private static final String KEY_ALIAS_1 = "TestKey"; 54 private static final String TAG = "EcdsaTest"; 55 56 @After tearDown()57 public void tearDown() throws Exception { 58 KeyStoreUtil.cleanUpKeyStore(); 59 } 60 getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, boolean isStrongBox)61 private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, 62 boolean isStrongBox) throws Exception { 63 KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) 64 .setDigests(KeyProperties.DIGEST_SHA224, 65 KeyProperties.DIGEST_SHA256, 66 KeyProperties.DIGEST_SHA384, 67 KeyProperties.DIGEST_SHA512) 68 .setIsStrongBoxBacked(isStrongBox) 69 .build(); 70 KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, 71 keyProtection); 72 return (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); 73 } 74 75 /** 76 * Determines the Hash name from the ECDSA algorithm. There is a small inconsistency in the naming 77 * of algorithms. The Oracle standard use no hyphen in SHA256WithECDSA but uses a hyphen in the 78 * message digest, i.e., SHA-256. 79 */ getHashAlgorithm(String ecdsaAlgorithm)80 private String getHashAlgorithm(String ecdsaAlgorithm) { 81 ecdsaAlgorithm = ecdsaAlgorithm.toUpperCase(); 82 int idx = ecdsaAlgorithm.indexOf("WITH"); 83 if (idx > 0) { 84 if (ecdsaAlgorithm.startsWith("SHA")) { 85 return "SHA-" + ecdsaAlgorithm.substring(3, idx); 86 } else { 87 return ecdsaAlgorithm.substring(0, idx); 88 } 89 } 90 return ""; 91 } 92 93 /** 94 * Returns true if the signature scheme is deterministic. Even though a non-deterministic 95 * signature scheme can in principle return the same signature twice this should never happen in 96 * practice. 97 */ isDeterministic(Signature signer, PrivateKey priv)98 private boolean isDeterministic(Signature signer, PrivateKey priv) throws Exception { 99 byte[][] signature = new byte[2][]; 100 byte[] message = new byte[1]; 101 for (int i = 0; i < 2; i++) { 102 signer.initSign(priv); 103 signer.update(message); 104 signature[i] = signer.sign(); 105 } 106 return Arrays.equals(signature[0], signature[1]); 107 } 108 109 /** 110 * Returns number of count messages to sign. If the signature scheme is deterministic then the 111 * messages are all different. If the signature scheme is randomized then the messages are all 112 * the same. If the messages signed are all the same then it may be easier to detect a bias. 113 */ getMessagesToSign(int count, Signature signer, PrivateKey priv)114 private byte[][] getMessagesToSign(int count, Signature signer, PrivateKey priv) 115 throws Exception { 116 byte[][] messages = new byte[count][]; 117 if (isDeterministic(signer, priv)) { 118 for (int i = 0; i < count; i++) { 119 messages[i] = ByteBuffer.allocate(4).putInt(i).array(); 120 } 121 } else { 122 byte[] msg = new byte[4]; 123 for (int i = 0; i < count; i++) { 124 messages[i] = msg; 125 } 126 } 127 return messages; 128 } 129 130 /** 131 * Extract the integer r from an ECDSA signature. This method implicitely assumes that the ECDSA 132 * signature is DER encoded. and that the order of the curve is smaller than 2^1024. 133 */ extractR(byte[] signature)134 BigInteger extractR(byte[] signature) throws Exception { 135 int startR = (signature[1] & 0x80) != 0 ? 3 : 2; 136 int lengthR = signature[startR + 1]; 137 return new BigInteger(Arrays.copyOfRange(signature, startR + 2, startR + 2 + lengthR)); 138 } 139 extractS(byte[] signature)140 BigInteger extractS(byte[] signature) throws Exception { 141 int startR = (signature[1] & 0x80) != 0 ? 3 : 2; 142 int lengthR = signature[startR + 1]; 143 int startS = startR + 2 + lengthR; 144 int lengthS = signature[startS + 1]; 145 return new BigInteger(Arrays.copyOfRange(signature, startS + 2, startS + 2 + lengthS)); 146 } 147 148 /** Extract the k that was used to sign the signature. */ extractK(byte[] signature, BigInteger h, ECPrivateKey priv)149 BigInteger extractK(byte[] signature, BigInteger h, ECPrivateKey priv) throws Exception { 150 BigInteger x = priv.getS(); 151 BigInteger n = priv.getParams().getOrder(); 152 BigInteger r = extractR(signature); 153 BigInteger s = extractS(signature); 154 BigInteger k = x.multiply(r).add(h).multiply(s.modInverse(n)).mod(n); 155 return k; 156 } 157 158 /** 159 * Computes the bias of samples as 160 * 161 * <p>abs(sum(e^(2 pi i s m / modulus) for s in samples) / sqrt(samples.length). 162 * 163 * <p>If the samples are taken from a uniform distribution in the range 0 .. modulus - 1 and the 164 * number of samples is significantly larger than L^2 then the probability that the result is 165 * larger than L is approximately e^(-L^2). The approximation can be derived from the assumption 166 * that samples taken from a uniform distribution give a result that approximates a standard 167 * complex normal distribution Z. I.e. Z has a density f_Z(z) = exp(-abs(z)^2) / pi. 168 * https://en.wikipedia.org/wiki/Complex_normal_distribution 169 */ bias(BigInteger[] samples, BigInteger modulus, BigInteger m)170 double bias(BigInteger[] samples, BigInteger modulus, BigInteger m) { 171 double sumReal = 0.0; 172 double sumImag = 0.0; 173 for (BigInteger s : samples) { 174 BigInteger r = s.multiply(m).mod(modulus); 175 // multiplier = 2 * pi / 2^52 176 double multiplier = 1.3951473992034527e-15; 177 // computes the quotent 2 * pi * r / modulus 178 double quot = r.shiftLeft(52).divide(modulus).doubleValue() * multiplier; 179 sumReal += Math.cos(quot); 180 sumImag += Math.sin(quot); 181 } 182 return Math.sqrt((sumReal * sumReal + sumImag * sumImag) / samples.length); 183 } 184 185 /** 186 * This test checks the basic functionality of ECDSA. It simply tries to generate a key, sign and 187 * verify a message for a given, algorithm and curve. 188 * 189 * @param algorithm the algorithm to test (e.g. "SHA256WithECDSA") 190 * @param curve the curve to test (e.g. "secp256r1") 191 * @return whether the algorithm and curve are supported. 192 * @throws Exception if an unexpected error occurred. 193 */ testParameters(String algorithm, String curve)194 boolean testParameters(String algorithm, String curve) throws Exception { 195 return testParameters(algorithm, curve, false); 196 } testParameters(String algorithm, String curve, boolean isStrongBox)197 boolean testParameters(String algorithm, String curve, boolean isStrongBox) throws Exception { 198 if (isStrongBox) { 199 KeyStoreUtil.assumeStrongBox(); 200 } 201 String message = "123400"; 202 203 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); 204 KeyPair keyPair; 205 try { 206 keyGen.initialize(new ECGenParameterSpec(curve)); 207 keyPair = keyGen.generateKeyPair(); 208 } catch (InvalidAlgorithmParameterException ex) { 209 // The curve is not supported. 210 // The documentation does not specify whether the method initialize 211 // has to reject unsupported curves or if only generateKeyPair checks 212 // whether the curve is supported. 213 return false; 214 } 215 ECPublicKey pub = (ECPublicKey) keyPair.getPublic(); 216 ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate(); 217 218 Signature signer; 219 Signature verifier; 220 try { 221 signer = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); 222 verifier = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); 223 } catch (NoSuchAlgorithmException ex) { 224 // The algorithm is not supported. 225 return false; 226 } 227 // Both algorithm and curve are supported. 228 // Hence, we expect that signing and verifying properly works. 229 byte[] messageBytes = message.getBytes("UTF-8"); 230 signer.initSign(getKeystorePrivateKey(pub, priv, isStrongBox)); 231 signer.update(messageBytes); 232 byte[] signature = signer.sign(); 233 verifier.initVerify(pub); 234 verifier.update(messageBytes); 235 assertTrue(verifier.verify(signature)); 236 return true; 237 } 238 239 /** 240 * This test checks the basic functionality of ECDSA. This mainly checks that the provider follows 241 * the JCA interface. 242 */ 243 @Test testBasic()244 public void testBasic() throws Exception { 245 String algorithm = "SHA256WithECDSA"; 246 String curve = "secp256r1"; 247 assertTrue(testParameters(algorithm, curve)); 248 } 249 @Test testBasic_StrongBox()250 public void testBasic_StrongBox() throws Exception { 251 String algorithm = "SHA256WithECDSA"; 252 String curve = "secp256r1"; 253 assertTrue(testParameters(algorithm, curve, true)); 254 } 255 256 /** Checks whether the one time key k in ECDSA is biased. */ testBias(String algorithm, String curve)257 public void testBias(String algorithm, String curve) throws Exception { 258 testBias(algorithm, curve, false); 259 } testBias(String algorithm, String curve, boolean isStrongBox)260 public void testBias(String algorithm, String curve, 261 boolean isStrongBox) throws Exception { 262 if (isStrongBox) { 263 KeyStoreUtil.assumeStrongBox(); 264 } 265 Signature signer = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); 266 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); 267 keyGen.initialize(new ECGenParameterSpec(curve)); 268 KeyPair keyPair = keyGen.generateKeyPair(); 269 270 ECPrivateKey priv = (ECPrivateKey)keyPair.getPrivate(); 271 PrivateKey keystorePrivateKey = getKeystorePrivateKey(keyPair.getPublic(), 272 keyPair.getPrivate(), isStrongBox); 273 // If we throw a fair coin tests times then the probability that 274 // either heads or tails appears less than mincount is less than 2^{-32}. 275 // Therefore the test below is not expected to fail unless the generation 276 // of the one time keys is indeed biased. 277 final int tests = 1024; 278 final int mincount = 410; 279 280 BigInteger[] kList = new BigInteger[tests]; 281 byte[][] message = getMessagesToSign(tests, signer, keystorePrivateKey); 282 signer.initSign(keystorePrivateKey); 283 String hashAlgorithm = getHashAlgorithm(algorithm); 284 for (int i = 0; i < tests; i++) { 285 signer.update(message[i]); 286 byte[] signature = signer.sign(); 287 byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(message[i]); 288 // TODO(bleichen): Truncate the digest if the digest size is larger than the 289 // curve size. 290 BigInteger h = new BigInteger(1, digest); 291 kList[i] = extractK(signature, h, priv); 292 } 293 294 // Checks whether the most significant bits and the least significant bits 295 // of the value k are unbiased. 296 int countMsb = 0; // count the number of k's with lsb set 297 int countLsb = 0; // count the number of k's with msb set 298 BigInteger q = priv.getParams().getOrder(); 299 BigInteger qHalf = q.shiftRight(1); 300 for (BigInteger k : kList) { 301 if (k.testBit(0)) { 302 countLsb++; 303 } 304 if (k.compareTo(qHalf) > 0) { 305 countMsb++; 306 } 307 } 308 if (countLsb < mincount || countLsb > tests - mincount) { 309 fail("Bias detected in the least significant bit of k:" + countLsb); 310 } 311 if (countMsb < mincount || countMsb > tests - mincount) { 312 fail("Bias detected in the most significant bit of k:" + countMsb); 313 } 314 315 // One situation where the bits above are not biased even if k itself is 316 // badly distributed is the case where the signer replaces s by 317 // min(s, q - s). Such a replacement is sometimes done to avoid signature 318 // malleability of ECDSA. 319 // Breitner and Heninger describe such cases in the paper 320 // "Biased Nonce Sense: Lattice Attacks against Weak ECDSA Signatures in Cryptocurrencies", 321 // https://eprint.iacr.org/2019/023.pdf 322 // The following tests should catch the bugs described in this paper. 323 // The threshold below has been chosen to give false positives with probability < 2^{-32}. 324 double threshold = 5; 325 326 // This test detects for example the case when either k or q-k is small. 327 double bias1 = bias(kList, q, BigInteger.ONE); 328 if (bias1 > threshold) { 329 fail("Bias for k detected. bias1 = " + bias1); 330 } 331 // Same as above but shifing by one bit. 332 double bias2 = bias(kList, q, BigInteger.valueOf(2)); 333 if (bias2 > threshold) { 334 fail("Bias for k detected. bias2 = " + bias2); 335 } 336 double bias3 = bias(kList, q, qHalf); 337 if (bias3 > threshold) { 338 fail("Bias for k detected. bias3 = " + bias3); 339 } 340 // Checks whether most significant bytes, words, dwords or qwords are strongly correlated. 341 for (int bits : new int[] {8, 16, 32, 64}) { 342 BigInteger multiplier = BigInteger.ONE.shiftLeft(bits).subtract(BigInteger.ONE); 343 double bias4 = bias(kList, q, multiplier); 344 if (bias4 > threshold) { 345 fail("Bias for k detected. bits = " + bits + " bias4 = " + bias4); 346 } 347 } 348 } 349 350 @Test testBiasSecp224r1()351 public void testBiasSecp224r1() throws Exception { 352 testBias("SHA224WithECDSA", "secp224r1"); 353 } 354 355 @Test testBiasSecp256r1()356 public void testBiasSecp256r1() throws Exception { 357 testBias("SHA256WithECDSA", "secp256r1"); 358 } 359 360 @Test testBiasSecp256r1_StrongBox()361 public void testBiasSecp256r1_StrongBox() throws Exception { 362 testBias("SHA256WithECDSA", "secp256r1", true); 363 } 364 365 @Test testBiasSecp384r1()366 public void testBiasSecp384r1() throws Exception { 367 testBias("SHA384WithECDSA", "secp384r1"); 368 } 369 370 @Test testBiasSecp521r1()371 public void testBiasSecp521r1() throws Exception { 372 testBias("SHA512WithECDSA", "secp521r1"); 373 } 374 375 @Test 376 @Ignore // Brainpool curve are not supported in AndroidKeyStore testBiasBrainpoolP256r1()377 public void testBiasBrainpoolP256r1() throws Exception { 378 testBias("SHA512WithECDSA", "brainpoolP256r1"); 379 } 380 381 /** 382 * This test uses the deterministic ECDSA implementation from BouncyCastle (if BouncyCastle is 383 * being tested.) 384 */ 385 @Test 386 @Ignore // Algorithm SHA256WithECDDSA is not supported in AndroidKeyStore. testBiasSecp256r1ECDDSA()387 public void testBiasSecp256r1ECDDSA() throws Exception { 388 testBias("SHA256WithECDDSA", "secp256r1"); 389 } 390 391 /** 392 * Tests initSign with a null value for SecureRandom. The expected behaviour is that a default 393 * instance of SecureRandom is used and that this instance is properly seeded. I.e., the expected 394 * behaviour is that Signature.initSign(ECPrivateKey, null) behaves like 395 * Signature.initSign(ECPrivateKey). If the signature scheme normally is randomized then 396 * Signature.initSign(ECprivateKey, null) should still be a randomized signature scheme. If the 397 * implementation is deterministic then we simply want this to work. 398 * 399 * <p>In principle, the correct behaviour is not really defined. However, if a provider would 400 * throw a null pointer exception then this can lead to unnecessary breakages. 401 */ testNullRandom(String algorithm, String curve)402 public void testNullRandom(String algorithm, String curve) throws Exception { 403 testNullRandom(algorithm, curve, false); 404 } testNullRandom(String algorithm, String curve, boolean isStrongBox)405 public void testNullRandom(String algorithm, String curve, boolean isStrongBox) 406 throws Exception { 407 if (isStrongBox) { 408 KeyStoreUtil.assumeStrongBox(); 409 } 410 int samples = 8; 411 Signature signer = Signature.getInstance(algorithm); 412 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); 413 keyGen.initialize(new ECGenParameterSpec(curve)); 414 KeyPair keyPair = keyGen.generateKeyPair(); 415 PrivateKey priv = getKeystorePrivateKey(keyPair.getPublic(), keyPair.getPrivate(), 416 isStrongBox); 417 byte[][] message = getMessagesToSign(samples, signer, priv); 418 HashSet<BigInteger> rSet = new HashSet<>(); 419 for (int i = 0; i < samples; i++) { 420 // This is the function call that is tested by this test. 421 signer.initSign(priv, null); 422 signer.update(message[i]); 423 byte[] signature = signer.sign(); 424 BigInteger r = extractR(signature); 425 assertTrue("Same r computed twice", rSet.add(r)); 426 } 427 } 428 429 @Test testNullRandomSecp224r1()430 public void testNullRandomSecp224r1() throws Exception { 431 testNullRandom("SHA224WithECDSA", "secp224r1"); 432 } 433 434 @Test testNullRandomSecp256r1()435 public void testNullRandomSecp256r1() throws Exception { 436 testNullRandom("SHA256WithECDSA", "secp256r1"); 437 } 438 439 @Test testNullRandomSecp256r1_StrongBox()440 public void testNullRandomSecp256r1_StrongBox() throws Exception { 441 testNullRandom("SHA256WithECDSA", "secp256r1", true); 442 } 443 444 @Test testNullRandomSecp384r1()445 public void testNullRandomSecp384r1() throws Exception { 446 testNullRandom("SHA384WithECDSA", "secp384r1"); 447 } 448 449 @Test testNullRandomSecp521r1()450 public void testNullRandomSecp521r1() throws Exception { 451 testNullRandom("SHA512WithECDSA", "secp521r1"); 452 } 453 454 /** 455 * This test uses the deterministic ECDSA implementation from BouncyCastle (if BouncyCastle is 456 * being tested.) 457 */ 458 @Test 459 @Ignore // Algorithm SHA256WithECdDSA is not supported in AndroidKeyStore. testNullRandomSecp256r1ECDDSA()460 public void testNullRandomSecp256r1ECDDSA() throws Exception { 461 testNullRandom("SHA256WithECdDSA", "secp256r1"); 462 } 463 } 464