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 |