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