oauth_request_signer.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/string_util.h"
20#include "base/stringprintf.h"
21#include "base/time.h"
22#include "crypto/hmac.h"
23#include "googleurl/src/gurl.h"
24
25namespace {
26
27static const int kHexBase = 16;
28static char kHexDigits[] = "0123456789ABCDEF";
29static const size_t kHmacDigestLength = 20;
30static const int kMaxNonceLength = 30;
31static const int kMinNonceLength = 15;
32
33static const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key";
34static const char kOAuthConsumerSecretLabel[] = "oauth_consumer_secret";
35static const char kOAuthNonceCharacters[] =
36    "abcdefghijklmnopqrstuvwyz"
37    "ABCDEFGHIJKLMNOPQRSTUVWYZ"
38    "0123456789_";
39static const char kOAuthNonceLabel[] = "oauth_nonce";
40static const char kOAuthSignatureLabel[] = "oauth_signature";
41static const char kOAuthSignatureMethodLabel[] = "oauth_signature_method";
42static const char kOAuthTimestampLabel[] = "oauth_timestamp";
43static const char kOAuthTokenLabel[] = "oauth_token";
44static const char kOAuthTokenSecretLabel[] = "oauth_token_secret";
45static const char kOAuthVersion[] = "1.0";
46static const char kOAuthVersionLabel[] = "oauth_version";
47
48enum ParseQueryState {
49  START_STATE,
50  KEYWORD_STATE,
51  VALUE_STATE,
52};
53
54const 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
65const 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
79std::string BuildBaseString(const GURL& request_base_url,
80                            OAuthRequestSigner::HttpMethod http_method,
81                            const std::string& base_parameters) {
82  return 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
90std::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
110std::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
121std::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.
130bool 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.
202bool 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.
220bool 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.
231bool 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|.
239void 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.
259bool 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
298bool 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
337std::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
364bool 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, &parameters))
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
392bool 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, &parameters);
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
425bool 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, &parameters);
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          StringPrintf("%s=\"%s\"",
452                       OAuthRequestSigner::Encode(param->first).c_str(),
453                       OAuthRequestSigner::Encode(param->second).c_str());
454    }
455    *signed_text_return = signed_text;
456  }
457  return is_signed;
458}
459