OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "chrome/common/net/gaia/oauth_request_signer.h" | |
6 | |
7 #include <cctype> | |
8 #include <cstddef> | |
9 #include <cstdlib> | |
10 #include <cstring> | |
11 #include <ctime> | |
12 #include <map> | |
13 #include <string> | |
14 | |
15 #include "base/base64.h" | |
16 #include "base/format_macros.h" | |
17 #include "base/logging.h" | |
18 #include "base/rand_util.h" | |
19 #include "base/string_util.h" | |
20 #include "base/stringprintf.h" | |
21 #include "base/time.h" | |
22 #include "crypto/hmac.h" | |
23 #include "googleurl/src/gurl.h" | |
24 | |
25 namespace { | |
26 | |
27 static const int kHexBase = 16; | |
28 static char kHexDigits[] = "0123456789ABCDEF"; | |
29 static const size_t kHmacDigestLength = 20; | |
30 static const int kMaxNonceLength = 30; | |
31 static const int kMinNonceLength = 15; | |
32 | |
33 static const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key"; | |
34 static const char kOAuthConsumerSecretLabel[] = "oauth_consumer_secret"; | |
35 static const char kOAuthNonceCharacters[] = | |
36 "abcdefghijklmnopqrstuvwyz" | |
37 "ABCDEFGHIJKLMNOPQRSTUVWYZ" | |
38 "0123456789_"; | |
39 static const char kOAuthNonceLabel[] = "oauth_nonce"; | |
40 static const char kOAuthSignatureLabel[] = "oauth_signature"; | |
41 static const char kOAuthSignatureMethodLabel[] = "oauth_signature_method"; | |
42 static const char kOAuthTimestampLabel[] = "oauth_timestamp"; | |
43 static const char kOAuthTokenLabel[] = "oauth_token"; | |
44 static const char kOAuthTokenSecretLabel[] = "oauth_token_secret"; | |
45 static const char kOAuthVersion[] = "1.0"; | |
46 static const char kOAuthVersionLabel[] = "oauth_version"; | |
47 | |
48 enum ParseQueryState { | |
49 START_STATE, | |
50 KEYWORD_STATE, | |
51 VALUE_STATE, | |
52 }; | |
53 | |
54 const std::string HttpMethodName(OAuthRequestSigner::HttpMethod method) { | |
55 switch (method) { | |
56 case OAuthRequestSigner::GET_METHOD: | |
57 return "GET"; | |
58 case OAuthRequestSigner::POST_METHOD: | |
59 return "POST"; | |
60 } | |
61 NOTREACHED(); | |
62 return *(new std::string()); | |
63 } | |
64 | |
65 const std::string SignatureMethodName( | |
66 OAuthRequestSigner::SignatureMethod method) { | |
67 switch (method) { | |
68 case OAuthRequestSigner::HMAC_SHA1_SIGNATURE: | |
69 return "HMAC-SHA1"; | |
70 case OAuthRequestSigner::RSA_SHA1_SIGNATURE: | |
71 return "RSA-SHA1"; | |
72 case OAuthRequestSigner::PLAINTEXT_SIGNATURE: | |
73 return "PLAINTEXT"; | |
74 } | |
75 NOTREACHED(); | |
76 return *(new std::string()); | |
77 } | |
78 | |
79 std::string BuildBaseString(const GURL& request_base_url, | |
80 OAuthRequestSigner::HttpMethod http_method, | |
81 const std::string& base_parameters) { | |
82 return StringPrintf("%s&%s&%s", | |
83 HttpMethodName(http_method).c_str(), | |
84 OAuthRequestSigner::Encode( | |
85 request_base_url.spec()).c_str(), | |
86 OAuthRequestSigner::Encode( | |
87 base_parameters).c_str()); | |
88 } | |
89 | |
90 std::string BuildBaseStringParameters( | |
91 const OAuthRequestSigner::Parameters& parameters) { | |
92 std::string result = ""; | |
93 OAuthRequestSigner::Parameters::const_iterator cursor; | |
94 OAuthRequestSigner::Parameters::const_iterator limit; | |
95 bool first = true; | |
96 for (cursor = parameters.begin(), limit = parameters.end(); | |
97 cursor != limit; | |
98 ++cursor) { | |
99 if (first) | |
100 first = false; | |
101 else | |
102 result += '&'; | |
103 result += OAuthRequestSigner::Encode(cursor->first); | |
104 result += '='; | |
105 result += OAuthRequestSigner::Encode(cursor->second); | |
106 } | |
107 return result; | |
108 } | |
109 | |
110 std::string GenerateNonce() { | |
111 char result[kMaxNonceLength + 1]; | |
112 int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) + | |
113 kMinNonceLength; | |
114 result[length] = '\0'; | |
115 for (int index = 0; index < length; ++index) | |
116 result[index] = kOAuthNonceCharacters[ | |
117 base::RandUint64() % (sizeof(kOAuthNonceCharacters) - 1)]; | |
118 return result; | |
119 } | |
120 | |
121 std::string GenerateTimestamp() { | |
122 return base::StringPrintf( | |
123 "%" PRId64, | |
124 (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds()); | |
125 } | |
126 | |
127 // Creates a string-to-string, keyword-value map from a parameter/query string | |
128 // that uses ampersand (&) to seperate paris and equals (=) to seperate | |
129 // keyword from value. | |
130 bool ParseQuery(const std::string& query, | |
131 OAuthRequestSigner::Parameters* parameters_result) { | |
132 std::string::const_iterator cursor; | |
133 std::string keyword; | |
134 std::string::const_iterator limit; | |
135 OAuthRequestSigner::Parameters parameters; | |
136 ParseQueryState state; | |
137 std::string value; | |
138 | |
139 state = START_STATE; | |
140 for (cursor = query.begin(), limit = query.end(); | |
141 cursor != limit; | |
142 ++cursor) { | |
143 char character = *cursor; | |
144 switch (state) { | |
145 case KEYWORD_STATE: | |
146 switch (character) { | |
147 case '&': | |
148 parameters[keyword] = value; | |
149 keyword = ""; | |
150 value = ""; | |
151 state = START_STATE; | |
152 break; | |
153 case '=': | |
154 state = VALUE_STATE; | |
155 break; | |
156 default: | |
157 keyword += character; | |
158 } | |
159 break; | |
160 case START_STATE: | |
161 switch (character) { | |
162 case '&': // Intentionally falling through | |
163 case '=': | |
164 return false; | |
165 default: | |
166 keyword += character; | |
167 state = KEYWORD_STATE; | |
168 } | |
169 break; | |
170 case VALUE_STATE: | |
171 switch (character) { | |
172 case '=': | |
173 return false; | |
174 case '&': | |
175 parameters[keyword] = value; | |
176 keyword = ""; | |
177 value = ""; | |
178 state = START_STATE; | |
179 break; | |
180 default: | |
181 value += character; | |
182 } | |
183 break; | |
184 } | |
185 } | |
186 switch (state) { | |
187 case START_STATE: | |
188 break; | |
189 case KEYWORD_STATE: // Intentionally falling through | |
190 case VALUE_STATE: | |
191 parameters[keyword] = value; | |
192 break; | |
193 default: | |
194 NOTREACHED(); | |
195 } | |
196 *parameters_result = parameters; | |
197 return true; | |
198 } | |
199 | |
200 // Creates the value for the oauth_signature parameter when the | |
201 // oauth_signature_method is HMAC-SHA1. | |
202 bool SignHmacSha1(const std::string& text, | |
203 const std::string& key, | |
204 std::string* signature_return) { | |
205 crypto::HMAC hmac(crypto::HMAC::SHA1); | |
206 DCHECK(hmac.DigestLength() == kHmacDigestLength); | |
207 unsigned char digest[kHmacDigestLength]; | |
208 bool result = hmac.Init(key) && | |
209 hmac.Sign(text, digest, kHmacDigestLength) && | |
210 base::Base64Encode(std::string(reinterpret_cast<const char*>(digest), | |
211 kHmacDigestLength), | |
212 signature_return); | |
213 return result; | |
214 } | |
215 | |
216 // Creates the value for the oauth_signature parameter when the | |
217 // oauth_signature_method is PLAINTEXT. | |
218 // | |
219 // Not yet implemented, and might never be. | |
220 bool SignPlaintext(const std::string& text, | |
221 const std::string& key, | |
222 std::string* result) { | |
223 NOTIMPLEMENTED(); | |
224 return false; | |
225 } | |
226 | |
227 // Creates the value for the oauth_signature parameter when the | |
228 // oauth_signature_method is RSA-SHA1. | |
229 // | |
230 // Not yet implemented, and might never be. | |
231 bool SignRsaSha1(const std::string& text, | |
232 const std::string& key, | |
233 std::string* result) { | |
234 NOTIMPLEMENTED(); | |
235 return false; | |
236 } | |
237 | |
238 // Adds parameters that are required by OAuth added as needed to |parameters|. | |
239 void PrepareParameters(OAuthRequestSigner::Parameters* parameters, | |
240 OAuthRequestSigner::SignatureMethod signature_method, | |
241 OAuthRequestSigner::HttpMethod http_method, | |
242 const std::string& consumer_key, | |
243 const std::string& token_key) { | |
244 if (parameters->find(kOAuthNonceLabel) == parameters->end()) | |
245 (*parameters)[kOAuthNonceLabel] = GenerateNonce(); | |
246 | |
247 if (parameters->find(kOAuthTimestampLabel) == parameters->end()) | |
248 (*parameters)[kOAuthTimestampLabel] = GenerateTimestamp(); | |
249 | |
250 (*parameters)[kOAuthConsumerKeyLabel] = consumer_key; | |
251 (*parameters)[kOAuthSignatureMethodLabel] = | |
252 SignatureMethodName(signature_method); | |
253 (*parameters)[kOAuthTokenLabel] = token_key; | |
254 (*parameters)[kOAuthVersionLabel] = kOAuthVersion; | |
255 } | |
256 | |
257 // Implements shared signing logic, generating the signature and storing it in | |
258 // |parameters|. Returns true if the signature has been generated succesfully. | |
259 bool SignParameters(const GURL& request_base_url, | |
260 OAuthRequestSigner::SignatureMethod signature_method, | |
261 OAuthRequestSigner::HttpMethod http_method, | |
262 const std::string& consumer_key, | |
263 const std::string& consumer_secret, | |
264 const std::string& token_key, | |
265 const std::string& token_secret, | |
266 OAuthRequestSigner::Parameters* parameters) { | |
267 DCHECK(request_base_url.is_valid()); | |
268 PrepareParameters(parameters, signature_method, http_method, | |
269 consumer_key, token_key); | |
270 std::string base_parameters = BuildBaseStringParameters(*parameters); | |
271 std::string base = BuildBaseString(request_base_url, http_method, | |
272 base_parameters); | |
273 std::string key = consumer_secret + '&' + token_secret; | |
274 bool is_signed = false; | |
275 std::string signature; | |
276 switch (signature_method) { | |
277 case OAuthRequestSigner::HMAC_SHA1_SIGNATURE: | |
278 is_signed = SignHmacSha1(base, key, &signature); | |
279 break; | |
280 case OAuthRequestSigner::RSA_SHA1_SIGNATURE: | |
281 is_signed = SignRsaSha1(base, key, &signature); | |
282 break; | |
283 case OAuthRequestSigner::PLAINTEXT_SIGNATURE: | |
284 is_signed = SignPlaintext(base, key, &signature); | |
285 break; | |
286 default: | |
287 NOTREACHED(); | |
288 } | |
289 if (is_signed) | |
290 (*parameters)[kOAuthSignatureLabel] = signature; | |
291 return is_signed; | |
292 } | |
293 | |
294 | |
295 } // namespace | |
296 | |
297 // static | |
298 bool OAuthRequestSigner::Decode(const std::string& text, | |
299 std::string* decoded_text) { | |
300 std::string accumulator = ""; | |
301 std::string::const_iterator cursor; | |
302 std::string::const_iterator limit; | |
303 for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { | |
304 char character = *cursor; | |
305 if (character == '%') { | |
306 ++cursor; | |
307 if (cursor == limit) | |
308 return false; | |
309 char* first = strchr(kHexDigits, *cursor); | |
310 if (!first) | |
311 return false; | |
312 int high = first - kHexDigits; | |
313 DCHECK(high >= 0 && high < kHexBase); | |
314 | |
315 ++cursor; | |
316 if (cursor == limit) | |
317 return false; | |
318 char* second = strchr(kHexDigits, *cursor); | |
319 if (!second) | |
320 return false; | |
321 int low = second - kHexDigits; | |
322 DCHECK(low >= 0 || low < kHexBase); | |
323 | |
324 char decoded = static_cast<char>(high * kHexBase + low); | |
325 DCHECK(!(IsAsciiAlpha(decoded) || IsAsciiDigit(decoded))); | |
326 DCHECK(!(decoded && strchr("-._~", decoded))); | |
327 accumulator += decoded; | |
328 } else { | |
329 accumulator += character; | |
330 } | |
331 } | |
332 *decoded_text = accumulator; | |
333 return true; | |
334 } | |
335 | |
336 // static | |
337 std::string OAuthRequestSigner::Encode(const std::string& text) { | |
338 std::string result = ""; | |
339 std::string::const_iterator cursor; | |
340 std::string::const_iterator limit; | |
341 for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { | |
342 char character = *cursor; | |
343 if (IsAsciiAlpha(character) || IsAsciiDigit(character)) { | |
344 result += character; | |
345 } else { | |
346 switch (character) { | |
347 case '-': | |
348 case '.': | |
349 case '_': | |
350 case '~': | |
351 result += character; | |
352 break; | |
353 default: | |
354 unsigned char byte = static_cast<unsigned char>(character); | |
355 result = result + '%' + kHexDigits[byte / kHexBase] + | |
356 kHexDigits[byte % kHexBase]; | |
357 } | |
358 } | |
359 } | |
360 return result; | |
361 } | |
362 | |
363 // static | |
364 bool OAuthRequestSigner::ParseAndSign(const GURL& request_url_with_parameters, | |
365 SignatureMethod signature_method, | |
366 HttpMethod http_method, | |
367 const std::string& consumer_key, | |
368 const std::string& consumer_secret, | |
369 const std::string& token_key, | |
370 const std::string& token_secret, | |
371 std::string* result) { | |
372 DCHECK(request_url_with_parameters.is_valid()); | |
373 Parameters parameters; | |
374 if (request_url_with_parameters.has_query()) { | |
375 const std::string& query = request_url_with_parameters.query(); | |
376 if (!query.empty()) { | |
377 if (!ParseQuery(query, ¶meters)) | |
378 return false; | |
379 } | |
380 } | |
381 std::string spec = request_url_with_parameters.spec(); | |
382 std::string url_without_parameters = spec; | |
383 std::string::size_type question = spec.find("?"); | |
384 if (question != std::string::npos) | |
385 url_without_parameters = spec.substr(0,question); | |
386 return SignURL(GURL(url_without_parameters), parameters, signature_method, | |
387 http_method, consumer_key, consumer_secret, token_key, | |
388 token_secret, result); | |
389 } | |
390 | |
391 // static | |
392 bool OAuthRequestSigner::SignURL( | |
393 const GURL& request_base_url, | |
394 const Parameters& request_parameters, | |
395 SignatureMethod signature_method, | |
396 HttpMethod http_method, | |
397 const std::string& consumer_key, | |
398 const std::string& consumer_secret, | |
399 const std::string& token_key, | |
400 const std::string& token_secret, | |
401 std::string* signed_text_return) { | |
402 DCHECK(request_base_url.is_valid()); | |
403 Parameters parameters(request_parameters); | |
404 bool is_signed = SignParameters(request_base_url, signature_method, | |
405 http_method, consumer_key, consumer_secret, | |
406 token_key, token_secret, ¶meters); | |
407 if (is_signed) { | |
408 std::string signed_text; | |
409 switch (http_method) { | |
410 case GET_METHOD: | |
411 signed_text = request_base_url.spec() + '?'; | |
412 // Intentionally falling through | |
413 case POST_METHOD: | |
414 signed_text += BuildBaseStringParameters(parameters); | |
415 break; | |
416 default: | |
417 NOTREACHED(); | |
418 } | |
419 *signed_text_return = signed_text; | |
420 } | |
421 return is_signed; | |
422 } | |
423 | |
424 // static | |
425 bool OAuthRequestSigner::SignAuthHeader( | |
426 const GURL& request_base_url, | |
427 const Parameters& request_parameters, | |
428 SignatureMethod signature_method, | |
429 HttpMethod http_method, | |
430 const std::string& consumer_key, | |
431 const std::string& consumer_secret, | |
432 const std::string& token_key, | |
433 const std::string& token_secret, | |
434 std::string* signed_text_return) { | |
435 DCHECK(request_base_url.is_valid()); | |
436 Parameters parameters(request_parameters); | |
437 bool is_signed = SignParameters(request_base_url, signature_method, | |
438 http_method, consumer_key, consumer_secret, | |
439 token_key, token_secret, ¶meters); | |
440 if (is_signed) { | |
441 std::string signed_text = "OAuth "; | |
442 bool first = true; | |
443 for (Parameters::const_iterator param = parameters.begin(); | |
444 param != parameters.end(); | |
445 ++param) { | |
446 if (first) | |
447 first = false; | |
448 else | |
449 signed_text += ", "; | |
450 signed_text += | |
451 StringPrintf("%s=\"%s\"", | |
452 OAuthRequestSigner::Encode(param->first).c_str(), | |
453 OAuthRequestSigner::Encode(param->second).c_str()); | |
454 } | |
455 *signed_text_return = signed_text; | |
456 } | |
457 return is_signed; | |
458 } | |
OLD | NEW |