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, ¶meters)) 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, ¶meters); 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, ¶meters); 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