summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAjitomi, Daisuke <ajitomi@gmail.com>2021-03-19 08:23:35 +0900
committerGitHub <noreply@github.com>2021-03-18 19:23:35 -0400
commit88972a9bd962fceee8251cfff0e41f3d77e56551 (patch)
treefcd3f25000fa0e3b751d2c77de7620b49493e6db
parentf6d4bbfa9feb9ed56c54de6011678adf48af4ac7 (diff)
downloadpyjwt-88972a9bd962fceee8251cfff0e41f3d77e56551.tar.gz
Add from_jwk to Ed25519Algorithm (Support kty: OKP). (#623)
* Support from_jwk on Ed25519Algorithm. * Update CHANGELOG.
-rw-r--r--CHANGELOG.rst1
-rw-r--r--jwt/algorithms.py31
-rw-r--r--tests/keys/jwk_okp_key_Ed25519.json6
-rw-r--r--tests/keys/jwk_okp_pub_Ed25519.json5
-rw-r--r--tests/test_algorithms.py67
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)