22from abc import ABC , abstractmethod
33from dataclasses import dataclass
44from enum import Enum
5- from typing import List , Optional
5+ from typing import Dict , List , Optional , Type
66
77from cryptography .hazmat .backends import default_backend
88from cryptography .hazmat .primitives import serialization
9+ from cryptography .hazmat .primitives .asymmetric .ec import (
10+ SECP256R1 ,
11+ SECP384R1 ,
12+ SECP521R1 ,
13+ EllipticCurve ,
14+ EllipticCurvePublicNumbers ,
15+ )
916from cryptography .hazmat .primitives .asymmetric .rsa import RSAPublicNumbers
1017
1118from guardpost .errors import UnsupportedFeatureError
@@ -17,6 +24,13 @@ def _raise_if_missing(value: dict, *keys: str) -> None:
1724 raise ValueError (f"Missing { key } " )
1825
1926
27+ _EC_CURVES : Dict [str , Type [EllipticCurve ]] = {
28+ "P-256" : SECP256R1 ,
29+ "P-384" : SECP384R1 ,
30+ "P-521" : SECP521R1 ,
31+ }
32+
33+
2034class KeyType (Enum ):
2135 EC = "EC"
2236 RSA = "RSA"
@@ -40,29 +54,51 @@ class JWK:
4054 A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data
4155 structure that represents a cryptographic key.
4256
57+ Supports RSA keys (kty="RSA") and EC keys (kty="EC") with curves
58+ P-256, P-384, and P-521.
59+
4360 For more information: https://datatracker.ietf.org/doc/html/rfc7517
4461 """
4562
4663 kty : KeyType
47- n : str
48- e : str
4964 pem : bytes
5065 kid : Optional [str ] = None
66+ # RSA parameters
67+ n : Optional [str ] = None
68+ e : Optional [str ] = None
69+ # EC parameters
70+ crv : Optional [str ] = None
71+ x : Optional [str ] = None
72+ y : Optional [str ] = None
5173
5274 @classmethod
5375 def from_dict (cls , value ) -> "JWK" :
5476 key_type = KeyType .from_str (value .get ("kty" ))
5577
56- if key_type != KeyType .RSA :
57- raise UnsupportedFeatureError ("This library supports only RSA public keys." )
58-
59- _raise_if_missing (value , "n" , "e" )
60- return cls (
61- kty = key_type ,
62- n = value ["n" ],
63- e = value ["e" ],
64- kid = value .get ("kid" ),
65- pem = rsa_pem_from_n_and_e (value ["n" ], value ["e" ]),
78+ if key_type == KeyType .RSA :
79+ _raise_if_missing (value , "n" , "e" )
80+ return cls (
81+ kty = key_type ,
82+ n = value ["n" ],
83+ e = value ["e" ],
84+ kid = value .get ("kid" ),
85+ pem = rsa_pem_from_n_and_e (value ["n" ], value ["e" ]),
86+ )
87+
88+ if key_type == KeyType .EC :
89+ _raise_if_missing (value , "crv" , "x" , "y" )
90+ return cls (
91+ kty = key_type ,
92+ crv = value ["crv" ],
93+ x = value ["x" ],
94+ y = value ["y" ],
95+ kid = value .get ("kid" ),
96+ pem = ec_pem_from_x_y_crv (value ["x" ], value ["y" ], value ["crv" ]),
97+ )
98+
99+ raise UnsupportedFeatureError (
100+ f"Unsupported key type: { key_type .value } . "
101+ "This library supports RSA and EC public keys."
66102 )
67103
68104
@@ -131,3 +167,22 @@ def rsa_pem_from_n_and_e(n: str, e: str) -> bytes:
131167 format = serialization .PublicFormat .SubjectPublicKeyInfo ,
132168 )
133169 )
170+
171+
172+ def ec_pem_from_x_y_crv (x : str , y : str , crv : str ) -> bytes :
173+ curve_cls = _EC_CURVES .get (crv )
174+ if curve_cls is None :
175+ raise ValueError (
176+ f"Unsupported EC curve: { crv !r} . "
177+ f"Supported curves: { ', ' .join (_EC_CURVES )} ."
178+ )
179+ x_int = _decode_value (x )
180+ y_int = _decode_value (y )
181+ return (
182+ EllipticCurvePublicNumbers (x = x_int , y = y_int , curve = curve_cls ())
183+ .public_key (default_backend ())
184+ .public_bytes (
185+ encoding = serialization .Encoding .PEM ,
186+ format = serialization .PublicFormat .SubjectPublicKeyInfo ,
187+ )
188+ )
0 commit comments