Skip to content

Commit de10ef4

Browse files
author
Gasper Zejn
committed
Implement to_dict and improve process_jwk to support private keys and
conform to standard and tests.
1 parent dd43565 commit de10ef4

File tree

1 file changed

+116
-4
lines changed

1 file changed

+116
-4
lines changed

jose/backends/rsa_backend.py

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,71 @@
44
from jose.backends.base import Key
55
from jose.constants import ALGORITHMS
66
from 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

1074
class 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

Comments
 (0)