forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathecdsa.cc
189 lines (154 loc) · 7.39 KB
/
ecdsa.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/client_update_protocol/ecdsa.h"
#include "base/cxx17_backports.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "crypto/random.h"
#include "crypto/sha2.h"
#include "crypto/signature_verifier.h"
namespace client_update_protocol {
namespace {
std::vector<uint8_t> SHA256HashStr(const base::StringPiece& str) {
std::vector<uint8_t> result(crypto::kSHA256Length);
crypto::SHA256HashString(str, &result.front(), result.size());
return result;
}
std::vector<uint8_t> SHA256HashVec(const std::vector<uint8_t>& vec) {
if (vec.empty())
return SHA256HashStr(base::StringPiece());
return SHA256HashStr(base::StringPiece(
reinterpret_cast<const char*>(&vec.front()), vec.size()));
}
bool ParseETagHeader(const base::StringPiece& etag_header_value_in,
std::vector<uint8_t>* ecdsa_signature_out,
std::vector<uint8_t>* request_hash_out) {
DCHECK(ecdsa_signature_out);
DCHECK(request_hash_out);
// The ETag value is a UTF-8 string, formatted as "S:H", where:
// * S is the ECDSA signature in DER-encoded ASN.1 form, converted to hex.
// * H is the SHA-256 hash of the observed request body, standard hex format.
// A Weak ETag is formatted as W/"S:H". This function treats it the same as a
// strong ETag.
base::StringPiece etag_header_value(etag_header_value_in);
// Remove the weak prefix, then remove the begin and the end quotes.
const char kWeakETagPrefix[] = "W/";
if (base::StartsWith(etag_header_value, kWeakETagPrefix))
etag_header_value.remove_prefix(base::size(kWeakETagPrefix) - 1);
if (etag_header_value.size() >= 2 &&
base::StartsWith(etag_header_value, "\"") &&
base::EndsWith(etag_header_value, "\"")) {
etag_header_value.remove_prefix(1);
etag_header_value.remove_suffix(1);
}
const base::StringPiece::size_type delim_pos = etag_header_value.find(':');
if (delim_pos == base::StringPiece::npos || delim_pos == 0 ||
delim_pos == etag_header_value.size() - 1)
return false;
const base::StringPiece sig_hex = etag_header_value.substr(0, delim_pos);
const base::StringPiece hash_hex = etag_header_value.substr(delim_pos + 1);
// Decode the ECDSA signature. Don't bother validating the contents of it;
// the SignatureValidator class will handle the actual DER decoding and
// ASN.1 parsing. Check for an expected size range only -- valid ECDSA
// signatures are between 8 and 72 bytes.
if (!base::HexStringToBytes(sig_hex, ecdsa_signature_out))
return false;
if (ecdsa_signature_out->size() < 8 || ecdsa_signature_out->size() > 72)
return false;
// Decode the SHA-256 hash; it should be exactly 32 bytes, no more or less.
if (!base::HexStringToBytes(hash_hex, request_hash_out))
return false;
if (request_hash_out->size() != crypto::kSHA256Length)
return false;
return true;
}
} // namespace
Ecdsa::Ecdsa(int key_version, const base::StringPiece& public_key)
: pub_key_version_(key_version),
public_key_(public_key.begin(), public_key.end()) {}
Ecdsa::~Ecdsa() = default;
std::unique_ptr<Ecdsa> Ecdsa::Create(int key_version,
const base::StringPiece& public_key) {
DCHECK_GT(key_version, 0);
DCHECK(!public_key.empty());
return base::WrapUnique(new Ecdsa(key_version, public_key));
}
void Ecdsa::OverrideNonceForTesting(int key_version, uint32_t nonce) {
DCHECK(!request_query_cup2key_.empty());
request_query_cup2key_ = base::StringPrintf("%d:%u", pub_key_version_, nonce);
}
void Ecdsa::SignRequest(const base::StringPiece& request_body,
std::string* query_params) {
DCHECK(query_params);
// Generate a random nonce to use for freshness, build the cup2key query
// string, and compute the SHA-256 hash of the request body. Set these
// two pieces of data aside to use during ValidateResponse().
uint32_t nonce = 0;
crypto::RandBytes(&nonce, sizeof(nonce));
request_query_cup2key_ = base::StringPrintf("%d:%u", pub_key_version_, nonce);
request_hash_ = SHA256HashStr(request_body);
// Return the query string for the user to send with the request.
std::string request_hash_hex =
base::HexEncode(&request_hash_.front(), request_hash_.size());
request_hash_hex = base::ToLowerASCII(request_hash_hex);
*query_params = base::StringPrintf("cup2key=%s&cup2hreq=%s",
request_query_cup2key_.c_str(),
request_hash_hex.c_str());
}
bool Ecdsa::ValidateResponse(const base::StringPiece& response_body,
const base::StringPiece& server_etag) {
DCHECK(!request_hash_.empty());
DCHECK(!request_query_cup2key_.empty());
if (response_body.empty() || server_etag.empty())
return false;
// Break the ETag into its two components (the ECDSA signature, and the
// hash of the request that the server observed) and decode to byte buffers.
std::vector<uint8_t> signature;
std::vector<uint8_t> observed_request_hash;
if (!ParseETagHeader(server_etag, &signature, &observed_request_hash))
return false;
// Check that the server's observed request hash is equal to the original
// request hash. (This is a quick rejection test; the signature test is
// authoritative, but slower.)
DCHECK_EQ(request_hash_.size(), crypto::kSHA256Length);
if (observed_request_hash.size() != crypto::kSHA256Length)
return false;
if (!std::equal(observed_request_hash.begin(), observed_request_hash.end(),
request_hash_.begin()))
return false;
// Next, build the buffer that the server will have signed on its end:
// hash( hash(request) | hash(response) | cup2key_query_string )
// When building the client's version of the buffer, it's important to use
// the original request hash that it attempted to send, and not the observed
// request hash that the server sent back to us.
const std::vector<uint8_t> response_hash = SHA256HashStr(response_body);
std::vector<uint8_t> signed_message;
signed_message.insert(signed_message.end(), request_hash_.begin(),
request_hash_.end());
signed_message.insert(signed_message.end(), response_hash.begin(),
response_hash.end());
signed_message.insert(signed_message.end(), request_query_cup2key_.begin(),
request_query_cup2key_.end());
const std::vector<uint8_t> signed_message_hash =
SHA256HashVec(signed_message);
// Initialize the signature verifier.
crypto::SignatureVerifier verifier;
if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, signature,
public_key_)) {
DVLOG(1) << "Couldn't init SignatureVerifier.";
return false;
}
// If the verification fails, that implies one of two outcomes:
// * The signature was modified
// * The buffer that the server signed does not match the buffer that the
// client assembled -- implying that either request body or response body
// was modified, or a different nonce value was used.
verifier.VerifyUpdate(signed_message_hash);
return verifier.VerifyFinal();
}
} // namespace client_update_protocol