From 1071fde2f715c9564d19f4e5eee6a5910c2839b1 Mon Sep 17 00:00:00 2001 From: Ib Lundgren Date: Wed, 24 Sep 2014 17:24:14 +0100 Subject: Allow alternative crypto library cryptography. 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. --- oauthlib/oauth1/rfc5849/signature.py | 138 ++++++++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 20 deletions(-) (limited to 'oauthlib/oauth1/rfc5849/signature.py') 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): -- cgit v1.2.1