• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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