Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4475)

Unified Diff: chrome/common/net/gaia/oauth_request_signer.cc

Issue 7171023: Adds the OAuthRequestSigner class to facilitate OAuth integration (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed indentation of switch cases and defaults. Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/common/net/gaia/oauth_request_signer.cc
diff --git a/chrome/common/net/gaia/oauth_request_signer.cc b/chrome/common/net/gaia/oauth_request_signer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..684b3c80ae04dce65fbfb4f7d610fe2312ba905a
--- /dev/null
+++ b/chrome/common/net/gaia/oauth_request_signer.cc
@@ -0,0 +1,421 @@
+// Copyright (c) 2011 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 "chrome/common/net/gaia/oauth_request_signer.h"
+
+#include <cctype>
+#include <cstdlib>
+#include <ctime>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/stringprintf.h"
+#include "crypto/hmac.h"
+#include "googleurl/src/gurl.h"
+
+static const int kMinNonceLength = 15;
Nicolas Zea 2011/06/16 18:59:27 Include the static globals in the anonymous namesp
Rick Campbell 2011/06/17 16:57:54 Done.
+static const int kMaxNonceLength = 30;
+static const int kMaxTimeLength = 16; // 11 + 1 is sufficient
+
+static const std::string OAuthConsumerKeyLabel = "oauth_consumer_key";
Mattias Nissler (ping if slow) 2011/06/16 17:20:34 I thought we have a policy to not non-POD statics?
Rick Campbell 2011/06/17 16:57:54 Done.
+static const std::string OAuthConsumerSecretLabel = "oauth_consumer_secret";
+static const std::string OAuthNonceLabel = "oauth_nonce";
+static const std::string OAuthSignatureLabel = "oauth_signature";
+static const std::string OAuthSignatureMethodLabel = "oauth_signature_method";
+static const std::string OAuthTimestampLabel = "oauth_timestamp";
+static const std::string OAuthTokenLabel = "oauth_token";
+static const std::string OAuthTokenSecretLabel = "oauth_token_secret";
+static const std::string OAUTH_VERSION = "1.0";
+static const std::string OAuthVersionLabel = "oauth_version";
+
+enum ParseQueryState {
+ START_STATE,
+ KEYWORD_STATE,
+ VALUE_STATE,
+};
+
+namespace {
+
+const std::string& HttpMethodName(OAuthRequestSigner::HttpMethod method) {
+ static std::string get_result = "GET";
Nicolas Zea 2011/06/16 18:59:27 Mattias's comment applies here and with the other
Rick Campbell 2011/06/17 16:57:54 Done. Also changed return type to not be a refere
+ static std::string post_result = "POST";
+
+ switch (method) {
+ case OAuthRequestSigner::GET_METHOD:
+ return get_result;
+ case OAuthRequestSigner::POST_METHOD:
+ return post_result;
+ }
+ NOTREACHED();
+ return *(new std::string());
+}
+
+const std::string& SignatureMethodName(
+ OAuthRequestSigner::SignatureMethod method) {
+ static std::string hmac_sha1_result = "HMAC-SHA1";
+ static std::string rsa_sha1_result = "RSA-SHA1";
+ static std::string plaintext_result = "PLAINTEXT";
+
+ switch (method) {
+ case OAuthRequestSigner::HMAC_SHA1_SIGNATURE:
+ return hmac_sha1_result;
+ case OAuthRequestSigner::RSA_SHA1_SIGNATURE:
+ return rsa_sha1_result;
+ case OAuthRequestSigner::PLAINTEXT_SIGNATURE:
+ return plaintext_result;
+ }
+ NOTREACHED();
+ return *(new std::string());
+}
+
+// The form of percent encoding used for OAuth request signing is very
+// specific and strict. See http://oauth.net/core/1.0/#encoding_parameters.
+//
+// Any character which is in the "unreserved set" must not be encoded.
+// All other characters must be encoded.
+//
+// The unreserved set is comprised of the alphanumeric characters and these
+// others:
+// - minus (-)
+// - period (.)
+// - underscore (_)
+// - tilde (~)
+void OAuthParameterEncode(std::ostream& output,
Mattias Nissler (ping if slow) 2011/06/16 17:20:34 static
Nicolas Zea 2011/06/16 18:59:27 stream's are only allowed to be used for logging u
Rick Campbell 2011/06/17 16:57:54 This is within an anonymous namespace. Not *exact
Rick Campbell 2011/06/17 16:57:54 Done.
+ const std::string& text) {
+ static std::string kHexDigits = "0123456789ABCDEF";
Rick Campbell 2011/06/17 16:57:54 Changed to const char[] and moved to top of anonym
+ std::string::const_iterator cursor;
+ std::string::const_iterator limit;
+ for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) {
+ char character = *cursor;
+ if (isalnum(character)) {
+ output << character;
+ } else {
+ switch (character) {
+ case '-':
+ case '.':
+ case '_':
+ case '~':
+ output << character;
+ break;
+ default:
+ unsigned char byte = static_cast<unsigned char>(character);
+ output << '%';
+ output << kHexDigits[byte / 16];
+ output << kHexDigits[byte % 16];
+ }
+ }
+ }
+}
+
+void OAuthParameterDecode(std::ostream& output,
Mattias Nissler (ping if slow) 2011/06/16 17:20:34 shouldn't all these helper functions either be sta
Rick Campbell 2011/06/17 16:57:54 Yes, they are within an anonymous namespace. I've
Rick Campbell 2011/06/17 16:57:54 The decode routine is not used (and implemented in
Mattias Nissler (ping if slow) 2011/06/17 17:10:04 No, that's fine, I missed the namespace declaratio
+ const std::string& text) {
+ static std::string kHexDigits = "0123456789ABCDEF";
Rick Campbell 2011/06/17 16:57:54 Changed to const char[]
+ std::string::const_iterator cursor;
+ std::string::const_iterator limit;
+ for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) {
+ char character = *cursor;
+ if (isalnum(character)) {
+ output << character;
+ } else {
+ switch (character) {
+ case '-':
+ case '.':
+ case '_':
+ case '~':
+ output << character;
+ break;
+ default:
+ unsigned char byte = static_cast<unsigned char>(character);
+ output << '%';
+ output << kHexDigits[byte / 16];
+ output << kHexDigits[byte % 16];
+ }
+ }
+ }
+}
+
+std::string EncodedOAuthParameter(const std::string& text) {
+ std::ostringstream output;
+ OAuthParameterEncode(output, text);
+ return output.str();
+}
+
+std::string BuildBaseString(const GURL& request_base_url,
+ OAuthRequestSigner::HttpMethod http_method,
+ const std::string base_parameters) {
+ std::ostringstream output;
+ output << HttpMethodName(http_method) << '&';
+ OAuthParameterEncode(output, request_base_url.spec());
+ output << '&';
+ OAuthParameterEncode(output, base_parameters);
+ return output.str();
+}
+
+std::string BuildBaseStringParameters(
+ const OAuthRequestSigner::Parameters& parameters) {
+ std::ostringstream output;
+ 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 {
+ output << '&';
+ }
+ OAuthParameterEncode(output, cursor->first);
+ output << '=';
+ OAuthParameterEncode(output, cursor->second);
+ }
+ return output.str();
+}
+
+std::string GenerateNonce() {
+ static const char SELECTION[] =
+ "abcdefghijklmnopqrstuvwyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWYZ"
+ "0123456789_";
+ char result[kMaxNonceLength + 1];
+ int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) +
+ kMinNonceLength;
+ result[length] = '\0';
+ for (int index = 0; index < length; ++index)
+ result[index] = SELECTION[base::RandUint64() % (sizeof(SELECTION) - 1)];
+ return result;
+}
+
+std::string GenerateTimestamp() {
+ time_t now;
+ char result [kMaxTimeLength];
+ time(&now);
Mattias Nissler (ping if slow) 2011/06/16 17:20:34 use base/time.h for portability?
Rick Campbell 2011/06/17 16:57:54 I'm going for Time::NowFromSystemTime().ToTimeT()
Mattias Nissler (ping if slow) 2011/06/17 17:10:04 Some time ago, I solved this by (Time::NowFromSyst
Rick Campbell 2011/06/17 18:53:45 Done.
+ base::StringPrintf("%ld", now);
+ return result;
+}
+
+// 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);
+ size_t digest_length = hmac.DigestLength();
+ unsigned char* digest = new unsigned char [digest_length];
+ hmac.Init(key);
+ return hmac.Sign(text, digest, digest_length) &&
+ base::Base64Encode(std::string(reinterpret_cast<const char*>(digest),
+ digest_length),
+ signature_return);
+}
+
+// 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;
+}
+
+}
Nicolas Zea 2011/06/16 18:59:27 "} // namespace"
Rick Campbell 2011/06/17 16:57:54 Done.
+
+// 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 Sign (GURL(url_without_parameters), parameters, signature_method,
+ http_method, consumer_key, consumer_secret, token_key,
+ token_secret, result);
+}
+
+// Returns a copy of request_parameters, with parameters that are required by
+// OAuth added as needed.
+OAuthRequestSigner::Parameters
+PrepareParameters(const OAuthRequestSigner::Parameters& request_parameters,
+ OAuthRequestSigner::SignatureMethod signature_method,
+ OAuthRequestSigner::HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& token_key) {
+ OAuthRequestSigner::Parameters result(request_parameters);
+
+ if (result.find(OAuthNonceLabel) == result.end())
+ result[OAuthNonceLabel] = GenerateNonce();
+
+ if (result.find(OAuthTimestampLabel) == result.end())
+ result[OAuthTimestampLabel] = GenerateTimestamp();
+
+ result[OAuthConsumerKeyLabel] = consumer_key;
+ result[OAuthSignatureMethodLabel] = SignatureMethodName(signature_method);
+ result[OAuthTokenLabel] = token_key;
+ result[OAuthVersionLabel] = OAUTH_VERSION;
+
+ return result;
+}
+
+// static
+bool OAuthRequestSigner::Sign(
+ 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 = PrepareParameters(request_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 HMAC_SHA1_SIGNATURE:
+ is_signed = SignHmacSha1(base, key, &signature);
+ break;
+ case RSA_SHA1_SIGNATURE:
+ is_signed = SignRsaSha1(base, key, &signature);
+ break;
+ case PLAINTEXT_SIGNATURE:
+ is_signed = SignPlaintext(base, key, &signature);
+ break;
+ default:
+ NOTREACHED();
+ }
+ 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 += base_parameters + '&' + OAuthSignatureLabel + '=' +
+ EncodedOAuthParameter(signature);
+ break;
+ default:
+ NOTREACHED();
+ }
+ *signed_text_return = signed_text;
+ }
+ return is_signed;
+}

Powered by Google App Engine
This is Rietveld 408576698