oauth_request_signer.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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 25namespace { 26 27const int kHexBase = 16; 28char kHexDigits[] = "0123456789ABCDEF"; 29const size_t kHmacDigestLength = 20; 30const int kMaxNonceLength = 30; 31const int kMinNonceLength = 15; 32 33const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key"; 34const char kOAuthNonceCharacters[] = 35 "abcdefghijklmnopqrstuvwyz" 36 "ABCDEFGHIJKLMNOPQRSTUVWYZ" 37 "0123456789_"; 38const char kOAuthNonceLabel[] = "oauth_nonce"; 39const char kOAuthSignatureLabel[] = "oauth_signature"; 40const char kOAuthSignatureMethodLabel[] = "oauth_signature_method"; 41const char kOAuthTimestampLabel[] = "oauth_timestamp"; 42const char kOAuthTokenLabel[] = "oauth_token"; 43const char kOAuthVersion[] = "1.0"; 44const char kOAuthVersionLabel[] = "oauth_version"; 45 46enum ParseQueryState { 47 START_STATE, 48 KEYWORD_STATE, 49 VALUE_STATE, 50}; 51 52const 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 63const 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 77std::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 88std::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 108std::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 119std::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. 128bool 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. 200bool 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 base::Base64Encode(std::string(reinterpret_cast<const char*>(digest), 209 kHmacDigestLength), 210 signature_return); 211 return result; 212} 213 214// Creates the value for the oauth_signature parameter when the 215// oauth_signature_method is PLAINTEXT. 216// 217// Not yet implemented, and might never be. 218bool SignPlaintext(const std::string& text, 219 const std::string& key, 220 std::string* result) { 221 NOTIMPLEMENTED(); 222 return false; 223} 224 225// Creates the value for the oauth_signature parameter when the 226// oauth_signature_method is RSA-SHA1. 227// 228// Not yet implemented, and might never be. 229bool SignRsaSha1(const std::string& text, 230 const std::string& key, 231 std::string* result) { 232 NOTIMPLEMENTED(); 233 return false; 234} 235 236// Adds parameters that are required by OAuth added as needed to |parameters|. 237void PrepareParameters(OAuthRequestSigner::Parameters* parameters, 238 OAuthRequestSigner::SignatureMethod signature_method, 239 OAuthRequestSigner::HttpMethod http_method, 240 const std::string& consumer_key, 241 const std::string& token_key) { 242 if (parameters->find(kOAuthNonceLabel) == parameters->end()) 243 (*parameters)[kOAuthNonceLabel] = GenerateNonce(); 244 245 if (parameters->find(kOAuthTimestampLabel) == parameters->end()) 246 (*parameters)[kOAuthTimestampLabel] = GenerateTimestamp(); 247 248 (*parameters)[kOAuthConsumerKeyLabel] = consumer_key; 249 (*parameters)[kOAuthSignatureMethodLabel] = 250 SignatureMethodName(signature_method); 251 (*parameters)[kOAuthTokenLabel] = token_key; 252 (*parameters)[kOAuthVersionLabel] = kOAuthVersion; 253} 254 255// Implements shared signing logic, generating the signature and storing it in 256// |parameters|. Returns true if the signature has been generated succesfully. 257bool SignParameters(const GURL& request_base_url, 258 OAuthRequestSigner::SignatureMethod signature_method, 259 OAuthRequestSigner::HttpMethod http_method, 260 const std::string& consumer_key, 261 const std::string& consumer_secret, 262 const std::string& token_key, 263 const std::string& token_secret, 264 OAuthRequestSigner::Parameters* parameters) { 265 DCHECK(request_base_url.is_valid()); 266 PrepareParameters(parameters, signature_method, http_method, 267 consumer_key, token_key); 268 std::string base_parameters = BuildBaseStringParameters(*parameters); 269 std::string base = BuildBaseString(request_base_url, http_method, 270 base_parameters); 271 std::string key = consumer_secret + '&' + token_secret; 272 bool is_signed = false; 273 std::string signature; 274 switch (signature_method) { 275 case OAuthRequestSigner::HMAC_SHA1_SIGNATURE: 276 is_signed = SignHmacSha1(base, key, &signature); 277 break; 278 case OAuthRequestSigner::RSA_SHA1_SIGNATURE: 279 is_signed = SignRsaSha1(base, key, &signature); 280 break; 281 case OAuthRequestSigner::PLAINTEXT_SIGNATURE: 282 is_signed = SignPlaintext(base, key, &signature); 283 break; 284 default: 285 NOTREACHED(); 286 } 287 if (is_signed) 288 (*parameters)[kOAuthSignatureLabel] = signature; 289 return is_signed; 290} 291 292 293} // namespace 294 295// static 296bool OAuthRequestSigner::Decode(const std::string& text, 297 std::string* decoded_text) { 298 std::string accumulator; 299 std::string::const_iterator cursor; 300 std::string::const_iterator limit; 301 for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { 302 char character = *cursor; 303 if (character == '%') { 304 ++cursor; 305 if (cursor == limit) 306 return false; 307 char* first = strchr(kHexDigits, *cursor); 308 if (!first) 309 return false; 310 int high = first - kHexDigits; 311 DCHECK(high >= 0 && high < kHexBase); 312 313 ++cursor; 314 if (cursor == limit) 315 return false; 316 char* second = strchr(kHexDigits, *cursor); 317 if (!second) 318 return false; 319 int low = second - kHexDigits; 320 DCHECK(low >= 0 || low < kHexBase); 321 322 char decoded = static_cast<char>(high * kHexBase + low); 323 DCHECK(!(IsAsciiAlpha(decoded) || IsAsciiDigit(decoded))); 324 DCHECK(!(decoded && strchr("-._~", decoded))); 325 accumulator += decoded; 326 } else { 327 accumulator += character; 328 } 329 } 330 *decoded_text = accumulator; 331 return true; 332} 333 334// static 335std::string OAuthRequestSigner::Encode(const std::string& text) { 336 std::string result; 337 std::string::const_iterator cursor; 338 std::string::const_iterator limit; 339 for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { 340 char character = *cursor; 341 if (IsAsciiAlpha(character) || IsAsciiDigit(character)) { 342 result += character; 343 } else { 344 switch (character) { 345 case '-': 346 case '.': 347 case '_': 348 case '~': 349 result += character; 350 break; 351 default: 352 unsigned char byte = static_cast<unsigned char>(character); 353 result = result + '%' + kHexDigits[byte / kHexBase] + 354 kHexDigits[byte % kHexBase]; 355 } 356 } 357 } 358 return result; 359} 360 361// static 362bool OAuthRequestSigner::ParseAndSign(const GURL& request_url_with_parameters, 363 SignatureMethod signature_method, 364 HttpMethod http_method, 365 const std::string& consumer_key, 366 const std::string& consumer_secret, 367 const std::string& token_key, 368 const std::string& token_secret, 369 std::string* result) { 370 DCHECK(request_url_with_parameters.is_valid()); 371 Parameters parameters; 372 if (request_url_with_parameters.has_query()) { 373 const std::string& query = request_url_with_parameters.query(); 374 if (!query.empty()) { 375 if (!ParseQuery(query, ¶meters)) 376 return false; 377 } 378 } 379 std::string spec = request_url_with_parameters.spec(); 380 std::string url_without_parameters = spec; 381 std::string::size_type question = spec.find("?"); 382 if (question != std::string::npos) 383 url_without_parameters = spec.substr(0,question); 384 return SignURL(GURL(url_without_parameters), parameters, signature_method, 385 http_method, consumer_key, consumer_secret, token_key, 386 token_secret, result); 387} 388 389// static 390bool OAuthRequestSigner::SignURL( 391 const GURL& request_base_url, 392 const Parameters& request_parameters, 393 SignatureMethod signature_method, 394 HttpMethod http_method, 395 const std::string& consumer_key, 396 const std::string& consumer_secret, 397 const std::string& token_key, 398 const std::string& token_secret, 399 std::string* signed_text_return) { 400 DCHECK(request_base_url.is_valid()); 401 Parameters parameters(request_parameters); 402 bool is_signed = SignParameters(request_base_url, signature_method, 403 http_method, consumer_key, consumer_secret, 404 token_key, token_secret, ¶meters); 405 if (is_signed) { 406 std::string signed_text; 407 switch (http_method) { 408 case GET_METHOD: 409 signed_text = request_base_url.spec() + '?'; 410 // Intentionally falling through 411 case POST_METHOD: 412 signed_text += BuildBaseStringParameters(parameters); 413 break; 414 default: 415 NOTREACHED(); 416 } 417 *signed_text_return = signed_text; 418 } 419 return is_signed; 420} 421 422// static 423bool OAuthRequestSigner::SignAuthHeader( 424 const GURL& request_base_url, 425 const Parameters& request_parameters, 426 SignatureMethod signature_method, 427 HttpMethod http_method, 428 const std::string& consumer_key, 429 const std::string& consumer_secret, 430 const std::string& token_key, 431 const std::string& token_secret, 432 std::string* signed_text_return) { 433 DCHECK(request_base_url.is_valid()); 434 Parameters parameters(request_parameters); 435 bool is_signed = SignParameters(request_base_url, signature_method, 436 http_method, consumer_key, consumer_secret, 437 token_key, token_secret, ¶meters); 438 if (is_signed) { 439 std::string signed_text = "OAuth "; 440 bool first = true; 441 for (Parameters::const_iterator param = parameters.begin(); 442 param != parameters.end(); 443 ++param) { 444 if (first) 445 first = false; 446 else 447 signed_text += ", "; 448 signed_text += 449 base::StringPrintf( 450 "%s=\"%s\"", 451 OAuthRequestSigner::Encode(param->first).c_str(), 452 OAuthRequestSigner::Encode(param->second).c_str()); 453 } 454 *signed_text_return = signed_text; 455 } 456 return is_signed; 457} 458