Skip to content

Commit 545931d

Browse files
Add to_jwk to Ed25519Algorithm. (jpadilla#642) (jpadilla#643)
* Add to_jwk to Ed25519Algorithm. (jpadilla#642) * add test for invalid key * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update CHANGELOG for jpadilla#643 * remove alg from jwk Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 7f6a236 commit 545931d

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Added
2727
- Add missing exceptions.InvalidKeyError to jwt module __init__ imports `#620 <https://github.com/jpadilla/pyjwt/pull/620>`__
2828
- Add support for ES256K algorithm `#629 <https://github.com/jpadilla/pyjwt/pull/629>`__
2929
- Add `from_jwk()` to Ed25519Algorithm `#621 <https://github.com/jpadilla/pyjwt/pull/621>`__
30+
- Add `to_jwk()` to Ed25519Algorithm `#643 <https://github.com/jpadilla/pyjwt/pull/643>`__
3031

3132
`v2.0.1 <https://github.com/jpadilla/pyjwt/compare/2.0.0...2.0.1>`__
3233
--------------------------------------------------------------------

jwt/algorithms.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
rsa_recover_prime_factors,
3838
)
3939
from cryptography.hazmat.primitives.serialization import (
40+
Encoding,
41+
NoEncryption,
42+
PrivateFormat,
43+
PublicFormat,
4044
load_pem_private_key,
4145
load_pem_public_key,
4246
load_ssh_public_key,
@@ -589,6 +593,45 @@ def verify(self, msg, key, sig):
589593
except cryptography.exceptions.InvalidSignature:
590594
return False
591595

596+
@staticmethod
597+
def to_jwk(key):
598+
if isinstance(key, Ed25519PublicKey):
599+
x = key.public_bytes(
600+
encoding=Encoding.Raw,
601+
format=PublicFormat.Raw,
602+
)
603+
604+
return json.dumps(
605+
{
606+
"x": base64url_encode(force_bytes(x)).decode(),
607+
"kty": "OKP",
608+
"crv": "Ed25519",
609+
}
610+
)
611+
612+
if isinstance(key, Ed25519PrivateKey):
613+
d = key.private_bytes(
614+
encoding=Encoding.Raw,
615+
format=PrivateFormat.Raw,
616+
encryption_algorithm=NoEncryption(),
617+
)
618+
619+
x = key.public_key().public_bytes(
620+
encoding=Encoding.Raw,
621+
format=PublicFormat.Raw,
622+
)
623+
624+
return json.dumps(
625+
{
626+
"x": base64url_encode(force_bytes(x)).decode(),
627+
"d": base64url_encode(force_bytes(d)).decode(),
628+
"kty": "OKP",
629+
"crv": "Ed25519",
630+
}
631+
)
632+
633+
raise InvalidKeyError("Not a public or private key")
634+
592635
@staticmethod
593636
def from_jwk(jwk):
594637
try:

tests/test_algorithms.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,28 @@ def test_ed25519_jwk_fails_on_invalid_json(self):
807807
v["d"] = "123"
808808
with pytest.raises(InvalidKeyError):
809809
algo.from_jwk(v)
810+
811+
def test_ed25519_to_jwk_works_with_from_jwk(self):
812+
algo = Ed25519Algorithm()
813+
814+
with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile:
815+
priv_key_1 = algo.from_jwk(keyfile.read())
816+
817+
with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile:
818+
pub_key_1 = algo.from_jwk(keyfile.read())
819+
820+
pub = algo.to_jwk(pub_key_1)
821+
pub_key_2 = algo.from_jwk(pub)
822+
pri = algo.to_jwk(priv_key_1)
823+
priv_key_2 = algo.from_jwk(pri)
824+
825+
signature_1 = algo.sign(b"Hello World!", priv_key_1)
826+
signature_2 = algo.sign(b"Hello World!", priv_key_2)
827+
assert algo.verify(b"Hello World!", pub_key_2, signature_1)
828+
assert algo.verify(b"Hello World!", pub_key_2, signature_2)
829+
830+
def test_ed25519_to_jwk_raises_exception_on_invalid_key(self):
831+
algo = Ed25519Algorithm()
832+
833+
with pytest.raises(InvalidKeyError):
834+
algo.to_jwk({"not": "a valid key"})

0 commit comments

Comments
 (0)