Skip to content

Commit

Permalink
Merge pull request #1338 from dhermes/swap-crypto-libs
Browse files Browse the repository at this point in the history
Swapping PyCrypto for pyOpenSSL.
  • Loading branch information
dhermes committed Jan 14, 2016
2 parents 3a2a1c5 + 948cccf commit 30acf33
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 69 deletions.
2 changes: 2 additions & 0 deletions gcloud/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class _Monkey(object):

def __init__(self, module, **kw):
self.module = module
if len(kw) == 0: # pragma: NO COVER
raise ValueError('_Monkey was used with nothing to monkey-patch')
self.to_restore = dict([(key, getattr(module, key)) for key in kw])
for key, value in kw.items():
setattr(module, key, value)
Expand Down
29 changes: 13 additions & 16 deletions gcloud/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
import six
from six.moves.urllib.parse import urlencode # pylint: disable=F0401

from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from OpenSSL import crypto

from oauth2client import client
from oauth2client.client import _get_application_default_credential_from_file
from oauth2client import crypt
Expand Down Expand Up @@ -161,19 +160,19 @@ def get_for_service_account_p12(client_email, private_key_path, scope=None):


def _get_pem_key(credentials):
"""Gets RSA key for a PEM payload from a credentials object.
"""Gets private key for a PEM payload from a credentials object.
:type credentials: :class:`client.SignedJwtAssertionCredentials`,
:class:`service_account._ServiceAccountCredentials`
:param credentials: The credentials used to create an RSA key
:param credentials: The credentials used to create a private key
for signing text.
:rtype: :class:`Crypto.PublicKey.RSA._RSAobj`
:returns: An RSA object used to sign text.
:rtype: :class:`OpenSSL.crypto.PKey`
:returns: A PKey object used to sign text.
:raises: `TypeError` if `credentials` is the wrong type.
"""
if isinstance(credentials, client.SignedJwtAssertionCredentials):
# Take our PKCS12 (.p12) key and make it into a RSA key we can use.
# Take our PKCS12 (.p12) text and convert to PEM text.
pem_text = crypt.pkcs12_key_as_pem(credentials.private_key,
credentials.private_key_password)
elif isinstance(credentials, service_account._ServiceAccountCredentials):
Expand All @@ -182,7 +181,7 @@ def _get_pem_key(credentials):
raise TypeError((credentials,
'not a valid service account credentials type'))

return RSA.importKey(pem_text)
return crypto.load_privatekey(crypto.FILETYPE_PEM, pem_text)


def _get_signature_bytes(credentials, string_to_sign):
Expand All @@ -192,7 +191,7 @@ def _get_signature_bytes(credentials, string_to_sign):
:class:`service_account._ServiceAccountCredentials`,
:class:`_GAECreds`
:param credentials: The credentials used for signing text (typically
involves the creation of an RSA key).
involves the creation of a PKey).
:type string_to_sign: string
:param string_to_sign: The string to be signed by the credentials.
Expand All @@ -204,13 +203,11 @@ def _get_signature_bytes(credentials, string_to_sign):
_, signed_bytes = app_identity.sign_blob(string_to_sign)
return signed_bytes
else:
pem_key = _get_pem_key(credentials)
# Sign the string with the RSA key.
signer = PKCS1_v1_5.new(pem_key)
# Sign the string with the PKey.
pkey = _get_pem_key(credentials)
if not isinstance(string_to_sign, six.binary_type):
string_to_sign = string_to_sign.encode('utf-8')
signature_hash = SHA256.new(string_to_sign)
return signer.sign(signature_hash)
return crypto.sign(pkey, string_to_sign, 'SHA256')


def _get_service_account_name(credentials):
Expand Down Expand Up @@ -246,7 +243,7 @@ def _get_signed_query_params(credentials, expiration, string_to_sign):
:type credentials: :class:`client.SignedJwtAssertionCredentials`,
:class:`service_account._ServiceAccountCredentials`
:param credentials: The credentials used to create an RSA key
:param credentials: The credentials used to create a private key
for signing text.
:type expiration: int or long
Expand Down
5 changes: 2 additions & 3 deletions gcloud/storage/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
"""

import base64

from Crypto.Hash import MD5
from hashlib import md5


class _PropertyMixin(object):
Expand Down Expand Up @@ -168,7 +167,7 @@ def _base64_md5hash(buffer_object):
:param buffer_object: Buffer containing bytes used to compute an MD5
hash (as base64).
"""
hash_obj = MD5.new()
hash_obj = md5()
_write_buffer_to_hash(buffer_object, hash_obj)
digest_bytes = hash_obj.digest()
return base64.b64encode(digest_bytes)
10 changes: 5 additions & 5 deletions gcloud/storage/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ def read(self, block_size):
BUFFER = _Buffer([b'', BYTES_TO_SIGN])
MD5 = _MD5(DIGEST_VAL)

with _Monkey(MUT, base64=BASE64, MD5=MD5):
with _Monkey(MUT, base64=BASE64, md5=MD5):
SIGNED_CONTENT = self._callFUT(BUFFER)

self.assertEqual(BUFFER._block_sizes, [8192, 8192])
self.assertTrue(SIGNED_CONTENT is DIGEST_VAL)
self.assertEqual(BASE64._called_b64encode, [DIGEST_VAL])
self.assertEqual(MD5._new_called, [None])
self.assertEqual(MD5._called, [None])
self.assertEqual(MD5.hash_obj.num_digest_calls, 1)
self.assertEqual(MD5.hash_obj._blocks, [BYTES_TO_SIGN])

Expand Down Expand Up @@ -200,10 +200,10 @@ class _MD5(object):

def __init__(self, digest_val):
self.hash_obj = _MD5Hash(digest_val)
self._new_called = []
self._called = []

def new(self, data=None):
self._new_called.append(data)
def __call__(self, data=None):
self._called.append(data)
return self.hash_obj


Expand Down
90 changes: 46 additions & 44 deletions gcloud/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,25 +247,29 @@ def _run_with_fake_crypto(self, credentials, private_key_text,
from gcloud import credentials as MUT

crypt = _Crypt()
pkcs_v1_5 = _PKCS1_v1_5()
rsa = _RSA()
sha256 = _SHA256()
load_result = object()
sign_result = object()
openssl_crypto = _OpenSSLCrypto(load_result, sign_result)

with _Monkey(MUT, crypt=crypt, RSA=rsa, PKCS1_v1_5=pkcs_v1_5,
SHA256=sha256):
with _Monkey(MUT, crypt=crypt, crypto=openssl_crypto):
result = self._callFUT(credentials, string_to_sign)

if crypt._pkcs12_key_as_pem_called:
self.assertEqual(crypt._private_key_text,
base64.b64encode(private_key_text))
self.assertEqual(crypt._private_key_password, 'notasecret')
# sha256._string_to_sign is always bytes.
if isinstance(string_to_sign, six.binary_type):
self.assertEqual(sha256._string_to_sign, string_to_sign)
self.assertEqual(openssl_crypto._loaded,
[(openssl_crypto.FILETYPE_PEM, _Crypt._KEY)])
else:
self.assertEqual(sha256._string_to_sign,
string_to_sign.encode('utf-8'))
self.assertEqual(result, b'DEADBEEF')
self.assertEqual(openssl_crypto._loaded,
[(openssl_crypto.FILETYPE_PEM, private_key_text)])

if not isinstance(string_to_sign, six.binary_type):
string_to_sign = string_to_sign.encode('utf-8')
self.assertEqual(openssl_crypto._signed,
[(load_result, string_to_sign, 'SHA256')])

self.assertEqual(result, sign_result)

def test_p12_type(self):
from oauth2client.client import SignedJwtAssertionCredentials
Expand Down Expand Up @@ -450,14 +454,19 @@ def test_signed_jwt_for_p12(self):
credentials = client.SignedJwtAssertionCredentials(
'dummy_service_account_name', PRIVATE_KEY, scopes)
crypt = _Crypt()
rsa = _RSA()
with _Monkey(MUT, crypt=crypt, RSA=rsa):
load_result = object()
openssl_crypto = _OpenSSLCrypto(load_result, None)

with _Monkey(MUT, crypt=crypt, crypto=openssl_crypto):
result = self._callFUT(credentials)

self.assertEqual(crypt._private_key_text,
base64.b64encode(PRIVATE_KEY))
self.assertEqual(crypt._private_key_password, 'notasecret')
self.assertEqual(result, 'imported:__PEM__')
self.assertEqual(result, load_result)
self.assertEqual(openssl_crypto._loaded,
[(openssl_crypto.FILETYPE_PEM, _Crypt._KEY)])
self.assertEqual(openssl_crypto._signed, [])

def test_service_account_via_json_key(self):
from oauth2client import service_account
Expand All @@ -476,12 +485,16 @@ def _get_private_key(private_key_pkcs8_text):
'dummy_service_account_id', 'dummy_service_account_email',
'dummy_private_key_id', PRIVATE_TEXT, scopes)

rsa = _RSA()
with _Monkey(MUT, RSA=rsa):
load_result = object()
openssl_crypto = _OpenSSLCrypto(load_result, None)

with _Monkey(MUT, crypto=openssl_crypto):
result = self._callFUT(credentials)

expected = 'imported:%s' % (PRIVATE_TEXT,)
self.assertEqual(result, expected)
self.assertEqual(result, load_result)
self.assertEqual(openssl_crypto._loaded,
[(openssl_crypto.FILETYPE_PEM, PRIVATE_TEXT)])
self.assertEqual(openssl_crypto._signed, [])


class Test__get_expiration_seconds(unittest2.TestCase):
Expand Down Expand Up @@ -596,43 +609,32 @@ def SignedJwtAssertionCredentials(self, **kw):
class _Crypt(object):

_pkcs12_key_as_pem_called = False
_KEY = '__PEM__'

def pkcs12_key_as_pem(self, private_key_text, private_key_password):
self._pkcs12_key_as_pem_called = True
self._private_key_text = private_key_text
self._private_key_password = private_key_password
return '__PEM__'
return self._KEY


class _RSA(object):
class _OpenSSLCrypto(object):

_imported = None
FILETYPE_PEM = object()

def importKey(self, pem):
self._imported = pem
return 'imported:%s' % pem
def __init__(self, load_result, sign_result):
self._loaded = []
self._load_result = load_result
self._signed = []
self._sign_result = sign_result

def load_privatekey(self, key_type, key_text):
self._loaded.append((key_type, key_text))
return self._load_result

class _PKCS1_v1_5(object):

_pem_key = _signature_hash = None

def new(self, pem_key):
self._pem_key = pem_key
return self

def sign(self, signature_hash):
self._signature_hash = signature_hash
return b'DEADBEEF'


class _SHA256(object):

_string_to_sign = None

def new(self, string_to_sign):
self._string_to_sign = string_to_sign
return self
def sign(self, pkey, to_sign, sign_algo):
self._signed.append((pkey, to_sign, sign_algo))
return self._sign_result


class _AppIdentity(object):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
'httplib2 >= 0.9.1',
'oauth2client >= 1.4.6',
'protobuf == 3.0.0a3',
'pycrypto',
'pyOpenSSL',
'six',
]

Expand Down

0 comments on commit 30acf33

Please sign in to comment.