Skip to content

Commit 77ed279

Browse files
author
Gasper Zejn
committed
Merge branch 'master' into python-rsa
2 parents 3495543 + 364fb0b commit 77ed279

File tree

15 files changed

+560
-204
lines changed

15 files changed

+560
-204
lines changed

.codecov.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# codecov.yml file, spec is visible:
2+
# https://github.com/codecov/support/wiki/Codecov-Yaml
3+
coverage:
4+
status:
5+
# pull-requests only
6+
patch:
7+
default:
8+
# coverage may fall by <1% and still be considered "passing"
9+
threshold: 1%

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
sudo: false
2+
# Travis infra requires pinning dist:precise, at least as of 2017-09-01
3+
# detail: https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch
4+
dist: precise
25
language: python
36
python:
47
- "2.6"

jose/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
__version__ = "1.3.2"
2+
__version__ = "1.4.0"
33
__author__ = 'Michael Davis'
44
__license__ = 'MIT'
55
__copyright__ = 'Copyright 2016 Michael Davis'

jose/backends/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ def public_key(self):
1616

1717
def to_pem(self):
1818
raise NotImplementedError()
19+
20+
def to_dict(self):
21+
raise NotImplementedError()

jose/backends/cryptography_backend.py

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der
44

55
from jose.backends.base import Key
6-
from jose.utils import base64_to_long
6+
from jose.utils import base64_to_long, long_to_base64
77
from jose.constants import ALGORITHMS
88
from jose.exceptions import JWKError
99

@@ -68,19 +68,26 @@ def _process_jwk(self, jwk_dict):
6868
if not jwk_dict.get('kty') == 'EC':
6969
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))
7070

71+
if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
72+
raise JWKError('Mandatory parameters are missing')
73+
7174
x = base64_to_long(jwk_dict.get('x'))
7275
y = base64_to_long(jwk_dict.get('y'))
73-
7476
curve = {
7577
'P-256': ec.SECP256R1,
7678
'P-384': ec.SECP384R1,
7779
'P-521': ec.SECP521R1,
7880
}[jwk_dict['crv']]
7981

80-
ec_pn = ec.EllipticCurvePublicNumbers(x, y, curve())
81-
verifying_key = ec_pn.public_key(self.cryptography_backend())
82+
public = ec.EllipticCurvePublicNumbers(x, y, curve())
8283

83-
return verifying_key
84+
if 'd' in jwk_dict:
85+
d = base64_to_long(jwk_dict.get('d'))
86+
private = ec.EllipticCurvePrivateNumbers(d, public)
87+
88+
return private.private_key(self.cryptography_backend())
89+
else:
90+
return public.public_key(self.cryptography_backend())
8491

8592
def sign(self, msg):
8693
if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size:
@@ -102,13 +109,16 @@ def verify(self, msg, sig):
102109
except:
103110
return False
104111

112+
def is_public(self):
113+
return hasattr(self.prepared_key, 'public_bytes')
114+
105115
def public_key(self):
106-
if hasattr(self.prepared_key, 'public_bytes'):
116+
if self.is_public():
107117
return self
108118
return self.__class__(self.prepared_key.public_key(), self._algorithm)
109119

110120
def to_pem(self):
111-
if hasattr(self.prepared_key, 'public_bytes'):
121+
if self.is_public():
112122
pem = self.prepared_key.public_bytes(
113123
encoding=serialization.Encoding.PEM,
114124
format=serialization.PublicFormat.SubjectPublicKeyInfo
@@ -121,6 +131,39 @@ def to_pem(self):
121131
)
122132
return pem
123133

134+
def to_dict(self):
135+
if not self.is_public():
136+
public_key = self.prepared_key.public_key()
137+
else:
138+
public_key = self.prepared_key
139+
140+
crv = {
141+
'secp256r1': 'P-256',
142+
'secp384r1': 'P-384',
143+
'secp521r1': 'P-521',
144+
}[self.prepared_key.curve.name]
145+
146+
# Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of
147+
# RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve
148+
# points must be encoded as octed-strings of this length.
149+
key_size = (self.prepared_key.curve.key_size + 7) // 8
150+
151+
data = {
152+
'alg': self._algorithm,
153+
'kty': 'EC',
154+
'crv': crv,
155+
'x': long_to_base64(public_key.public_numbers().x, size=key_size),
156+
'y': long_to_base64(public_key.public_numbers().y, size=key_size),
157+
}
158+
159+
if not self.is_public():
160+
data['d'] = long_to_base64(
161+
self.prepared_key.private_numbers().private_value,
162+
size=key_size
163+
)
164+
165+
return data
166+
124167

125168
class CryptographyRSAKey(Key):
126169
SHA256 = hashes.SHA256
@@ -171,17 +214,51 @@ def _process_jwk(self, jwk_dict):
171214

172215
e = base64_to_long(jwk_dict.get('e', 256))
173216
n = base64_to_long(jwk_dict.get('n'))
174-
175-
verifying_key = rsa.RSAPublicNumbers(e, n).public_key(self.cryptography_backend())
176-
return verifying_key
217+
public = rsa.RSAPublicNumbers(e, n)
218+
219+
if 'd' not in jwk_dict:
220+
return public.public_key(self.cryptography_backend())
221+
else:
222+
# This is a private key.
223+
d = base64_to_long(jwk_dict.get('d'))
224+
225+
extra_params = ['p', 'q', 'dp', 'dq', 'qi']
226+
227+
if any(k in jwk_dict for k in extra_params):
228+
# Precomputed private key parameters are available.
229+
if not all(k in jwk_dict for k in extra_params):
230+
# These values must be present when 'p' is according to
231+
# Section 6.3.2 of RFC7518, so if they are not we raise
232+
# an error.
233+
raise JWKError('Precomputed private key parameters are incomplete.')
234+
235+
p = base64_to_long(jwk_dict['p'])
236+
q = base64_to_long(jwk_dict['q'])
237+
dp = base64_to_long(jwk_dict['dp'])
238+
dq = base64_to_long(jwk_dict['dq'])
239+
qi = base64_to_long(jwk_dict['qi'])
240+
else:
241+
# The precomputed private key parameters are not available,
242+
# so we use cryptography's API to fill them in.
243+
p, q = rsa.rsa_recover_prime_factors(n, e, d)
244+
dp = rsa.rsa_crt_dmp1(d, p)
245+
dq = rsa.rsa_crt_dmq1(d, q)
246+
qi = rsa.rsa_crt_iqmp(p, q)
247+
248+
private = rsa.RSAPrivateNumbers(p, q, d, dp, dq, qi, public)
249+
250+
return private.private_key(self.cryptography_backend())
177251

178252
def sign(self, msg):
179-
signer = self.prepared_key.signer(
180-
padding.PKCS1v15(),
181-
self.hash_alg()
182-
)
183-
signer.update(msg)
184-
signature = signer.finalize()
253+
try:
254+
signer = self.prepared_key.signer(
255+
padding.PKCS1v15(),
256+
self.hash_alg()
257+
)
258+
signer.update(msg)
259+
signature = signer.finalize()
260+
except Exception as e:
261+
raise JWKError(e)
185262
return signature
186263

187264
def verify(self, msg, sig):
@@ -197,13 +274,16 @@ def verify(self, msg, sig):
197274
except InvalidSignature:
198275
return False
199276

277+
def is_public(self):
278+
return hasattr(self.prepared_key, 'public_bytes')
279+
200280
def public_key(self):
201-
if hasattr(self.prepared_key, 'public_bytes'):
281+
if self.is_public():
202282
return self
203283
return self.__class__(self.prepared_key.public_key(), self._algorithm)
204284

205285
def to_pem(self):
206-
if hasattr(self.prepared_key, 'public_bytes'):
286+
if self.is_public():
207287
return self.prepared_key.public_bytes(
208288
encoding=serialization.Encoding.PEM,
209289
format=serialization.PublicFormat.SubjectPublicKeyInfo
@@ -214,3 +294,28 @@ def to_pem(self):
214294
format=serialization.PrivateFormat.TraditionalOpenSSL,
215295
encryption_algorithm=serialization.NoEncryption()
216296
)
297+
298+
def to_dict(self):
299+
if not self.is_public():
300+
public_key = self.prepared_key.public_key()
301+
else:
302+
public_key = self.prepared_key
303+
304+
data = {
305+
'alg': self._algorithm,
306+
'kty': 'RSA',
307+
'n': long_to_base64(public_key.public_numbers().n),
308+
'e': long_to_base64(public_key.public_numbers().e),
309+
}
310+
311+
if not self.is_public():
312+
data.update({
313+
'd': long_to_base64(self.prepared_key.private_numbers().d),
314+
'p': long_to_base64(self.prepared_key.private_numbers().p),
315+
'q': long_to_base64(self.prepared_key.private_numbers().q),
316+
'dp': long_to_base64(self.prepared_key.private_numbers().dmp1),
317+
'dq': long_to_base64(self.prepared_key.private_numbers().dmq1),
318+
'qi': long_to_base64(self.prepared_key.private_numbers().iqmp),
319+
})
320+
321+
return data

jose/backends/ecdsa_backend.py

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from jose.constants import ALGORITHMS
88
from jose.exceptions import JWKError
9-
from jose.utils import base64_to_long
9+
from jose.utils import base64_to_long, long_to_base64
1010

1111

1212
class ECDSAECKey(Key):
@@ -72,16 +72,23 @@ def _process_jwk(self, jwk_dict):
7272
if not jwk_dict.get('kty') == 'EC':
7373
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))
7474

75-
x = base64_to_long(jwk_dict.get('x'))
76-
y = base64_to_long(jwk_dict.get('y'))
75+
if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
76+
raise JWKError('Mandatory parameters are missing')
7777

78-
if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
79-
raise JWKError("Point: %s, %s is not a valid point" % (x, y))
78+
if 'd' in jwk_dict:
79+
# We are dealing with a private key; the secret exponent is enough
80+
# to create an ecdsa key.
81+
d = base64_to_long(jwk_dict.get('d'))
82+
return ecdsa.keys.SigningKey.from_secret_exponent(d, self.curve)
83+
else:
84+
x = base64_to_long(jwk_dict.get('x'))
85+
y = base64_to_long(jwk_dict.get('y'))
8086

81-
point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order)
82-
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, self.curve)
87+
if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
88+
raise JWKError("Point: %s, %s is not a valid point" % (x, y))
8389

84-
return verifying_key
90+
point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order)
91+
return ecdsa.keys.VerifyingKey.from_public_point(point, self.curve)
8592

8693
def sign(self, msg):
8794
return self.prepared_key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string)
@@ -92,10 +99,46 @@ def verify(self, msg, sig):
9299
except:
93100
return False
94101

102+
def is_public(self):
103+
return isinstance(self.prepared_key, ecdsa.VerifyingKey)
104+
95105
def public_key(self):
96-
if isinstance(self.prepared_key, ecdsa.VerifyingKey):
106+
if self.is_public():
97107
return self
98108
return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm)
99109

100110
def to_pem(self):
101111
return self.prepared_key.to_pem()
112+
113+
def to_dict(self):
114+
if not self.is_public():
115+
public_key = self.prepared_key.get_verifying_key()
116+
else:
117+
public_key = self.prepared_key
118+
119+
crv = {
120+
ecdsa.curves.NIST256p: 'P-256',
121+
ecdsa.curves.NIST384p: 'P-384',
122+
ecdsa.curves.NIST521p: 'P-521',
123+
}[self.prepared_key.curve]
124+
125+
# Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of
126+
# RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve
127+
# points must be encoded as octed-strings of this length.
128+
key_size = self.prepared_key.curve.baselen
129+
130+
data = {
131+
'alg': self._algorithm,
132+
'kty': 'EC',
133+
'crv': crv,
134+
'x': long_to_base64(public_key.pubkey.point.x(), size=key_size),
135+
'y': long_to_base64(public_key.pubkey.point.y(), size=key_size),
136+
}
137+
138+
if not self.is_public():
139+
data['d'] = long_to_base64(
140+
self.prepared_key.privkey.secret_multiplier,
141+
size=key_size
142+
)
143+
144+
return data

0 commit comments

Comments
 (0)