Skip to content

Commit 1a23c39

Browse files
authored
Merge pull request #42 from friedcell/extendable
Easier extending/replacing of key algorithms
2 parents 8556fc2 + fae83ff commit 1a23c39

File tree

4 files changed

+74
-61
lines changed

4 files changed

+74
-61
lines changed

jose/constants.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import hashlib
22

3-
class ALGORITHMS(object):
3+
4+
class Algorithms(object):
45
NONE = 'none'
56
HS256 = 'HS256'
67
HS384 = 'HS384'
@@ -12,13 +13,13 @@ class ALGORITHMS(object):
1213
ES384 = 'ES384'
1314
ES512 = 'ES512'
1415

15-
HMAC = (HS256, HS384, HS512)
16-
RSA = (RS256, RS384, RS512)
17-
EC = (ES256, ES384, ES512)
16+
HMAC = set([HS256, HS384, HS512])
17+
RSA = set([RS256, RS384, RS512])
18+
EC = set([ES256, ES384, ES512])
1819

19-
SUPPORTED = HMAC + RSA + EC
20+
SUPPORTED = HMAC.union(RSA).union(EC)
2021

21-
ALL = SUPPORTED + (NONE, )
22+
ALL = SUPPORTED.union([NONE])
2223

2324
HASHES = {
2425
HS256: hashlib.sha256,
@@ -31,3 +32,8 @@ class ALGORITHMS(object):
3132
ES384: hashlib.sha384,
3233
ES512: hashlib.sha512,
3334
}
35+
36+
KEYS = {}
37+
38+
39+
ALGORITHMS = Algorithms()

jose/jwk.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ def base64_to_long(data):
4747
return int_arr_to_long(struct.unpack('%sB' % len(_d), _d))
4848

4949

50+
def get_key(algorithm):
51+
if algorithm in ALGORITHMS.KEYS:
52+
return ALGORITHMS.KEYS[algorithm]
53+
elif algorithm in ALGORITHMS.HMAC:
54+
return HMACKey
55+
elif algorithm in ALGORITHMS.RSA:
56+
return RSAKey
57+
elif algorithm in ALGORITHMS.EC:
58+
return ECKey
59+
return None
60+
61+
62+
def register_key(algorithm, key_class):
63+
if not issubclass(key_class, Key):
64+
raise TypeError("Key class not a subclass of jwk.Key")
65+
ALGORITHMS.KEYS[algorithm] = key_class
66+
ALGORITHMS.SUPPORTED.add(algorithm)
67+
return True
68+
69+
5070
def construct(key_data, algorithm=None):
5171
"""
5272
Construct a Key object for the given algorithm with the given
@@ -60,14 +80,10 @@ def construct(key_data, algorithm=None):
6080
if not algorithm:
6181
raise JWKError('Unable to find a algorithm for key: %s' % key_data)
6282

63-
if algorithm in ALGORITHMS.HMAC:
64-
return HMACKey(key_data, algorithm)
65-
66-
if algorithm in ALGORITHMS.RSA:
67-
return RSAKey(key_data, algorithm)
68-
69-
if algorithm in ALGORITHMS.EC:
70-
return ECKey(key_data, algorithm)
83+
key_class = get_key(algorithm)
84+
if not key_class:
85+
raise JWKError('Unable to find a algorithm for key: %s' % key_data)
86+
return key_class(key_data, algorithm)
7187

7288

7389
def get_algorithm_object(algorithm):
@@ -91,11 +107,8 @@ class Key(object):
91107
"""
92108
A simple interface for implementing JWK keys.
93109
"""
94-
prepared_key = None
95-
hash_alg = None
96-
97-
def _process_jwk(self, jwk_dict):
98-
raise NotImplementedError()
110+
def __init__(self, key, algorithm):
111+
pass
99112

100113
def sign(self, msg):
101114
raise NotImplementedError()
@@ -112,13 +125,9 @@ class HMACKey(Key):
112125
SHA256 = hashlib.sha256
113126
SHA384 = hashlib.sha384
114127
SHA512 = hashlib.sha512
115-
valid_hash_algs = ALGORITHMS.HMAC
116-
117-
prepared_key = None
118-
hash_alg = None
119128

120129
def __init__(self, key, algorithm):
121-
if algorithm not in self.valid_hash_algs:
130+
if algorithm not in ALGORITHMS.HMAC:
122131
raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm)
123132
self.hash_alg = get_algorithm_object(algorithm)
124133

@@ -174,14 +183,10 @@ class RSAKey(Key):
174183
SHA256 = Crypto.Hash.SHA256
175184
SHA384 = Crypto.Hash.SHA384
176185
SHA512 = Crypto.Hash.SHA512
177-
valid_hash_algs = ALGORITHMS.RSA
178-
179-
prepared_key = None
180-
hash_alg = None
181186

182187
def __init__(self, key, algorithm):
183188

184-
if algorithm not in self.valid_hash_algs:
189+
if algorithm not in ALGORITHMS.RSA:
185190
raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm)
186191
self.hash_alg = get_algorithm_object(algorithm)
187192

@@ -242,7 +247,7 @@ def verify(self, msg, sig):
242247
try:
243248
return PKCS1_v1_5.new(self.prepared_key).verify(self.hash_alg.new(msg), sig)
244249
except Exception as e:
245-
raise JWKError(e)
250+
return False
246251

247252

248253
class ECKey(Key):
@@ -257,24 +262,19 @@ class ECKey(Key):
257262
SHA256 = hashlib.sha256
258263
SHA384 = hashlib.sha384
259264
SHA512 = hashlib.sha512
260-
valid_hash_algs = ALGORITHMS.EC
261265

262-
curve_map = {
266+
CURVE_MAP = {
263267
SHA256: ecdsa.curves.NIST256p,
264268
SHA384: ecdsa.curves.NIST384p,
265269
SHA512: ecdsa.curves.NIST521p,
266270
}
267271

268-
prepared_key = None
269-
hash_alg = None
270-
curve = None
271-
272272
def __init__(self, key, algorithm):
273-
if algorithm not in self.valid_hash_algs:
273+
if algorithm not in ALGORITHMS.EC:
274274
raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm)
275275
self.hash_alg = get_algorithm_object(algorithm)
276276

277-
self.curve = self.curve_map.get(self.hash_alg)
277+
self.curve = self.CURVE_MAP.get(self.hash_alg)
278278

279279
if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
280280
self.prepared_key = key

tests/algorithms/test_base.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
1+
from jose.jwk import Key
12

2-
# from jose.jwk import Key
3-
# from jose.exceptions import JOSEError
3+
import pytest
44

5-
# import pytest
65

6+
@pytest.fixture
7+
def alg():
8+
return Key("key", "ALG")
79

8-
# @pytest.fixture
9-
# def alg():
10-
# return Key()
1110

11+
class TestBaseAlgorithm:
1212

13-
# class TestBaseAlgorithm:
13+
def test_sign_is_interface(self, alg):
14+
with pytest.raises(NotImplementedError):
15+
alg.sign('msg')
1416

15-
# def test_prepare_key_is_interface(self, alg):
16-
# with pytest.raises(JOSEError):
17-
# alg.prepare_key('secret')
17+
def test_verify_is_interface(self, alg):
18+
with pytest.raises(NotImplementedError):
19+
alg.verify('msg', 'sig')
1820

19-
# def test_sign_is_interface(self, alg):
20-
# with pytest.raises(JOSEError):
21-
# alg.sign('msg', 'secret')
22-
23-
# def test_verify_is_interface(self, alg):
24-
# with pytest.raises(JOSEError):
25-
# alg.verify('msg', 'secret', 'sig')

tests/test_jwk.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
21
from jose import jwk
32
from jose.exceptions import JWKError
43

54
import pytest
65

7-
86
hmac_key = {
97
"kty": "oct",
108
"kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037",
@@ -35,10 +33,7 @@ class TestJWK:
3533

3634
def test_interface(self):
3735

38-
key = jwk.Key()
39-
40-
with pytest.raises(NotImplementedError):
41-
key._process_jwk(None)
36+
key = jwk.Key("key", "ALG")
4237

4338
with pytest.raises(NotImplementedError):
4439
key.sign('')
@@ -115,3 +110,20 @@ def test_construct_from_jwk_missing_alg(self):
115110

116111
with pytest.raises(JWKError):
117112
key = jwk.construct(hmac_key)
113+
114+
with pytest.raises(JWKError):
115+
key = jwk.construct("key", algorithm="NONEXISTENT")
116+
117+
def test_get_key(self):
118+
assert jwk.get_key("HS256") == jwk.HMACKey
119+
assert jwk.get_key("RS256") == jwk.RSAKey
120+
assert jwk.get_key("ES256") == jwk.ECKey
121+
122+
assert jwk.get_key("NONEXISTENT") == None
123+
124+
def test_register_key(self):
125+
assert jwk.register_key("ALG", jwk.Key)
126+
assert jwk.get_key("ALG") == jwk.Key
127+
128+
with pytest.raises(TypeError):
129+
assert jwk.register_key("ALG", object)

0 commit comments

Comments
 (0)