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 <stddef.h>
8 
9 #include <cctype>
10 #include <cstddef>
11 #include <cstdlib>
12 #include <cstring>
13 #include <ctime>
14 #include <map>
15 #include <string>
16 
17 #include "base/base64.h"
18 #include "base/check.h"
19 #include "base/format_macros.h"
20 #include "base/notreached.h"
21 #include "base/rand_util.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/time/time.h"
25 #include "crypto/hmac.h"
26 #include "url/gurl.h"
27 
28 namespace {
29 
30 const int kHexBase = 16;
31 char kHexDigits[] = "0123456789ABCDEF";
32 const size_t kHmacDigestLength = 20;
33 const int kMaxNonceLength = 30;
34 const int kMinNonceLength = 15;
35 
36 const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key";
37 const char kOAuthNonceCharacters[] =
38     "abcdefghijklmnopqrstuvwyz"
39     "ABCDEFGHIJKLMNOPQRSTUVWYZ"
40     "0123456789_";
41 const char kOAuthNonceLabel[] = "oauth_nonce";
42 const char kOAuthSignatureLabel[] = "oauth_signature";
43 const char kOAuthSignatureMethodLabel[] = "oauth_signature_method";
44 const char kOAuthTimestampLabel[] = "oauth_timestamp";
45 const char kOAuthTokenLabel[] = "oauth_token";
46 const char kOAuthVersion[] = "1.0";
47 const char kOAuthVersionLabel[] = "oauth_version";
48 
49 enum ParseQueryState {
50   START_STATE,
51   KEYWORD_STATE,
52   VALUE_STATE,
53 };
54 
HttpMethodName(OAuthRequestSigner::HttpMethod method)55 const std::string HttpMethodName(OAuthRequestSigner::HttpMethod method) {
56   switch (method) {
57     case OAuthRequestSigner::GET_METHOD:
58       return "GET";
59     case OAuthRequestSigner::POST_METHOD:
60       return "POST";
61   }
62   NOTREACHED();
63   return std::string();
64 }
65 
SignatureMethodName(OAuthRequestSigner::SignatureMethod method)66 const std::string SignatureMethodName(
67     OAuthRequestSigner::SignatureMethod method) {
68   switch (method) {
69     case OAuthRequestSigner::HMAC_SHA1_SIGNATURE:
70       return "HMAC-SHA1";
71     case OAuthRequestSigner::RSA_SHA1_SIGNATURE:
72       return "RSA-SHA1";
73     case OAuthRequestSigner::PLAINTEXT_SIGNATURE:
74       return "PLAINTEXT";
75   }
76   NOTREACHED();
77   return std::string();
78 }
79 
BuildBaseString(const GURL & request_base_url,OAuthRequestSigner::HttpMethod http_method,const std::string & base_parameters)80 std::string BuildBaseString(const GURL& request_base_url,
81                             OAuthRequestSigner::HttpMethod http_method,
82                             const std::string& base_parameters) {
83   return base::StringPrintf("%s&%s&%s",
84                             HttpMethodName(http_method).c_str(),
85                             OAuthRequestSigner::Encode(
86                                 request_base_url.spec()).c_str(),
87                             OAuthRequestSigner::Encode(
88                                 base_parameters).c_str());
89 }
90 
BuildBaseStringParameters(const OAuthRequestSigner::Parameters & parameters)91 std::string BuildBaseStringParameters(
92     const OAuthRequestSigner::Parameters& parameters) {
93   std::string result;
94   OAuthRequestSigner::Parameters::const_iterator cursor;
95   OAuthRequestSigner::Parameters::const_iterator limit;
96   bool first = true;
97   for (cursor = parameters.begin(), limit = parameters.end();
98        cursor != limit;
99        ++cursor) {
100     if (first)
101       first = false;
102     else
103       result += '&';
104     result += OAuthRequestSigner::Encode(cursor->first);
105     result += '=';
106     result += OAuthRequestSigner::Encode(cursor->second);
107   }
108   return result;
109 }
110 
GenerateNonce()111 std::string GenerateNonce() {
112   char result[kMaxNonceLength + 1];
113   int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) +
114       kMinNonceLength;
115   result[length] = '\0';
116   for (int index = 0; index < length; ++index)
117     result[index] = kOAuthNonceCharacters[
118         base::RandUint64() % (sizeof(kOAuthNonceCharacters) - 1)];
119   return result;
120 }
121 
GenerateTimestamp()122 std::string GenerateTimestamp() {
123   return base::StringPrintf(
124       "%" PRId64,
125       (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds());
126 }
127 
128 // Creates a string-to-string, keyword-value map from a parameter/query string
129 // that uses ampersand (&) to seperate paris and equals (=) to seperate
130 // keyword from value.
ParseQuery(const std::string & query,OAuthRequestSigner::Parameters * parameters_result)131 bool ParseQuery(const std::string& query,
132                 OAuthRequestSigner::Parameters* parameters_result) {
133   std::string::const_iterator cursor;
134   std::string keyword;
135   std::string::const_iterator limit;
136   OAuthRequestSigner::Parameters parameters;
137   ParseQueryState state;
138   std::string value;
139 
140   state = START_STATE;
141   for (cursor = query.begin(), limit = query.end();
142        cursor != limit;
143        ++cursor) {
144     char character = *cursor;
145     switch (state) {
146       case KEYWORD_STATE:
147         switch (character) {
148           case '&':
149             parameters[keyword] = value;
150             keyword = "";
151             value = "";
152             state = START_STATE;
153             break;
154           case '=':
155             state = VALUE_STATE;
156             break;
157           default:
158             keyword += character;
159         }
160         break;
161       case START_STATE:
162         switch (character) {
163           case '&':  // Intentionally falling through
164           case '=':
165             return false;
166           default:
167             keyword += character;
168             state = KEYWORD_STATE;
169         }
170         break;
171       case VALUE_STATE:
172         switch (character) {
173           case '=':
174             return false;
175           case '&':
176             parameters[keyword] = value;
177             keyword = "";
178             value = "";
179             state = START_STATE;
180             break;
181           default:
182             value += character;
183         }
184         break;
185     }
186   }
187   switch (state) {
188     case START_STATE:
189       break;
190     case KEYWORD_STATE:  // Intentionally falling through
191     case VALUE_STATE:
192       parameters[keyword] = value;
193       break;
194     default:
195       NOTREACHED();
196   }
197   *parameters_result = parameters;
198   return true;
199 }
200 
201 // Creates the value for the oauth_signature parameter when the
202 // oauth_signature_method is HMAC-SHA1.
SignHmacSha1(const std::string & text,const std::string & key,std::string * signature_return)203 bool SignHmacSha1(const std::string& text,
204                   const std::string& key,
205                   std::string* signature_return) {
206   crypto::HMAC hmac(crypto::HMAC::SHA1);
207   DCHECK(hmac.DigestLength() == kHmacDigestLength);
208   unsigned char digest[kHmacDigestLength];
209   bool result = hmac.Init(key) &&
210       hmac.Sign(text, digest, kHmacDigestLength);
211   if (result) {
212     base::Base64Encode(
213         std::string(reinterpret_cast<const char*>(digest), kHmacDigestLength),
214         signature_return);
215   }
216   return result;
217 }
218 
219 // Creates the value for the oauth_signature parameter when the
220 // oauth_signature_method is PLAINTEXT.
221 //
222 // Not yet implemented, and might never be.
SignPlaintext(const std::string & text,const std::string & key,std::string * result)223 bool SignPlaintext(const std::string& text,
224                    const std::string& key,
225                    std::string* result) {
226   NOTIMPLEMENTED();
227   return false;
228 }
229 
230 // Creates the value for the oauth_signature parameter when the
231 // oauth_signature_method is RSA-SHA1.
232 //
233 // Not yet implemented, and might never be.
SignRsaSha1(const std::string & text,const std::string & key,std::string * result)234 bool SignRsaSha1(const std::string& text,
235                  const std::string& key,
236                  std::string* result) {
237   NOTIMPLEMENTED();
238   return false;
239 }
240 
241 // Adds parameters that are required by OAuth added as needed to |parameters|.
PrepareParameters(OAuthRequestSigner::Parameters * parameters,OAuthRequestSigner::SignatureMethod signature_method,OAuthRequestSigner::HttpMethod http_method,const std::string & consumer_key,const std::string & token_key)242 void PrepareParameters(OAuthRequestSigner::Parameters* parameters,
243                        OAuthRequestSigner::SignatureMethod signature_method,
244                        OAuthRequestSigner::HttpMethod http_method,
245                        const std::string& consumer_key,
246                        const std::string& token_key) {
247   if (parameters->find(kOAuthNonceLabel) == parameters->end())
248     (*parameters)[kOAuthNonceLabel] = GenerateNonce();
249 
250   if (parameters->find(kOAuthTimestampLabel) == parameters->end())
251     (*parameters)[kOAuthTimestampLabel] = GenerateTimestamp();
252 
253   (*parameters)[kOAuthConsumerKeyLabel] = consumer_key;
254   (*parameters)[kOAuthSignatureMethodLabel] =
255       SignatureMethodName(signature_method);
256   (*parameters)[kOAuthTokenLabel] = token_key;
257   (*parameters)[kOAuthVersionLabel] = kOAuthVersion;
258 }
259 
260 // Implements shared signing logic, generating the signature and storing it in
261 // |parameters|. Returns true if the signature has been generated succesfully.
SignParameters(const GURL & request_base_url,OAuthRequestSigner::SignatureMethod signature_method,OAuthRequestSigner::HttpMethod http_method,const std::string & consumer_key,const std::string & consumer_secret,const std::string & token_key,const std::string & token_secret,OAuthRequestSigner::Parameters * parameters)262 bool SignParameters(const GURL& request_base_url,
263                     OAuthRequestSigner::SignatureMethod signature_method,
264                     OAuthRequestSigner::HttpMethod http_method,
265                     const std::string& consumer_key,
266                     const std::string& consumer_secret,
267                     const std::string& token_key,
268                     const std::string& token_secret,
269                     OAuthRequestSigner::Parameters* parameters) {
270   DCHECK(request_base_url.is_valid());
271   PrepareParameters(parameters, signature_method, http_method,
272                     consumer_key, token_key);
273   std::string base_parameters = BuildBaseStringParameters(*parameters);
274   std::string base = BuildBaseString(request_base_url, http_method,
275                                      base_parameters);
276   std::string key = consumer_secret + '&' + token_secret;
277   bool is_signed = false;
278   std::string signature;
279   switch (signature_method) {
280     case OAuthRequestSigner::HMAC_SHA1_SIGNATURE:
281       is_signed = SignHmacSha1(base, key, &signature);
282       break;
283     case OAuthRequestSigner::RSA_SHA1_SIGNATURE:
284       is_signed = SignRsaSha1(base, key, &signature);
285       break;
286     case OAuthRequestSigner::PLAINTEXT_SIGNATURE:
287       is_signed = SignPlaintext(base, key, &signature);
288       break;
289     default:
290       NOTREACHED();
291   }
292   if (is_signed)
293     (*parameters)[kOAuthSignatureLabel] = signature;
294   return is_signed;
295 }
296 
297 
298 }  // namespace
299 
300 // static
Decode(const std::string & text,std::string * decoded_text)301 bool OAuthRequestSigner::Decode(const std::string& text,
302                                 std::string* decoded_text) {
303   std::string accumulator;
304   std::string::const_iterator cursor;
305   std::string::const_iterator limit;
306   for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) {
307     char character = *cursor;
308     if (character == '%') {
309       ++cursor;
310       if (cursor == limit)
311         return false;
312       char* first = strchr(kHexDigits, *cursor);
313       if (!first)
314         return false;
315       int high = first - kHexDigits;
316       DCHECK(high >= 0 && high < kHexBase);
317 
318       ++cursor;
319       if (cursor == limit)
320         return false;
321       char* second = strchr(kHexDigits, *cursor);
322       if (!second)
323         return false;
324       int low = second - kHexDigits;
325       DCHECK(low >= 0 || low < kHexBase);
326 
327       char decoded = static_cast<char>(high * kHexBase + low);
328       DCHECK(!(base::IsAsciiAlpha(decoded) || base::IsAsciiDigit(decoded)));
329       DCHECK(!(decoded && strchr("-._~", decoded)));
330       accumulator += decoded;
331     } else {
332       accumulator += character;
333     }
334   }
335   *decoded_text = accumulator;
336   return true;
337 }
338 
339 // static
Encode(const std::string & text)340 std::string OAuthRequestSigner::Encode(const std::string& text) {
341   std::string result;
342   std::string::const_iterator cursor;
343   std::string::const_iterator limit;
344   for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) {
345     char character = *cursor;
346     if (base::IsAsciiAlpha(character) || base::IsAsciiDigit(character)) {
347       result += character;
348     } else {
349       switch (character) {
350         case '-':
351         case '.':
352         case '_':
353         case '~':
354           result += character;
355           break;
356         default:
357           unsigned char byte = static_cast<unsigned char>(character);
358           result = result + '%' + kHexDigits[byte / kHexBase] +
359               kHexDigits[byte % kHexBase];
360       }
361     }
362   }
363   return result;
364 }
365 
366 // static
ParseAndSign(const GURL & request_url_with_parameters,SignatureMethod signature_method,HttpMethod http_method,const std::string & consumer_key,const std::string & consumer_secret,const std::string & token_key,const std::string & token_secret,std::string * result)367 bool OAuthRequestSigner::ParseAndSign(const GURL& request_url_with_parameters,
368                                       SignatureMethod signature_method,
369                                       HttpMethod http_method,
370                                       const std::string& consumer_key,
371                                       const std::string& consumer_secret,
372                                       const std::string& token_key,
373                                       const std::string& token_secret,
374                                       std::string* result) {
375   DCHECK(request_url_with_parameters.is_valid());
376   Parameters parameters;
377   if (request_url_with_parameters.has_query()) {
378     const std::string& query = request_url_with_parameters.query();
379     if (!query.empty()) {
380       if (!ParseQuery(query, &parameters))
381         return false;
382     }
383   }
384   std::string spec = request_url_with_parameters.spec();
385   std::string url_without_parameters = spec;
386   std::string::size_type question = spec.find("?");
387   if (question != std::string::npos)
388     url_without_parameters = spec.substr(0,question);
389   return SignURL(GURL(url_without_parameters), parameters, signature_method,
390                  http_method, consumer_key, consumer_secret, token_key,
391                  token_secret, result);
392 }
393 
394 // static
SignURL(const GURL & request_base_url,const Parameters & request_parameters,SignatureMethod signature_method,HttpMethod http_method,const std::string & consumer_key,const std::string & consumer_secret,const std::string & token_key,const std::string & token_secret,std::string * signed_text_return)395 bool OAuthRequestSigner::SignURL(
396     const GURL& request_base_url,
397     const Parameters& request_parameters,
398     SignatureMethod signature_method,
399     HttpMethod http_method,
400     const std::string& consumer_key,
401     const std::string& consumer_secret,
402     const std::string& token_key,
403     const std::string& token_secret,
404     std::string* signed_text_return) {
405   DCHECK(request_base_url.is_valid());
406   Parameters parameters(request_parameters);
407   bool is_signed = SignParameters(request_base_url, signature_method,
408                                   http_method, consumer_key, consumer_secret,
409                                   token_key, token_secret, &parameters);
410   if (is_signed) {
411     std::string signed_text;
412     switch (http_method) {
413       case GET_METHOD:
414         signed_text = request_base_url.spec() + '?';
415         FALLTHROUGH;
416       case POST_METHOD:
417         signed_text += BuildBaseStringParameters(parameters);
418         break;
419       default:
420         NOTREACHED();
421     }
422     *signed_text_return = signed_text;
423   }
424   return is_signed;
425 }
426 
427 // static
SignAuthHeader(const GURL & request_base_url,const Parameters & request_parameters,SignatureMethod signature_method,HttpMethod http_method,const std::string & consumer_key,const std::string & consumer_secret,const std::string & token_key,const std::string & token_secret,std::string * signed_text_return)428 bool OAuthRequestSigner::SignAuthHeader(
429     const GURL& request_base_url,
430     const Parameters& request_parameters,
431     SignatureMethod signature_method,
432     HttpMethod http_method,
433     const std::string& consumer_key,
434     const std::string& consumer_secret,
435     const std::string& token_key,
436     const std::string& token_secret,
437     std::string* signed_text_return) {
438   DCHECK(request_base_url.is_valid());
439   Parameters parameters(request_parameters);
440   bool is_signed = SignParameters(request_base_url, signature_method,
441                                   http_method, consumer_key, consumer_secret,
442                                   token_key, token_secret, &parameters);
443   if (is_signed) {
444     std::string signed_text = "OAuth ";
445     bool first = true;
446     for (Parameters::const_iterator param = parameters.begin();
447          param != parameters.end();
448          ++param) {
449       if (first)
450         first = false;
451       else
452         signed_text += ", ";
453       signed_text +=
454           base::StringPrintf(
455               "%s=\"%s\"",
456               OAuthRequestSigner::Encode(param->first).c_str(),
457               OAuthRequestSigner::Encode(param->second).c_str());
458     }
459     *signed_text_return = signed_text;
460   }
461   return is_signed;
462 }
463