diff options
author | Evert Lammerts <evert@lucipher.net> | 2017-11-06 22:11:09 +0100 |
---|---|---|
committer | José Padilla <jpadilla@webapplicate.com> | 2020-08-24 10:24:57 -0400 |
commit | 703b274cece91a79e89f14b62058949252d5c9a3 (patch) | |
tree | 8d40703168fd65c97312e6665fcdc810c3474ef6 | |
parent | c38533546ad68da0d61142c37984a66127525c9b (diff) | |
download | pyjwt-703b274cece91a79e89f14b62058949252d5c9a3.tar.gz |
ECAlgorithm.to_jwk
-rw-r--r-- | jwt/algorithms.py | 54 | ||||
-rw-r--r-- | tests/keys/__init__.py | 8 | ||||
-rw-r--r-- | tests/keys/jwk_ec_key.json | 9 | ||||
-rw-r--r-- | tests/keys/jwk_ec_key_P-256.json | 8 | ||||
-rw-r--r-- | tests/keys/jwk_ec_key_P-384.json | 8 | ||||
-rw-r--r-- | tests/keys/jwk_ec_key_P-521.json | 8 | ||||
-rw-r--r-- | tests/keys/jwk_ec_pub.json | 8 | ||||
-rw-r--r-- | tests/keys/jwk_ec_pub_P-256.json | 7 | ||||
-rw-r--r-- | tests/keys/jwk_ec_pub_P-384.json | 7 | ||||
-rw-r--r-- | tests/keys/jwk_ec_pub_P-521.json | 7 | ||||
-rw-r--r-- | tests/test_algorithms.py | 87 |
11 files changed, 184 insertions, 27 deletions
diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 5723984..45bb3ee 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -44,6 +44,7 @@ try: Ed25519PrivateKey, Ed25519PublicKey, ) + from cryptography.utils import int_from_bytes has_crypto = True except ImportError: @@ -439,6 +440,59 @@ if has_crypto: # noqa: C901 except InvalidSignature: return False + @staticmethod + def from_jwk(jwk): + + try: + obj = json.loads(jwk) + except ValueError: + raise InvalidKeyError('Key is not valid JSON') + + if obj.get('kty') != 'EC': + raise InvalidKeyError('Not an Elliptic curve key') + + if 'x' not in obj or 'y' not in obj: + raise InvalidKeyError('Not an Elliptic curve key') + + x = base64url_decode(force_bytes(obj.get('x'))) + y = base64url_decode(force_bytes(obj.get('y'))) + + curve = obj.get('crv') + if curve == 'P-256': + if len(x) == len(y) == 32: + curve_obj = ec.SECP256R1() + else: + raise InvalidKeyError("Coords should be 32 bytes for curve P-256") + elif curve == 'P-384': + if len(x) == len(y) == 48: + curve_obj = ec.SECP384R1() + else: + raise InvalidKeyError("Coords should be 48 bytes for curve P-384") + elif curve == 'P-521': + if len(x) == len(y) == 66: + curve_obj = ec.SECP521R1() + else: + raise InvalidKeyError("Coords should be 66 bytes for curve P-521") + else: + raise InvalidKeyError("Invalid curve: {}".format(curve)) + + public_numbers = ec.EllipticCurvePublicNumbers( + x=int_from_bytes(x, 'big'), y=int_from_bytes(y, 'big'), curve=curve_obj + ) + + if 'd' not in obj: + return public_numbers.public_key(default_backend()) + + d = base64url_decode(force_bytes(obj.get('d'))) + if len(d) != len(x): + raise InvalidKeyError( + "D should be {} bytes for curve {}", len(x), curve + ) + + return ec.EllipticCurvePrivateNumbers( + int_from_bytes(d, 'big'), public_numbers + ).private_key(default_backend()) + class RSAPSSAlgorithm(RSAAlgorithm): """ Performs a signature using RSASSA-PSS with MGF1 diff --git a/tests/keys/__init__.py b/tests/keys/__init__.py index 6027285..347be0d 100644 --- a/tests/keys/__init__.py +++ b/tests/keys/__init__.py @@ -43,12 +43,12 @@ if has_crypto: keyobj = json.load(infile) return ec.EllipticCurvePrivateNumbers( - private_value=decode_value(keyobj["d"]), - public_numbers=load_ec_pub_key().public_numbers(), + private_value=decode_value(keyobj['d']), + public_numbers=load_ec_pub_key_p_521().public_numbers() ) - def load_ec_pub_key(): - with open(os.path.join(BASE_PATH, "jwk_ec_pub.json")) as infile: + def load_ec_pub_key_p_521(): + with open(os.path.join(BASE_PATH, 'jwk_ec_pub_P-521.json'), 'r') as infile: keyobj = json.load(infile) return ec.EllipticCurvePublicNumbers( diff --git a/tests/keys/jwk_ec_key.json b/tests/keys/jwk_ec_key.json deleted file mode 100644 index a7fa999..0000000 --- a/tests/keys/jwk_ec_key.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kty": "EC", - "kid": "bilbo.baggins@hobbiton.example", - "use": "sig", - "crv": "P-521", - "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", - "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", - "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt" -} diff --git a/tests/keys/jwk_ec_key_P-256.json b/tests/keys/jwk_ec_key_P-256.json new file mode 100644 index 0000000..2befdf2 --- /dev/null +++ b/tests/keys/jwk_ec_key_P-256.json @@ -0,0 +1,8 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.256@hobbiton.example", + "crv": "P-256", + "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4=", + "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU=", + "d": "9GJquUJf57a9sev-u8-PoYlIezIPqI_vGpIaiu4zyZk=" +}
\ No newline at end of file diff --git a/tests/keys/jwk_ec_key_P-384.json b/tests/keys/jwk_ec_key_P-384.json new file mode 100644 index 0000000..db19c6f --- /dev/null +++ b/tests/keys/jwk_ec_key_P-384.json @@ -0,0 +1,8 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.384@hobbiton.example", + "crv": "P-384", + "x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J", + "y": "eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy", + "d": "xKPj5IXjiHpQpLOgyMGo6lg_DUp738SuXkiugCFMxbGNKTyTprYPfJz42wTOXbtd" +}
\ No newline at end of file diff --git a/tests/keys/jwk_ec_key_P-521.json b/tests/keys/jwk_ec_key_P-521.json new file mode 100644 index 0000000..28c54be --- /dev/null +++ b/tests/keys/jwk_ec_key_P-521.json @@ -0,0 +1,8 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.521@hobbiton.example", + "crv": "P-521", + "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", + "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", + "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt" +} diff --git a/tests/keys/jwk_ec_pub.json b/tests/keys/jwk_ec_pub.json deleted file mode 100644 index 5259ceb..0000000 --- a/tests/keys/jwk_ec_pub.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "kty": "EC", - "kid": "bilbo.baggins@hobbiton.example", - "use": "sig", - "crv": "P-521", - "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", - "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" -} diff --git a/tests/keys/jwk_ec_pub_P-256.json b/tests/keys/jwk_ec_pub_P-256.json new file mode 100644 index 0000000..eac918e --- /dev/null +++ b/tests/keys/jwk_ec_pub_P-256.json @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.256@hobbiton.example", + "crv": "P-256", + "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4=", + "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU=" +}
\ No newline at end of file diff --git a/tests/keys/jwk_ec_pub_P-384.json b/tests/keys/jwk_ec_pub_P-384.json new file mode 100644 index 0000000..69b298d --- /dev/null +++ b/tests/keys/jwk_ec_pub_P-384.json @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.384@hobbiton.example", + "crv": "P-384", + "x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J", + "y": "eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy" +}
\ No newline at end of file diff --git a/tests/keys/jwk_ec_pub_P-521.json b/tests/keys/jwk_ec_pub_P-521.json new file mode 100644 index 0000000..e624136 --- /dev/null +++ b/tests/keys/jwk_ec_pub_P-521.json @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.521@hobbiton.example", + "crv": "P-521", + "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", + "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" +} diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 4af4e6e..3ff7788 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -17,8 +17,7 @@ try: RSAPSSAlgorithm, Ed25519Algorithm, ) - from .keys import load_rsa_pub_key, load_ec_pub_key - + from .keys import load_rsa_pub_key, load_ec_pub_key_p_521 has_crypto = True except ImportError: has_crypto = False @@ -195,9 +194,85 @@ class TestAlgorithms: result = algo.verify(message, pub_key, sig) assert not result - @pytest.mark.skipif( - not has_crypto, reason="Not supported without cryptography library" - ) + @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library') + def test_ec_jwk_public_and_private_keys_should_parse_and_verify(self): + tests = { + 'P-256': ECAlgorithm.SHA256, + 'P-384': ECAlgorithm.SHA384, + 'P-521': ECAlgorithm.SHA512 + } + for (curve, hash) in tests.items(): + algo = ECAlgorithm(hash) + + with open(key_path('jwk_ec_pub_{}.json'.format(curve)), 'r') as keyfile: + pub_key = algo.from_jwk(keyfile.read()) + + with open(key_path('jwk_ec_key_{}.json'.format(curve)), 'r') as keyfile: + priv_key = algo.from_jwk(keyfile.read()) + + signature = algo.sign(force_bytes('Hello World!'), priv_key) + assert algo.verify(force_bytes('Hello World!'), pub_key, signature) + + @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library') + def test_ec_jwk_fails_on_invalid_json(self): + algo = ECAlgorithm(ECAlgorithm.SHA512) + + valid_points = { + 'P-256': { + 'x': 'PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4=', + 'y': 'ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU=' + }, + 'P-384': { + 'x': 'IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J', + 'y': 'eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy' + }, + 'P-521': { + 'x': 'AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt', + 'y': 'AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1' + } + } + + # Invalid JSON + with pytest.raises(InvalidKeyError): + algo.from_jwk('<this isn\'t json>') + + # Bad key type + with pytest.raises(InvalidKeyError): + algo.from_jwk('{"kty": "RSA"}') + + # Missing data + with pytest.raises(InvalidKeyError): + algo.from_jwk('{"kty": "EC"}') + with pytest.raises(InvalidKeyError): + algo.from_jwk('{"kty": "EC", "x": "1"}') + with pytest.raises(InvalidKeyError): + algo.from_jwk('{"kty": "EC", "y": "1"}') + + # Missing curve + with pytest.raises(InvalidKeyError): + algo.from_jwk('{"kty": "EC", "x": "dGVzdA==", "y": "dGVzdA=="}') + + # EC coordinates not equally long + with pytest.raises(InvalidKeyError): + algo.from_jwk('{"kty": "EC", "x": "dGVzdHRlc3Q=", "y": "dGVzdA=="}') + + # EC coordinates length invalid + for curve in ('P-256', 'P-384', 'P-521'): + with pytest.raises(InvalidKeyError): + algo.from_jwk( + '{{"kty": "EC", "crv": "{}", "x": "dGVzdA==", ' + '"y": "dGVzdA=="}}'.format(curve) + ) + + # EC private key length invalid + for (curve, point) in valid_points.items(): + with pytest.raises(InvalidKeyError): + algo.from_jwk( + '{{"kty": "EC", "crv": "{}", "x": "{}", "y": "{}", ' + '"d": "dGVzdA=="}}'.format(curve, point['x'], point['y']) + ) + + @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library') def test_rsa_jwk_public_and_private_keys_should_parse_and_verify(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) @@ -670,7 +745,7 @@ class TestAlgorithmsRFC7520: ) algo = ECAlgorithm(ECAlgorithm.SHA512) - key = algo.prepare_key(load_ec_pub_key()) + key = algo.prepare_key(load_ec_pub_key_p_521()) result = algo.verify(signing_input, key, signature) assert result |