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.assertEquals; 17 18 import com.google.gson.JsonElement; 19 import com.google.gson.JsonObject; 20 import java.math.BigInteger; 21 import java.security.InvalidKeyException; 22 import java.security.KeyFactory; 23 import java.security.NoSuchAlgorithmException; 24 import java.security.PrivateKey; 25 import java.security.PublicKey; 26 import java.security.spec.ECPrivateKeySpec; 27 import java.security.spec.InvalidKeySpecException; 28 import java.security.spec.X509EncodedKeySpec; 29 import javax.crypto.KeyAgreement; 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.JUnit4; 33 34 /** This test uses test vectors in JSON format to check implementations of ECDH. */ 35 @RunWith(JUnit4.class) 36 public class JsonEcdhTest { 37 38 /** Convenience mehtod to get a String from a JsonObject */ getString(JsonObject object, String name)39 protected static String getString(JsonObject object, String name) throws Exception { 40 return object.get(name).getAsString(); 41 } 42 43 /** Convenience method to get a BigInteger from a JsonObject */ getBigInteger(JsonObject object, String name)44 protected static BigInteger getBigInteger(JsonObject object, String name) throws Exception { 45 return JsonUtil.asBigInteger(object.get(name)); 46 } 47 48 /** Convenience method to get a byte array from a JsonObject */ getBytes(JsonObject object, String name)49 protected static byte[] getBytes(JsonObject object, String name) throws Exception { 50 return JsonUtil.asByteArray(object.get(name)); 51 } 52 53 /** 54 * Example for test vector 55 * { 56 * "algorithm" : "ECDH", 57 * "header" : [], 58 * "notes" : { 59 * "AddSubChain" : "The private key has a special value....", 60 * } 61 * "generatorVersion" : "0.7", 62 * "numberOfTests" : 308, 63 * "testGroups" : [ 64 * { 65 * "type" : "EcdhTest", 66 * "tests" : [ 67 * { 68 * "comment" : "normal case", 69 * "curve" : "secp224r1", 70 * "private" : "565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328", 71 * "public" : "30...", 72 * "result" : "valid", 73 * "shared" : "b8ecdb552d39228ee332bafe4886dbff272f7109edf933bc7542bd4f", 74 * "tcId" : 1 75 * }, 76 * ... 77 **/ testEcdhComp(String filename)78 public void testEcdhComp(String filename) throws Exception { 79 JsonObject test = JsonUtil.getTestVectors(filename); 80 81 // This test expects test vectors as defined in wycheproof/schemas/ecdh_test_schema.json. 82 // In particular, this means that the public keys use X509 encoding. 83 // Test vectors with different encodings of the keys have a different schema. 84 final String expectedSchema = "ecdh_test_schema.json"; 85 String schema = test.get("schema").getAsString(); 86 assertEquals("Unexpected schema in file:" + filename, expectedSchema, schema); 87 88 int numTests = test.get("numberOfTests").getAsInt(); 89 int passedTests = 0; 90 int rejectedTests = 0; // invalid test vectors leading to exceptions 91 int skippedTests = 0; // valid test vectors leading to exceptions 92 int errors = 0; 93 for (JsonElement g : test.getAsJsonArray("testGroups")) { 94 JsonObject group = g.getAsJsonObject(); 95 String curve = getString(group, "curve"); 96 for (JsonElement t : group.getAsJsonArray("tests")) { 97 JsonObject testcase = t.getAsJsonObject(); 98 int tcid = testcase.get("tcId").getAsInt(); 99 String comment = getString(testcase, "comment"); 100 BigInteger priv = getBigInteger(testcase, "private"); 101 byte[] publicEncoded = getBytes(testcase, "public"); 102 String result = getString(testcase, "result"); 103 String expectedHex = getString(testcase, "shared"); 104 KeyFactory kf = KeyFactory.getInstance("EC"); 105 try { 106 ECPrivateKeySpec spec = new ECPrivateKeySpec(priv, EcUtil.getCurveSpecRef(curve)); 107 PrivateKey privKey = kf.generatePrivate(spec); 108 X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(publicEncoded); 109 PublicKey pubKey = kf.generatePublic(x509keySpec); 110 KeyAgreement ka = KeyAgreement.getInstance("ECDH"); 111 ka.init(privKey); 112 ka.doPhase(pubKey, true); 113 String sharedHex = TestUtil.bytesToHex(ka.generateSecret()); 114 if (result.equals("invalid")) { 115 System.out.println( 116 "Computed ECDH with invalid parameters" 117 + " tcId:" 118 + tcid 119 + " comment:" 120 + comment 121 + " shared:" 122 + sharedHex); 123 errors++; 124 } else if (!expectedHex.equals(sharedHex)) { 125 System.out.println( 126 "Incorrect ECDH computation" 127 + " tcId:" 128 + tcid 129 + " comment:" 130 + comment 131 + "\nshared:" 132 + sharedHex 133 + "\nexpected:" 134 + expectedHex); 135 errors++; 136 } else { 137 passedTests++; 138 } 139 } catch (InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException ex) { 140 // These are the exception that we expect to see when a curve is not implemented 141 // or when a key is not valid. 142 if (result.equals("valid")) { 143 skippedTests++; 144 } else { 145 rejectedTests++; 146 } 147 } catch (Exception ex) { 148 // Other exceptions typically indicate that something is wrong with the implementation. 149 System.out.println( 150 "Test vector with tcId:" + tcid + " comment:" + comment + " throws:" + ex.toString()); 151 errors++; 152 } 153 } 154 } 155 assertEquals(0, errors); 156 assertEquals(numTests, passedTests + rejectedTests + skippedTests); 157 } 158 159 @Test testSecp224r1()160 public void testSecp224r1() throws Exception { 161 testEcdhComp("ecdh_secp224r1_test.json"); 162 } 163 164 @Test testSecp256r1()165 public void testSecp256r1() throws Exception { 166 testEcdhComp("ecdh_secp256r1_test.json"); 167 } 168 169 @Test testSecp384r1()170 public void testSecp384r1() throws Exception { 171 testEcdhComp("ecdh_secp384r1_test.json"); 172 } 173 174 @Test testSecp521r1()175 public void testSecp521r1() throws Exception { 176 testEcdhComp("ecdh_secp521r1_test.json"); 177 } 178 179 @Test testSecp256k1()180 public void testSecp256k1() throws Exception { 181 testEcdhComp("ecdh_secp256k1_test.json"); 182 } 183 184 @Test testBrainpoolP224r1()185 public void testBrainpoolP224r1() throws Exception { 186 testEcdhComp("ecdh_brainpoolP224r1_test.json"); 187 } 188 189 @Test testBrainpoolP256r1()190 public void testBrainpoolP256r1() throws Exception { 191 testEcdhComp("ecdh_brainpoolP256r1_test.json"); 192 } 193 194 @Test testBrainpoolP320r1()195 public void testBrainpoolP320r1() throws Exception { 196 testEcdhComp("ecdh_brainpoolP320r1_test.json"); 197 } 198 199 @Test testBrainpoolP384r1()200 public void testBrainpoolP384r1() throws Exception { 201 testEcdhComp("ecdh_brainpoolP384r1_test.json"); 202 } 203 204 @Test testBrainpoolP512r1()205 public void testBrainpoolP512r1() throws Exception { 206 testEcdhComp("ecdh_brainpoolP512r1_test.json"); 207 } 208 209 } 210