| Index: third_party/oauth2client/crypt.py
 | 
| diff --git a/third_party/oauth2client/crypt.py b/third_party/oauth2client/crypt.py
 | 
| index 2d31815dd6357b5c7573a4eebc6792b168b4d6a4..b5b8cab22e002432388f51233728c2fe044a9c79 100644
 | 
| --- a/third_party/oauth2client/crypt.py
 | 
| +++ b/third_party/oauth2client/crypt.py
 | 
| @@ -1,7 +1,6 @@
 | 
| -#!/usr/bin/python2.4
 | 
|  # -*- coding: utf-8 -*-
 | 
|  #
 | 
| -# Copyright (C) 2011 Google Inc.
 | 
| +# Copyright 2014 Google Inc. All rights reserved.
 | 
|  #
 | 
|  # Licensed under the Apache License, Version 2.0 (the "License");
 | 
|  # you may not use this file except in compliance with the License.
 | 
| @@ -14,13 +13,15 @@
 | 
|  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
|  # See the License for the specific language governing permissions and
 | 
|  # limitations under the License.
 | 
| +"""Crypto-related routines for oauth2client."""
 | 
|  
 | 
|  import base64
 | 
| -import hashlib
 | 
| +import json
 | 
|  import logging
 | 
| +import sys
 | 
|  import time
 | 
|  
 | 
| -from anyjson import simplejson
 | 
| +from third_party import six
 | 
|  
 | 
|  
 | 
|  CLOCK_SKEW_SECS = 300  # 5 minutes in seconds
 | 
| @@ -38,7 +39,6 @@ class AppIdentityError(Exception):
 | 
|  try:
 | 
|    from OpenSSL import crypto
 | 
|  
 | 
| -
 | 
|    class OpenSSLVerifier(object):
 | 
|      """Verifies the signature on a message."""
 | 
|  
 | 
| @@ -62,6 +62,8 @@ try:
 | 
|          key that this object was constructed with.
 | 
|        """
 | 
|        try:
 | 
| +        if isinstance(message, six.text_type):
 | 
| +          message = message.encode('utf-8')
 | 
|          crypto.verify(self._pubkey, signature, message, 'sha256')
 | 
|          return True
 | 
|        except:
 | 
| @@ -104,15 +106,17 @@ try:
 | 
|        """Signs a message.
 | 
|  
 | 
|        Args:
 | 
| -        message: string, Message to be signed.
 | 
| +        message: bytes, Message to be signed.
 | 
|  
 | 
|        Returns:
 | 
|          string, The signature of the message for the given key.
 | 
|        """
 | 
| +      if isinstance(message, six.text_type):
 | 
| +        message = message.encode('utf-8')
 | 
|        return crypto.sign(self._key, message, 'sha256')
 | 
|  
 | 
|      @staticmethod
 | 
| -    def from_string(key, password='notasecret'):
 | 
| +    def from_string(key, password=b'notasecret'):
 | 
|        """Construct a Signer instance from a string.
 | 
|  
 | 
|        Args:
 | 
| @@ -125,21 +129,45 @@ try:
 | 
|        Raises:
 | 
|          OpenSSL.crypto.Error if the key can't be parsed.
 | 
|        """
 | 
| -      if key.startswith('-----BEGIN '):
 | 
| -        pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
 | 
| +      parsed_pem_key = _parse_pem_key(key)
 | 
| +      if parsed_pem_key:
 | 
| +        pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
 | 
|        else:
 | 
| +        if isinstance(password, six.text_type):
 | 
| +          password = password.encode('utf-8')
 | 
|          pkey = crypto.load_pkcs12(key, password).get_privatekey()
 | 
|        return OpenSSLSigner(pkey)
 | 
|  
 | 
| +
 | 
| +  def pkcs12_key_as_pem(private_key_text, private_key_password):
 | 
| +    """Convert the contents of a PKCS12 key to PEM using OpenSSL.
 | 
| +
 | 
| +    Args:
 | 
| +      private_key_text: String. Private key.
 | 
| +      private_key_password: String. Password for PKCS12.
 | 
| +
 | 
| +    Returns:
 | 
| +      String. PEM contents of ``private_key_text``.
 | 
| +    """
 | 
| +    decoded_body = base64.b64decode(private_key_text)
 | 
| +    if isinstance(private_key_password, six.string_types):
 | 
| +      private_key_password = private_key_password.encode('ascii')
 | 
| +
 | 
| +    pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
 | 
| +    return crypto.dump_privatekey(crypto.FILETYPE_PEM,
 | 
| +                                  pkcs12.get_privatekey())
 | 
|  except ImportError:
 | 
|    OpenSSLVerifier = None
 | 
|    OpenSSLSigner = None
 | 
| +  def pkcs12_key_as_pem(*args, **kwargs):
 | 
| +    raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
 | 
|  
 | 
|  
 | 
|  try:
 | 
|    from Crypto.PublicKey import RSA
 | 
|    from Crypto.Hash import SHA256
 | 
|    from Crypto.Signature import PKCS1_v1_5
 | 
| +  from Crypto.Util.asn1 import DerSequence
 | 
|  
 | 
|  
 | 
|    class PyCryptoVerifier(object):
 | 
| @@ -181,14 +209,17 @@ try:
 | 
|  
 | 
|        Returns:
 | 
|          Verifier instance.
 | 
| -
 | 
| -      Raises:
 | 
| -        NotImplementedError if is_x509_cert is true.
 | 
|        """
 | 
|        if is_x509_cert:
 | 
| -        raise NotImplementedError(
 | 
| -            'X509 certs are not supported by the PyCrypto library. '
 | 
| -            'Try using PyOpenSSL if native code is an option.')
 | 
| +        if isinstance(key_pem, six.text_type):
 | 
| +          key_pem = key_pem.encode('ascii')
 | 
| +        pemLines = key_pem.replace(b' ', b'').split()
 | 
| +        certDer = _urlsafe_b64decode(b''.join(pemLines[1:-1]))
 | 
| +        certSeq = DerSequence()
 | 
| +        certSeq.decode(certDer)
 | 
| +        tbsSeq = DerSequence()
 | 
| +        tbsSeq.decode(certSeq[0])
 | 
| +        pubkey = RSA.importKey(tbsSeq[6])
 | 
|        else:
 | 
|          pubkey = RSA.importKey(key_pem)
 | 
|        return PyCryptoVerifier(pubkey)
 | 
| @@ -214,6 +245,8 @@ try:
 | 
|        Returns:
 | 
|          string, The signature of the message for the given key.
 | 
|        """
 | 
| +      if isinstance(message, six.text_type):
 | 
| +        message = message.encode('utf-8')
 | 
|        return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
 | 
|  
 | 
|      @staticmethod
 | 
| @@ -230,11 +263,12 @@ try:
 | 
|        Raises:
 | 
|          NotImplementedError if they key isn't in PEM format.
 | 
|        """
 | 
| -      if key.startswith('-----BEGIN '):
 | 
| -        pkey = RSA.importKey(key)
 | 
| +      parsed_pem_key = _parse_pem_key(key)
 | 
| +      if parsed_pem_key:
 | 
| +        pkey = RSA.importKey(parsed_pem_key)
 | 
|        else:
 | 
|          raise NotImplementedError(
 | 
| -            'PKCS12 format is not supported by the PyCrpto library. '
 | 
| +            'PKCS12 format is not supported by the PyCrypto library. '
 | 
|              'Try converting to a "PEM" '
 | 
|              '(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
 | 
|              'or using PyOpenSSL if native code is an option.')
 | 
| @@ -256,19 +290,39 @@ else:
 | 
|                      'PyOpenSSL, or PyCrypto 2.6 or later')
 | 
|  
 | 
|  
 | 
| +def _parse_pem_key(raw_key_input):
 | 
| +  """Identify and extract PEM keys.
 | 
| +
 | 
| +  Determines whether the given key is in the format of PEM key, and extracts
 | 
| +  the relevant part of the key if it is.
 | 
| +
 | 
| +  Args:
 | 
| +    raw_key_input: The contents of a private key file (either PEM or PKCS12).
 | 
| +
 | 
| +  Returns:
 | 
| +    string, The actual key if the contents are from a PEM file, or else None.
 | 
| +  """
 | 
| +  offset = raw_key_input.find(b'-----BEGIN ')
 | 
| +  if offset != -1:
 | 
| +    return raw_key_input[offset:]
 | 
| +
 | 
| +
 | 
|  def _urlsafe_b64encode(raw_bytes):
 | 
| -  return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
 | 
| +  if isinstance(raw_bytes, six.text_type):
 | 
| +    raw_bytes = raw_bytes.encode('utf-8')
 | 
| +  return base64.urlsafe_b64encode(raw_bytes).decode('ascii').rstrip('=')
 | 
|  
 | 
|  
 | 
|  def _urlsafe_b64decode(b64string):
 | 
|    # Guard against unicode strings, which base64 can't handle.
 | 
| -  b64string = b64string.encode('ascii')
 | 
| -  padded = b64string + '=' * (4 - len(b64string) % 4)
 | 
| +  if isinstance(b64string, six.text_type):
 | 
| +    b64string = b64string.encode('ascii')
 | 
| +  padded = b64string + b'=' * (4 - len(b64string) % 4)
 | 
|    return base64.urlsafe_b64decode(padded)
 | 
|  
 | 
|  
 | 
|  def _json_encode(data):
 | 
| -  return simplejson.dumps(data, separators = (',', ':'))
 | 
| +  return json.dumps(data, separators=(',', ':'))
 | 
|  
 | 
|  
 | 
|  def make_signed_jwt(signer, payload):
 | 
| @@ -286,8 +340,8 @@ def make_signed_jwt(signer, payload):
 | 
|    header = {'typ': 'JWT', 'alg': 'RS256'}
 | 
|  
 | 
|    segments = [
 | 
| -          _urlsafe_b64encode(_json_encode(header)),
 | 
| -          _urlsafe_b64encode(_json_encode(payload)),
 | 
| +      _urlsafe_b64encode(_json_encode(header)),
 | 
| +      _urlsafe_b64encode(_json_encode(payload)),
 | 
|    ]
 | 
|    signing_input = '.'.join(segments)
 | 
|  
 | 
| @@ -318,9 +372,8 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
 | 
|    """
 | 
|    segments = jwt.split('.')
 | 
|  
 | 
| -  if (len(segments) != 3):
 | 
| -    raise AppIdentityError(
 | 
| -      'Wrong number of segments in token: %s' % jwt)
 | 
| +  if len(segments) != 3:
 | 
| +    raise AppIdentityError('Wrong number of segments in token: %s' % jwt)
 | 
|    signed = '%s.%s' % (segments[0], segments[1])
 | 
|  
 | 
|    signature = _urlsafe_b64decode(segments[2])
 | 
| @@ -328,15 +381,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
 | 
|    # Parse token.
 | 
|    json_body = _urlsafe_b64decode(segments[1])
 | 
|    try:
 | 
| -    parsed = simplejson.loads(json_body)
 | 
| +    parsed = json.loads(json_body.decode('utf-8'))
 | 
|    except:
 | 
|      raise AppIdentityError('Can\'t parse token: %s' % json_body)
 | 
|  
 | 
|    # Check signature.
 | 
|    verified = False
 | 
| -  for (keyname, pem) in certs.items():
 | 
| +  for pem in certs.values():
 | 
|      verifier = Verifier.from_string(pem, True)
 | 
| -    if (verifier.verify(signed, signature)):
 | 
| +    if verifier.verify(signed, signature):
 | 
|        verified = True
 | 
|        break
 | 
|    if not verified:
 | 
| @@ -349,21 +402,20 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
 | 
|    earliest = iat - CLOCK_SKEW_SECS
 | 
|  
 | 
|    # Check expiration timestamp.
 | 
| -  now = long(time.time())
 | 
| +  now = int(time.time())
 | 
|    exp = parsed.get('exp')
 | 
|    if exp is None:
 | 
|      raise AppIdentityError('No exp field in token: %s' % json_body)
 | 
|    if exp >= now + MAX_TOKEN_LIFETIME_SECS:
 | 
| -    raise AppIdentityError(
 | 
| -      'exp field too far in future: %s' % json_body)
 | 
| +    raise AppIdentityError('exp field too far in future: %s' % json_body)
 | 
|    latest = exp + CLOCK_SKEW_SECS
 | 
|  
 | 
|    if now < earliest:
 | 
|      raise AppIdentityError('Token used too early, %d < %d: %s' %
 | 
| -      (now, earliest, json_body))
 | 
| +                           (now, earliest, json_body))
 | 
|    if now > latest:
 | 
|      raise AppIdentityError('Token used too late, %d > %d: %s' %
 | 
| -      (now, latest, json_body))
 | 
| +                           (now, latest, json_body))
 | 
|  
 | 
|    # Check audience.
 | 
|    if audience is not None:
 | 
| @@ -372,6 +424,6 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
 | 
|        raise AppIdentityError('No aud field in token: %s' % json_body)
 | 
|      if aud != audience:
 | 
|        raise AppIdentityError('Wrong recipient, %s != %s: %s' %
 | 
| -          (aud, audience, json_body))
 | 
| +                             (aud, audience, json_body))
 | 
|  
 | 
| -  return parsed
 | 
| +  return parsed
 | 
| 
 |