diff options
author | Ajitomi, Daisuke <ajitomi@gmail.com> | 2021-03-19 08:23:35 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-18 19:23:35 -0400 |
commit | 88972a9bd962fceee8251cfff0e41f3d77e56551 (patch) | |
tree | fcd3f25000fa0e3b751d2c77de7620b49493e6db | |
parent | f6d4bbfa9feb9ed56c54de6011678adf48af4ac7 (diff) | |
download | pyjwt-88972a9bd962fceee8251cfff0e41f3d77e56551.tar.gz |
Add from_jwk to Ed25519Algorithm (Support kty: OKP). (#623)
* Support from_jwk on Ed25519Algorithm.
* Update CHANGELOG.
-rw-r--r-- | CHANGELOG.rst | 1 | ||||
-rw-r--r-- | jwt/algorithms.py | 31 | ||||
-rw-r--r-- | tests/keys/jwk_okp_key_Ed25519.json | 6 | ||||
-rw-r--r-- | tests/keys/jwk_okp_pub_Ed25519.json | 5 | ||||
-rw-r--r-- | tests/test_algorithms.py | 67 |
5 files changed, 110 insertions, 0 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c3850a5..5c46c97 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,7 @@ Added - Add caching by default to PyJWKClient `#611 <https://github.com/jpadilla/pyjwt/pull/611>`__ - Add missing exceptions.InvalidKeyError to jwt module __init__ imports `#620 <https://github.com/jpadilla/pyjwt/pull/620>`__ - Add support for ES256K algorithm `#629 <https://github.com/jpadilla/pyjwt/pull/629>`__ +- Add `from_jwk()` to Ed25519Algorithm `#621 <https://github.com/jpadilla/pyjwt/pull/621>`__ `v2.0.1 <https://github.com/jpadilla/pyjwt/compare/2.0.0...2.0.1>`__ -------------------------------------------------------------------- diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 7784a94..50719be 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -586,3 +586,34 @@ if has_crypto: return True # If no exception was raised, the signature is valid. except cryptography.exceptions.InvalidSignature: return False + + @staticmethod + def from_jwk(jwk): + try: + if isinstance(jwk, str): + obj = json.loads(jwk) + elif isinstance(jwk, dict): + obj = jwk + else: + raise ValueError + except ValueError: + raise InvalidKeyError("Key is not valid JSON") + + if obj.get("kty") != "OKP": + raise InvalidKeyError("Not an Octet Key Pair") + + curve = obj.get("crv") + if curve != "Ed25519": + raise InvalidKeyError(f"Invalid curve: {curve}") + + if "x" not in obj: + raise InvalidKeyError('OKP should have "x" parameter') + x = base64url_decode(obj.get("x")) + + try: + if "d" not in obj: + return Ed25519PublicKey.from_public_bytes(x) + d = base64url_decode(obj.get("d")) + return Ed25519PrivateKey.from_private_bytes(d) + except ValueError as err: + raise InvalidKeyError("Invalid key parameter") from err diff --git a/tests/keys/jwk_okp_key_Ed25519.json b/tests/keys/jwk_okp_key_Ed25519.json new file mode 100644 index 0000000..6451fe9 --- /dev/null +++ b/tests/keys/jwk_okp_key_Ed25519.json @@ -0,0 +1,6 @@ +{ + "kty":"OKP", + "crv":"Ed25519", + "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" +} diff --git a/tests/keys/jwk_okp_pub_Ed25519.json b/tests/keys/jwk_okp_pub_Ed25519.json new file mode 100644 index 0000000..4ffdb5f --- /dev/null +++ b/tests/keys/jwk_okp_pub_Ed25519.json @@ -0,0 +1,5 @@ +{ + "kty":"OKP", + "crv":"Ed25519", + "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" +} diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 2341c1c..2144d48 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -733,3 +733,70 @@ class TestEd25519Algorithms: jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) assert jwt_pub_key_first == jwt_pub_key_second + + def test_ed25519_jwk_private_key_should_parse_and_verify(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + key = algo.from_jwk(keyfile.read()) + + signature = algo.sign(b"Hello World!", key) + assert algo.verify(b"Hello World!", key.public_key(), signature) + + def test_ed25519_jwk_public_key_should_parse_and_verify(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + priv_key = algo.from_jwk(keyfile.read()) + + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + pub_key = algo.from_jwk(keyfile.read()) + + signature = algo.sign(b"Hello World!", priv_key) + assert algo.verify(b"Hello World!", pub_key, signature) + + def test_ed25519_jwk_fails_on_invalid_json(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + valid_pub = json.loads(keyfile.read()) + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + valid_key = json.loads(keyfile.read()) + + # Invalid instance type + with pytest.raises(InvalidKeyError): + algo.from_jwk(123) + + # Invalid JSON + with pytest.raises(InvalidKeyError): + algo.from_jwk("<this isn't json>") + + # Invalid kty, not "OKP" + v = valid_pub.copy() + v["kty"] = "oct" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Invalid crv, not "Ed25519" + v = valid_pub.copy() + v["crv"] = "P-256" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Missing x + v = valid_pub.copy() + del v["x"] + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Invalid x + v = valid_pub.copy() + v["x"] = "123" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Invalid d + v = valid_key.copy() + v["d"] = "123" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) |