| // Copyright (c) 2012 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 "google_apis/gaia/oauth_request_signer.h" |
| |
| #include <stddef.h> |
| |
| #include <cctype> |
| #include <cstddef> |
| #include <cstdlib> |
| #include <cstring> |
| #include <ctime> |
| #include <map> |
| #include <string> |
| |
| #include "base/base64.h" |
| #include "base/format_macros.h" |
| #include "base/logging.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "crypto/hmac.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| const int kHexBase = 16; |
| char kHexDigits[] = "0123456789ABCDEF"; |
| const size_t kHmacDigestLength = 20; |
| const int kMaxNonceLength = 30; |
| const int kMinNonceLength = 15; |
| |
| const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key"; |
| const char kOAuthNonceCharacters[] = |
| "abcdefghijklmnopqrstuvwyz" |
| "ABCDEFGHIJKLMNOPQRSTUVWYZ" |
| "0123456789_"; |
| const char kOAuthNonceLabel[] = "oauth_nonce"; |
| const char kOAuthSignatureLabel[] = "oauth_signature"; |
| const char kOAuthSignatureMethodLabel[] = "oauth_signature_method"; |
| const char kOAuthTimestampLabel[] = "oauth_timestamp"; |
| const char kOAuthTokenLabel[] = "oauth_token"; |
| const char kOAuthVersion[] = "1.0"; |
| const char kOAuthVersionLabel[] = "oauth_version"; |
| |
| enum ParseQueryState { |
| START_STATE, |
| KEYWORD_STATE, |
| VALUE_STATE, |
| }; |
| |
| const std::string HttpMethodName(OAuthRequestSigner::HttpMethod method) { |
| switch (method) { |
| case OAuthRequestSigner::GET_METHOD: |
| return "GET"; |
| case OAuthRequestSigner::POST_METHOD: |
| return "POST"; |
| } |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| const std::string SignatureMethodName( |
| OAuthRequestSigner::SignatureMethod method) { |
| switch (method) { |
| case OAuthRequestSigner::HMAC_SHA1_SIGNATURE: |
| return "HMAC-SHA1"; |
| case OAuthRequestSigner::RSA_SHA1_SIGNATURE: |
| return "RSA-SHA1"; |
| case OAuthRequestSigner::PLAINTEXT_SIGNATURE: |
| return "PLAINTEXT"; |
| } |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| std::string BuildBaseString(const GURL& request_base_url, |
| OAuthRequestSigner::HttpMethod http_method, |
| const std::string& base_parameters) { |
| return base::StringPrintf("%s&%s&%s", |
| HttpMethodName(http_method).c_str(), |
| OAuthRequestSigner::Encode( |
| request_base_url.spec()).c_str(), |
| OAuthRequestSigner::Encode( |
| base_parameters).c_str()); |
| } |
| |
| std::string BuildBaseStringParameters( |
| const OAuthRequestSigner::Parameters& parameters) { |
| std::string result; |
| OAuthRequestSigner::Parameters::const_iterator cursor; |
| OAuthRequestSigner::Parameters::const_iterator limit; |
| bool first = true; |
| for (cursor = parameters.begin(), limit = parameters.end(); |
| cursor != limit; |
| ++cursor) { |
| if (first) |
| first = false; |
| else |
| result += '&'; |
| result += OAuthRequestSigner::Encode(cursor->first); |
| result += '='; |
| result += OAuthRequestSigner::Encode(cursor->second); |
| } |
| return result; |
| } |
| |
| std::string GenerateNonce() { |
| char result[kMaxNonceLength + 1]; |
| int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) + |
| kMinNonceLength; |
| result[length] = '\0'; |
| for (int index = 0; index < length; ++index) |
| result[index] = kOAuthNonceCharacters[ |
| base::RandUint64() % (sizeof(kOAuthNonceCharacters) - 1)]; |
| return result; |
| } |
| |
| std::string GenerateTimestamp() { |
| return base::StringPrintf( |
| "%" PRId64, |
| (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds()); |
| } |
| |
| // Creates a string-to-string, keyword-value map from a parameter/query string |
| // that uses ampersand (&) to seperate paris and equals (=) to seperate |
| // keyword from value. |
| bool ParseQuery(const std::string& query, |
| OAuthRequestSigner::Parameters* parameters_result) { |
| std::string::const_iterator cursor; |
| std::string keyword; |
| std::string::const_iterator limit; |
| OAuthRequestSigner::Parameters parameters; |
| ParseQueryState state; |
| std::string value; |
| |
| state = START_STATE; |
| for (cursor = query.begin(), limit = query.end(); |
| cursor != limit; |
| ++cursor) { |
| char character = *cursor; |
| switch (state) { |
| case KEYWORD_STATE: |
| switch (character) { |
| case '&': |
| parameters[keyword] = value; |
| keyword = ""; |
| value = ""; |
| state = START_STATE; |
| break; |
| case '=': |
| state = VALUE_STATE; |
| break; |
| default: |
| keyword += character; |
| } |
| break; |
| case START_STATE: |
| switch (character) { |
| case '&': // Intentionally falling through |
| case '=': |
| return false; |
| default: |
| keyword += character; |
| state = KEYWORD_STATE; |
| } |
| break; |
| case VALUE_STATE: |
| switch (character) { |
| case '=': |
| return false; |
| case '&': |
| parameters[keyword] = value; |
| keyword = ""; |
| value = ""; |
| state = START_STATE; |
| break; |
| default: |
| value += character; |
| } |
| break; |
| } |
| } |
| switch (state) { |
| case START_STATE: |
| break; |
| case KEYWORD_STATE: // Intentionally falling through |
| case VALUE_STATE: |
| parameters[keyword] = value; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| *parameters_result = parameters; |
| return true; |
| } |
| |
| // Creates the value for the oauth_signature parameter when the |
| // oauth_signature_method is HMAC-SHA1. |
| bool SignHmacSha1(const std::string& text, |
| const std::string& key, |
| std::string* signature_return) { |
| crypto::HMAC hmac(crypto::HMAC::SHA1); |
| DCHECK(hmac.DigestLength() == kHmacDigestLength); |
| unsigned char digest[kHmacDigestLength]; |
| bool result = hmac.Init(key) && |
| hmac.Sign(text, digest, kHmacDigestLength); |
| if (result) { |
| base::Base64Encode( |
| std::string(reinterpret_cast<const char*>(digest), kHmacDigestLength), |
| signature_return); |
| } |
| return result; |
| } |
| |
| // Creates the value for the oauth_signature parameter when the |
| // oauth_signature_method is PLAINTEXT. |
| // |
| // Not yet implemented, and might never be. |
| bool SignPlaintext(const std::string& text, |
| const std::string& key, |
| std::string* result) { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| // Creates the value for the oauth_signature parameter when the |
| // oauth_signature_method is RSA-SHA1. |
| // |
| // Not yet implemented, and might never be. |
| bool SignRsaSha1(const std::string& text, |
| const std::string& key, |
| std::string* result) { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| // Adds parameters that are required by OAuth added as needed to |parameters|. |
| void PrepareParameters(OAuthRequestSigner::Parameters* parameters, |
| OAuthRequestSigner::SignatureMethod signature_method, |
| OAuthRequestSigner::HttpMethod http_method, |
| const std::string& consumer_key, |
| const std::string& token_key) { |
| if (parameters->find(kOAuthNonceLabel) == parameters->end()) |
| (*parameters)[kOAuthNonceLabel] = GenerateNonce(); |
| |
| if (parameters->find(kOAuthTimestampLabel) == parameters->end()) |
| (*parameters)[kOAuthTimestampLabel] = GenerateTimestamp(); |
| |
| (*parameters)[kOAuthConsumerKeyLabel] = consumer_key; |
| (*parameters)[kOAuthSignatureMethodLabel] = |
| SignatureMethodName(signature_method); |
| (*parameters)[kOAuthTokenLabel] = token_key; |
| (*parameters)[kOAuthVersionLabel] = kOAuthVersion; |
| } |
| |
| // Implements shared signing logic, generating the signature and storing it in |
| // |parameters|. Returns true if the signature has been generated succesfully. |
| bool 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) { |
| DCHECK(request_base_url.is_valid()); |
| PrepareParameters(parameters, signature_method, http_method, |
| consumer_key, token_key); |
| std::string base_parameters = BuildBaseStringParameters(*parameters); |
| std::string base = BuildBaseString(request_base_url, http_method, |
| base_parameters); |
| std::string key = consumer_secret + '&' + token_secret; |
| bool is_signed = false; |
| std::string signature; |
| switch (signature_method) { |
| case OAuthRequestSigner::HMAC_SHA1_SIGNATURE: |
| is_signed = SignHmacSha1(base, key, &signature); |
| break; |
| case OAuthRequestSigner::RSA_SHA1_SIGNATURE: |
| is_signed = SignRsaSha1(base, key, &signature); |
| break; |
| case OAuthRequestSigner::PLAINTEXT_SIGNATURE: |
| is_signed = SignPlaintext(base, key, &signature); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| if (is_signed) |
| (*parameters)[kOAuthSignatureLabel] = signature; |
| return is_signed; |
| } |
| |
| |
| } // namespace |
| |
| // static |
| bool OAuthRequestSigner::Decode(const std::string& text, |
| std::string* decoded_text) { |
| std::string accumulator; |
| std::string::const_iterator cursor; |
| std::string::const_iterator limit; |
| for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { |
| char character = *cursor; |
| if (character == '%') { |
| ++cursor; |
| if (cursor == limit) |
| return false; |
| char* first = strchr(kHexDigits, *cursor); |
| if (!first) |
| return false; |
| int high = first - kHexDigits; |
| DCHECK(high >= 0 && high < kHexBase); |
| |
| ++cursor; |
| if (cursor == limit) |
| return false; |
| char* second = strchr(kHexDigits, *cursor); |
| if (!second) |
| return false; |
| int low = second - kHexDigits; |
| DCHECK(low >= 0 || low < kHexBase); |
| |
| char decoded = static_cast<char>(high * kHexBase + low); |
| DCHECK(!(base::IsAsciiAlpha(decoded) || base::IsAsciiDigit(decoded))); |
| DCHECK(!(decoded && strchr("-._~", decoded))); |
| accumulator += decoded; |
| } else { |
| accumulator += character; |
| } |
| } |
| *decoded_text = accumulator; |
| return true; |
| } |
| |
| // static |
| std::string OAuthRequestSigner::Encode(const std::string& text) { |
| std::string result; |
| std::string::const_iterator cursor; |
| std::string::const_iterator limit; |
| for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { |
| char character = *cursor; |
| if (base::IsAsciiAlpha(character) || base::IsAsciiDigit(character)) { |
| result += character; |
| } else { |
| switch (character) { |
| case '-': |
| case '.': |
| case '_': |
| case '~': |
| result += character; |
| break; |
| default: |
| unsigned char byte = static_cast<unsigned char>(character); |
| result = result + '%' + kHexDigits[byte / kHexBase] + |
| kHexDigits[byte % kHexBase]; |
| } |
| } |
| } |
| return result; |
| } |
| |
| // static |
| bool OAuthRequestSigner::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) { |
| DCHECK(request_url_with_parameters.is_valid()); |
| Parameters parameters; |
| if (request_url_with_parameters.has_query()) { |
| const std::string& query = request_url_with_parameters.query(); |
| if (!query.empty()) { |
| if (!ParseQuery(query, ¶meters)) |
| return false; |
| } |
| } |
| std::string spec = request_url_with_parameters.spec(); |
| std::string url_without_parameters = spec; |
| std::string::size_type question = spec.find("?"); |
| if (question != std::string::npos) |
| url_without_parameters = spec.substr(0,question); |
| return SignURL(GURL(url_without_parameters), parameters, signature_method, |
| http_method, consumer_key, consumer_secret, token_key, |
| token_secret, result); |
| } |
| |
| // static |
| bool OAuthRequestSigner::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) { |
| DCHECK(request_base_url.is_valid()); |
| Parameters parameters(request_parameters); |
| bool is_signed = SignParameters(request_base_url, signature_method, |
| http_method, consumer_key, consumer_secret, |
| token_key, token_secret, ¶meters); |
| if (is_signed) { |
| std::string signed_text; |
| switch (http_method) { |
| case GET_METHOD: |
| signed_text = request_base_url.spec() + '?'; |
| // Intentionally falling through |
| case POST_METHOD: |
| signed_text += BuildBaseStringParameters(parameters); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| *signed_text_return = signed_text; |
| } |
| return is_signed; |
| } |
| |
| // static |
| bool OAuthRequestSigner::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) { |
| DCHECK(request_base_url.is_valid()); |
| Parameters parameters(request_parameters); |
| bool is_signed = SignParameters(request_base_url, signature_method, |
| http_method, consumer_key, consumer_secret, |
| token_key, token_secret, ¶meters); |
| if (is_signed) { |
| std::string signed_text = "OAuth "; |
| bool first = true; |
| for (Parameters::const_iterator param = parameters.begin(); |
| param != parameters.end(); |
| ++param) { |
| if (first) |
| first = false; |
| else |
| signed_text += ", "; |
| signed_text += |
| base::StringPrintf( |
| "%s=\"%s\"", |
| OAuthRequestSigner::Encode(param->first).c_str(), |
| OAuthRequestSigner::Encode(param->second).c_str()); |
| } |
| *signed_text_return = signed_text; |
| } |
| return is_signed; |
| } |