diff --git a/trustgraph-flow/trustgraph/iam/service/iam.py b/trustgraph-flow/trustgraph/iam/service/iam.py index 023362f4..b2d561c8 100644 --- a/trustgraph-flow/trustgraph/iam/service/iam.py +++ b/trustgraph-flow/trustgraph/iam/service/iam.py @@ -16,8 +16,8 @@ import os import secrets import uuid -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ed25519 from trustgraph.schema import ( IamResponse, Error, @@ -39,7 +39,6 @@ API_KEY_RANDOM_BYTES = 24 JWT_ISSUER = "trustgraph-iam" JWT_TTL_SECONDS = 3600 -RSA_KEY_SIZE = 2048 def _now_iso(): @@ -129,10 +128,11 @@ def _b64url(data): def _generate_signing_keypair(): - """Return (kid, private_pem, public_pem) for a fresh RSA keypair.""" - key = rsa.generate_private_key( - public_exponent=65537, key_size=RSA_KEY_SIZE, - ) + """Return (kid, private_pem, public_pem) for a fresh Ed25519 + keypair. Ed25519 / EdDSA: small (32-byte public key), fast, + deterministic, side-channel-resistant by construction, free of + NIST-curve baggage.""" + key = ed25519.Ed25519PrivateKey.generate() private_pem = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, @@ -147,8 +147,17 @@ def _generate_signing_keypair(): def _sign_jwt(kid, private_pem, claims): - """Produce a compact-serialisation RS256 JWT for ``claims``.""" - header = {"alg": "RS256", "typ": "JWT", "kid": kid} + """Produce a compact-serialisation EdDSA (Ed25519) JWT for + ``claims``.""" + key = serialization.load_pem_private_key( + private_pem.encode("ascii"), password=None, + ) + if not isinstance(key, ed25519.Ed25519PrivateKey): + raise RuntimeError( + f"signing key is not Ed25519: {type(key).__name__}" + ) + + header = {"alg": "EdDSA", "typ": "JWT", "kid": kid} header_b = _b64url(json.dumps( header, separators=(",", ":"), sort_keys=True, ).encode("utf-8")) @@ -156,11 +165,8 @@ def _sign_jwt(kid, private_pem, claims): claims, separators=(",", ":"), sort_keys=True, ).encode("utf-8")) signing_input = f"{header_b}.{payload_b}".encode("ascii") + signature = key.sign(signing_input) - key = serialization.load_pem_private_key( - private_pem.encode("ascii"), password=None, - ) - signature = key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256()) return f"{header_b}.{payload_b}.{_b64url(signature)}"