44from jose .backends .base import Key
55from jose .constants import ALGORITHMS
66from jose .exceptions import JWKError
7- from jose .utils import base64_to_long
7+ from jose .utils import base64_to_long , long_to_base64
8+
9+
10+ # Functions gcd and rsa_recover_prime_factors were copied from cryptography 1.9
11+ # to enable pure python rsa module to be in compliance with section 6.3.1 of RFC7518
12+ # which requires only private exponent (d) for private key.
13+
14+ def _gcd (a , b ):
15+ """Calculate the Greatest Common Divisor of a and b.
16+
17+ Unless b==0, the result will have the same sign as b (so that when
18+ b is divided by it, the result comes out positive).
19+ """
20+ while b :
21+ a , b = b , a % b
22+ return a
23+
24+
25+ # Controls the number of iterations rsa_recover_prime_factors will perform
26+ # to obtain the prime factors. Each iteration increments by 2 so the actual
27+ # maximum attempts is half this number.
28+ _MAX_RECOVERY_ATTEMPTS = 1000
29+
30+
31+ def _rsa_recover_prime_factors (n , e , d ):
32+ """
33+ Compute factors p and q from the private exponent d. We assume that n has
34+ no more than two factors. This function is adapted from code in PyCrypto.
35+ """
36+ # See 8.2.2(i) in Handbook of Applied Cryptography.
37+ ktot = d * e - 1
38+ # The quantity d*e-1 is a multiple of phi(n), even,
39+ # and can be represented as t*2^s.
40+ t = ktot
41+ while t % 2 == 0 :
42+ t = t // 2
43+ # Cycle through all multiplicative inverses in Zn.
44+ # The algorithm is non-deterministic, but there is a 50% chance
45+ # any candidate a leads to successful factoring.
46+ # See "Digitalized Signatures and Public Key Functions as Intractable
47+ # as Factorization", M. Rabin, 1979
48+ spotted = False
49+ a = 2
50+ while not spotted and a < _MAX_RECOVERY_ATTEMPTS :
51+ k = t
52+ # Cycle through all values a^{t*2^i}=a^k
53+ while k < ktot :
54+ cand = pow (a , k , n )
55+ # Check if a^k is a non-trivial root of unity (mod n)
56+ if cand != 1 and cand != (n - 1 ) and pow (cand , 2 , n ) == 1 :
57+ # We have found a number such that (cand-1)(cand+1)=0 (mod n).
58+ # Either of the terms divides n.
59+ p = _gcd (cand + 1 , n )
60+ spotted = True
61+ break
62+ k *= 2
63+ # This value was not any good... let's try another!
64+ a += 2
65+ if not spotted :
66+ raise ValueError ("Unable to compute factors p and q from exponent d." )
67+ # Found !
68+ q , r = divmod (n , p )
69+ assert r == 0
70+ p , q = sorted ((p , q ), reverse = True )
71+ return (p , q )
872
973
1074class RSAKey (Key ):
@@ -24,7 +88,7 @@ def __init__(self, key, algorithm):
2488 self ._algorithm = algorithm
2589
2690 if isinstance (key , dict ):
27- self .prepared_key = self ._process_jwk (key )
91+ self ._prepared_key = self ._process_jwk (key )
2892 return
2993
3094 if isinstance (key , (pyrsa .PublicKey , pyrsa .PrivateKey )):
@@ -52,8 +116,28 @@ def _process_jwk(self, jwk_dict):
52116 e = base64_to_long (jwk_dict .get ('e' ))
53117 n = base64_to_long (jwk_dict .get ('n' ))
54118
55- verifying_key = pyrsa .PublicKey (e = e , n = n )
56- return verifying_key
119+ if not 'd' in jwk_dict :
120+ return pyrsa .PublicKey (e = e , n = n )
121+ else :
122+ d = base64_to_long (jwk_dict .get ('d' ))
123+ extra_params = ['p' , 'q' , 'dp' , 'dq' , 'qi' ]
124+
125+ if any (k in jwk_dict for k in extra_params ):
126+ # Precomputed private key parameters are available.
127+ if not all (k in jwk_dict for k in extra_params ):
128+ # These values must be present when 'p' is according to
129+ # Section 6.3.2 of RFC7518, so if they are not we raise
130+ # an error.
131+ raise JWKError ('Precomputed private key parameters are incomplete.' )
132+
133+ p = base64_to_long (jwk_dict ['p' ])
134+ q = base64_to_long (jwk_dict ['q' ])
135+ return pyrsa .PrivateKey (e = e , n = n , d = d , p = p , q = q )
136+ else :
137+ p , q = _rsa_recover_prime_factors (n , e , d )
138+ return pyrsa .PrivateKey (n = n , e = e , d = d , p = p , q = q )
139+
140+
57141
58142 def sign (self , msg ):
59143 return pyrsa .sign (msg , self ._prepared_key , self .hash_alg )
@@ -65,6 +149,9 @@ def verify(self, msg, sig):
65149 except pyrsa .pkcs1 .VerificationError :
66150 return False
67151
152+ def is_public (self ):
153+ return isinstance (self ._prepared_key , pyrsa .PublicKey )
154+
68155 def public_key (self ):
69156 if isinstance (self ._prepared_key , pyrsa .PublicKey ):
70157 return self
@@ -81,3 +168,28 @@ def to_pem(self):
81168 der = self ._prepared_key .save_pkcs1 (format = 'DER' )
82169 pem = rsa .pem .save_pem (header + der , pem_marker = 'PUBLIC KEY' )
83170 return pem
171+
172+ def to_dict (self ):
173+ if not self .is_public ():
174+ public_key = self .public_key ()._prepared_key
175+ else :
176+ public_key = self ._prepared_key
177+
178+ data = {
179+ 'alg' : self ._algorithm ,
180+ 'kty' : 'RSA' ,
181+ 'n' : long_to_base64 (public_key .n ),
182+ 'e' : long_to_base64 (public_key .e ),
183+ }
184+
185+ if not self .is_public ():
186+ data .update ({
187+ 'd' : long_to_base64 (self ._prepared_key .d ),
188+ 'p' : long_to_base64 (self ._prepared_key .p ),
189+ 'q' : long_to_base64 (self ._prepared_key .q ),
190+ 'dp' : long_to_base64 (self ._prepared_key .exp1 ),
191+ 'dq' : long_to_base64 (self ._prepared_key .exp2 ),
192+ 'qi' : long_to_base64 (self ._prepared_key .coef ),
193+ })
194+
195+ return data
0 commit comments