Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Side by Side Diff: crypto/cup.cc

Issue 15793005: Per discussion, implement the Omaha Client Update Protocol (CUP) in src/crypto. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013 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
5 #include "crypto/cup.h"
6
7 #include "base/base64.h"
8 #include "base/sha1.h"
9 #include "base/string_util.h"
10 #include "base/stringprintf.h"
11 #include "crypto/hmac.h"
12 #include "crypto/random.h"
13
14 // NOTE: LoadPublicKey() and EncryptSharedKey() are implemented individually
15 // for each possible crypto engine; this file only holds common functionality.
16 // See cup_nss.cc or cup_openssl.cc for implementations of those methods.
Ryan Sleevi 2013/05/30 02:28:08 Generally unnecessary; this is understood when dea
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
17
18 namespace crypto {
19
20 ClientUpdateProtocol::ClientUpdateProtocol()
21 : v_(0) {
22 }
23
24 ClientUpdateProtocol::~ClientUpdateProtocol() {
25 }
26
27 bool ClientUpdateProtocol::Init(int key_version, const char* public_key) {
28 if (v_ > 0) {
Ryan Sleevi 2013/05/30 02:28:08 style nit: This is not strictly required by the st
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
29 return false; // Already initialized.
30 }
31
32 DCHECK(key_version > 0 && public_key);
Ryan Sleevi 2013/05/30 02:28:08 DCHECK_GT(key_version, 0); DCHECK(public_key);
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
33 if (key_version <= 0 || !public_key) {
34 return false; // At least one mandatory parameter is not valid.
35 }
36
37 size_t key_size = LoadPublicKey(public_key);
38 if (key_size < HashDigestSize()) {
39 return false; // Public key couldn't be loaded, or is too small to be used.
40 }
41
42 InitializeEntropy(key_size);
43 if (!BuildSharedKey()) {
44 return false; // Failed to generate w.
45 }
46
47 v_ = key_version;
48 return true;
49 }
50
51 bool ClientUpdateProtocol::SignRequest(const base::StringPiece& url_host,
52 const base::StringPiece& url_path,
53 const base::StringPiece& url_query,
54 const base::StringPiece& request_body,
55 std::string* url_query_out,
56 std::string* cp_out) {
57 if (vw_.empty()) {
58 return false; // We haven't been initialized.
59 }
60
61 // Compute the URL for the "outer" CUP request -- append a query string (or
62 // append to an existing query string, if there is one) containing vw.
63 std::string cup_url_query(url_query.data());
64 if (url_query.find('?') != std::string::npos) {
65 cup_url_query.append("&w=");
66 } else {
67 cup_url_query.append("?w=");
68 }
69 cup_url_query.append(vw_);
70
71 const std::string packed_url = base::StringPrintf("//%s%s%s",
72 url_host.data(),
73 url_path.data(),
74 cup_url_query.data());
Ryan Sleevi 2013/05/30 02:28:08 I'm generally not a fan of constructing and manipu
Ryan Myers (chromium) 2013/05/30 21:01:10 Agreed. I've pushed this out to the caller; they
75
76 // Compute hw = HASH(HASH(v|w)|HASH(request_url)|HASH(body)). We'll need to
77 // keep the value of hw for later to validate the server's response.
78 std::vector<uint8> internal_hashes;
79 std::vector<uint8> h;
80
81 h = Hash(vw_);
82 internal_hashes.insert(internal_hashes.end(), h.begin(), h.end());
83 h = Hash(packed_url);
84 internal_hashes.insert(internal_hashes.end(), h.begin(), h.end());
85 h = Hash(request_body);
86 internal_hashes.insert(internal_hashes.end(), h.begin(), h.end());
87 DCHECK(internal_hashes.size() == 3 * HashDigestSize());
Ryan Sleevi 2013/05/30 02:28:08 DCHECK_EQ
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
88
89 hw_ = Hash(internal_hashes);
90
91 // Sign the challenge hash (hw) using the shared key (sk) to produce the
92 // client proof (cp).
93 std::vector<uint8> cp = SymSign(sk_, 3, &hw_, NULL, NULL);
94 if (cp.empty()) {
95 hw_.clear();
96 return false; // HMAC failed for some reason.
97 }
98
99 if (url_query_out) {
100 *url_query_out = cup_url_query;
101 }
102 if (cp_out) {
103 *cp_out = UrlSafeB64Encode(cp);
104 }
105
106 return true;
107 }
108
109 bool ClientUpdateProtocol::ValidateResponse(
110 const base::StringPiece& response_body,
111 const base::StringPiece& c_in,
112 const base::StringPiece& sp_in) {
113 if (hw_.empty()) {
114 return false; // There hasn't been a call to SignRequest() yet.
115 }
116
117 // If our request got to the server in one piece, it's going to use its
118 // private key to decrypt v|w, yielding the contents of r. It can use
119 // that to recreate sk, then compute hw and SymSign(3|hw) on its end,
120 // and ensure that the cp we sent matches up. It will then use sk to
121 // sign its response.
122
Ryan Sleevi 2013/05/30 02:28:08 comment nit: Rewrite the comment without using "ou
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
123 std::vector<uint8> hm = Hash(response_body);
124 std::vector<uint8> hc = Hash(c_in);
125 std::vector<uint8> expected_hmac = SymSign(sk_, 1, &hw_, &hm, &hc);
126 std::string expected_sp = UrlSafeB64Encode(expected_hmac);
127
128 return sp_in == expected_sp;
Ryan Sleevi 2013/05/30 02:28:08 SECURITY: This raises red flags as an unsafe compa
Ryan Myers (chromium) 2013/05/30 21:01:10 Replaced with HMAC::Verify().
129 }
130
131 void ClientUpdateProtocol::InitializeEntropy(size_t public_key_length) {
132 DCHECK(public_key_length >= HashDigestSize());
Ryan Sleevi 2013/05/30 02:28:08 DCHECK_GE
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
133
134 if (public_key_length >= HashDigestSize()) {
135 std::vector<uint8> entropy(public_key_length - HashDigestSize());
136 crypto::RandBytes(&entropy[0], entropy.size());
137 r_ = RsaPad(public_key_length, entropy);
138 }
139 }
140
141 bool ClientUpdateProtocol::BuildSharedKey() {
142 // Derive a new new shared key sk' as the hash of r_. This will be used for
143 // the HMAC step.
144 sk_ = Hash(r_);
145
146 // Encrypt r with pk[v] to generate challenge w. (We no longer need r after
147 // this step.)
148 if (!EncryptSharedKey()) {
149 return false;
150 }
151 r_.clear();
152
153 // Generate the versioned challenge (v|w) by Base64-encoding w. Since this
154 // string is typically appended to the request URL, it needs a URL-safe
155 // variant of Base64.
156 vw_ = base::StringPrintf("%d:%s", v_, UrlSafeB64Encode(w_).c_str());
157
158 return true;
159 }
160
161 std::string ClientUpdateProtocol::UrlSafeB64Encode(
162 const std::vector<uint8>& data) {
163 // This function was originally based off Omaha's base/security/b64.c, which
164 // is a URL-safe variant of Base64 with no padding. Rather than copy-paste an
165 // implementation here, we just call the standard Base64 encoder, and fix up.
166
167 std::string result;
168 const base::StringPiece input(reinterpret_cast<const char*>(&data[0]),
169 data.size());
170 if (!base::Base64Encode(input, &result)) {
171 return std::string();
172 }
173
174 // Do an tr|+/|-_| on the output, and strip any '=' padding.
175 for (std::string::iterator it = result.begin(); it != result.end(); ++it) {
176 switch (*it) {
177 case '+':
178 *it = '-';
179 continue;
180 case '/':
181 *it = '_';
182 continue;
183 default:
184 continue;
185 }
186 }
187 TrimString(result, "=", &result);
188
189 return result;
190 }
191
192 // This class needs to implement the same hashing functions as the Google Update
193 // server; for now, this is SHA-1, but we may change this to SHA-256 in the
194 // near future. Hence, the wrappers.
195
196 size_t ClientUpdateProtocol::HashDigestSize() {
197 return base::kSHA1Length;
198 }
199
200 std::vector<uint8> ClientUpdateProtocol::Hash(const std::vector<uint8>& data) {
201 std::vector<uint8> result(HashDigestSize());
202 base::SHA1HashBytes(data.empty() ? NULL : &data[0],
203 data.size(),
204 &result[0]);
205 return result;
206 }
207
208 std::vector<uint8> ClientUpdateProtocol::Hash(const base::StringPiece& sdata) {
209 std::vector<uint8> result(HashDigestSize());
210 base::SHA1HashBytes(sdata.empty() ?
211 NULL :
212 reinterpret_cast<const unsigned char*>(sdata.data()),
213 sdata.length(),
214 &result[0]);
215 return result;
216 }
217
218 std::vector<uint8> ClientUpdateProtocol::SymSign(const std::vector<uint8>& key,
219 uint8 id,
220 const std::vector<uint8>* h1,
221 const std::vector<uint8>* h2,
222 const std::vector<uint8>* h3) {
223 crypto::HMAC hmac(crypto::HMAC::SHA1);
224 if (!hmac.Init(&key[0], key.size())) {
225 return std::vector<uint8>();
226 }
227
228 std::vector<uint8> data;
229 data.push_back(id);
230 const std::vector<uint8>* args[] = { h1, h2, h3 };
231 for (size_t i = 0; i != arraysize(args); ++i) {
232 if (args[i]) {
233 DCHECK(args[i]->size() == HashDigestSize());
Ryan Sleevi 2013/05/30 02:28:08 DCHECK_EQ
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
234 data.insert(data.end(), args[i]->begin(), args[i]->end());
235 }
236 }
237
238 std::vector<uint8> result(hmac.DigestLength());
239 if (!hmac.Sign(base::StringPiece(reinterpret_cast<const char*>(&data[0]),
240 data.size()),
241 &result[0],
242 result.size())) {
243 return std::vector<uint8>();
244 }
245
246 return result;
247 }
248
249 std::vector<uint8> ClientUpdateProtocol::RsaPad(
250 size_t rsa_key_size,
251 const std::vector<uint8>& entropy) {
252 DCHECK(rsa_key_size >= HashDigestSize());
Ryan Sleevi 2013/05/30 02:28:08 DCHECK_GE
Ryan Myers (chromium) 2013/05/30 21:01:10 Done.
253
254 // The result gets padded with zeros if the result size is greater than
255 // the size of the buffer provided by the caller.
256 std::vector<uint8> result(entropy);
257 result.resize(rsa_key_size - HashDigestSize());
258
259 // For use with RSA, the input needs to be smaller than the RSA modulus,
260 // which has always the msb set.
261 result[0] &= 127; // Reset msb
262 result[0] |= 64; // Set second highest bit.
263
264 std::vector<uint8> digest = Hash(result);
265 result.insert(result.end(), digest.begin(), digest.end());
266 DCHECK(result.size() == rsa_key_size);
Ryan Sleevi 2013/05/30 02:28:08 DCHECK_GE
Ryan Myers (chromium) 2013/05/30 21:01:10 Done (used DCHECK_EQ).
267 return result;
268 }
269
270 } // namespace crypto
271
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698