summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvert Lammerts <evert@lucipher.net>2017-11-06 22:11:09 +0100
committerJosé Padilla <jpadilla@webapplicate.com>2020-08-24 10:24:57 -0400
commit703b274cece91a79e89f14b62058949252d5c9a3 (patch)
tree8d40703168fd65c97312e6665fcdc810c3474ef6
parentc38533546ad68da0d61142c37984a66127525c9b (diff)
downloadpyjwt-703b274cece91a79e89f14b62058949252d5c9a3.tar.gz
ECAlgorithm.to_jwk
-rw-r--r--jwt/algorithms.py54
-rw-r--r--tests/keys/__init__.py8
-rw-r--r--tests/keys/jwk_ec_key.json9
-rw-r--r--tests/keys/jwk_ec_key_P-256.json8
-rw-r--r--tests/keys/jwk_ec_key_P-384.json8
-rw-r--r--tests/keys/jwk_ec_key_P-521.json8
-rw-r--r--tests/keys/jwk_ec_pub.json8
-rw-r--r--tests/keys/jwk_ec_pub_P-256.json7
-rw-r--r--tests/keys/jwk_ec_pub_P-384.json7
-rw-r--r--tests/keys/jwk_ec_pub_P-521.json7
-rw-r--r--tests/test_algorithms.py87
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