root/google_apis/gaia/oauth_request_signer.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. HttpMethodName
  2. SignatureMethodName
  3. BuildBaseString
  4. BuildBaseStringParameters
  5. GenerateNonce
  6. GenerateTimestamp
  7. ParseQuery
  8. SignHmacSha1
  9. SignPlaintext
  10. SignRsaSha1
  11. PrepareParameters
  12. SignParameters
  13. Decode
  14. Encode
  15. ParseAndSign
  16. SignURL
  17. SignAuthHeader

// 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 <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(!(IsAsciiAlpha(decoded) || 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 (IsAsciiAlpha(character) || 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, &parameters))
        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, &parameters);
  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, &parameters);
  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;
}

/* [<][>][^][v][top][bottom][index][help] */