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