Skip to content

Commit 4bfd491

Browse files
authored
Merge pull request #16 from jschlyter/key_from_jwk_dict_private
Improve key_from_jwk_dict
2 parents ddafa27 + efc80ab commit 4bfd491

File tree

3 files changed

+119
-4
lines changed

3 files changed

+119
-4
lines changed

src/cryptojwt/jwk/jwk.py

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,47 @@
1919
from .hmac import SYMKey
2020

2121

22-
def key_from_jwk_dict(jwk_dict):
22+
EC_PUBLIC_REQUIRED = frozenset(['crv', 'x', 'y'])
23+
EC_PUBLIC = EC_PUBLIC_REQUIRED
24+
EC_PRIVATE_REQUIRED = frozenset(['d'])
25+
EC_PRIVATE_OPTIONAL = frozenset()
26+
EC_PRIVATE = EC_PRIVATE_REQUIRED | EC_PRIVATE_OPTIONAL
27+
28+
RSA_PUBLIC_REQUIRED = frozenset(['e', 'n'])
29+
RSA_PUBLIC = RSA_PUBLIC_REQUIRED
30+
RSA_PRIVATE_REQUIRED = frozenset(['p', 'q', 'd'])
31+
RSA_PRIVATE_OPTIONAL = frozenset(['qi', 'dp', 'dq'])
32+
RSA_PRIVATE = RSA_PRIVATE_REQUIRED | RSA_PRIVATE_OPTIONAL
33+
34+
35+
def ensure_ec_params(jwk_dict, private):
36+
"""Ensure all required EC parameters are present in dictionary"""
37+
provided = frozenset(jwk_dict.keys())
38+
if private is not None and private:
39+
required = EC_PUBLIC_REQUIRED | EC_PRIVATE_REQUIRED
40+
else:
41+
required = EC_PUBLIC_REQUIRED
42+
return ensure_params('EC', provided, required)
43+
44+
45+
def ensure_rsa_params(jwk_dict, private):
46+
"""Ensure all required RSA parameters are present in dictionary"""
47+
provided = frozenset(jwk_dict.keys())
48+
if private is not None and private:
49+
required = RSA_PUBLIC_REQUIRED | RSA_PRIVATE_REQUIRED
50+
else:
51+
required = RSA_PUBLIC_REQUIRED
52+
return ensure_params('RSA', provided, required)
53+
54+
55+
def ensure_params(kty, provided, required):
56+
"""Ensure all required parameters are present in dictionary"""
57+
if not required <= provided:
58+
missing = required - provided
59+
raise MissingValue('Missing properties for kty={}, {}'.format(kty, str(list(missing))))
60+
61+
62+
def key_from_jwk_dict(jwk_dict, private=None):
2363
"""Load JWK from dictionary
2464
2565
:param jwk_dict: Dictionary representing a JWK
@@ -28,7 +68,17 @@ def key_from_jwk_dict(jwk_dict):
2868
# uncouple from the original item
2969
_jwk_dict = copy.copy(jwk_dict)
3070

71+
if 'kty' not in _jwk_dict:
72+
raise MissingValue('kty missing')
73+
3174
if _jwk_dict['kty'] == 'EC':
75+
ensure_ec_params(_jwk_dict, private)
76+
77+
if private is not None and not private:
78+
# remove private components
79+
for v in EC_PRIVATE:
80+
_jwk_dict.pop(v, None)
81+
3282
if _jwk_dict["crv"] in NIST2SEC:
3383
curve = NIST2SEC[_jwk_dict["crv"]]()
3484
else:
@@ -50,6 +100,13 @@ def key_from_jwk_dict(jwk_dict):
50100
backends.default_backend())
51101
return ECKey(**_jwk_dict)
52102
elif _jwk_dict['kty'] == 'RSA':
103+
ensure_rsa_params(_jwk_dict, private)
104+
105+
if private is not None and not private:
106+
# remove private components
107+
for v in RSA_PRIVATE:
108+
_jwk_dict.pop(v, None)
109+
53110
rsa_pub_numbers = rsa.RSAPublicNumbers(
54111
base64url_to_long(_jwk_dict["e"]),
55112
base64url_to_long(_jwk_dict["n"]))
@@ -81,13 +138,13 @@ def key_from_jwk_dict(jwk_dict):
81138
else:
82139
_jwk_dict['pub_key'] = rsa_pub_numbers.public_key(
83140
backends.default_backend())
84-
141+
85142
if _jwk_dict['kty'] != "RSA":
86143
raise WrongKeyType('"{}" should have been "RSA"'.format(_jwk_dict[
87144
'kty']))
88145
return RSAKey(**_jwk_dict)
89146
elif _jwk_dict['kty'] == 'oct':
90-
if not 'key' in _jwk_dict and not 'k' in _jwk_dict:
147+
if 'key' not in _jwk_dict and 'k' not in _jwk_dict:
91148
raise MissingValue(
92149
'There has to be one of "k" or "key" in a symmetric key')
93150

tests/jwk_private_ec_key.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"kty": "EC",
3+
"crv": "P-256",
4+
"alg": "ES256",
5+
"x": "Ijdv9ZAD7QaEYnEqY5als5NFbNP_LsyZgJMTcQhNsYo",
6+
"y": "Hpc9vdz77lQG0NJf5FZAaeOJTR-bMjIw9kkCE1duxKE",
7+
"d": "omnOYCv3UxQRIogRcd2VIHhaj46FTZU8GfdYS4qX2_w"
8+
}

tests/test_02_jwk.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def test_get_key():
215215
assert key.key
216216

217217

218-
def test_private_key_from_jwk():
218+
def test_private_rsa_key_from_jwk():
219219
keys = []
220220

221221
kspec = json.loads(open(full_path("jwk_private_key.json")).read())
@@ -240,6 +240,56 @@ def test_private_key_from_jwk():
240240
assert _eq(list(_d.keys()), kspec.keys())
241241

242242

243+
def test_public_key_from_jwk():
244+
keys = []
245+
246+
kspec = json.loads(open(full_path("jwk_private_key.json")).read())
247+
keys.append(key_from_jwk_dict(kspec, private=False))
248+
249+
key = keys[0]
250+
251+
assert isinstance(key.n, (bytes, str))
252+
assert isinstance(key.e, (bytes, str))
253+
254+
_d = key.to_dict()
255+
256+
assert _eq(list(_d.keys()), ['n', 'alg', 'e', 'ext', 'key_ops', 'kty'])
257+
258+
259+
def test_ec_private_key_from_jwk():
260+
keys = []
261+
262+
kspec = json.loads(open(full_path("jwk_private_ec_key.json")).read())
263+
keys.append(key_from_jwk_dict(kspec))
264+
265+
key = keys[0]
266+
267+
assert isinstance(key.x, (bytes, str))
268+
assert isinstance(key.y, (bytes, str))
269+
assert isinstance(key.d, (bytes, str))
270+
271+
_d = key.to_dict()
272+
273+
assert _eq(list(_d.keys()), ['alg', 'kty', 'crv', 'x', 'y', 'd'])
274+
assert _eq(list(_d.keys()), kspec.keys())
275+
276+
277+
def test_ec_public_key_from_jwk():
278+
keys = []
279+
280+
kspec = json.loads(open(full_path("jwk_private_ec_key.json")).read())
281+
keys.append(key_from_jwk_dict(kspec, private=False))
282+
283+
key = keys[0]
284+
285+
assert isinstance(key.x, (bytes, str))
286+
assert isinstance(key.y, (bytes, str))
287+
288+
_d = key.to_dict()
289+
290+
assert _eq(list(_d.keys()), ['x', 'y', 'alg', 'crv', 'kty'])
291+
292+
243293
def test_rsa_pubkey_from_x509_cert_chain():
244294
cert = "MIID0jCCArqgAwIBAgIBSTANBgkqhkiG9w0BAQQFADCBiDELMAkGA1UEBhMCREUxEDAOBgNVBAgTB0JhdmF" \
245295
"yaWExEzARBgNVBAoTCkJpb0lEIEdtYkgxLzAtBgNVBAMTJkJpb0lEIENsaWVudCBDZXJ0aWZpY2F0aW9uIE" \

0 commit comments

Comments
 (0)