11import hashlib
22import hmac
3+ import json
34
4- from .compat import binary_type , constant_time_compare , is_string_type
5+
6+ from .compat import constant_time_compare , string_types
57from .exceptions import InvalidKeyError
6- from .utils import der_to_raw_signature , raw_to_der_signature
8+ from .utils import (
9+ base64url_decode , base64url_encode , der_to_raw_signature ,
10+ force_bytes , force_unicode , from_base64url_uint , raw_to_der_signature ,
11+ to_base64url_uint
12+ )
713
814try :
915 from cryptography .hazmat .primitives import hashes
1016 from cryptography .hazmat .primitives .serialization import (
1117 load_pem_private_key , load_pem_public_key , load_ssh_public_key
1218 )
1319 from cryptography .hazmat .primitives .asymmetric .rsa import (
14- RSAPrivateKey , RSAPublicKey
20+ RSAPrivateKey , RSAPublicKey , RSAPrivateNumbers , RSAPublicNumbers ,
21+ rsa_recover_prime_factors , rsa_crt_dmp1 , rsa_crt_dmq1 , rsa_crt_iqmp
1522 )
1623 from cryptography .hazmat .primitives .asymmetric .ec import (
1724 EllipticCurvePrivateKey , EllipticCurvePublicKey
@@ -32,6 +39,7 @@ def _get_crypto_algorithms():
3239 'RS512' : None ,
3340 'ES256' : None ,
3441 'ES384' : None ,
42+ 'ES521' : None ,
3543 'ES512' : None ,
3644 'PS256' : None ,
3745 'PS384' : None ,
@@ -44,6 +52,7 @@ def _get_crypto_algorithms():
4452 crypto_algorithms ['RS512' ] = RSAAlgorithm (RSAAlgorithm .SHA512 )
4553 crypto_algorithms ['ES256' ] = ECAlgorithm (ECAlgorithm .SHA256 )
4654 crypto_algorithms ['ES384' ] = ECAlgorithm (ECAlgorithm .SHA384 )
55+ crypto_algorithms ['ES521' ] = ECAlgorithm (ECAlgorithm .SHA512 ),
4756 crypto_algorithms ['ES512' ] = ECAlgorithm (ECAlgorithm .SHA512 )
4857 crypto_algorithms ['PS256' ] = RSAPSSAlgorithm (RSAPSSAlgorithm .SHA256 )
4958 crypto_algorithms ['PS384' ] = RSAPSSAlgorithm (RSAPSSAlgorithm .SHA384 )
@@ -106,6 +115,20 @@ def verify(self, msg, key, sig):
106115 """
107116 raise NotImplementedError
108117
118+ @staticmethod
119+ def to_jwk (key_obj ):
120+ """
121+ Serializes a given RSA key into a JWK
122+ """
123+ raise NotImplementedError
124+
125+ @staticmethod
126+ def from_jwk (jwk ):
127+ """
128+ Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object
129+ """
130+ raise NotImplementedError
131+
109132
110133class NoneAlgorithm (Algorithm ):
111134 """
@@ -141,11 +164,7 @@ def __init__(self, hash_alg):
141164 self .hash_alg = hash_alg
142165
143166 def prepare_key (self , key ):
144- if not is_string_type (key ):
145- raise TypeError ('Expecting a string- or bytes-formatted key.' )
146-
147- if not isinstance (key , binary_type ):
148- key = key .encode ('utf-8' )
167+ key = force_bytes (key )
149168
150169 invalid_strings = [
151170 b'-----BEGIN PUBLIC KEY-----' ,
@@ -160,6 +179,22 @@ def prepare_key(self, key):
160179
161180 return key
162181
182+ @staticmethod
183+ def to_jwk (key_obj ):
184+ return json .dumps ({
185+ 'k' : force_unicode (base64url_encode (force_bytes (key_obj ))),
186+ 'kty' : 'oct'
187+ })
188+
189+ @staticmethod
190+ def from_jwk (jwk ):
191+ obj = json .loads (jwk )
192+
193+ if obj .get ('kty' ) != 'oct' :
194+ raise InvalidKeyError ('Not an HMAC key' )
195+
196+ return base64url_decode (obj ['k' ])
197+
163198 def sign (self , msg , key ):
164199 return hmac .new (key , msg , self .hash_alg ).digest ()
165200
@@ -185,9 +220,8 @@ def prepare_key(self, key):
185220 isinstance (key , RSAPublicKey ):
186221 return key
187222
188- if is_string_type (key ):
189- if not isinstance (key , binary_type ):
190- key = key .encode ('utf-8' )
223+ if isinstance (key , string_types ):
224+ key = force_bytes (key )
191225
192226 try :
193227 if key .startswith (b'ssh-rsa' ):
@@ -201,6 +235,105 @@ def prepare_key(self, key):
201235
202236 return key
203237
238+ @staticmethod
239+ def to_jwk (key_obj ):
240+ obj = None
241+
242+ if getattr (key_obj , 'private_numbers' , None ):
243+ # Private key
244+ numbers = key_obj .private_numbers ()
245+
246+ obj = {
247+ 'kty' : 'RSA' ,
248+ 'key_ops' : ['sign' ],
249+ 'n' : force_unicode (to_base64url_uint (numbers .public_numbers .n )),
250+ 'e' : force_unicode (to_base64url_uint (numbers .public_numbers .e )),
251+ 'd' : force_unicode (to_base64url_uint (numbers .d )),
252+ 'p' : force_unicode (to_base64url_uint (numbers .p )),
253+ 'q' : force_unicode (to_base64url_uint (numbers .q )),
254+ 'dp' : force_unicode (to_base64url_uint (numbers .dmp1 )),
255+ 'dq' : force_unicode (to_base64url_uint (numbers .dmq1 )),
256+ 'qi' : force_unicode (to_base64url_uint (numbers .iqmp ))
257+ }
258+
259+ elif getattr (key_obj , 'verifier' , None ):
260+ # Public key
261+ numbers = key_obj .public_numbers ()
262+
263+ obj = {
264+ 'kty' : 'RSA' ,
265+ 'key_ops' : ['verify' ],
266+ 'n' : force_unicode (to_base64url_uint (numbers .n )),
267+ 'e' : force_unicode (to_base64url_uint (numbers .e ))
268+ }
269+ else :
270+ raise InvalidKeyError ('Not a public or private key' )
271+
272+ return json .dumps (obj )
273+
274+ @staticmethod
275+ def from_jwk (jwk ):
276+ try :
277+ obj = json .loads (jwk )
278+ except ValueError :
279+ raise InvalidKeyError ('Key is not valid JSON' )
280+
281+ if obj .get ('kty' ) != 'RSA' :
282+ raise InvalidKeyError ('Not an RSA key' )
283+
284+ if 'd' in obj and 'e' in obj and 'n' in obj :
285+ # Private key
286+ if 'oth' in obj :
287+ raise InvalidKeyError ('Unsupported RSA private key: > 2 primes not supported' )
288+
289+ other_props = ['p' , 'q' , 'dp' , 'dq' , 'qi' ]
290+ props_found = [prop in obj for prop in other_props ]
291+ any_props_found = any (props_found )
292+
293+ if any_props_found and not all (props_found ):
294+ raise InvalidKeyError ('RSA key must include all parameters if any are present besides d' )
295+
296+ public_numbers = RSAPublicNumbers (
297+ from_base64url_uint (obj ['e' ]), from_base64url_uint (obj ['n' ])
298+ )
299+
300+ if any_props_found :
301+ numbers = RSAPrivateNumbers (
302+ d = from_base64url_uint (obj ['d' ]),
303+ p = from_base64url_uint (obj ['p' ]),
304+ q = from_base64url_uint (obj ['q' ]),
305+ dmp1 = from_base64url_uint (obj ['dp' ]),
306+ dmq1 = from_base64url_uint (obj ['dq' ]),
307+ iqmp = from_base64url_uint (obj ['qi' ]),
308+ public_numbers = public_numbers
309+ )
310+ else :
311+ d = from_base64url_uint (obj ['d' ])
312+ p , q = rsa_recover_prime_factors (
313+ public_numbers .n , d , public_numbers .e
314+ )
315+
316+ numbers = RSAPrivateNumbers (
317+ d = d ,
318+ p = p ,
319+ q = q ,
320+ dmp1 = rsa_crt_dmp1 (d , p ),
321+ dmq1 = rsa_crt_dmq1 (d , q ),
322+ iqmp = rsa_crt_iqmp (p , q ),
323+ public_numbers = public_numbers
324+ )
325+
326+ return numbers .private_key (default_backend ())
327+ elif 'n' in obj and 'e' in obj :
328+ # Public key
329+ numbers = RSAPublicNumbers (
330+ from_base64url_uint (obj ['e' ]), from_base64url_uint (obj ['n' ])
331+ )
332+
333+ return numbers .public_key (default_backend ())
334+ else :
335+ raise InvalidKeyError ('Not a public or private key' )
336+
204337 def sign (self , msg , key ):
205338 signer = key .signer (
206339 padding .PKCS1v15 (),
@@ -242,9 +375,8 @@ def prepare_key(self, key):
242375 isinstance (key , EllipticCurvePublicKey ):
243376 return key
244377
245- if is_string_type (key ):
246- if not isinstance (key , binary_type ):
247- key = key .encode ('utf-8' )
378+ if isinstance (key , string_types ):
379+ key = force_bytes (key )
248380
249381 # Attempt to load key. We don't know if it's
250382 # a Signing Key or a Verifying Key, so we try
0 commit comments