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.
5 #include "google_apis/gaia/oauth_request_signer.h"
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"
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;
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"
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";
48 enum ParseQueryState
{
54 const std::string
HttpMethodName(OAuthRequestSigner::HttpMethod method
) {
56 case OAuthRequestSigner::GET_METHOD
:
58 case OAuthRequestSigner::POST_METHOD
:
65 const std::string
SignatureMethodName(
66 OAuthRequestSigner::SignatureMethod method
) {
68 case OAuthRequestSigner::HMAC_SHA1_SIGNATURE
:
70 case OAuthRequestSigner::RSA_SHA1_SIGNATURE
:
72 case OAuthRequestSigner::PLAINTEXT_SIGNATURE
:
79 std::string
BuildBaseString(const GURL
& request_base_url
,
80 OAuthRequestSigner::HttpMethod http_method
,
81 const std::string
& base_parameters
) {
82 return base::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());
90 std::string
BuildBaseStringParameters(
91 const OAuthRequestSigner::Parameters
& parameters
) {
93 OAuthRequestSigner::Parameters::const_iterator cursor
;
94 OAuthRequestSigner::Parameters::const_iterator limit
;
96 for (cursor
= parameters
.begin(), limit
= parameters
.end();
103 result
+= OAuthRequestSigner::Encode(cursor
->first
);
105 result
+= OAuthRequestSigner::Encode(cursor
->second
);
110 std::string
GenerateNonce() {
111 char result
[kMaxNonceLength
+ 1];
112 int length
= base::RandUint64() % (kMaxNonceLength
- kMinNonceLength
+ 1) +
114 result
[length
] = '\0';
115 for (int index
= 0; index
< length
; ++index
)
116 result
[index
] = kOAuthNonceCharacters
[
117 base::RandUint64() % (sizeof(kOAuthNonceCharacters
) - 1)];
121 std::string
GenerateTimestamp() {
122 return base::StringPrintf(
124 (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds());
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
;
134 std::string::const_iterator limit
;
135 OAuthRequestSigner::Parameters parameters
;
136 ParseQueryState state
;
140 for (cursor
= query
.begin(), limit
= query
.end();
143 char character
= *cursor
;
148 parameters
[keyword
] = value
;
157 keyword
+= character
;
162 case '&': // Intentionally falling through
166 keyword
+= character
;
167 state
= KEYWORD_STATE
;
175 parameters
[keyword
] = value
;
189 case KEYWORD_STATE
: // Intentionally falling through
191 parameters
[keyword
] = value
;
196 *parameters_result
= parameters
;
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
),
216 // Creates the value for the oauth_signature parameter when the
217 // oauth_signature_method is PLAINTEXT.
219 // Not yet implemented, and might never be.
220 bool SignPlaintext(const std::string
& text
,
221 const std::string
& key
,
222 std::string
* result
) {
227 // Creates the value for the oauth_signature parameter when the
228 // oauth_signature_method is RSA-SHA1.
230 // Not yet implemented, and might never be.
231 bool SignRsaSha1(const std::string
& text
,
232 const std::string
& key
,
233 std::string
* result
) {
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();
247 if (parameters
->find(kOAuthTimestampLabel
) == parameters
->end())
248 (*parameters
)[kOAuthTimestampLabel
] = GenerateTimestamp();
250 (*parameters
)[kOAuthConsumerKeyLabel
] = consumer_key
;
251 (*parameters
)[kOAuthSignatureMethodLabel
] =
252 SignatureMethodName(signature_method
);
253 (*parameters
)[kOAuthTokenLabel
] = token_key
;
254 (*parameters
)[kOAuthVersionLabel
] = kOAuthVersion
;
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
,
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
);
280 case OAuthRequestSigner::RSA_SHA1_SIGNATURE
:
281 is_signed
= SignRsaSha1(base
, key
, &signature
);
283 case OAuthRequestSigner::PLAINTEXT_SIGNATURE
:
284 is_signed
= SignPlaintext(base
, key
, &signature
);
290 (*parameters
)[kOAuthSignatureLabel
] = signature
;
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
== '%') {
309 char* first
= strchr(kHexDigits
, *cursor
);
312 int high
= first
- kHexDigits
;
313 DCHECK(high
>= 0 && high
< kHexBase
);
318 char* second
= strchr(kHexDigits
, *cursor
);
321 int low
= second
- kHexDigits
;
322 DCHECK(low
>= 0 || low
< kHexBase
);
324 char decoded
= static_cast<char>(high
* kHexBase
+ low
);
325 DCHECK(!(IsAsciiAlpha(decoded
) || IsAsciiDigit(decoded
)));
326 DCHECK(!(decoded
&& strchr("-._~", decoded
)));
327 accumulator
+= decoded
;
329 accumulator
+= character
;
332 *decoded_text
= accumulator
;
337 std::string
OAuthRequestSigner::Encode(const std::string
& text
) {
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
)) {
354 unsigned char byte
= static_cast<unsigned char>(character
);
355 result
= result
+ '%' + kHexDigits
[byte
/ kHexBase
] +
356 kHexDigits
[byte
% kHexBase
];
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
))
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
);
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
);
408 std::string signed_text
;
409 switch (http_method
) {
411 signed_text
= request_base_url
.spec() + '?';
412 // Intentionally falling through
414 signed_text
+= BuildBaseStringParameters(parameters
);
419 *signed_text_return
= signed_text
;
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
);
441 std::string signed_text
= "OAuth ";
443 for (Parameters::const_iterator param
= parameters
.begin();
444 param
!= parameters
.end();
453 OAuthRequestSigner::Encode(param
->first
).c_str(),
454 OAuthRequestSigner::Encode(param
->second
).c_str());
456 *signed_text_return
= signed_text
;