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