diff options
author | Ib Lundgren <ib.lundgren@gmail.com> | 2014-09-24 17:24:14 +0100 |
---|---|---|
committer | Ib Lundgren <ib.lundgren@gmail.com> | 2014-09-24 17:24:14 +0100 |
commit | 1071fde2f715c9564d19f4e5eee6a5910c2839b1 (patch) | |
tree | e2371623fe9f1c9780fc06c7e9e920ad7e499b0c | |
parent | 39013947bd2e242dda85fb0f150c49be23fd7510 (diff) | |
download | oauthlib-alternative_crypto_library.tar.gz |
Allow alternative crypto library cryptography.alternative_crypto_library
An alternative to PyCrypto is taking form and it would be
nice to allow users to choose which library they prefer to use
for their RSA signing. CC #226.
-rw-r--r-- | oauthlib/common.py | 5 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/signature.py | 138 | ||||
-rwxr-xr-x | setup.py | 7 | ||||
-rw-r--r-- | tests/oauth1/rfc5849/test_signatures.py | 24 | ||||
-rw-r--r-- | tox.ini | 1 |
5 files changed, 146 insertions, 29 deletions
diff --git a/oauthlib/common.py b/oauthlib/common.py index d3e2edb..01ca775 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -329,6 +329,11 @@ def to_unicode(data, encoding='UTF-8'): return data +def to_bytes(data, encoding='UTF-8'): + data = to_unicode(data, encoding=encoding) + return data.encode(encoding) + + class CaseInsensitiveDict(dict): """Basic case insensitive dict with strings only keys.""" diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 58a3497..0963c7b 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -26,13 +26,16 @@ from __future__ import absolute_import, unicode_literals import binascii import hashlib import hmac +import os + try: import urlparse except ImportError: import urllib.parse as urlparse + from . import utils from oauthlib.common import urldecode, extract_params, safe_string_equals -from oauthlib.common import bytes_type, unicode_type +from oauthlib.common import bytes_type, unicode_type, to_bytes, to_unicode def construct_base_string(http_method, base_string_uri, @@ -408,12 +411,120 @@ def normalize_parameters(params): return '&'.join(parameter_parts) +def cryptography_rsa_signer(): + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.primitives.serialization import load_pem_private_key + + def sign(key_data, base_string): + key = load_pem_private_key(to_bytes(key_data), None, backend=default_backend()) + signer = key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(to_bytes(base_string)) + return to_unicode(binascii.b2a_base64(signer.finalize())[:-1]) + return sign + + +def cryptography_rsa_verifier(): + from cryptography.hazmat.backends.openssl.backend import Backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.primitives.serialization import load_pem_public_key + from cryptography.exceptions import InvalidSignature + + def verifier(message, signature, public_key): + sig = binascii.a2b_base64(to_bytes(signature)) + key = load_pem_public_key(to_bytes(public_key), backend=Backend()) + v = key.verifier(sig, padding.PKCS1v15(), hashes.SHA1()) + v.update(to_bytes(message)) + try: + v.verify() + return True + except InvalidSignature: + return False + return verifier + + +def pycrypto_rsa_signer(): + from Crypto.PublicKey import RSA + from Crypto.Signature import PKCS1_v1_5 + from Crypto.Hash import SHA + def sign(key_data, base_string): + key = RSA.importKey(key_data) + if isinstance(base_string, unicode_type): + base_string = base_string.encode('utf-8') + h = SHA.new(base_string) + p = PKCS1_v1_5.new(key) + return binascii.b2a_base64(p.sign(h))[:-1].decode('utf-8') + return sign + + +def pycrypto_rsa_verifier(): + from Crypto.PublicKey import RSA + from Crypto.Signature import PKCS1_v1_5 + from Crypto.Hash import SHA + def verifier(message, signature, public_key): + key = RSA.importKey(public_key) + h = SHA.new(message.encode('utf-8')) + p = PKCS1_v1_5.new(key) + sig = binascii.a2b_base64(signature.encode('utf-8')) + return p.verify(h, sig) + return verifier + + +def get_rsa_signer(): + """Check available crypto libraries and return a signing function. + + Currently we support both Cryptography as well as PyCrypto with + a preference for the former if available. + """ + if not os.environ.get('OAUTHLIB_FORCE_PYCRYPTO', None): + try: + import cryptography + return cryptography_rsa_signer() + except ImportError: + pass + + try: + import Crypto + return pycrypto_rsa_signer() + except ImportError: + pass + + raise ImportError('No available cryptography library found. Please install' + ' either pycrypto or cryptography.') + + +def get_rsa_verifier(): + """Check available crypto libraries and return a signing function. + + Currently we support both Cryptography as well as PyCrypto with + a preference for the former if available. + """ + if not os.environ.get('OAUTHLIB_FORCE_PYCRYPTO', None): + try: + import cryptography + return cryptography_rsa_verifier() + except ImportError: + pass + + try: + import Crypto + return pycrypto_rsa_verifier() + except ImportError: + pass + + raise ImportError('No available cryptography library found. Please install' + ' either pycrypto or cryptography.') + + def sign_hmac_sha1_with_client(base_string, client): - return sign_hmac_sha1(base_string, + return sign_hmac_sha1(base_string, client.client_secret, client.resource_owner_secret ) + def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): """**HMAC-SHA1** @@ -482,16 +593,8 @@ def sign_rsa_sha1(base_string, rsa_private_key): .. _`RFC3447, Section 8.2`: http://tools.ietf.org/html/rfc3447#section-8.2 """ - # TODO: finish RSA documentation - from Crypto.PublicKey import RSA - from Crypto.Signature import PKCS1_v1_5 - from Crypto.Hash import SHA - key = RSA.importKey(rsa_private_key) - if isinstance(base_string, unicode_type): - base_string = base_string.encode('utf-8') - h = SHA.new(base_string) - p = PKCS1_v1_5.new(key) - return binascii.b2a_base64(p.sign(h))[:-1].decode('utf-8') + signer = get_rsa_signer() + return signer(rsa_private_key, base_string) def sign_rsa_sha1_with_client(base_string, client): @@ -536,6 +639,7 @@ def sign_plaintext(client_secret, resource_owner_secret): def sign_plaintext_with_client(base_string, client): return sign_plaintext(client.client_secret, client.resource_owner_secret) + def verify_hmac_sha1(request, client_secret=None, resource_owner_secret=None): """Verify a HMAC-SHA1 signature. @@ -578,17 +682,11 @@ def verify_rsa_sha1(request, rsa_public_key): .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2 """ - from Crypto.PublicKey import RSA - from Crypto.Signature import PKCS1_v1_5 - from Crypto.Hash import SHA - key = RSA.importKey(rsa_public_key) + verifier = get_rsa_verifier() norm_params = normalize_parameters(request.params) uri = normalize_base_string_uri(request.uri) message = construct_base_string(request.http_method, uri, norm_params) - h = SHA.new(message.encode('utf-8')) - p = PKCS1_v1_5.new(key) - sig = binascii.a2b_base64(request.signature.encode('utf-8')) - return p.verify(h, sig) + return verifier(message, request.signature, rsa_public_key) def verify_plaintext(request, client_secret=None, resource_owner_secret=None): @@ -20,8 +20,9 @@ def fread(fn): if sys.version_info[0] == 3: tests_require = ['nose', 'pycrypto', 'pyjwt'] else: - tests_require = ['nose', 'unittest2', 'pycrypto', 'mock', 'pyjwt'] -rsa_require = ['pycrypto'] + tests_require = ['nose', 'unittest2', 'cryptography', 'pycrypto', 'mock', 'pyjwt'] +rsa_require = ['cryptography'] +rsa_alternative_require = ['pycrypto'] signedtoken_require = ['pycrypto', 'pyjwt'] requires = [] @@ -41,7 +42,7 @@ setup( packages=find_packages(exclude=('docs', 'tests', 'tests.*')), test_suite='nose.collector', tests_require=tests_require, - extras_require={'test': tests_require, 'rsa': rsa_require, 'signedtoken': signedtoken_require}, + extras_require={'test': tests_require, 'rsa': rsa_require, 'rsa_alt': rsa_alternative_require, 'signedtoken': signedtoken_require}, install_requires=requires, classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py index aca6142..b57dc8a 100644 --- a/tests/oauth1/rfc5849/test_signatures.py +++ b/tests/oauth1/rfc5849/test_signatures.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals +import os + try: from urllib import quote except ImportError: @@ -13,7 +15,7 @@ from oauthlib.oauth1.rfc5849.signature import normalize_parameters from oauthlib.oauth1.rfc5849.signature import sign_hmac_sha1, sign_hmac_sha1_with_client from oauthlib.oauth1.rfc5849.signature import sign_rsa_sha1, sign_rsa_sha1_with_client from oauthlib.oauth1.rfc5849.signature import sign_plaintext, sign_plaintext_with_client -from oauthlib.common import unicode_type +from oauthlib.common import unicode_type, to_unicode from ...unittest import TestCase @@ -27,7 +29,7 @@ class SignatureTests(TestCase): def decode(self): for k, v in self.items(): - self[k] = v.decode('utf-8') + self[k] = to_unicode(v) uri_query = "b5=%3D%253D&a3=a&c%40=&a2=r%20b&c2=&a3=2+q" authorization_header = """OAuth realm="Example", @@ -314,6 +316,13 @@ class SignatureTests(TestCase): control_signature = self.control_signature_rsa_sha1 + os.environ['OAUTHLIB_FORCE_PYCRYPTO'] = '' + sign = sign_rsa_sha1(base_string, private_key) + self.assertEquals(sign, control_signature) + sign = sign_rsa_sha1(base_string.decode('utf-8'), private_key) + self.assertEquals(sign, control_signature) + + os.environ['OAUTHLIB_FORCE_PYCRYPTO'] = '1' sign = sign_rsa_sha1(base_string, private_key) self.assertEquals(sign, control_signature) sign = sign_rsa_sha1(base_string.decode('utf-8'), private_key) @@ -327,16 +336,19 @@ class SignatureTests(TestCase): control_signature = self.control_signature_rsa_sha1 + os.environ['OAUTHLIB_FORCE_PYCRYPTO'] = '' sign = sign_rsa_sha1_with_client(base_string, self.client) - self.assertEquals(sign, control_signature) - self.client.decode() ## Decode `rsa_private_key` from UTF-8 - sign = sign_rsa_sha1_with_client(base_string, self.client) - self.assertEquals(sign, control_signature) + os.environ['OAUTHLIB_FORCE_PYCRYPTO'] = '1' + sign = sign_rsa_sha1_with_client(base_string, self.client) + self.assertEquals(sign, control_signature) + self.client.decode() ## Decode `rsa_private_key` from UTF-8 + sign = sign_rsa_sha1_with_client(base_string, self.client) + self.assertEquals(sign, control_signature) control_signature_plaintext = ( "ECrDNoq1VYzzzzzzzzzyAK7TwZNtPnkqatqZZZZ&" @@ -6,6 +6,7 @@ deps=nose pycrypto mock pyjwt + git+https://github.com/michael-hart/cryptography.git commands=nosetests -w tests [testenv:py26] |