11from __future__ import annotations
22import typing as t
33from functools import cached_property
4+ from cryptography .hazmat .primitives import hashes
45from cryptography .hazmat .primitives .asymmetric .ec import (
56 generate_private_key ,
7+ derive_private_key ,
68 ECDH ,
79 EllipticCurvePublicKey ,
810 EllipticCurvePrivateKey ,
1315 SECP384R1 ,
1416 SECP521R1 ,
1517)
16- from cryptography .hazmat .backends import default_backend
18+ from cryptography .hazmat .primitives .kdf .hkdf import HKDF
19+ from cryptography .hazmat .primitives .kdf .pbkdf2 import PBKDF2HMAC
1720from ..errors import InvalidExchangeKeyError , InvalidKeyCurveError
1821from .._rfc7517 .models import CurveKey
1922from .._rfc7517 .pem import CryptographyBinding
2023from .._rfc7517 .types import KeyParameters , AnyKey
21- from ..util import base64_to_int , int_to_base64
24+ from ..util import base64_to_int , int_to_base64 , to_bytes
2225from ..registry import KeyParameter
2326
2427__all__ = ["ECKey" ]
@@ -51,14 +54,10 @@ def register_curve(cls, name: str, curve: t.Type[EllipticCurve]) -> None:
5154 @classmethod
5255 def generate_private_key (cls , name : str ) -> EllipticCurvePrivateKey :
5356 if name not in cls ._dss_curves :
54- raise InvalidKeyCurveError ("Invalid crv value: '{}'" . format ( name ) )
57+ raise InvalidKeyCurveError (f "Invalid crv value: '{ name } '" )
5558
5659 curve = cls ._dss_curves [name ]()
57- raw_key = generate_private_key (
58- curve = curve ,
59- backend = default_backend (),
60- )
61- return raw_key
60+ return generate_private_key (curve = curve )
6261
6362 @classmethod
6463 def import_private_key (cls , obj : ECDictKey ) -> EllipticCurvePrivateKey :
@@ -70,7 +69,7 @@ def import_private_key(cls, obj: ECDictKey) -> EllipticCurvePrivateKey:
7069 )
7170 d = base64_to_int (obj ["d" ])
7271 private_numbers = EllipticCurvePrivateNumbers (d , public_numbers )
73- return private_numbers .private_key (default_backend () )
72+ return private_numbers .private_key ()
7473
7574 @classmethod
7675 def export_private_key (cls , key : EllipticCurvePrivateKey ) -> ECDictKey :
@@ -90,7 +89,7 @@ def import_public_key(cls, obj: ECDictKey) -> EllipticCurvePublicKey:
9089 base64_to_int (obj ["y" ]),
9190 curve ,
9291 )
93- return public_numbers .public_key (default_backend () )
92+ return public_numbers .public_key ()
9493
9594 @classmethod
9695 def export_public_key (cls , key : EllipticCurvePublicKey ) -> ECDictKey :
@@ -102,6 +101,13 @@ def export_public_key(cls, key: EllipticCurvePublicKey) -> ECDictKey:
102101 }
103102
104103
104+ # register default curves with their DSS (Digital Signature Standard) names
105+ # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
106+ ECBinding .register_curve ("P-256" , SECP256R1 )
107+ ECBinding .register_curve ("P-384" , SECP384R1 )
108+ ECBinding .register_curve ("P-521" , SECP521R1 )
109+
110+
105111class ECKey (CurveKey [EllipticCurvePrivateKey , EllipticCurvePublicKey ]):
106112 key_type = "EC"
107113 #: Registry definition for EC Key
@@ -175,20 +181,85 @@ def generate_key(
175181 """
176182 if crv is None :
177183 crv = "P-256"
178-
179184 raw_key = cls .binding .generate_private_key (crv )
180- if private :
181- key = cls (raw_key , raw_key , parameters )
185+ return _wrap_key (cls , raw_key , private , auto_kid , parameters )
186+
187+ @classmethod
188+ def derive_key (
189+ cls : t .Type ["ECKey" ],
190+ secret : bytes | str ,
191+ crv : str = "P-256" ,
192+ parameters : KeyParameters | None = None ,
193+ private : bool = True ,
194+ auto_kid : bool = False ,
195+ kdf_name : t .Literal ["HKDF" , "PBKDF2" ] = "HKDF" ,
196+ kdf_options : dict [str , t .Any ] | None = None ,
197+ ) -> "ECKey" :
198+ """
199+ Generate an elliptic curve cryptographic key derived from a secret input using a key
200+ derivation function (KDF). This allows the creation of deterministic elliptic curve
201+ keys based on given input data, curve specification, and KDF options.
202+
203+ :param secret: The input secret used for key derivation
204+ :param crv: ECKey curve name
205+ :param parameters: extra parameter in JWK
206+ :param private: generate a private key or public key
207+ :param auto_kid: add ``kid`` automatically
208+ :param kdf_name: Key derivation function name
209+ :param kdf_options: Additional options for the KDF
210+ """
211+ try :
212+ curve_class = cls .binding ._dss_curves [crv ]
213+ except KeyError :
214+ raise InvalidKeyCurveError (f"Invalid crv value: '{ crv } '" )
215+
216+ curve = curve_class ()
217+ length = (curve .group_order .bit_length () + 7 ) // 8 * 2
218+
219+ if kdf_options is None :
220+ kdf_options = {}
221+
222+ algorithm = kdf_options .pop ("algorithm" , None )
223+ if algorithm is None :
224+ algorithm = hashes .SHA256 ()
225+
226+ kdf_options .setdefault ("salt" , to_bytes (f"joserfc:EC:{ kdf_name } :{ crv } " ))
227+ if kdf_name == "HKDF" :
228+ kdf_options .setdefault ("info" , b"" )
229+ hkdf = HKDF (
230+ algorithm = algorithm ,
231+ length = length ,
232+ ** kdf_options ,
233+ )
234+ seed = hkdf .derive (to_bytes (secret ))
235+ elif kdf_name == "PBKDF2" :
236+ kdf_options .setdefault ("iterations" , 100000 )
237+ pbkdf2 = PBKDF2HMAC (
238+ algorithm = algorithm ,
239+ length = length ,
240+ ** kdf_options ,
241+ )
242+ seed = pbkdf2 .derive (to_bytes (secret ))
182243 else :
183- pub_key = raw_key .public_key ()
184- key = cls (pub_key , pub_key , parameters )
185- if auto_kid :
186- key .ensure_kid ()
187- return key
244+ raise ValueError (f"Invalid kdf value: '{ kdf_name } '" )
188245
246+ d = int .from_bytes (seed , "big" ) % curve .group_order
247+ raw_key = derive_private_key (d , curve )
248+ return _wrap_key (cls , raw_key , private , auto_kid , parameters )
189249
190- # register default curves with their DSS (Digital Signature Standard) names
191- # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
192- ECBinding .register_curve ("P-256" , SECP256R1 )
193- ECBinding .register_curve ("P-384" , SECP384R1 )
194- ECBinding .register_curve ("P-521" , SECP521R1 )
250+
251+ def _wrap_key (
252+ cls : t .Type ["ECKey" ],
253+ raw_key : EllipticCurvePrivateKey ,
254+ private : bool ,
255+ auto_kid : bool ,
256+ parameters : KeyParameters | None = None ,
257+ ) -> ECKey :
258+ if private :
259+ key = cls (raw_key , raw_key , parameters )
260+ else :
261+ pub_key = raw_key .public_key ()
262+ key = cls (pub_key , pub_key , parameters )
263+ if auto_kid :
264+ key .ensure_kid ()
265+ return key
0 commit comments