sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
dcheng | a0ee5fb | 2016-04-26 02:46:55 | [diff] [blame] | 5 | #include "components/client_update_protocol/ecdsa.h" |
| 6 | |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 7 | #include <stdint.h> |
| 8 | |
| 9 | #include <limits> |
dcheng | a0ee5fb | 2016-04-26 02:46:55 | [diff] [blame] | 10 | #include <memory> |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 11 | #include <vector> |
| 12 | |
| 13 | #include "base/base64.h" |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 14 | #include "base/strings/string_piece.h" |
| 15 | #include "base/strings/stringprintf.h" |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 16 | #include "crypto/random.h" |
| 17 | #include "crypto/secure_util.h" |
| 18 | #include "testing/gtest/include/gtest/gtest.h" |
| 19 | |
mab | bb61b52a | 2016-03-17 23:37:23 | [diff] [blame] | 20 | namespace client_update_protocol { |
sorin | 1bc5eff | 2016-02-17 18:45:17 | [diff] [blame] | 21 | |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 22 | namespace { |
| 23 | |
| 24 | std::string GetPublicKeyForTesting() { |
| 25 | // How to generate this key: |
| 26 | // openssl ecparam -genkey -name prime256v1 -out ecpriv.pem |
| 27 | // openssl ec -in ecpriv.pem -pubout -out ecpub.pem |
| 28 | |
| 29 | static const char kCupEcdsaTestKey_Base64[] = |
| 30 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJNOjKyN6UHyUGkGow+xCmQthQXUo" |
| 31 | "9sd7RIXSpVIM768UlbGb/5JrnISjSYejCc/pxQooI6mJTzWL3pZb5TA1DA=="; |
| 32 | |
| 33 | std::string result; |
| 34 | if (!base::Base64Decode(std::string(kCupEcdsaTestKey_Base64), &result)) |
| 35 | return std::string(); |
| 36 | |
| 37 | return result; |
| 38 | } |
| 39 | |
| 40 | } // end namespace |
| 41 | |
| 42 | class CupEcdsaTest : public testing::Test { |
| 43 | protected: |
| 44 | void SetUp() override { |
mab | bb61b52a | 2016-03-17 23:37:23 | [diff] [blame] | 45 | cup_ = Ecdsa::Create(8, GetPublicKeyForTesting()); |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 46 | ASSERT_TRUE(cup_.get()); |
| 47 | } |
| 48 | |
Zinovy Nis | aa5aee39 | 2018-05-08 16:18:41 | [diff] [blame] | 49 | Ecdsa& CUP() { return *cup_; } |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 50 | |
| 51 | private: |
dcheng | a0ee5fb | 2016-04-26 02:46:55 | [diff] [blame] | 52 | std::unique_ptr<Ecdsa> cup_; |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 53 | }; |
| 54 | |
| 55 | TEST_F(CupEcdsaTest, SignRequest) { |
| 56 | static const char kRequest[] = "TestSequenceForCupEcdsaUnitTest"; |
| 57 | static const char kRequestHash[] = |
| 58 | "&cup2hreq=" |
| 59 | "cde1f7dc1311ed96813057ca321c2f5a17ea2c9c776ee0eb31965f7985a3074a"; |
| 60 | static const char kKeyId[] = "cup2key=8:"; |
| 61 | |
| 62 | std::string query; |
| 63 | CUP().SignRequest(kRequest, &query); |
| 64 | std::string query2; |
| 65 | CUP().SignRequest(kRequest, &query2); |
| 66 | |
| 67 | EXPECT_FALSE(query.empty()); |
| 68 | EXPECT_FALSE(query2.empty()); |
| 69 | EXPECT_EQ(0UL, query.find(kKeyId)); |
| 70 | EXPECT_EQ(0UL, query2.find(kKeyId)); |
| 71 | EXPECT_NE(std::string::npos, query.find(kRequestHash)); |
| 72 | EXPECT_NE(std::string::npos, query2.find(kRequestHash)); |
| 73 | |
| 74 | // In theory, this is a flaky test, as there's nothing preventing the RNG |
| 75 | // from returning the same nonce twice in a row. In practice, this should |
| 76 | // be fine. |
| 77 | EXPECT_NE(query, query2); |
| 78 | } |
| 79 | |
| 80 | TEST_F(CupEcdsaTest, ValidateResponse_TestETagParsing) { |
| 81 | // Invalid ETags must be gracefully rejected without a crash. |
| 82 | std::string query_discard; |
| 83 | CUP().SignRequest("Request_A", &query_discard); |
mab | 2f07cb9 | 2016-05-10 20:55:51 | [diff] [blame] | 84 | CUP().OverrideNonceForTesting(8, 12345); |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 85 | |
| 86 | // Expect a pass for a well-formed etag. |
| 87 | EXPECT_TRUE(CUP().ValidateResponse( |
| 88 | "Response_A", |
| 89 | "3044" |
| 90 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 91 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 92 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 93 | |
| 94 | // Reject empty etags. |
| 95 | EXPECT_FALSE(CUP().ValidateResponse("Response_A", "")); |
| 96 | |
| 97 | // Reject etags with zero-length hashes or signatures, even if the other |
| 98 | // component is wellformed. |
| 99 | EXPECT_FALSE(CUP().ValidateResponse("Response_A", ":")); |
| 100 | EXPECT_FALSE(CUP().ValidateResponse( |
| 101 | "Response_A", |
| 102 | "3044" |
| 103 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 104 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 105 | ":")); |
| 106 | EXPECT_FALSE(CUP().ValidateResponse( |
| 107 | "Response_A", |
| 108 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 109 | |
| 110 | // Reject etags with non-hex content in either component. |
| 111 | EXPECT_FALSE(CUP().ValidateResponse( |
| 112 | "Response_A", |
| 113 | "3044" |
| 114 | "02207fb15d24e66c168ac150458__ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 115 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 116 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 117 | EXPECT_FALSE(CUP().ValidateResponse( |
| 118 | "Response_A", |
| 119 | "3044" |
| 120 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 121 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 122 | ":2727bc2b3c33feb6800a830f4055901d__7d65a84184c5fbeb3f816db0a243f5")); |
| 123 | |
| 124 | // Reject etags where either/both component has a length that's not a |
| 125 | // multiple of 2 (i.e. not a valid hex encoding). |
| 126 | EXPECT_FALSE(CUP().ValidateResponse( |
| 127 | "Response_A", |
| 128 | "3044" |
| 129 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 130 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10" |
| 131 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 132 | EXPECT_FALSE(CUP().ValidateResponse( |
| 133 | "Response_A", |
| 134 | "3044" |
| 135 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 136 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 137 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f")); |
| 138 | EXPECT_FALSE(CUP().ValidateResponse( |
| 139 | "Response_A", |
| 140 | "3044" |
| 141 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 142 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10" |
| 143 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f")); |
| 144 | |
| 145 | // Reject etags where the hash is even, but not 256 bits. |
| 146 | EXPECT_FALSE(CUP().ValidateResponse( |
| 147 | "Response_A", |
| 148 | "3044" |
| 149 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 150 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 151 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 152 | EXPECT_FALSE(CUP().ValidateResponse( |
| 153 | "Response_A", |
| 154 | "3044" |
| 155 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 156 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 157 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5ff")); |
| 158 | |
| 159 | // Reject etags where the signature field is too small to be valid. (Note that |
| 160 | // the case isn't even a signature -- it's a validly encoded ASN.1 NULL.) |
| 161 | EXPECT_FALSE(CUP().ValidateResponse( |
| 162 | "Response_A", |
| 163 | "0500" |
| 164 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 165 | |
| 166 | // Reject etags where the signature field is too big to be a valid signature. |
| 167 | // (This is a validly formed structure, but both ints are over 256 bits.) |
| 168 | EXPECT_FALSE(CUP().ValidateResponse( |
| 169 | "Response_A", |
| 170 | "3048" |
| 171 | "202207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| 172 | "202207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| 173 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5ff")); |
| 174 | |
| 175 | // Reject etags where the signature is valid DER-encoded ASN.1, but is not |
| 176 | // an ECDSA signature. (This is actually stressing crypto's SignatureValidator |
| 177 | // library, and not CUP's use of it, but it's worth testing here.) Cases: |
| 178 | // * Something that's not a sequence |
| 179 | // * Sequences that contain things other than ints (i.e. octet strings) |
| 180 | // * Sequences that contain a negative int. |
| 181 | EXPECT_FALSE(CUP().ValidateResponse( |
| 182 | "Response_A", |
| 183 | "0406020100020100" |
| 184 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 185 | EXPECT_FALSE(CUP().ValidateResponse( |
| 186 | "Response_A", |
| 187 | "3044" |
| 188 | "06200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
| 189 | "06200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
| 190 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 191 | EXPECT_FALSE(CUP().ValidateResponse( |
| 192 | "Response_A", |
| 193 | "3046" |
| 194 | "02047fffffff" |
| 195 | "0220ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| 196 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 197 | |
| 198 | // Reject etags where the signature is not a valid DER encoding. (Again, this |
| 199 | // is stressing SignatureValidator.) Test cases are: |
| 200 | // * No length field |
| 201 | // * Zero length field |
| 202 | // * One of the ints has truncated content |
| 203 | // * One of the ints has content longer than its length field |
| 204 | // * A positive int is improperly zero-padded |
| 205 | EXPECT_FALSE(CUP().ValidateResponse( |
| 206 | "Response_A", |
| 207 | "30" |
| 208 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 209 | EXPECT_FALSE(CUP().ValidateResponse( |
| 210 | "Response_A", |
| 211 | "3000" |
| 212 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 213 | EXPECT_FALSE(CUP().ValidateResponse( |
| 214 | "Response_A", |
| 215 | "3044" |
| 216 | "02207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" |
| 217 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 218 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 219 | EXPECT_FALSE(CUP().ValidateResponse( |
| 220 | "Response_A", |
| 221 | "3044" |
| 222 | "02207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00" |
| 223 | "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 224 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243")); |
| 225 | EXPECT_FALSE(CUP().ValidateResponse( |
| 226 | "Response_A", |
| 227 | "3044" |
| 228 | "022000007f24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656" |
| 229 | "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e" |
| 230 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 231 | } |
| 232 | |
| 233 | TEST_F(CupEcdsaTest, ValidateResponse_TestSigning) { |
| 234 | std::string query_discard; |
| 235 | CUP().SignRequest("Request_A", &query_discard); |
mab | 2f07cb9 | 2016-05-10 20:55:51 | [diff] [blame] | 236 | CUP().OverrideNonceForTesting(8, 12345); |
sorin | 58086d5 | 2016-02-02 18:30:46 | [diff] [blame] | 237 | |
| 238 | // How to generate an ECDSA signature: |
| 239 | // echo -n Request_A | sha256sum | cut -d " " -f 1 > h |
| 240 | // echo -n Response_A | sha256sum | cut -d " " -f 1 >> h |
| 241 | // cat h | xxd -r -p > hbin |
| 242 | // echo -n 8:12345 >> hbin |
| 243 | // sha256sum hbin | cut -d " " -f 1 | xxd -r -p > hbin2 |
| 244 | // openssl dgst -hex -sha256 -sign ecpriv.pem hbin2 | cut -d " " -f 2 > sig |
| 245 | // echo -n :Request_A | sha256sum | cut -d " " -f 1 >> sig |
| 246 | // cat sig |
| 247 | // It's useful to throw this in a bash script and parameterize it if you're |
| 248 | // updating this unit test. |
| 249 | |
| 250 | // Valid case: |
| 251 | // * Send "Request_A" with key 8 / nonce 12345 to server. |
| 252 | // * Receive "Response_A", signature, and observed request hash from server. |
| 253 | // * Signature signs HASH(Request_A) | HASH(Response_A) | 8:12345. |
| 254 | // * Observed hash matches HASH(Request_A). |
| 255 | EXPECT_TRUE(CUP().ValidateResponse( |
| 256 | "Response_A", |
| 257 | "3045022077a2d004f1643a92af5d356877c3434c46519ce32882d6e30ef6d154ee9775e3" |
| 258 | "022100aca63c77d34152bdc0918ae0629e82b59314e5459f607cdc5ac95f1a4b7c31a2" |
| 259 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 260 | |
| 261 | // Failure case: "Request_A" made it to the server intact, but the response |
| 262 | // body is modified to "Response_B" on return. The signature is now invalid. |
| 263 | EXPECT_FALSE(CUP().ValidateResponse( |
| 264 | "Response_B", |
| 265 | "3045022077a2d004f1643a92af5d356877c3434c46519ce32882d6e30ef6d154ee9775e3" |
| 266 | "022100aca63c77d34152bdc0918ae0629e82b59314e5459f607cdc5ac95f1a4b7c31a2" |
| 267 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 268 | |
| 269 | // Failure case: Request body was modified to "Request_B" before it reached |
| 270 | // the server. Test a fast reject based on the observed_hash parameter. |
| 271 | EXPECT_FALSE(CUP().ValidateResponse( |
| 272 | "Response_B", |
| 273 | "304402206289a7765f0371c7c48796779747f1166707d5937a99af518845f44af95876" |
| 274 | "8c0220139fe935fde3e6b416ee742f91c6a480113762d78d889a2661de37576866d21c" |
| 275 | ":80e3ef1b373efe5f2a8383a0cf9c89fb2e0cbb8e85db4813655ff5dc05009e7e")); |
| 276 | |
| 277 | // Failure case: Request body was modified to "Request_B" before it reached |
| 278 | // the server. Test a slow reject based on a signature mismatch. |
| 279 | EXPECT_FALSE(CUP().ValidateResponse( |
| 280 | "Response_B", |
| 281 | "304402206289a7765f0371c7c48796779747f1166707d5937a99af518845f44af95876" |
| 282 | "8c0220139fe935fde3e6b416ee742f91c6a480113762d78d889a2661de37576866d21c" |
| 283 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 284 | |
| 285 | // Failure case: Request/response are intact, but the signature is invalid |
| 286 | // because it was signed against a different nonce (67890). |
| 287 | EXPECT_FALSE(CUP().ValidateResponse( |
| 288 | "Response_A", |
| 289 | "3046022100d3bbb1fb4451c8e04a07fe95404cc39121ed0e0bc084f87de19d52eee50a97" |
| 290 | "bf022100dd7d41d467be2af98d9116b0c7ba09740d54578c02a02f74da5f089834be3403" |
| 291 | ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); |
| 292 | } |
sorin | 1bc5eff | 2016-02-17 18:45:17 | [diff] [blame] | 293 | |
mab | bb61b52a | 2016-03-17 23:37:23 | [diff] [blame] | 294 | } // namespace client_update_protocol |