Skip to content

Commit

Permalink
Add support for Ed448/EdDSA. (#675)
Browse files Browse the repository at this point in the history
* Add support for Ed448/EdDSA.

* Add test for verification using EdDSA private key.
  • Loading branch information
dajiaji committed Oct 3, 2021
1 parent 19ce9c5 commit e7a6c02
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -23,6 +23,8 @@ Fixed
Added
~~~~~

- Add support for Ed448/EdDSA. `#675 <https://github.com/jpadilla/pyjwt/pull/675>`__

`v2.1.0 <https://github.com/jpadilla/pyjwt/compare/2.0.1...2.1.0>`__
--------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/algorithms.rst
Expand Up @@ -17,7 +17,7 @@ This library currently supports:
* PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256
* PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384
* PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512
* EdDSA - Ed25519 signature using SHA-512. Provides 128-bit security
* EdDSA - Both Ed25519 signature using SHA-512 and Ed448 signature using SHA-3 are supported. Ed25519 and Ed448 provide 128-bit and 224-bit security respectively.

Asymmetric (Public-key) Algorithms
----------------------------------
Expand Down
50 changes: 32 additions & 18 deletions jwt/algorithms.py
Expand Up @@ -22,6 +22,10 @@
EllipticCurvePrivateKey,
EllipticCurvePublicKey,
)
from cryptography.hazmat.primitives.asymmetric.ed448 import (
Ed448PrivateKey,
Ed448PublicKey,
)
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
Expand Down Expand Up @@ -93,7 +97,7 @@ def get_default_algorithms():
"PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
"PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
"PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512),
"EdDSA": Ed25519Algorithm(),
"EdDSA": OKPAlgorithm(),
}
)

Expand Down Expand Up @@ -534,9 +538,9 @@ def verify(self, msg, key, sig):
except InvalidSignature:
return False

class Ed25519Algorithm(Algorithm):
class OKPAlgorithm(Algorithm):
"""
Performs signing and verification operations using Ed25519
Performs signing and verification operations using EdDSA
This class requires ``cryptography>=2.6`` to be installed.
"""
Expand All @@ -546,7 +550,10 @@ def __init__(self, **kwargs):

def prepare_key(self, key):

if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey)):
if isinstance(
key,
(Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey),
):
return key

if isinstance(key, (bytes, str)):
Expand All @@ -565,28 +572,30 @@ def prepare_key(self, key):

def sign(self, msg, key):
"""
Sign a message ``msg`` using the Ed25519 private key ``key``
Sign a message ``msg`` using the EdDSA private key ``key``
:param str|bytes msg: Message to sign
:param Ed25519PrivateKey key: A :class:`.Ed25519PrivateKey` instance
:param Ed25519PrivateKey}Ed448PrivateKey key: A :class:`.Ed25519PrivateKey`
or :class:`.Ed448PrivateKey` iinstance
:return bytes signature: The signature, as bytes
"""
msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg
return key.sign(msg)

def verify(self, msg, key, sig):
"""
Verify a given ``msg`` against a signature ``sig`` using the Ed25519 key ``key``
Verify a given ``msg`` against a signature ``sig`` using the EdDSA key ``key``
:param str|bytes sig: Ed25519 signature to check ``msg`` against
:param str|bytes sig: EdDSA signature to check ``msg`` against
:param str|bytes msg: Message to sign
:param Ed25519PrivateKey|Ed25519PublicKey key: A private or public Ed25519 key instance
:param Ed25519PrivateKey|Ed25519PublicKey|Ed448PrivateKey|Ed448PublicKey key:
A private or public EdDSA key instance
:return bool verified: True if signature is valid, False if not.
"""
try:
msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg
sig = bytes(sig, "utf-8") if type(sig) is not bytes else sig

if isinstance(key, Ed25519PrivateKey):
if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)):
key = key.public_key()
key.verify(sig, msg)
return True # If no exception was raised, the signature is valid.
Expand All @@ -595,21 +604,21 @@ def verify(self, msg, key, sig):

@staticmethod
def to_jwk(key):
if isinstance(key, Ed25519PublicKey):
if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)):
x = key.public_bytes(
encoding=Encoding.Raw,
format=PublicFormat.Raw,
)

crv = "Ed25519" if isinstance(key, Ed25519PublicKey) else "Ed448"
return json.dumps(
{
"x": base64url_encode(force_bytes(x)).decode(),
"kty": "OKP",
"crv": "Ed25519",
"crv": crv,
}
)

if isinstance(key, Ed25519PrivateKey):
if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)):
d = key.private_bytes(
encoding=Encoding.Raw,
format=PrivateFormat.Raw,
Expand All @@ -621,12 +630,13 @@ def to_jwk(key):
format=PublicFormat.Raw,
)

crv = "Ed25519" if isinstance(key, Ed25519PrivateKey) else "Ed448"
return json.dumps(
{
"x": base64url_encode(force_bytes(x)).decode(),
"d": base64url_encode(force_bytes(d)).decode(),
"kty": "OKP",
"crv": "Ed25519",
"crv": crv,
}
)

Expand All @@ -648,7 +658,7 @@ def from_jwk(jwk):
raise InvalidKeyError("Not an Octet Key Pair")

curve = obj.get("crv")
if curve != "Ed25519":
if curve != "Ed25519" and curve != "Ed448":
raise InvalidKeyError(f"Invalid curve: {curve}")

if "x" not in obj:
Expand All @@ -657,8 +667,12 @@ def from_jwk(jwk):

try:
if "d" not in obj:
return Ed25519PublicKey.from_public_bytes(x)
if curve == "Ed25519":
return Ed25519PublicKey.from_public_bytes(x)
return Ed448PublicKey.from_public_bytes(x)
d = base64url_decode(obj.get("d"))
return Ed25519PrivateKey.from_private_bytes(d)
if curve == "Ed25519":
return Ed25519PrivateKey.from_private_bytes(d)
return Ed448PrivateKey.from_private_bytes(d)
except ValueError as err:
raise InvalidKeyError("Invalid key parameter") from err
9 changes: 9 additions & 0 deletions tests/keys/jwk_okp_key_Ed448.json
@@ -0,0 +1,9 @@
{
"kty": "OKP",
"kid": "sig_ed448_01",
"crv": "Ed448",
"use": "sig",
"x": "kvqP7TzMosCQCpNcW8qY2HmVmpPYUEIGn-sQWQgoWlAZbWpnXpXqAT6yMoYA08pkJm7P_HKZoHwA",
"d": "Zh5xx0r_0tq39xj-8jGuCwAA6wsDim2ME7cX_iXzqDRgPN8lsZZHu60AO7m31Fa4NtHO07eU63q8",
"alg": "EdDSA"
}
8 changes: 8 additions & 0 deletions tests/keys/jwk_okp_pub_Ed448.json
@@ -0,0 +1,8 @@
{
"kty": "OKP",
"kid": "sig_ed448_01",
"crv": "Ed448",
"use": "sig",
"x": "kvqP7TzMosCQCpNcW8qY2HmVmpPYUEIGn-sQWQgoWlAZbWpnXpXqAT6yMoYA08pkJm7P_HKZoHwA",
"alg": "EdDSA"
}

0 comments on commit e7a6c02

Please sign in to comment.