-
-
Notifications
You must be signed in to change notification settings - Fork 694
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revived PyCrypto and ecdsa-based algorithms as optional jwt.contrib m…
…odules.
- Loading branch information
1 parent
d9727ca
commit 507d2c9
Showing
9 changed files
with
275 additions
and
12 deletions.
There are no files selected for viewing
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Note: This file is named py_ecdsa.py because import behavior in Python 2 | ||
# would cause ecdsa.py to squash the ecdsa library that it depends upon. | ||
|
||
import hashlib | ||
|
||
import ecdsa | ||
|
||
from jwt.algorithms import Algorithm | ||
from jwt.compat import string_types, text_type | ||
|
||
|
||
class ECAlgorithm(Algorithm): | ||
""" | ||
Performs signing and verification operations using | ||
ECDSA and the specified hash function | ||
This class requires the ecdsa package to be installed. | ||
This is based off of the implementation in PyJWT 0.3.2 | ||
""" | ||
def __init__(self, hash_alg): | ||
self.hash_alg = hash_alg | ||
|
||
SHA256, SHA384, SHA512 = hashlib.sha256, hashlib.sha384, hashlib.sha512 | ||
|
||
def prepare_key(self, key): | ||
|
||
if isinstance(key, ecdsa.SigningKey) or \ | ||
isinstance(key, ecdsa.VerifyingKey): | ||
return key | ||
|
||
if isinstance(key, string_types): | ||
if isinstance(key, text_type): | ||
key = key.encode('utf-8') | ||
|
||
# Attempt to load key. We don't know if it's | ||
# a Signing Key or a Verifying Key, so we try | ||
# the Verifying Key first. | ||
try: | ||
key = ecdsa.VerifyingKey.from_pem(key) | ||
except ecdsa.der.UnexpectedDER: | ||
key = ecdsa.SigningKey.from_pem(key) | ||
|
||
else: | ||
raise TypeError('Expecting a PEM-formatted key.') | ||
|
||
return key | ||
|
||
def sign(self, msg, key): | ||
return key.sign(msg, hashfunc=self.hash_alg, | ||
sigencode=ecdsa.util.sigencode_der) | ||
|
||
def verify(self, msg, key, sig): | ||
try: | ||
return key.verify(sig, msg, hashfunc=self.hash_alg, | ||
sigdecode=ecdsa.util.sigdecode_der) | ||
except ecdsa.der.UnexpectedDER: | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import Crypto.Hash.SHA256 | ||
import Crypto.Hash.SHA384 | ||
import Crypto.Hash.SHA512 | ||
|
||
from Crypto.PublicKey import RSA | ||
from Crypto.Signature import PKCS1_v1_5 | ||
|
||
from jwt.algorithms import Algorithm | ||
from jwt.compat import string_types, text_type | ||
|
||
|
||
class RSAAlgorithm(Algorithm): | ||
""" | ||
Performs signing and verification operations using | ||
RSASSA-PKCS-v1_5 and the specified hash function. | ||
This class requires PyCrypto package to be installed. | ||
This is based off of the implementation in PyJWT 0.3.2 | ||
""" | ||
def __init__(self, hash_alg): | ||
self.hash_alg = hash_alg | ||
|
||
SHA256, SHA384, SHA512 = (Crypto.Hash.SHA256, Crypto.Hash.SHA384, | ||
Crypto.Hash.SHA512) | ||
|
||
def prepare_key(self, key): | ||
|
||
if isinstance(key, RSA._RSAobj): | ||
return key | ||
|
||
if isinstance(key, string_types): | ||
if isinstance(key, text_type): | ||
key = key.encode('utf-8') | ||
|
||
key = RSA.importKey(key) | ||
else: | ||
raise TypeError('Expecting a PEM- or RSA-formatted key.') | ||
|
||
return key | ||
|
||
def sign(self, msg, key): | ||
return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg)) | ||
|
||
def verify(self, msg, key, sig): | ||
return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import base64 | ||
|
||
from ..compat import unittest | ||
from ..utils import ensure_bytes, ensure_unicode, key_path | ||
|
||
try: | ||
from jwt.contrib.algorithms.pycrypto import RSAAlgorithm | ||
has_pycrypto = True | ||
except ImportError: | ||
has_pycrypto = False | ||
|
||
try: | ||
from jwt.contrib.algorithms.py_ecdsa import ECAlgorithm | ||
has_ecdsa = True | ||
except ImportError: | ||
has_ecdsa = False | ||
|
||
|
||
@unittest.skipIf(not has_pycrypto, 'Not supported without PyCrypto library') | ||
class TestPycryptoAlgorithms(unittest.TestCase): | ||
def setUp(self): # noqa | ||
pass | ||
|
||
def test_rsa_should_parse_pem_public_key(self): | ||
algo = RSAAlgorithm(RSAAlgorithm.SHA256) | ||
|
||
with open(key_path('testkey2_rsa.pub.pem'), 'r') as pem_key: | ||
algo.prepare_key(pem_key.read()) | ||
|
||
def test_rsa_should_accept_unicode_key(self): | ||
algo = RSAAlgorithm(RSAAlgorithm.SHA256) | ||
|
||
with open(key_path('testkey_rsa'), 'r') as rsa_key: | ||
algo.prepare_key(ensure_unicode(rsa_key.read())) | ||
|
||
def test_rsa_should_reject_non_string_key(self): | ||
algo = RSAAlgorithm(RSAAlgorithm.SHA256) | ||
|
||
with self.assertRaises(TypeError): | ||
algo.prepare_key(None) | ||
|
||
def test_rsa_verify_should_return_false_if_signature_invalid(self): | ||
algo = RSAAlgorithm(RSAAlgorithm.SHA256) | ||
|
||
jwt_message = ensure_bytes('Hello World!') | ||
|
||
jwt_sig = base64.b64decode(ensure_bytes( | ||
'yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp' | ||
'10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl' | ||
'2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix' | ||
'sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX' | ||
'fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA' | ||
'APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA==')) | ||
|
||
jwt_sig += ensure_bytes('123') # Signature is now invalid | ||
|
||
with open(key_path('testkey_rsa.pub'), 'r') as keyfile: | ||
jwt_pub_key = algo.prepare_key(keyfile.read()) | ||
|
||
result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) | ||
self.assertFalse(result) | ||
|
||
def test_rsa_verify_should_return_true_if_signature_valid(self): | ||
algo = RSAAlgorithm(RSAAlgorithm.SHA256) | ||
|
||
jwt_message = ensure_bytes('Hello World!') | ||
|
||
jwt_sig = base64.b64decode(ensure_bytes( | ||
'yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp' | ||
'10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl' | ||
'2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix' | ||
'sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX' | ||
'fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA' | ||
'APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA==')) | ||
|
||
with open(key_path('testkey_rsa.pub'), 'r') as keyfile: | ||
jwt_pub_key = algo.prepare_key(keyfile.read()) | ||
|
||
result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) | ||
self.assertTrue(result) | ||
|
||
|
||
@unittest.skipIf(not has_ecdsa, 'Not supported without ecdsa library') | ||
class TestEcdsaAlgorithms(unittest.TestCase): | ||
def test_ec_should_reject_non_string_key(self): | ||
algo = ECAlgorithm(ECAlgorithm.SHA256) | ||
|
||
with self.assertRaises(TypeError): | ||
algo.prepare_key(None) | ||
|
||
def test_ec_should_accept_unicode_key(self): | ||
algo = ECAlgorithm(ECAlgorithm.SHA256) | ||
|
||
with open(key_path('testkey_ec'), 'r') as ec_key: | ||
algo.prepare_key(ensure_unicode(ec_key.read())) | ||
|
||
def test_ec_verify_should_return_false_if_signature_invalid(self): | ||
algo = ECAlgorithm(ECAlgorithm.SHA256) | ||
|
||
jwt_message = ensure_bytes('Hello World!') | ||
|
||
jwt_sig = base64.b64decode(ensure_bytes( | ||
'MIGIAkIB9vYz+inBL8aOTA4auYz/zVuig7TT1bQgKROIQX9YpViHkFa4DT5' | ||
'5FuFKn9XzVlk90p6ldEj42DC9YecXHbC2t+cCQgCicY+8f3f/KCNtWK7cif' | ||
'6vdsVwm6Lrjs0Ag6ZqCf+olN11hVt1qKBC4lXppqB1gNWEmNQaiz1z2QRyc' | ||
'zJ8hSJmbw==')) | ||
|
||
jwt_sig += ensure_bytes('123') # Signature is now invalid | ||
|
||
with open(key_path('testkey_ec.pub'), 'r') as keyfile: | ||
jwt_pub_key = algo.prepare_key(keyfile.read()) | ||
|
||
result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) | ||
self.assertFalse(result) | ||
|
||
def test_ec_verify_should_return_true_if_signature_valid(self): | ||
algo = ECAlgorithm(ECAlgorithm.SHA256) | ||
|
||
jwt_message = ensure_bytes('Hello World!') | ||
|
||
jwt_sig = base64.b64decode(ensure_bytes( | ||
'MIGIAkIB9vYz+inBL8aOTA4auYz/zVuig7TT1bQgKROIQX9YpViHkFa4DT5' | ||
'5FuFKn9XzVlk90p6ldEj42DC9YecXHbC2t+cCQgCicY+8f3f/KCNtWK7cif' | ||
'6vdsVwm6Lrjs0Ag6ZqCf+olN11hVt1qKBC4lXppqB1gNWEmNQaiz1z2QRyc' | ||
'zJ8hSJmbw==')) | ||
|
||
with open(key_path('testkey_ec.pub'), 'r') as keyfile: | ||
jwt_pub_key = algo.prepare_key(keyfile.read()) | ||
|
||
result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) | ||
self.assertTrue(result) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters