summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIb Lundgren <ib.lundgren@gmail.com>2014-09-24 17:24:14 +0100
committerIb Lundgren <ib.lundgren@gmail.com>2014-09-24 17:24:14 +0100
commit1071fde2f715c9564d19f4e5eee6a5910c2839b1 (patch)
treee2371623fe9f1c9780fc06c7e9e920ad7e499b0c
parent39013947bd2e242dda85fb0f150c49be23fd7510 (diff)
downloadoauthlib-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.py5
-rw-r--r--oauthlib/oauth1/rfc5849/signature.py138
-rwxr-xr-xsetup.py7
-rw-r--r--tests/oauth1/rfc5849/test_signatures.py24
-rw-r--r--tox.ini1
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):
diff --git a/setup.py b/setup.py
index 91daa35..f2b79f3 100755
--- a/setup.py
+++ b/setup.py
@@ -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&"
diff --git a/tox.ini b/tox.ini
index be16305..abe3850 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,6 +6,7 @@ deps=nose
pycrypto
mock
pyjwt
+ git+https://github.com/michael-hart/cryptography.git
commands=nosetests -w tests
[testenv:py26]