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