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 const int kHexBase = 16;
     28 char kHexDigits[] = "0123456789ABCDEF";
     29 const size_t kHmacDigestLength = 20;
     30 const int kMaxNonceLength = 30;
     31 const int kMinNonceLength = 15;
     32 
     33 const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key";
     34 const char kOAuthNonceCharacters[] =
     35     "abcdefghijklmnopqrstuvwyz"
     36     "ABCDEFGHIJKLMNOPQRSTUVWYZ"
     37     "0123456789_";
     38 const char kOAuthNonceLabel[] = "oauth_nonce";
     39 const char kOAuthSignatureLabel[] = "oauth_signature";
     40 const char kOAuthSignatureMethodLabel[] = "oauth_signature_method";
     41 const char kOAuthTimestampLabel[] = "oauth_timestamp";
     42 const char kOAuthTokenLabel[] = "oauth_token";
     43 const char kOAuthVersion[] = "1.0";
     44 const char kOAuthVersionLabel[] = "oauth_version";
     45 
     46 enum ParseQueryState {
     47   START_STATE,
     48   KEYWORD_STATE,
     49   VALUE_STATE,
     50 };
     51 
     52 const std::string HttpMethodName(OAuthRequestSigner::HttpMethod method) {
     53   switch (method) {
     54     case OAuthRequestSigner::GET_METHOD:
     55       return "GET";
     56     case OAuthRequestSigner::POST_METHOD:
     57       return "POST";
     58   }
     59   NOTREACHED();
     60   return std::string();
     61 }
     62 
     63 const std::string SignatureMethodName(
     64     OAuthRequestSigner::SignatureMethod method) {
     65   switch (method) {
     66     case OAuthRequestSigner::HMAC_SHA1_SIGNATURE:
     67       return "HMAC-SHA1";
     68     case OAuthRequestSigner::RSA_SHA1_SIGNATURE:
     69       return "RSA-SHA1";
     70     case OAuthRequestSigner::PLAINTEXT_SIGNATURE:
     71       return "PLAINTEXT";
     72   }
     73   NOTREACHED();
     74   return std::string();
     75 }
     76 
     77 std::string BuildBaseString(const GURL& request_base_url,
     78                             OAuthRequestSigner::HttpMethod http_method,
     79                             const std::string& base_parameters) {
     80   return base::StringPrintf("%s&%s&%s",
     81                             HttpMethodName(http_method).c_str(),
     82                             OAuthRequestSigner::Encode(
     83                                 request_base_url.spec()).c_str(),
     84                             OAuthRequestSigner::Encode(
     85                                 base_parameters).c_str());
     86 }
     87 
     88 std::string BuildBaseStringParameters(
     89     const OAuthRequestSigner::Parameters& parameters) {
     90   std::string result;
     91   OAuthRequestSigner::Parameters::const_iterator cursor;
     92   OAuthRequestSigner::Parameters::const_iterator limit;
     93   bool first = true;
     94   for (cursor = parameters.begin(), limit = parameters.end();
     95        cursor != limit;
     96        ++cursor) {
     97     if (first)
     98       first = false;
     99     else
    100       result += '&';
    101     result += OAuthRequestSigner::Encode(cursor->first);
    102     result += '=';
    103     result += OAuthRequestSigner::Encode(cursor->second);
    104   }
    105   return result;
    106 }
    107 
    108 std::string GenerateNonce() {
    109   char result[kMaxNonceLength + 1];
    110   int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) +
    111       kMinNonceLength;
    112   result[length] = '\0';
    113   for (int index = 0; index < length; ++index)
    114     result[index] = kOAuthNonceCharacters[
    115         base::RandUint64() % (sizeof(kOAuthNonceCharacters) - 1)];
    116   return result;
    117 }
    118 
    119 std::string GenerateTimestamp() {
    120   return base::StringPrintf(
    121       "%" PRId64,
    122       (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds());
    123 }
    124 
    125 // Creates a string-to-string, keyword-value map from a parameter/query string
    126 // that uses ampersand (&) to seperate paris and equals (=) to seperate
    127 // keyword from value.
    128 bool ParseQuery(const std::string& query,
    129                 OAuthRequestSigner::Parameters* parameters_result) {
    130   std::string::const_iterator cursor;
    131   std::string keyword;
    132   std::string::const_iterator limit;
    133   OAuthRequestSigner::Parameters parameters;
    134   ParseQueryState state;
    135   std::string value;
    136 
    137   state = START_STATE;
    138   for (cursor = query.begin(), limit = query.end();
    139        cursor != limit;
    140        ++cursor) {
    141     char character = *cursor;
    142     switch (state) {
    143       case KEYWORD_STATE:
    144         switch (character) {
    145           case '&':
    146             parameters[keyword] = value;
    147             keyword = "";
    148             value = "";
    149             state = START_STATE;
    150             break;
    151           case '=':
    152             state = VALUE_STATE;
    153             break;
    154           default:
    155             keyword += character;
    156         }
    157         break;
    158       case START_STATE:
    159         switch (character) {
    160           case '&':  // Intentionally falling through
    161           case '=':
    162             return false;
    163           default:
    164             keyword += character;
    165             state = KEYWORD_STATE;
    166         }
    167         break;
    168       case VALUE_STATE:
    169         switch (character) {
    170           case '=':
    171             return false;
    172           case '&':
    173             parameters[keyword] = value;
    174             keyword = "";
    175             value = "";
    176             state = START_STATE;
    177             break;
    178           default:
    179             value += character;
    180         }
    181         break;
    182     }
    183   }
    184   switch (state) {
    185     case START_STATE:
    186       break;
    187     case KEYWORD_STATE:  // Intentionally falling through
    188     case VALUE_STATE:
    189       parameters[keyword] = value;
    190       break;
    191     default:
    192       NOTREACHED();
    193   }
    194   *parameters_result = parameters;
    195   return true;
    196 }
    197 
    198 // Creates the value for the oauth_signature parameter when the
    199 // oauth_signature_method is HMAC-SHA1.
    200 bool SignHmacSha1(const std::string& text,
    201                   const std::string& key,
    202                   std::string* signature_return) {
    203   crypto::HMAC hmac(crypto::HMAC::SHA1);
    204   DCHECK(hmac.DigestLength() == kHmacDigestLength);
    205   unsigned char digest[kHmacDigestLength];
    206   bool result = hmac.Init(key) &&
    207       hmac.Sign(text, digest, kHmacDigestLength);
    208   if (result) {
    209     base::Base64Encode(
    210         std::string(reinterpret_cast<const char*>(digest), kHmacDigestLength),
    211         signature_return);
    212   }
    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