| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 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 "chrome/common/net/gaia/oauth_request_signer.h" |
| 6 |
| 7 #include <cctype> |
| 8 #include <cstdlib> |
| 9 #include <ctime> |
| 10 #include <map> |
| 11 #include <string> |
| 12 |
| 13 #include "base/base64.h" |
| 14 #include "base/format_macros.h" |
| 15 #include "base/logging.h" |
| 16 #include "base/rand_util.h" |
| 17 #include "base/stringprintf.h" |
| 18 #include "base/time.h" |
| 19 #include "crypto/hmac.h" |
| 20 #include "googleurl/src/gurl.h" |
| 21 |
| 22 namespace { |
| 23 |
| 24 static const int kHexBase = 16; |
| 25 static char kHexDigits[] = "0123456789ABCDEF"; |
| 26 |
| 27 static const int kMaxNonceLength = 30; |
| 28 static const int kMinNonceLength = 15; |
| 29 |
| 30 static const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key"; |
| 31 static const char kOAuthConsumerSecretLabel[] = "oauth_consumer_secret"; |
| 32 static const char kOAuthNonceCharacters[] = |
| 33 "abcdefghijklmnopqrstuvwyz" |
| 34 "ABCDEFGHIJKLMNOPQRSTUVWYZ" |
| 35 "0123456789_"; |
| 36 static const char kOAuthNonceLabel[] = "oauth_nonce"; |
| 37 static const char kOAuthSignatureLabel[] = "oauth_signature"; |
| 38 static const char kOAuthSignatureMethodLabel[] = "oauth_signature_method"; |
| 39 static const char kOAuthTimestampLabel[] = "oauth_timestamp"; |
| 40 static const char kOAuthTokenLabel[] = "oauth_token"; |
| 41 static const char kOAuthTokenSecretLabel[] = "oauth_token_secret"; |
| 42 static const char kOAuthVersion[] = "1.0"; |
| 43 static const char kOAuthVersionLabel[] = "oauth_version"; |
| 44 |
| 45 enum ParseQueryState { |
| 46 START_STATE, |
| 47 KEYWORD_STATE, |
| 48 VALUE_STATE, |
| 49 }; |
| 50 |
| 51 const std::string HttpMethodName(OAuthRequestSigner::HttpMethod method) { |
| 52 switch (method) { |
| 53 case OAuthRequestSigner::GET_METHOD: |
| 54 return "GET"; |
| 55 case OAuthRequestSigner::POST_METHOD: |
| 56 return "POST"; |
| 57 } |
| 58 NOTREACHED(); |
| 59 return *(new std::string()); |
| 60 } |
| 61 |
| 62 const std::string SignatureMethodName( |
| 63 OAuthRequestSigner::SignatureMethod method) { |
| 64 switch (method) { |
| 65 case OAuthRequestSigner::HMAC_SHA1_SIGNATURE: |
| 66 return "HMAC-SHA1"; |
| 67 case OAuthRequestSigner::RSA_SHA1_SIGNATURE: |
| 68 return "RSA-SHA1"; |
| 69 case OAuthRequestSigner::PLAINTEXT_SIGNATURE: |
| 70 return "PLAINTEXT"; |
| 71 } |
| 72 NOTREACHED(); |
| 73 return *(new std::string()); |
| 74 } |
| 75 |
| 76 // The form of percent encoding used for OAuth request signing is very |
| 77 // specific and strict. See http://oauth.net/core/1.0/#encoding_parameters. |
| 78 // |
| 79 // Any character which is in the "unreserved set" must not be encoded. |
| 80 // All other characters must be encoded. |
| 81 // |
| 82 // The unreserved set is comprised of the alphanumeric characters and these |
| 83 // others: |
| 84 // - minus (-) |
| 85 // - period (.) |
| 86 // - underscore (_) |
| 87 // - tilde (~) |
| 88 std::string EncodedOAuthParameter(const std::string& text) { |
| 89 std::string result = ""; |
| 90 std::string::const_iterator cursor; |
| 91 std::string::const_iterator limit; |
| 92 for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) { |
| 93 char character = *cursor; |
| 94 if (isalnum(character)) { |
| 95 result += character; |
| 96 } else { |
| 97 switch (character) { |
| 98 case '-': |
| 99 case '.': |
| 100 case '_': |
| 101 case '~': |
| 102 result += character; |
| 103 break; |
| 104 default: |
| 105 unsigned char byte = static_cast<unsigned char>(character); |
| 106 result = result + '%' + kHexDigits[byte / kHexBase] + |
| 107 kHexDigits[byte % kHexBase]; |
| 108 } |
| 109 } |
| 110 } |
| 111 return result; |
| 112 } |
| 113 |
| 114 std::string BuildBaseString(const GURL& request_base_url, |
| 115 OAuthRequestSigner::HttpMethod http_method, |
| 116 const std::string base_parameters) { |
| 117 return StringPrintf("%s&%s&%s", |
| 118 HttpMethodName(http_method).c_str(), |
| 119 EncodedOAuthParameter(request_base_url.spec()).c_str(), |
| 120 EncodedOAuthParameter(base_parameters).c_str()); |
| 121 } |
| 122 |
| 123 std::string BuildBaseStringParameters( |
| 124 const OAuthRequestSigner::Parameters& parameters) { |
| 125 std::string result = ""; |
| 126 OAuthRequestSigner::Parameters::const_iterator cursor; |
| 127 OAuthRequestSigner::Parameters::const_iterator limit; |
| 128 bool first = true; |
| 129 for (cursor = parameters.begin(), limit = parameters.end(); |
| 130 cursor != limit; |
| 131 ++cursor) { |
| 132 if (first) { |
| 133 first = false; |
| 134 } else { |
| 135 result += '&'; |
| 136 } |
| 137 result += EncodedOAuthParameter(cursor->first); |
| 138 result += '='; |
| 139 result += EncodedOAuthParameter(cursor->second); |
| 140 } |
| 141 return result; |
| 142 } |
| 143 |
| 144 std::string GenerateNonce() { |
| 145 char result[kMaxNonceLength + 1]; |
| 146 int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) + |
| 147 kMinNonceLength; |
| 148 result[length] = '\0'; |
| 149 for (int index = 0; index < length; ++index) |
| 150 result[index] = kOAuthNonceCharacters[ |
| 151 base::RandUint64() % (sizeof(kOAuthNonceCharacters) - 1)]; |
| 152 return result; |
| 153 } |
| 154 |
| 155 std::string GenerateTimestamp() { |
| 156 return base::StringPrintf( |
| 157 "%" PRId64, |
| 158 (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds()); |
| 159 } |
| 160 |
| 161 // Creates a string-to-string, keyword-value map from a parameter/query string |
| 162 // that uses ampersand (&) to seperate paris and equals (=) to seperate |
| 163 // keyword from value. |
| 164 bool ParseQuery(const std::string& query, |
| 165 OAuthRequestSigner::Parameters* parameters_result) { |
| 166 std::string::const_iterator cursor; |
| 167 std::string keyword; |
| 168 std::string::const_iterator limit; |
| 169 OAuthRequestSigner::Parameters parameters; |
| 170 ParseQueryState state; |
| 171 std::string value; |
| 172 |
| 173 state = START_STATE; |
| 174 for (cursor = query.begin(), limit = query.end(); |
| 175 cursor != limit; |
| 176 ++cursor) { |
| 177 char character = *cursor; |
| 178 switch (state) { |
| 179 case KEYWORD_STATE: |
| 180 switch (character) { |
| 181 case '&': |
| 182 parameters[keyword] = value; |
| 183 keyword = ""; |
| 184 value = ""; |
| 185 state = START_STATE; |
| 186 break; |
| 187 case '=': |
| 188 state = VALUE_STATE; |
| 189 break; |
| 190 default: |
| 191 keyword += character; |
| 192 } |
| 193 break; |
| 194 case START_STATE: |
| 195 switch (character) { |
| 196 case '&': // Intentionally falling through |
| 197 case '=': |
| 198 return false; |
| 199 default: |
| 200 keyword += character; |
| 201 state = KEYWORD_STATE; |
| 202 } |
| 203 break; |
| 204 case VALUE_STATE: |
| 205 switch (character) { |
| 206 case '=': |
| 207 return false; |
| 208 case '&': |
| 209 parameters[keyword] = value; |
| 210 keyword = ""; |
| 211 value = ""; |
| 212 state = START_STATE; |
| 213 break; |
| 214 default: |
| 215 value += character; |
| 216 } |
| 217 break; |
| 218 } |
| 219 } |
| 220 switch (state) { |
| 221 case START_STATE: |
| 222 break; |
| 223 case KEYWORD_STATE: // Intentionally falling through |
| 224 case VALUE_STATE: |
| 225 parameters[keyword] = value; |
| 226 break; |
| 227 default: |
| 228 NOTREACHED(); |
| 229 } |
| 230 *parameters_result = parameters; |
| 231 return true; |
| 232 } |
| 233 |
| 234 // Creates the value for the oauth_signature parameter when the |
| 235 // oauth_signature_method is HMAC-SHA1. |
| 236 bool SignHmacSha1(const std::string& text, |
| 237 const std::string& key, |
| 238 std::string* signature_return) { |
| 239 crypto::HMAC hmac(crypto::HMAC::SHA1); |
| 240 size_t digest_length = hmac.DigestLength(); |
| 241 unsigned char* digest = new unsigned char [digest_length]; |
| 242 hmac.Init(key); |
| 243 return hmac.Sign(text, digest, digest_length) && |
| 244 base::Base64Encode(std::string(reinterpret_cast<const char*>(digest), |
| 245 digest_length), |
| 246 signature_return); |
| 247 } |
| 248 |
| 249 // Creates the value for the oauth_signature parameter when the |
| 250 // oauth_signature_method is PLAINTEXT. |
| 251 // |
| 252 // Not yet implemented, and might never be. |
| 253 bool SignPlaintext(const std::string& text, |
| 254 const std::string& key, |
| 255 std::string* result) { |
| 256 NOTIMPLEMENTED(); |
| 257 return false; |
| 258 } |
| 259 |
| 260 // Creates the value for the oauth_signature parameter when the |
| 261 // oauth_signature_method is RSA-SHA1. |
| 262 // |
| 263 // Not yet implemented, and might never be. |
| 264 bool SignRsaSha1(const std::string& text, |
| 265 const std::string& key, |
| 266 std::string* result) { |
| 267 NOTIMPLEMENTED(); |
| 268 return false; |
| 269 } |
| 270 |
| 271 } // namespace |
| 272 |
| 273 // static |
| 274 bool OAuthRequestSigner::ParseAndSign(const GURL& request_url_with_parameters, |
| 275 SignatureMethod signature_method, |
| 276 HttpMethod http_method, |
| 277 const std::string& consumer_key, |
| 278 const std::string& consumer_secret, |
| 279 const std::string& token_key, |
| 280 const std::string& token_secret, |
| 281 std::string* result) { |
| 282 DCHECK(request_url_with_parameters.is_valid()); |
| 283 Parameters parameters; |
| 284 if (request_url_with_parameters.has_query()) { |
| 285 const std::string& query = request_url_with_parameters.query(); |
| 286 if (!query.empty()) { |
| 287 if (!ParseQuery(query, ¶meters)) |
| 288 return false; |
| 289 } |
| 290 } |
| 291 std::string spec = request_url_with_parameters.spec(); |
| 292 std::string url_without_parameters = spec; |
| 293 std::string::size_type question = spec.find("?"); |
| 294 if (question != std::string::npos) { |
| 295 url_without_parameters = spec.substr(0,question); |
| 296 } |
| 297 return Sign (GURL(url_without_parameters), parameters, signature_method, |
| 298 http_method, consumer_key, consumer_secret, token_key, |
| 299 token_secret, result); |
| 300 } |
| 301 |
| 302 // Returns a copy of request_parameters, with parameters that are required by |
| 303 // OAuth added as needed. |
| 304 OAuthRequestSigner::Parameters |
| 305 PrepareParameters(const OAuthRequestSigner::Parameters& request_parameters, |
| 306 OAuthRequestSigner::SignatureMethod signature_method, |
| 307 OAuthRequestSigner::HttpMethod http_method, |
| 308 const std::string& consumer_key, |
| 309 const std::string& token_key) { |
| 310 OAuthRequestSigner::Parameters result(request_parameters); |
| 311 |
| 312 if (result.find(kOAuthNonceLabel) == result.end()) |
| 313 result[kOAuthNonceLabel] = GenerateNonce(); |
| 314 |
| 315 if (result.find(kOAuthTimestampLabel) == result.end()) |
| 316 result[kOAuthTimestampLabel] = GenerateTimestamp(); |
| 317 |
| 318 result[kOAuthConsumerKeyLabel] = consumer_key; |
| 319 result[kOAuthSignatureMethodLabel] = SignatureMethodName(signature_method); |
| 320 result[kOAuthTokenLabel] = token_key; |
| 321 result[kOAuthVersionLabel] = kOAuthVersion; |
| 322 |
| 323 return result; |
| 324 } |
| 325 |
| 326 // static |
| 327 bool OAuthRequestSigner::Sign( |
| 328 const GURL& request_base_url, |
| 329 const Parameters& request_parameters, |
| 330 SignatureMethod signature_method, |
| 331 HttpMethod http_method, |
| 332 const std::string& consumer_key, |
| 333 const std::string& consumer_secret, |
| 334 const std::string& token_key, |
| 335 const std::string& token_secret, |
| 336 std::string* signed_text_return) { |
| 337 DCHECK(request_base_url.is_valid()); |
| 338 Parameters parameters = PrepareParameters(request_parameters, |
| 339 signature_method, |
| 340 http_method, |
| 341 consumer_key, |
| 342 token_key); |
| 343 std::string base_parameters = BuildBaseStringParameters(parameters); |
| 344 std::string base = BuildBaseString(request_base_url, |
| 345 http_method, |
| 346 base_parameters); |
| 347 std::string key = consumer_secret + '&' + token_secret; |
| 348 bool is_signed = false; |
| 349 std::string signature; |
| 350 switch (signature_method) { |
| 351 case HMAC_SHA1_SIGNATURE: |
| 352 is_signed = SignHmacSha1(base, key, &signature); |
| 353 break; |
| 354 case RSA_SHA1_SIGNATURE: |
| 355 is_signed = SignRsaSha1(base, key, &signature); |
| 356 break; |
| 357 case PLAINTEXT_SIGNATURE: |
| 358 is_signed = SignPlaintext(base, key, &signature); |
| 359 break; |
| 360 default: |
| 361 NOTREACHED(); |
| 362 } |
| 363 if (is_signed) { |
| 364 std::string signed_text; |
| 365 switch (http_method) { |
| 366 case GET_METHOD: |
| 367 signed_text = request_base_url.spec() + '?'; |
| 368 // Intentionally falling through |
| 369 case POST_METHOD: |
| 370 signed_text += base_parameters + '&' + kOAuthSignatureLabel + '=' + |
| 371 EncodedOAuthParameter(signature); |
| 372 break; |
| 373 default: |
| 374 NOTREACHED(); |
| 375 } |
| 376 *signed_text_return = signed_text; |
| 377 } |
| 378 return is_signed; |
| 379 } |
| OLD | NEW |