Home | History | Annotate | Download | only in gaia
      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 <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/strings/string_util.h"
     20 #include "base/strings/stringprintf.h"
     21 #include "base/time/time.h"
     22 #include "crypto/hmac.h"
     23 #include "url/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 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 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 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());
     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, &parameters))
    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, &parameters);
    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, &parameters);
    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           base::StringPrintf(
    452               "%s=\"%s\"",
    453               OAuthRequestSigner::Encode(param->first).c_str(),
    454               OAuthRequestSigner::Encode(param->second).c_str());
    455     }
    456     *signed_text_return = signed_text;
    457   }
    458   return is_signed;
    459 }
    460