From 9e4bbb2892d1e38da3c71c063f85dfb1f0457ad0 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 16 Mar 2016 00:50:57 -0500 Subject: [PATCH 1/6] Refactor JWK --- jose/jwk.py | 304 +++++++++++++------------ jose/jws.py | 18 +- tests/algorithms/test_EC.py | 17 +- tests/algorithms/test_HMAC.py | 21 +- tests/algorithms/test_RSA.py | 19 +- tests/algorithms/test_algorithms.py | 16 -- tests/algorithms/test_base.py | 32 +-- tests/rfc/test_rfc7520.py | 329 +++++++++++++++------------- tests/test_jwk.py | 0 tests/test_jwt.py | 2 + 10 files changed, 378 insertions(+), 380 deletions(-) delete mode 100644 tests/algorithms/test_algorithms.py create mode 100644 tests/test_jwk.py diff --git a/jose/jwk.py b/jose/jwk.py index b4410edf..efed5bb0 100644 --- a/jose/jwk.py +++ b/jose/jwk.py @@ -17,8 +17,7 @@ from jose.constants import ALGORITHMS from jose.exceptions import JWKError -from jose.exceptions import JWSError -from jose.exceptions import JOSEError +from jose.utils import base64url_decode # PyCryptodome's RSA module doesn't have PyCrypto's _RSAobj class # Instead it has a class named RsaKey, which serves the same purpose. @@ -33,41 +32,6 @@ long = int -def get_algorithm_object(algorithm): - """ - Returns an algorithm object for the given algorithm. - """ - - if algorithm == ALGORITHMS.HS256: - return HMACKey(HMACKey.SHA256) - - if algorithm == ALGORITHMS.HS384: - return HMACKey(HMACKey.SHA384) - - if algorithm == ALGORITHMS.HS512: - return HMACKey(HMACKey.SHA512) - - if algorithm == ALGORITHMS.RS256: - return RSAKey(RSAKey.SHA256) - - if algorithm == ALGORITHMS.RS384: - return RSAKey(RSAKey.SHA384) - - if algorithm == ALGORITHMS.RS512: - return RSAKey(RSAKey.SHA512) - - if algorithm == ALGORITHMS.ES256: - return ECKey(ECKey.SHA256) - - if algorithm == ALGORITHMS.ES384: - return ECKey(ECKey.SHA384) - - if algorithm == ALGORITHMS.ES512: - return ECKey(ECKey.SHA512) - - raise JWSError('Algorithm not supported: %s' % algorithm) - - def int_arr_to_long(arr): return long(''.join(["%02x" % byte for byte in arr]), 16) @@ -81,99 +45,62 @@ def base64_to_long(data): return int_arr_to_long(struct.unpack('%sB' % len(_d), _d)) -class Key(object): +def construct(key_data, algorithm=None): """ - The interface for an JWK used to sign and verify tokens. + Construct a Key object for the given algorithm with the given + key_data. """ - prepared_key = None - def process_sign(self, msg, key): - """ - Processes a signature for the given algorithm. + # Allow for pulling the algorithm off of the passed in jwk. + if not algorithm and isinstance(key_data, dict): + algorithm = key_data.get('alg', None) - This method should be overriden by the implementing algortihm. - """ - raise NotImplementedError + if not algorithm: + raise JWKError('Unable to find a algorithm for key: %s' % key_data) - def process_verify(self, msg, key, sig): - """ - Processes a verification for the given algorithm. - - This method should be overriden by the implementing algorithm. - """ - raise NotImplementedError - - def process_prepare_key(self, key): - """ - Processes preparing a key for the given algorithm. - - This method should be overriden by the implementing algorithm. - """ - raise NotImplementedError - - def process_deserialize(self): - """ - Processes deserializing a key into a JWK JSON format. + if algorithm == ALGORITHMS.HS256: + return HMACKey(key_data, HMACKey.SHA256) - This method should be overriden by the implementing Key class. - """ - raise NotImplementedError + if algorithm == ALGORITHMS.HS384: + return HMACKey(key_data, HMACKey.SHA384) - def process_jwk(self, jwk): - """ - Process a JWK dict into a Key object. + if algorithm == ALGORITHMS.HS512: + return HMACKey(key_data, HMACKey.SHA512) - This method shold be overriden by the implementing Key class. - """ - raise NotImplementedError + if algorithm == ALGORITHMS.RS256: + return RSAKey(key_data, RSAKey.SHA256) - def prepare_key(self, key): - """ - Performs necessary validation and conversions on the key and returns - the key value in the proper format for sign() and verify(). + if algorithm == ALGORITHMS.RS384: + return RSAKey(key_data, RSAKey.SHA384) - This is used to catch any library errors and throw a JOSEError. + if algorithm == ALGORITHMS.RS512: + return RSAKey(key_data, RSAKey.SHA512) - Raises: - TypeError: If an invalid key is attempted to be used. - """ - try: - key = self.process_prepare_key(key) - except Exception as e: - raise JOSEError(e) + if algorithm == ALGORITHMS.ES256: + return ECKey(key_data, ECKey.SHA256) - self.prepared_key = key - return key + if algorithm == ALGORITHMS.ES384: + return ECKey(key_data, ECKey.SHA384) - def sign(self, msg, key): - """ - Returns a digital signature for the specified message - using the specified key value. + if algorithm == ALGORITHMS.ES512: + return ECKey(key_data, ECKey.SHA512) - This is used to catch any library errors and throw a JOSEError. - Raises: - JOSEError: When there is an error creating a signature. - """ - try: - return self.process_sign(msg, key) - except Exception as e: - raise JOSEError(e) +class Key(object): + """ + A simple interface for implementing JWK keys. + """ + prepared_key = None + hash_alg = None - def verify(self, msg, key, sig): - """ - Verifies that the specified digital signature is valid - for the specified message and key values. + def _process_jwk(self, jwk_dict): + raise NotImplementedError() - This is used to catch any library errors and throw a JOSEError. + def sign(self, msg): + raise NotImplementedError() - Raises: - JOSEError: When there is an error verifiying the signature. - """ - try: - return self.process_verify(msg, key, sig) - except Exception as e: - raise JOSEError(e) + def verify(self, msg, sig): + raise NotImplementedError() class HMACKey(Key): @@ -184,13 +111,22 @@ class HMACKey(Key): SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 + valid_hash_algs = (SHA256, SHA384, SHA512) - def __init__(self, hash_alg): + prepared_key = None + hash_alg = None + + def __init__(self, key, hash_alg): + if hash_alg not in self.valid_hash_algs: + raise JWKError('hash_alg: %s is not a valid hash algorithm' % hash_alg) self.hash_alg = hash_alg - def process_prepare_key(self, key): + if isinstance(key, dict): + self.prepared_key = self._process_jwk(key) + return + if not isinstance(key, six.string_types) and not isinstance(key, bytes): - raise TypeError('Expecting a string- or bytes-formatted key.') + raise JWKError('Expecting a string- or bytes-formatted key.') if isinstance(key, six.text_type): key = key.encode('utf-8') @@ -202,17 +138,28 @@ def process_prepare_key(self, key): ] if any([string_value in key for string_value in invalid_strings]): - raise Exception( + raise JWKError( 'The specified key is an asymmetric key or x509 certificate and' ' should not be used as an HMAC secret.') - return key + self.prepared_key = key - def process_sign(self, msg, key): - return hmac.new(key, msg, self.hash_alg).digest() + def _process_jwk(self, jwk_dict): + if not jwk_dict.get('kty') == 'oct': + raise JWKError("Incorrect key type. Expected: 'oct', Recieved: %s" % jwk_dict.get('kty')) - def process_verify(self, msg, key, sig): - return sig == self.sign(msg, key) + k = jwk_dict.get('k') + k = k.encode('utf-8') + k = bytes(k) + k = base64url_decode(k) + + return k + + def sign(self, msg): + return hmac.new(self.prepared_key, msg, self.hash_alg).digest() + + def verify(self, msg, sig): + return sig == self.sign(msg) class RSAKey(Key): @@ -222,46 +169,63 @@ class RSAKey(Key): This class requires PyCrypto package to be installed. This is based off of the implementation in PyJWT 0.3.2 """ + SHA256 = Crypto.Hash.SHA256 SHA384 = Crypto.Hash.SHA384 SHA512 = Crypto.Hash.SHA512 + valid_hash_algs = (SHA256, SHA384, SHA512) - def __init__(self, hash_alg): - self.hash_alg = hash_alg + prepared_key = None + hash_alg = None - def process_prepare_key(self, key): + def __init__(self, key, hash_alg): - if isinstance(key, (_RSAKey, RSAKey)): - return key + if hash_alg not in self.valid_hash_algs: + raise JWKError('hash_alg: %s is not a valid hash algorithm' % hash_alg) + self.hash_alg = hash_alg + + if isinstance(key, _RSAKey): + self.prepared_key = key + return if isinstance(key, dict): - return self.process_jwk(key) + self.prepared_key = self._process_jwk(key) + return if isinstance(key, six.string_types): if isinstance(key, six.text_type): key = key.encode('utf-8') - key = RSA.importKey(key) - else: - raise TypeError('Expecting a PEM- or RSA-formatted key.') + try: + self.prepared_key = RSA.importKey(key) + except Exception as e: + raise JWKError(e) - return key + return - def process_jwk(self, jwk): + raise JWKError('Unable to parse an RSA_JWK from key: %s' % key) - if not jwk.get('kty') == 'RSA': - raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk.get('kty')) + def _process_jwk(self, jwk_dict): + if not jwk_dict.get('kty') == 'RSA': + raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty')) - e = base64_to_long(jwk.get('e', 256)) - n = base64_to_long(jwk.get('n')) + e = base64_to_long(jwk_dict.get('e', 256)) + n = base64_to_long(jwk_dict.get('n')) - return RSA.construct((n, e)) + self.prepared_key = RSA.construct((n, e)) + return self.prepared_key - def process_sign(self, msg, key): - return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg)) + def sign(self, msg): + try: + return PKCS1_v1_5.new(self.prepared_key).sign(self.hash_alg.new(msg)) + except Exception as e: + raise JWKError(e) - def process_verify(self, msg, key, sig): - return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig) + def verify(self, msg, sig): + try: + return PKCS1_v1_5.new(self.prepared_key).verify(self.hash_alg.new(msg), sig) + except Exception as e: + raise JWKError(e) class ECKey(Key): @@ -276,15 +240,32 @@ class ECKey(Key): SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 + valid_hash_algs = (SHA256, SHA384, SHA512) + + curve_map = { + SHA256: ecdsa.curves.NIST256p, + SHA384: ecdsa.curves.NIST384p, + SHA512: ecdsa.curves.NIST521p, + } - def __init__(self, hash_alg): + prepared_key = None + hash_alg = None + curve = None + + def __init__(self, key, hash_alg): + if hash_alg not in self.valid_hash_algs: + raise JWKError('hash_alg: %s is not a valid hash algorithm' % hash_alg) self.hash_alg = hash_alg - def process_prepare_key(self, key): + self.curve = self.curve_map.get(self.hash_alg) - if isinstance(key, ecdsa.SigningKey) or \ - isinstance(key, ecdsa.VerifyingKey): - return key + if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)): + self.prepared_key = key + return + + if isinstance(key, dict): + self.prepared_key = self._process_jwk(key) + return if isinstance(key, six.string_types): if isinstance(key, six.text_type): @@ -297,17 +278,34 @@ def process_prepare_key(self, key): key = ecdsa.VerifyingKey.from_pem(key) except ecdsa.der.UnexpectedDER: key = ecdsa.SigningKey.from_pem(key) + except Exception as e: + raise JWKError(e) + + self.prepared_key = key + return + + raise JWKError('Unable to parse an ECKey from key: %s' % key) + + def _process_jwk(self, jwk_dict): + if not jwk_dict.get('kty') == 'EC': + raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty')) + + x = base64_to_long(jwk_dict.get('x')) + y = base64_to_long(jwk_dict.get('y')) + + if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y): + raise JWKError("Point: %s, %s is not a valid point" % (x, y)) - else: - raise TypeError('Expecting a PEM-formatted key.') + point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order) + verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, self.curve) - return key + return verifying_key - def process_sign(self, msg, key): - return key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string) + def sign(self, msg): + return self.prepared_key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string) - def process_verify(self, msg, key, sig): + def verify(self, msg, sig): try: - return key.verify(sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string) + return self.prepared_key.verify(sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string) except: return False diff --git a/jose/jws.py b/jose/jws.py index af15bb77..0ebb701f 100644 --- a/jose/jws.py +++ b/jose/jws.py @@ -5,7 +5,7 @@ from collections import Mapping -from jose.jwk import get_algorithm_object +from jose import jwk from jose.constants import ALGORITHMS from jose.exceptions import JWSError from jose.exceptions import JWSSignatureError @@ -157,12 +157,15 @@ def _encode_payload(payload): return base64url_encode(payload) -def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key): +def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key_data): signing_input = b'.'.join([encoded_header, encoded_claims]) try: - alg_obj = get_algorithm_object(algorithm) - key = alg_obj.prepare_key(key) - signature = alg_obj.sign(signing_input, key) + key = jwk.construct(key_data, algorithm) + signature = key.sign(signing_input) + + # alg_obj = get_algorithm_object(algorithm) + # key = alg_obj.prepare_key(key) + # signature = alg_obj.sign(signing_input, key) except Exception as e: raise JWSError(e) @@ -216,10 +219,9 @@ def _verify_signature(signing_input, header, signature, key='', algorithms=None) raise JWSError('The specified alg value is not allowed') try: - alg_obj = get_algorithm_object(alg) - key = alg_obj.prepare_key(key) + key = jwk.construct(key, alg) - if not alg_obj.verify(signing_input, key, signature): + if not key.verify(signing_input, signature): raise JWSSignatureError() except JWSSignatureError: diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index 08ecdce9..8393f15b 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -5,11 +5,6 @@ import ecdsa import pytest - -@pytest.fixture -def alg(): - return ECKey(ECKey.SHA256) - private_key = """-----BEGIN EC PRIVATE KEY----- MHQCAQEEIIAK499svJugZZfsTsgL2tc7kH/CpzQbkr4g55CEWQyPoAcGBSuBBAAK oUQDQgAEsOnVqWVPfjte2nI0Ay3oTZVehCUtH66nJM8z6flUluHxhLG8ZTTCkJAZ @@ -19,16 +14,16 @@ def alg(): class TestECAlgorithm: - def test_EC_key(self, alg): + def test_EC_key(self): key = ecdsa.SigningKey.from_pem(private_key) - alg.prepare_key(key) + ECKey(key, ECKey.SHA256) - def test_string_secret(self, alg): + def test_string_secret(self): key = 'secret' with pytest.raises(JOSEError): - alg.prepare_key(key) + ECKey(key, ECKey.SHA256) - def test_object(self, alg): + def test_object(self): key = object() with pytest.raises(JOSEError): - alg.prepare_key(key) + ECKey(key, ECKey.SHA256) diff --git a/tests/algorithms/test_HMAC.py b/tests/algorithms/test_HMAC.py index aeb6b537..7bac7b21 100644 --- a/tests/algorithms/test_HMAC.py +++ b/tests/algorithms/test_HMAC.py @@ -5,18 +5,21 @@ import pytest -@pytest.fixture -def alg(): - return HMACKey(HMACKey.SHA256) - - class TestHMACAlgorithm: - def test_non_string_key(self, alg): + def test_non_string_key(self): with pytest.raises(JOSEError): - alg.prepare_key(object()) + HMACKey(object(), HMACKey.SHA256) - def test_RSA_key(self, alg): + def test_RSA_key(self): key = "-----BEGIN PUBLIC KEY-----" with pytest.raises(JOSEError): - alg.prepare_key(key) + HMACKey(key, HMACKey.SHA256) + + key = "-----BEGIN CERTIFICATE-----" + with pytest.raises(JOSEError): + HMACKey(key, HMACKey.SHA256) + + key = "ssh-rsa" + with pytest.raises(JOSEError): + HMACKey(key, HMACKey.SHA256) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 3dd5bbb6..a7931267 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -6,12 +6,6 @@ import pytest - -@pytest.fixture -def alg(): - return RSAKey(RSAKey.SHA256) - - private_key = """-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX @@ -67,16 +61,15 @@ def alg(): class TestRSAAlgorithm: - def test_RSA_key(self, alg): - key = RSA.importKey(private_key) - alg.prepare_key(key) + def test_RSA_key(self): + RSAKey(private_key, RSAKey.SHA256) - def test_string_secret(self, alg): + def test_string_secret(self): key = 'secret' with pytest.raises(JOSEError): - alg.prepare_key(key) + RSAKey(key, RSAKey.SHA256) - def test_object(self, alg): + def test_object(self): key = object() with pytest.raises(JOSEError): - alg.prepare_key(key) + RSAKey(key, RSAKey.SHA256) diff --git a/tests/algorithms/test_algorithms.py b/tests/algorithms/test_algorithms.py deleted file mode 100644 index 888705d1..00000000 --- a/tests/algorithms/test_algorithms.py +++ /dev/null @@ -1,16 +0,0 @@ - -from jose.jwk import get_algorithm_object - -import pytest - - -@pytest.fixture -def test(): - pass - - -class TestGetAlgorithm: - - def test_get_algorithm(self): - with pytest.raises(Exception): - get_algorithm_object('SOMETHING') diff --git a/tests/algorithms/test_base.py b/tests/algorithms/test_base.py index 2c5e063a..8d3a7f02 100644 --- a/tests/algorithms/test_base.py +++ b/tests/algorithms/test_base.py @@ -1,25 +1,25 @@ -from jose.jwk import Key -from jose.exceptions import JOSEError +# from jose.jwk import Key +# from jose.exceptions import JOSEError -import pytest +# import pytest -@pytest.fixture -def alg(): - return Key() +# @pytest.fixture +# def alg(): +# return Key() -class TestBaseAlgorithm: +# class TestBaseAlgorithm: - def test_prepare_key_is_interface(self, alg): - with pytest.raises(JOSEError): - alg.prepare_key('secret') +# def test_prepare_key_is_interface(self, alg): +# with pytest.raises(JOSEError): +# alg.prepare_key('secret') - def test_sign_is_interface(self, alg): - with pytest.raises(JOSEError): - alg.sign('msg', 'secret') +# def test_sign_is_interface(self, alg): +# with pytest.raises(JOSEError): +# alg.sign('msg', 'secret') - def test_verify_is_interface(self, alg): - with pytest.raises(JOSEError): - alg.verify('msg', 'secret', 'sig') +# def test_verify_is_interface(self, alg): +# with pytest.raises(JOSEError): +# alg.verify('msg', 'secret', 'sig') diff --git a/tests/rfc/test_rfc7520.py b/tests/rfc/test_rfc7520.py index 32a11a68..5a907b1d 100644 --- a/tests/rfc/test_rfc7520.py +++ b/tests/rfc/test_rfc7520.py @@ -8,10 +8,10 @@ expected_payload = b"It\xe2\x80\x99s a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, there\xe2\x80\x99s no knowing where you might be swept off to." -# [Docs] [txt|pdf] [draft-ietf-jose-c...] - - - +# [Docs] [txt|pdf] [draft-ietf-jose-c...] + + + # Internet Engineering Task Force (IETF) M. Miller # Request for Comments: 7520 Cisco Systems, Inc. # Category: Informational May 2015 @@ -64,7 +64,7 @@ # Miller Informational [Page 1] - + # RFC 7520 JOSE Cookbook May 2015 @@ -120,7 +120,7 @@ # Miller Informational [Page 2] - + # RFC 7520 JOSE Cookbook May 2015 @@ -176,7 +176,7 @@ # Miller Informational [Page 3] - + # RFC 7520 JOSE Cookbook May 2015 @@ -232,7 +232,7 @@ # Miller Informational [Page 4] - + # RFC 7520 JOSE Cookbook May 2015 @@ -288,7 +288,7 @@ # Miller Informational [Page 5] - + # RFC 7520 JOSE Cookbook May 2015 @@ -330,6 +330,15 @@ # SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" # } +ec_public_key = { + "kty": "EC", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "crv": "P-521", + "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", + "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" +} + # Figure 1: Elliptic Curve P-521 Public Key # The field "kty" value of "EC" identifies this as an Elliptic Curve @@ -344,7 +353,7 @@ # Miller Informational [Page 6] - + # RFC 7520 JOSE Cookbook May 2015 @@ -400,7 +409,7 @@ # Miller Informational [Page 7] - + # RFC 7520 JOSE Cookbook May 2015 @@ -426,6 +435,14 @@ # "e": "AQAB" # } +rsa_public_jwk = { + "kty": "RSA", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", + "e": "AQAB" +} + # Figure 3: RSA 2048-Bit Public Key # The field "kty" value of "RSA" identifies this as an RSA key. The @@ -456,7 +473,7 @@ # Miller Informational [Page 8] - + # RFC 7520 JOSE Cookbook May 2015 @@ -512,7 +529,7 @@ # Miller Informational [Page 9] - + # RFC 7520 JOSE Cookbook May 2015 @@ -544,6 +561,14 @@ # "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" # } +hmac_key = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "alg": "HS256", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" +} + # Figure 5: HMAC SHA-256 Symmetric Key # The field "kty" value of "oct" identifies this as a symmetric key. @@ -568,7 +593,7 @@ # Miller Informational [Page 10] - + # RFC 7520 JOSE Cookbook May 2015 @@ -624,7 +649,7 @@ # Miller Informational [Page 11] - + # RFC 7520 JOSE Cookbook May 2015 @@ -680,7 +705,7 @@ # Miller Informational [Page 12] - + # RFC 7520 JOSE Cookbook May 2015 @@ -736,7 +761,7 @@ # Miller Informational [Page 13] - + # RFC 7520 JOSE Cookbook May 2015 @@ -759,6 +784,15 @@ # Figure 13: JWS Compact Serialization +class TestFourOneThree: + + token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg" + + def test_signature(self): + + payload = verify(self.token, rsa_public_jwk, 'RS256') + assert payload == expected_payload + # The resulting JWS object using the general JWS JSON Serialization: # { @@ -792,7 +826,7 @@ # Miller Informational [Page 14] - + # RFC 7520 JOSE Cookbook May 2015 @@ -817,36 +851,6 @@ # Figure 15: Flattened JWS JSON Serialization - -class TestFourOne: - - public_jwk = { - "kty": "RSA", - "kid": "bilbo.baggins@hobbiton.example", - "use": "sig", - "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", - "e": "AQAB" - } - - token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9" \ - ".SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9" \ - "vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXI" \ - "gZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9" \ - "mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZ" \ - "opdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfW" \ - "Ra-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFx" \ - "nt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI" \ - "-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg" - - def test_signature(self): - - key = RSAKey(RSAKey.SHA256) - - prepared_key = key.prepare_key(self.public_jwk) - payload = verify(self.token, prepared_key, 'RS256') - - assert payload == expected_payload - # 4.2. RSA-PSS Signature # This example illustrates signing content using the "PS384" (RSASSA- @@ -878,7 +882,7 @@ def test_signature(self): # Miller Informational [Page 15] - + # RFC 7520 JOSE Cookbook May 2015 @@ -934,7 +938,7 @@ def test_signature(self): # Miller Informational [Page 16] - + # RFC 7520 JOSE Cookbook May 2015 @@ -990,7 +994,7 @@ def test_signature(self): # Miller Informational [Page 17] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1046,7 +1050,7 @@ def test_signature(self): # Miller Informational [Page 18] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1102,7 +1106,7 @@ def test_signature(self): # Miller Informational [Page 19] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1156,9 +1160,19 @@ def test_signature(self): # Figure 27: JWS Compact Serialization +class TestFourThreeThree: + + token = "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2" + + def test_signature(self): + + payload = verify(self.token, ec_public_key, 'ES512') + assert payload == expected_payload + + # Miller Informational [Page 20] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1214,7 +1228,7 @@ def test_signature(self): # Miller Informational [Page 21] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1270,7 +1284,7 @@ def test_signature(self): # Miller Informational [Page 22] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1306,7 +1320,14 @@ def test_signature(self): # Figure 34: JWS Compact Serialization +class TestFourFourThree: + token = "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" + + def test_signature(self): + + payload = verify(self.token, hmac_key, 'HS256') + assert payload == expected_payload @@ -1326,7 +1347,7 @@ def test_signature(self): # Miller Informational [Page 23] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1382,7 +1403,7 @@ def test_signature(self): # Miller Informational [Page 24] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1438,7 +1459,7 @@ def test_signature(self): # Miller Informational [Page 25] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1494,7 +1515,7 @@ def test_signature(self): # Miller Informational [Page 26] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1550,7 +1571,7 @@ def test_signature(self): # Miller Informational [Page 27] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1606,7 +1627,7 @@ def test_signature(self): # Miller Informational [Page 28] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1662,7 +1683,7 @@ def test_signature(self): # Miller Informational [Page 29] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1718,7 +1739,7 @@ def test_signature(self): # Miller Informational [Page 30] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1774,7 +1795,7 @@ def test_signature(self): # Miller Informational [Page 31] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1830,7 +1851,7 @@ def test_signature(self): # Miller Informational [Page 32] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1886,7 +1907,7 @@ def test_signature(self): # Miller Informational [Page 33] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1942,7 +1963,7 @@ def test_signature(self): # Miller Informational [Page 34] - + # RFC 7520 JOSE Cookbook May 2015 @@ -1998,7 +2019,7 @@ def test_signature(self): # Miller Informational [Page 35] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2054,7 +2075,7 @@ def test_signature(self): # Miller Informational [Page 36] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2110,7 +2131,7 @@ def test_signature(self): # Miller Informational [Page 37] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2166,7 +2187,7 @@ def test_signature(self): # Miller Informational [Page 38] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2222,7 +2243,7 @@ def test_signature(self): # Miller Informational [Page 39] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2278,7 +2299,7 @@ def test_signature(self): # Miller Informational [Page 40] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2334,7 +2355,7 @@ def test_signature(self): # Miller Informational [Page 41] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2390,7 +2411,7 @@ def test_signature(self): # Miller Informational [Page 42] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2446,7 +2467,7 @@ def test_signature(self): # Miller Informational [Page 43] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2502,7 +2523,7 @@ def test_signature(self): # Miller Informational [Page 44] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2558,7 +2579,7 @@ def test_signature(self): # Miller Informational [Page 45] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2614,7 +2635,7 @@ def test_signature(self): # Miller Informational [Page 46] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2670,7 +2691,7 @@ def test_signature(self): # Miller Informational [Page 47] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2726,7 +2747,7 @@ def test_signature(self): # Miller Informational [Page 48] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2782,7 +2803,7 @@ def test_signature(self): # Miller Informational [Page 49] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2838,7 +2859,7 @@ def test_signature(self): # Miller Informational [Page 50] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2894,7 +2915,7 @@ def test_signature(self): # Miller Informational [Page 51] - + # RFC 7520 JOSE Cookbook May 2015 @@ -2950,7 +2971,7 @@ def test_signature(self): # Miller Informational [Page 52] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3006,7 +3027,7 @@ def test_signature(self): # Miller Informational [Page 53] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3062,7 +3083,7 @@ def test_signature(self): # Miller Informational [Page 54] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3118,7 +3139,7 @@ def test_signature(self): # Miller Informational [Page 55] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3174,7 +3195,7 @@ def test_signature(self): # Miller Informational [Page 56] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3230,7 +3251,7 @@ def test_signature(self): # Miller Informational [Page 57] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3286,7 +3307,7 @@ def test_signature(self): # Miller Informational [Page 58] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3342,7 +3363,7 @@ def test_signature(self): # Miller Informational [Page 59] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3398,7 +3419,7 @@ def test_signature(self): # Miller Informational [Page 60] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3454,7 +3475,7 @@ def test_signature(self): # Miller Informational [Page 61] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3510,7 +3531,7 @@ def test_signature(self): # Miller Informational [Page 62] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3566,7 +3587,7 @@ def test_signature(self): # Miller Informational [Page 63] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3622,7 +3643,7 @@ def test_signature(self): # Miller Informational [Page 64] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3678,7 +3699,7 @@ def test_signature(self): # Miller Informational [Page 65] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3734,7 +3755,7 @@ def test_signature(self): # Miller Informational [Page 66] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3790,7 +3811,7 @@ def test_signature(self): # Miller Informational [Page 67] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3846,7 +3867,7 @@ def test_signature(self): # Miller Informational [Page 68] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3902,7 +3923,7 @@ def test_signature(self): # Miller Informational [Page 69] - + # RFC 7520 JOSE Cookbook May 2015 @@ -3958,7 +3979,7 @@ def test_signature(self): # Miller Informational [Page 70] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4014,7 +4035,7 @@ def test_signature(self): # Miller Informational [Page 71] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4070,7 +4091,7 @@ def test_signature(self): # Miller Informational [Page 72] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4126,7 +4147,7 @@ def test_signature(self): # Miller Informational [Page 73] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4182,7 +4203,7 @@ def test_signature(self): # Miller Informational [Page 74] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4238,7 +4259,7 @@ def test_signature(self): # Miller Informational [Page 75] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4294,7 +4315,7 @@ def test_signature(self): # Miller Informational [Page 76] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4350,7 +4371,7 @@ def test_signature(self): # Miller Informational [Page 77] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4406,7 +4427,7 @@ def test_signature(self): # Miller Informational [Page 78] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4462,7 +4483,7 @@ def test_signature(self): # Miller Informational [Page 79] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4518,7 +4539,7 @@ def test_signature(self): # Miller Informational [Page 80] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4574,7 +4595,7 @@ def test_signature(self): # Miller Informational [Page 81] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4630,7 +4651,7 @@ def test_signature(self): # Miller Informational [Page 82] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4686,7 +4707,7 @@ def test_signature(self): # Miller Informational [Page 83] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4742,7 +4763,7 @@ def test_signature(self): # Miller Informational [Page 84] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4798,7 +4819,7 @@ def test_signature(self): # Miller Informational [Page 85] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4854,7 +4875,7 @@ def test_signature(self): # Miller Informational [Page 86] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4910,7 +4931,7 @@ def test_signature(self): # Miller Informational [Page 87] - + # RFC 7520 JOSE Cookbook May 2015 @@ -4966,7 +4987,7 @@ def test_signature(self): # Miller Informational [Page 88] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5022,7 +5043,7 @@ def test_signature(self): # Miller Informational [Page 89] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5078,7 +5099,7 @@ def test_signature(self): # Miller Informational [Page 90] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5134,7 +5155,7 @@ def test_signature(self): # Miller Informational [Page 91] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5190,7 +5211,7 @@ def test_signature(self): # Miller Informational [Page 92] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5246,7 +5267,7 @@ def test_signature(self): # Miller Informational [Page 93] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5302,7 +5323,7 @@ def test_signature(self): # Miller Informational [Page 94] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5358,7 +5379,7 @@ def test_signature(self): # Miller Informational [Page 95] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5414,7 +5435,7 @@ def test_signature(self): # Miller Informational [Page 96] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5470,7 +5491,7 @@ def test_signature(self): # Miller Informational [Page 97] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5526,7 +5547,7 @@ def test_signature(self): # Miller Informational [Page 98] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5582,7 +5603,7 @@ def test_signature(self): # Miller Informational [Page 99] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5638,7 +5659,7 @@ def test_signature(self): # Miller Informational [Page 100] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5694,7 +5715,7 @@ def test_signature(self): # Miller Informational [Page 101] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5750,7 +5771,7 @@ def test_signature(self): # Miller Informational [Page 102] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5806,7 +5827,7 @@ def test_signature(self): # Miller Informational [Page 103] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5862,7 +5883,7 @@ def test_signature(self): # Miller Informational [Page 104] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5918,7 +5939,7 @@ def test_signature(self): # Miller Informational [Page 105] - + # RFC 7520 JOSE Cookbook May 2015 @@ -5974,7 +5995,7 @@ def test_signature(self): # Miller Informational [Page 106] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6030,7 +6051,7 @@ def test_signature(self): # Miller Informational [Page 107] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6086,7 +6107,7 @@ def test_signature(self): # Miller Informational [Page 108] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6142,7 +6163,7 @@ def test_signature(self): # Miller Informational [Page 109] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6198,7 +6219,7 @@ def test_signature(self): # Miller Informational [Page 110] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6254,7 +6275,7 @@ def test_signature(self): # Miller Informational [Page 111] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6310,7 +6331,7 @@ def test_signature(self): # Miller Informational [Page 112] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6366,7 +6387,7 @@ def test_signature(self): # Miller Informational [Page 113] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6422,7 +6443,7 @@ def test_signature(self): # Miller Informational [Page 114] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6478,7 +6499,7 @@ def test_signature(self): # Miller Informational [Page 115] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6534,7 +6555,7 @@ def test_signature(self): # Miller Informational [Page 116] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6590,7 +6611,7 @@ def test_signature(self): # Miller Informational [Page 117] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6646,7 +6667,7 @@ def test_signature(self): # Miller Informational [Page 118] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6702,7 +6723,7 @@ def test_signature(self): # Miller Informational [Page 119] - + # RFC 7520 JOSE Cookbook May 2015 @@ -6760,4 +6781,4 @@ def test_signature(self): # Miller Informational [Page 120] -# Html markup produced by rfcmarkup 1.116, available from https://tools.ietf.org/tools/rfcmarkup/ \ No newline at end of file +# Html markup produced by rfcmarkup 1.116, available from https://tools.ietf.org/tools/rfcmarkup/ diff --git a/tests/test_jwk.py b/tests/test_jwk.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 8b4a8f67..298fe7d9 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -20,6 +20,7 @@ def claims(): def key(): return 'secret' + @pytest.fixture def headers(): headers = { @@ -27,6 +28,7 @@ def headers(): } return headers + class TestJWT: def test_non_default_alg(self, claims, key): From 6dfedac694fc69b8c98265d31f418d1cf02afa7d Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 21 Jun 2016 08:13:29 -0500 Subject: [PATCH 2/6] Add minimal docs --- docs/jwk/api.rst | 6 ++++++ docs/jwk/index.rst | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 docs/jwk/api.rst create mode 100644 docs/jwk/index.rst diff --git a/docs/jwk/api.rst b/docs/jwk/api.rst new file mode 100644 index 00000000..245e66d0 --- /dev/null +++ b/docs/jwk/api.rst @@ -0,0 +1,6 @@ + +JWK API +^^^^^^^ + +.. automodule:: jose.jwk + :members: diff --git a/docs/jwk/index.rst b/docs/jwk/index.rst new file mode 100644 index 00000000..e59ce362 --- /dev/null +++ b/docs/jwk/index.rst @@ -0,0 +1,27 @@ +JSON Web Key +============== + +JSON Web Keys (JWK) are a JSON data structure representing a cryptographic key. + +Examples +^^^^^^^^ + +Verifying token signatures +-------------------------- + +.. code:: python + + >>> from jose import jwk + >>> + >>> token = "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" + >>> hmac_key = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "alg": "HS256", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + } + >>> + >>> key = jwk.construct(key_data) + >>> + >>> key.verify(token) From d2d3a08f17b3c096f262cca270c895e3e101db83 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 21 Jun 2016 09:27:26 -0500 Subject: [PATCH 3/6] Update JWK tests --- tests/test_jwk.py | 118 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_jws.py | 1 - 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/tests/test_jwk.py b/tests/test_jwk.py index e69de29b..3c109640 100644 --- a/tests/test_jwk.py +++ b/tests/test_jwk.py @@ -0,0 +1,118 @@ + +from jose import jwk +from jose.exceptions import JWKError + +import pytest + +hmac_key = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "alg": "HS256", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" +} + +rsa_key = { + "kty": "RSA", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", + "e": "AQAB" +} + +ec_key = { + "kty": "EC", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "crv": "P-521", + "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", + "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" +} + + +class TestJWK: + + def test_interface(self): + + key = jwk.Key() + + with pytest.raises(NotImplementedError): + key._process_jwk(None) + + with pytest.raises(NotImplementedError): + key.sign('') + + with pytest.raises(NotImplementedError): + key.verify('', '') + + def test_invalid_hash_alg(self): + with pytest.raises(JWKError): + key = jwk.HMACKey(hmac_key, 'RS512') + + with pytest.raises(JWKError): + key = jwk.RSAKey(rsa_key, 'HS512') + + with pytest.raises(JWKError): + key = jwk.ECKey(ec_key, 'RS512') + + def test_invalid_jwk(self): + + with pytest.raises(JWKError): + key = jwk.HMACKey(rsa_key, 'HS512') + + with pytest.raises(JWKError): + key = jwk.HMACKey(hmac_key, 'RS512') + + with pytest.raises(JWKError): + key = jwk.HMACKey(rsa_key, 'EC512') + + def test_RSAKey_errors(self): + + rsa_key = { + "kty": "RSA", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", + "e": "AQAB" + } + + with pytest.raises(JWKError): + key = jwk.RSAKey(rsa_key, 'HS256') + + rsa_key = { + "kty": "oct", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", + "e": "AQAB" + } + + with pytest.raises(JWKError): + key = jwk.RSAKey(rsa_key, 'RS256') + + + + def test_construct_from_jwk(self): + + hmac_key = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "alg": "HS256", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + } + + key = jwk.construct(hmac_key) + assert isinstance(key, jwk.Key) + + def test_construct_from_jwk_missing_alg(self): + + hmac_key = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + } + + with pytest.raises(JWKError): + key = jwk.construct(hmac_key) diff --git a/tests/test_jws.py b/tests/test_jws.py index 9713a75f..ad77dc5a 100644 --- a/tests/test_jws.py +++ b/tests/test_jws.py @@ -1,5 +1,4 @@ - from jose import jws from jose.constants import ALGORITHMS from jose.exceptions import JWSError From ad8f5c3f5ca09a579b4fdb3841260c1768087d3b Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Jun 2016 11:08:32 -0500 Subject: [PATCH 4/6] Add documentation links --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 3713be03..63cd390c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Contents jws/index jwt/index + jwk/index APIs @@ -33,6 +34,7 @@ APIs jws/api jwt/api + jwk/api Principles From 67e3e5efaf10ccb5e44bd719df290b1a99b77ad7 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Jun 2016 21:44:12 -0500 Subject: [PATCH 5/6] Finalize jwk refactor --- jose/jws.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jose/jws.py b/jose/jws.py index 0ebb701f..8d1ab472 100644 --- a/jose/jws.py +++ b/jose/jws.py @@ -162,10 +162,6 @@ def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key_data) try: key = jwk.construct(key_data, algorithm) signature = key.sign(signing_input) - - # alg_obj = get_algorithm_object(algorithm) - # key = alg_obj.prepare_key(key) - # signature = alg_obj.sign(signing_input, key) except Exception as e: raise JWSError(e) From ca8efcf38be42677d609cf271aaffdf9e569db0f Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Jun 2016 23:01:49 -0500 Subject: [PATCH 6/6] Refactor JWK algorithms --- jose/jwk.py | 69 +++++++++++++++++------------------ requirements-dev.txt | 2 +- tests/algorithms/test_EC.py | 9 +++-- tests/algorithms/test_HMAC.py | 11 +++--- tests/algorithms/test_RSA.py | 18 +++++++-- tests/test_jwk.py | 9 ++--- 6 files changed, 65 insertions(+), 53 deletions(-) diff --git a/jose/jwk.py b/jose/jwk.py index efed5bb0..21398e48 100644 --- a/jose/jwk.py +++ b/jose/jwk.py @@ -58,32 +58,31 @@ def construct(key_data, algorithm=None): if not algorithm: raise JWKError('Unable to find a algorithm for key: %s' % key_data) - if algorithm == ALGORITHMS.HS256: - return HMACKey(key_data, HMACKey.SHA256) + if algorithm in ALGORITHMS.HMAC: + return HMACKey(key_data, algorithm) - if algorithm == ALGORITHMS.HS384: - return HMACKey(key_data, HMACKey.SHA384) + if algorithm in ALGORITHMS.RSA: + return RSAKey(key_data, algorithm) - if algorithm == ALGORITHMS.HS512: - return HMACKey(key_data, HMACKey.SHA512) + if algorithm in ALGORITHMS.EC: + return ECKey(key_data, algorithm) - if algorithm == ALGORITHMS.RS256: - return RSAKey(key_data, RSAKey.SHA256) - if algorithm == ALGORITHMS.RS384: - return RSAKey(key_data, RSAKey.SHA384) +def get_algorithm_object(algorithm): - if algorithm == ALGORITHMS.RS512: - return RSAKey(key_data, RSAKey.SHA512) - - if algorithm == ALGORITHMS.ES256: - return ECKey(key_data, ECKey.SHA256) - - if algorithm == ALGORITHMS.ES384: - return ECKey(key_data, ECKey.SHA384) + algorithms = { + ALGORITHMS.HS256: HMACKey.SHA256, + ALGORITHMS.HS384: HMACKey.SHA384, + ALGORITHMS.HS512: HMACKey.SHA512, + ALGORITHMS.RS256: RSAKey.SHA256, + ALGORITHMS.RS384: RSAKey.SHA384, + ALGORITHMS.RS512: RSAKey.SHA512, + ALGORITHMS.ES256: ECKey.SHA256, + ALGORITHMS.ES384: ECKey.SHA384, + ALGORITHMS.ES512: ECKey.SHA512, + } - if algorithm == ALGORITHMS.ES512: - return ECKey(key_data, ECKey.SHA512) + return algorithms.get(algorithm, None) class Key(object): @@ -111,15 +110,15 @@ class HMACKey(Key): SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 - valid_hash_algs = (SHA256, SHA384, SHA512) + valid_hash_algs = ALGORITHMS.HMAC prepared_key = None hash_alg = None - def __init__(self, key, hash_alg): - if hash_alg not in self.valid_hash_algs: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % hash_alg) - self.hash_alg = hash_alg + def __init__(self, key, algorithm): + if algorithm not in self.valid_hash_algs: + raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + self.hash_alg = get_algorithm_object(algorithm) if isinstance(key, dict): self.prepared_key = self._process_jwk(key) @@ -173,16 +172,16 @@ class RSAKey(Key): SHA256 = Crypto.Hash.SHA256 SHA384 = Crypto.Hash.SHA384 SHA512 = Crypto.Hash.SHA512 - valid_hash_algs = (SHA256, SHA384, SHA512) + valid_hash_algs = ALGORITHMS.RSA prepared_key = None hash_alg = None - def __init__(self, key, hash_alg): + def __init__(self, key, algorithm): - if hash_alg not in self.valid_hash_algs: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % hash_alg) - self.hash_alg = hash_alg + if algorithm not in self.valid_hash_algs: + raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + self.hash_alg = get_algorithm_object(algorithm) if isinstance(key, _RSAKey): self.prepared_key = key @@ -240,7 +239,7 @@ class ECKey(Key): SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 - valid_hash_algs = (SHA256, SHA384, SHA512) + valid_hash_algs = ALGORITHMS.EC curve_map = { SHA256: ecdsa.curves.NIST256p, @@ -252,10 +251,10 @@ class ECKey(Key): hash_alg = None curve = None - def __init__(self, key, hash_alg): - if hash_alg not in self.valid_hash_algs: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % hash_alg) - self.hash_alg = hash_alg + def __init__(self, key, algorithm): + if algorithm not in self.valid_hash_algs: + raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + self.hash_alg = get_algorithm_object(algorithm) self.curve = self.curve_map.get(self.hash_alg) diff --git a/requirements-dev.txt b/requirements-dev.txt index da01cc1f..9a7399c8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,4 +11,4 @@ ecdsa==0.13 wsgiref==0.1.2 -r requirements.txt --r requirements-rtd.txt \ No newline at end of file +-r requirements-rtd.txt diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index 8393f15b..279f261f 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -1,6 +1,7 @@ -from jose.jwk import ECKey +from jose.constants import ALGORITHMS from jose.exceptions import JOSEError +from jose.jwk import ECKey import ecdsa import pytest @@ -16,14 +17,14 @@ class TestECAlgorithm: def test_EC_key(self): key = ecdsa.SigningKey.from_pem(private_key) - ECKey(key, ECKey.SHA256) + ECKey(key, ALGORITHMS.ES256) def test_string_secret(self): key = 'secret' with pytest.raises(JOSEError): - ECKey(key, ECKey.SHA256) + ECKey(key, ALGORITHMS.ES256) def test_object(self): key = object() with pytest.raises(JOSEError): - ECKey(key, ECKey.SHA256) + ECKey(key, ALGORITHMS.ES256) diff --git a/tests/algorithms/test_HMAC.py b/tests/algorithms/test_HMAC.py index 7bac7b21..314c7b3e 100644 --- a/tests/algorithms/test_HMAC.py +++ b/tests/algorithms/test_HMAC.py @@ -1,6 +1,7 @@ -from jose.jwk import HMACKey +from jose.constants import ALGORITHMS from jose.exceptions import JOSEError +from jose.jwk import HMACKey import pytest @@ -9,17 +10,17 @@ class TestHMACAlgorithm: def test_non_string_key(self): with pytest.raises(JOSEError): - HMACKey(object(), HMACKey.SHA256) + HMACKey(object(), ALGORITHMS.HS256) def test_RSA_key(self): key = "-----BEGIN PUBLIC KEY-----" with pytest.raises(JOSEError): - HMACKey(key, HMACKey.SHA256) + HMACKey(key, ALGORITHMS.HS256) key = "-----BEGIN CERTIFICATE-----" with pytest.raises(JOSEError): - HMACKey(key, HMACKey.SHA256) + HMACKey(key, ALGORITHMS.HS256) key = "ssh-rsa" with pytest.raises(JOSEError): - HMACKey(key, HMACKey.SHA256) + HMACKey(key, ALGORITHMS.HS256) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index a7931267..026674fe 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,11 +1,19 @@ +import sys + from jose.jwk import RSAKey +from jose.constants import ALGORITHMS from jose.exceptions import JOSEError from Crypto.PublicKey import RSA import pytest +# Deal with integer compatibilities between Python 2 and 3. +# Using `from builtins import int` is not supported on AppEngine. +if sys.version_info > (3,): + long = int + private_key = """-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX @@ -62,14 +70,18 @@ class TestRSAAlgorithm: def test_RSA_key(self): - RSAKey(private_key, RSAKey.SHA256) + RSAKey(private_key, ALGORITHMS.RS256) + + def test_RSA_key_instance(self): + key = RSA.construct((long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), long(65537))) + RSAKey(key, ALGORITHMS.RS256) def test_string_secret(self): key = 'secret' with pytest.raises(JOSEError): - RSAKey(key, RSAKey.SHA256) + RSAKey(key, ALGORITHMS.RS256) def test_object(self): key = object() with pytest.raises(JOSEError): - RSAKey(key, RSAKey.SHA256) + RSAKey(key, ALGORITHMS.RS256) diff --git a/tests/test_jwk.py b/tests/test_jwk.py index 3c109640..d29e3213 100644 --- a/tests/test_jwk.py +++ b/tests/test_jwk.py @@ -4,6 +4,7 @@ import pytest + hmac_key = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", @@ -58,13 +59,13 @@ def test_invalid_hash_alg(self): def test_invalid_jwk(self): with pytest.raises(JWKError): - key = jwk.HMACKey(rsa_key, 'HS512') + key = jwk.HMACKey(rsa_key, 'HS256') with pytest.raises(JWKError): - key = jwk.HMACKey(hmac_key, 'RS512') + key = jwk.RSAKey(hmac_key, 'RS256') with pytest.raises(JWKError): - key = jwk.HMACKey(rsa_key, 'EC512') + key = jwk.ECKey(rsa_key, 'ES256') def test_RSAKey_errors(self): @@ -90,8 +91,6 @@ def test_RSAKey_errors(self): with pytest.raises(JWKError): key = jwk.RSAKey(rsa_key, 'RS256') - - def test_construct_from_jwk(self): hmac_key = {