1 /* Copyright (c) 2016, Google Inc.
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15 #include <stdio.h>
16
17 #include <utility>
18 #include <vector>
19
20 #include <gtest/gtest.h>
21
22 #include <openssl/bn.h>
23 #include <openssl/bytestring.h>
24 #include <openssl/crypto.h>
25 #include <openssl/ec.h>
26 #include <openssl/ec_key.h>
27 #include <openssl/ecdh.h>
28 #include <openssl/err.h>
29 #include <openssl/evp.h>
30 #include <openssl/nid.h>
31 #include <openssl/sha.h>
32
33 #include "../test/file_test.h"
34 #include "../test/test_util.h"
35 #include "../test/wycheproof_util.h"
36
37
GetCurve(FileTest * t,const char * key)38 static const EC_GROUP *GetCurve(FileTest *t, const char *key) {
39 std::string curve_name;
40 if (!t->GetAttribute(&curve_name, key)) {
41 return nullptr;
42 }
43
44 if (curve_name == "P-224") {
45 return EC_group_p224();
46 }
47 if (curve_name == "P-256") {
48 return EC_group_p256();
49 }
50 if (curve_name == "P-384") {
51 return EC_group_p384();
52 }
53 if (curve_name == "P-521") {
54 return EC_group_p521();
55 }
56
57 t->PrintLine("Unknown curve '%s'", curve_name.c_str());
58 return nullptr;
59 }
60
GetBIGNUM(FileTest * t,const char * key)61 static bssl::UniquePtr<BIGNUM> GetBIGNUM(FileTest *t, const char *key) {
62 std::vector<uint8_t> bytes;
63 if (!t->GetBytes(&bytes, key)) {
64 return nullptr;
65 }
66
67 return bssl::UniquePtr<BIGNUM>(BN_bin2bn(bytes.data(), bytes.size(), nullptr));
68 }
69
TEST(ECDHTest,TestVectors)70 TEST(ECDHTest, TestVectors) {
71 FileTestGTest("crypto/ecdh_extra/ecdh_tests.txt", [](FileTest *t) {
72 const EC_GROUP *group = GetCurve(t, "Curve");
73 ASSERT_TRUE(group);
74 bssl::UniquePtr<BIGNUM> priv_key = GetBIGNUM(t, "Private");
75 ASSERT_TRUE(priv_key);
76 bssl::UniquePtr<BIGNUM> x = GetBIGNUM(t, "X");
77 ASSERT_TRUE(x);
78 bssl::UniquePtr<BIGNUM> y = GetBIGNUM(t, "Y");
79 ASSERT_TRUE(y);
80 bssl::UniquePtr<BIGNUM> peer_x = GetBIGNUM(t, "PeerX");
81 ASSERT_TRUE(peer_x);
82 bssl::UniquePtr<BIGNUM> peer_y = GetBIGNUM(t, "PeerY");
83 ASSERT_TRUE(peer_y);
84 std::vector<uint8_t> z;
85 ASSERT_TRUE(t->GetBytes(&z, "Z"));
86
87 bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
88 ASSERT_TRUE(key);
89 bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group));
90 ASSERT_TRUE(pub_key);
91 bssl::UniquePtr<EC_POINT> peer_pub_key(EC_POINT_new(group));
92 ASSERT_TRUE(peer_pub_key);
93 ASSERT_TRUE(EC_KEY_set_group(key.get(), group));
94 ASSERT_TRUE(EC_KEY_set_private_key(key.get(), priv_key.get()));
95 ASSERT_TRUE(EC_POINT_set_affine_coordinates_GFp(group, pub_key.get(),
96 x.get(), y.get(), nullptr));
97 ASSERT_TRUE(EC_POINT_set_affine_coordinates_GFp(
98 group, peer_pub_key.get(), peer_x.get(), peer_y.get(), nullptr));
99 ASSERT_TRUE(EC_KEY_set_public_key(key.get(), pub_key.get()));
100 ASSERT_TRUE(EC_KEY_check_key(key.get()));
101
102 std::vector<uint8_t> actual_z;
103 // Make |actual_z| larger than expected to ensure |ECDH_compute_key| returns
104 // the right amount of data.
105 actual_z.resize(z.size() + 1);
106 int ret = ECDH_compute_key(actual_z.data(), actual_z.size(),
107 peer_pub_key.get(), key.get(), nullptr);
108 ASSERT_GE(ret, 0);
109 EXPECT_EQ(Bytes(z), Bytes(actual_z.data(), static_cast<size_t>(ret)));
110
111 // Test |ECDH_compute_key| truncates.
112 actual_z.resize(z.size() - 1);
113 ret = ECDH_compute_key(actual_z.data(), actual_z.size(), peer_pub_key.get(),
114 key.get(), nullptr);
115 ASSERT_GE(ret, 0);
116 EXPECT_EQ(Bytes(z.data(), z.size() - 1),
117 Bytes(actual_z.data(), static_cast<size_t>(ret)));
118
119 // Test that |ECDH_compute_key_fips| hashes as expected.
120 uint8_t digest[SHA256_DIGEST_LENGTH], expected_digest[SHA256_DIGEST_LENGTH];
121 ASSERT_TRUE(ECDH_compute_key_fips(digest, sizeof(digest),
122 peer_pub_key.get(), key.get()));
123 SHA256(z.data(), z.size(), expected_digest);
124 EXPECT_EQ(Bytes(digest), Bytes(expected_digest));
125 });
126 }
127
128
RunWycheproofTest(FileTest * t)129 static void RunWycheproofTest(FileTest *t) {
130 t->IgnoreInstruction("encoding");
131
132 const EC_GROUP *group = GetWycheproofCurve(t, "curve", true);
133 ASSERT_TRUE(group);
134 bssl::UniquePtr<BIGNUM> priv_key = GetWycheproofBIGNUM(t, "private", false);
135 ASSERT_TRUE(priv_key);
136 std::vector<uint8_t> peer_spki;
137 ASSERT_TRUE(t->GetBytes(&peer_spki, "public"));
138 WycheproofResult result;
139 ASSERT_TRUE(GetWycheproofResult(t, &result));
140 std::vector<uint8_t> shared;
141 ASSERT_TRUE(t->GetBytes(&shared, "shared"));
142 // BoringSSL supports compressed coordinates.
143 bool is_valid = result.IsValid({"CompressedPoint"});
144
145 // Wycheproof stores the peer key in an SPKI to mimic a Java API mistake.
146 // This is non-standard and error-prone.
147 CBS cbs;
148 CBS_init(&cbs, peer_spki.data(), peer_spki.size());
149 bssl::UniquePtr<EVP_PKEY> peer_evp(EVP_parse_public_key(&cbs));
150 if (!peer_evp || CBS_len(&cbs) != 0) {
151 EXPECT_FALSE(is_valid);
152 return;
153 }
154 EC_KEY *peer_ec = EVP_PKEY_get0_EC_KEY(peer_evp.get());
155 ASSERT_TRUE(peer_ec);
156
157 bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
158 ASSERT_TRUE(key);
159 ASSERT_TRUE(EC_KEY_set_group(key.get(), group));
160 ASSERT_TRUE(EC_KEY_set_private_key(key.get(), priv_key.get()));
161
162 std::vector<uint8_t> actual((EC_GROUP_get_degree(group) + 7) / 8);
163 int ret =
164 ECDH_compute_key(actual.data(), actual.size(),
165 EC_KEY_get0_public_key(peer_ec), key.get(), nullptr);
166 if (is_valid) {
167 EXPECT_EQ(static_cast<int>(actual.size()), ret);
168 EXPECT_EQ(Bytes(shared), Bytes(actual.data(), static_cast<size_t>(ret)));
169 } else {
170 EXPECT_EQ(-1, ret);
171 }
172 }
173
TEST(ECDHTest,WycheproofP224)174 TEST(ECDHTest, WycheproofP224) {
175 FileTestGTest("third_party/wycheproof_testvectors/ecdh_secp224r1_test.txt",
176 RunWycheproofTest);
177 }
178
TEST(ECDHTest,WycheproofP256)179 TEST(ECDHTest, WycheproofP256) {
180 FileTestGTest("third_party/wycheproof_testvectors/ecdh_secp256r1_test.txt",
181 RunWycheproofTest);
182 }
183
TEST(ECDHTest,WycheproofP384)184 TEST(ECDHTest, WycheproofP384) {
185 FileTestGTest("third_party/wycheproof_testvectors/ecdh_secp384r1_test.txt",
186 RunWycheproofTest);
187 }
188
TEST(ECDHTest,WycheproofP512)189 TEST(ECDHTest, WycheproofP512) {
190 FileTestGTest("third_party/wycheproof_testvectors/ecdh_secp521r1_test.txt",
191 RunWycheproofTest);
192 }
193
194 // MakeCustomGroup returns an |EC_GROUP| containing a non-standard group. (P-256
195 // with the wrong generator.)
MakeCustomGroup()196 static bssl::UniquePtr<EC_GROUP> MakeCustomGroup() {
197 static const uint8_t kP[] = {
198 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
199 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
200 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
201 };
202 static const uint8_t kA[] = {
203 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
204 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
205 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
206 };
207 static const uint8_t kB[] = {
208 0x5a, 0xc6, 0x35, 0xd8, 0xaa, 0x3a, 0x93, 0xe7, 0xb3, 0xeb, 0xbd,
209 0x55, 0x76, 0x98, 0x86, 0xbc, 0x65, 0x1d, 0x06, 0xb0, 0xcc, 0x53,
210 0xb0, 0xf6, 0x3b, 0xce, 0x3c, 0x3e, 0x27, 0xd2, 0x60, 0x4b,
211 };
212 static const uint8_t kX[] = {
213 0xe6, 0x2b, 0x69, 0xe2, 0xbf, 0x65, 0x9f, 0x97, 0xbe, 0x2f, 0x1e,
214 0x0d, 0x94, 0x8a, 0x4c, 0xd5, 0x97, 0x6b, 0xb7, 0xa9, 0x1e, 0x0d,
215 0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e, 0x9d, 0xdc, 0xba, 0x5a,
216 };
217 static const uint8_t kY[] = {
218 0x01, 0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9, 0xc3, 0xc4, 0xa3,
219 0x1e, 0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a, 0x1c, 0xf5,
220 0x1d, 0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1,
221 };
222 static const uint8_t kOrder[] = {
223 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
224 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17,
225 0x9e, 0x84, 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51,
226 };
227 bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
228 bssl::UniquePtr<BIGNUM> p(BN_bin2bn(kP, sizeof(kP), nullptr));
229 bssl::UniquePtr<BIGNUM> a(BN_bin2bn(kA, sizeof(kA), nullptr));
230 bssl::UniquePtr<BIGNUM> b(BN_bin2bn(kB, sizeof(kB), nullptr));
231 bssl::UniquePtr<BIGNUM> x(BN_bin2bn(kX, sizeof(kX), nullptr));
232 bssl::UniquePtr<BIGNUM> y(BN_bin2bn(kY, sizeof(kY), nullptr));
233 bssl::UniquePtr<BIGNUM> order(BN_bin2bn(kOrder, sizeof(kOrder), nullptr));
234 if (!ctx || !p || !a || !b || !x || !y || !order) {
235 return nullptr;
236 }
237 bssl::UniquePtr<EC_GROUP> group(
238 EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), ctx.get()));
239 if (!group) {
240 return nullptr;
241 }
242 bssl::UniquePtr<EC_POINT> generator(EC_POINT_new(group.get()));
243 if (!generator ||
244 !EC_POINT_set_affine_coordinates_GFp(group.get(), generator.get(),
245 x.get(), y.get(), ctx.get()) ||
246 !EC_GROUP_set_generator(group.get(), generator.get(), order.get(),
247 BN_value_one())) {
248 return nullptr;
249 }
250 return group;
251 }
252
TEST(ECDHTest,GroupMismatch)253 TEST(ECDHTest, GroupMismatch) {
254 const size_t num_curves = EC_get_builtin_curves(nullptr, 0);
255 std::vector<EC_builtin_curve> curves(num_curves);
256 EC_get_builtin_curves(curves.data(), num_curves);
257
258 // Instantiate all the built-in curves.
259 std::vector<bssl::UniquePtr<EC_GROUP>> groups;
260 for (const auto &curve : curves) {
261 groups.emplace_back(EC_GROUP_new_by_curve_name(curve.nid));
262 ASSERT_TRUE(groups.back());
263 }
264
265 // Also create some arbitrary group. (This is P-256 with the wrong generator.)
266 groups.push_back(MakeCustomGroup());
267 ASSERT_TRUE(groups.back());
268
269 for (const auto &a : groups) {
270 for (const auto &b : groups) {
271 if (a.get() == b.get()) {
272 continue;
273 }
274
275 bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
276 ASSERT_TRUE(key);
277 ASSERT_TRUE(EC_KEY_set_group(key.get(), a.get()));
278 ASSERT_TRUE(EC_KEY_generate_key(key.get()));
279
280 // ECDH across the groups should not work.
281 char out[64];
282 const EC_POINT *peer = EC_GROUP_get0_generator(b.get());
283 EXPECT_EQ(-1,
284 ECDH_compute_key(out, sizeof(out), peer, key.get(), nullptr));
285 ERR_clear_error();
286 }
287 }
288 }
289