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