Skip to content

Commit 2f66e16

Browse files
author
Michael Davis
committed
Merge pull request #4 from mpdavis/python3
Python3 support
2 parents b3e0d64 + 0ecb6c4 commit 2f66e16

File tree

14 files changed

+139
-84
lines changed

14 files changed

+139
-84
lines changed

.travis.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
language: python
2-
python: "2.7"
2+
env:
3+
- TOXENV=py26
4+
- TOXENV=py27
5+
- TOXENV=py33
6+
- TOXENV=py34
37
install:
48
- pip install -r requirements-dev.txt
5-
- pip install codecov
9+
- pip install -U tox codecov
610
script:
7-
- py.test --cov-report term-missing --cov jose
11+
- tox
812
after_success:
9-
- codecov
13+
- codecov

jose/jwa.py renamed to jose/jwk.py

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
import hashlib
33
import hmac
44
import six
5-
6-
from jose.constants import ALGORITHMS
7-
from jose.exceptions import JWSError
8-
from jose.exceptions import JOSEError
5+
import struct
96

107
import Crypto.Hash.SHA256
118
import Crypto.Hash.SHA384
@@ -16,7 +13,10 @@
1613

1714
import ecdsa
1815

19-
from jose.utils import constant_time_compare
16+
from jose.constants import ALGORITHMS
17+
from jose.exceptions import JWKError
18+
from jose.exceptions import JWSError
19+
from jose.exceptions import JOSEError
2020

2121

2222
def get_algorithm_object(algorithm):
@@ -25,39 +25,41 @@ def get_algorithm_object(algorithm):
2525
"""
2626

2727
if algorithm == ALGORITHMS.HS256:
28-
return HMACAlgorithm(HMACAlgorithm.SHA256)
28+
return HMACKey(HMACKey.SHA256)
2929

3030
if algorithm == ALGORITHMS.HS384:
31-
return HMACAlgorithm(HMACAlgorithm.SHA384)
31+
return HMACKey(HMACKey.SHA384)
3232

3333
if algorithm == ALGORITHMS.HS512:
34-
return HMACAlgorithm(HMACAlgorithm.SHA512)
34+
return HMACKey(HMACKey.SHA512)
3535

3636
if algorithm == ALGORITHMS.RS256:
37-
return RSAAlgorithm(RSAAlgorithm.SHA256)
37+
return RSAKey(RSAKey.SHA256)
3838

3939
if algorithm == ALGORITHMS.RS384:
40-
return RSAAlgorithm(RSAAlgorithm.SHA384)
40+
return RSAKey(RSAKey.SHA384)
4141

4242
if algorithm == ALGORITHMS.RS512:
43-
return RSAAlgorithm(RSAAlgorithm.SHA512)
43+
return RSAKey(RSAKey.SHA512)
4444

4545
if algorithm == ALGORITHMS.ES256:
46-
return ECAlgorithm(ECAlgorithm.SHA256)
46+
return ECKey(ECKey.SHA256)
4747

4848
if algorithm == ALGORITHMS.ES384:
49-
return ECAlgorithm(ECAlgorithm.SHA384)
49+
return ECKey(ECKey.SHA384)
5050

5151
if algorithm == ALGORITHMS.ES512:
52-
return ECAlgorithm(ECAlgorithm.SHA512)
52+
return ECKey(ECKey.SHA512)
5353

5454
raise JWSError('Algorithm not supported: %s' % algorithm)
5555

5656

57-
class Algorithm(object):
57+
class Key(object):
5858
"""
59-
The interface for an algorithm used to sign and verify tokens.
59+
The interface for an JWK used to sign and verify tokens.
6060
"""
61+
prepared_key = None
62+
6163
def process_sign(self, msg, key):
6264
"""
6365
Processes a signature for the given algorithm.
@@ -82,6 +84,22 @@ def process_prepare_key(self, key):
8284
"""
8385
raise NotImplementedError
8486

87+
def process_deserilialize(self):
88+
"""
89+
Processes deserializing a key into a JWK JSON format.
90+
91+
This method should be overriden by the implementing Key class.
92+
"""
93+
raise NotImplementedError
94+
95+
def process_jwk(self, jwk):
96+
"""
97+
Process a JWK dict into a Key object.
98+
99+
This method shold be overriden by the implementing Key class.
100+
"""
101+
raise NotImplementedError
102+
85103
def prepare_key(self, key):
86104
"""
87105
Performs necessary validation and conversions on the key and returns
@@ -93,10 +111,13 @@ def prepare_key(self, key):
93111
TypeError: If an invalid key is attempted to be used.
94112
"""
95113
try:
96-
return self.process_prepare_key(key)
97-
except Exception, e:
114+
key = self.process_prepare_key(key)
115+
except Exception as e:
98116
raise JOSEError(e)
99117

118+
self.prepared_key = key
119+
return key
120+
100121
def sign(self, msg, key):
101122
"""
102123
Returns a digital signature for the specified message
@@ -109,7 +130,7 @@ def sign(self, msg, key):
109130
"""
110131
try:
111132
return self.process_sign(msg, key)
112-
except Exception, e:
133+
except Exception as e:
113134
raise JOSEError(e)
114135

115136
def verify(self, msg, key, sig):
@@ -124,11 +145,11 @@ def verify(self, msg, key, sig):
124145
"""
125146
try:
126147
return self.process_verify(msg, key, sig)
127-
except Exception, e:
148+
except Exception as e:
128149
raise JOSEError(e)
129150

130151

131-
class HMACAlgorithm(Algorithm):
152+
class HMACKey(Key):
132153
"""
133154
Performs signing and verification operations using HMAC
134155
and the specified hash function.
@@ -164,10 +185,10 @@ def process_sign(self, msg, key):
164185
return hmac.new(key, msg, self.hash_alg).digest()
165186

166187
def process_verify(self, msg, key, sig):
167-
return constant_time_compare(sig, self.sign(msg, key))
188+
return sig == self.sign(msg, key)
168189

169190

170-
class RSAAlgorithm(Algorithm):
191+
class RSAKey(Key):
171192
"""
172193
Performs signing and verification operations using
173194
RSASSA-PKCS-v1_5 and the specified hash function.
@@ -183,9 +204,12 @@ def __init__(self, hash_alg):
183204

184205
def process_prepare_key(self, key):
185206

186-
if isinstance(key, RSA._RSAobj):
207+
if isinstance(key, (RSA._RSAobj, RSAKey)):
187208
return key
188209

210+
if isinstance(key, dict):
211+
return self.process_jwk(key)
212+
189213
if isinstance(key, six.string_types):
190214
if isinstance(key, six.text_type):
191215
key = key.encode('utf-8')
@@ -196,14 +220,33 @@ def process_prepare_key(self, key):
196220

197221
return key
198222

223+
def process_jwk(self, jwk):
224+
225+
def urlsafe_b64decode(encoded):
226+
import base64
227+
if not encoded:
228+
return encoded
229+
modulo = len(encoded) % 4
230+
if modulo != 0:
231+
encoded += ('=' * (4 - modulo))
232+
return base64.b64decode(encoded)
233+
234+
if not jwk.get('kty') == 'RSA':
235+
raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk.get('kty'))
236+
237+
e = bytes(jwk.get('e', 256))
238+
n = bytes(jwk.get('n'))
239+
240+
return RSA.construct((long(n), long(e)))
241+
199242
def process_sign(self, msg, key):
200243
return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg))
201244

202245
def process_verify(self, msg, key, sig):
203246
return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig)
204247

205248

206-
class ECAlgorithm(Algorithm):
249+
class ECKey(Key):
207250
"""
208251
Performs signing and verification operations using
209252
ECDSA and the specified hash function

jose/jws.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from collections import Mapping
77

8-
from jose.jwa import get_algorithm_object
8+
from jose.jwk import get_algorithm_object
99
from jose.constants import ALGORITHMS
1010
from jose.exceptions import JWSError
1111
from jose.utils import base64url_encode
@@ -108,12 +108,14 @@ def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key):
108108
alg_obj = get_algorithm_object(algorithm)
109109
key = alg_obj.prepare_key(key)
110110
signature = alg_obj.sign(signing_input, key)
111-
except Exception, e:
111+
except Exception as e:
112112
raise JWSError(e)
113113

114114
encoded_signature = base64url_encode(signature)
115115

116-
return b'.'.join([encoded_header, encoded_claims, encoded_signature])
116+
encoded_string = b'.'.join([encoded_header, encoded_claims, encoded_signature])
117+
118+
return encoded_string.decode('utf-8')
117119

118120

119121
def _load(jwt):

jose/utils.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1+
12
import base64
2-
import six
3-
import struct
43

54

65
def base64url_decode(input):
@@ -36,21 +35,3 @@ def timedelta_total_seconds(delta):
3635
delta (timedelta): A timedelta to convert to seconds.
3736
"""
3837
return delta.days * 24 * 60 * 60 + delta.seconds
39-
40-
41-
def constant_time_compare(a, b):
42-
"""Helper method to compare two strings in constant time.
43-
44-
Strings need to be compared in constant time when worried
45-
about timing attacks.
46-
47-
Args:
48-
a (str): The first string to compare.
49-
b (str): The second string to compare.
50-
"""
51-
if len(a) != len(b):
52-
return False
53-
result = 0
54-
for x, y in zip(a, b):
55-
result |= ord(x) ^ ord(y)
56-
return result == 0

key.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
key = {
2+
"kty": "RSA",
3+
4+
"use": "sig",
5+
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
6+
"e": "AQAB"
7+
}

test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from jose import jwt
2+
3+
claims = {
4+
'test': 1
5+
}
6+
7+
test = {
8+
"kty": "RSA",
9+
"alg": "RS256",
10+
"use": "sig",
11+
"kid": "e52f68a6c1d04d1451c8437c319ed1eb2425f3a2",
12+
"n": "ufmhFfZJ8D9TZIGqlWfpVlA9VftAdEWop71G4xnoPC6Rk7RIOHG5P59tVqdA_uINgOzqWd4DZDiyajwZU-SoxwGUjfrijBsge-Ul_HTwVM0kwAorizSm97--rderM3b9KzkatJqizmIG7Dm7A06USMWGlSeTKs_RYDFGM7QZWncQVvtCYu_XJfuc0DCa1PyAFzwmBrEliv0tZEogWUien0HQ95Y-EJrxb-CgKt7fd3gfI0wAJtg-h7QyZWX4UH8ae3VnfeUZp6dg7SLswfNxc2W7UdTgwOaokkxzRNq5qmzIT6Cz-vMjl_Mf6VaLr7e4Y357vwUzqh9ool-DDlEjVw",
13+
"e": "AQAB"
14+
}
15+
16+
token = jwt.encode(claims, test, algorithm='RS256')
17+
18+
print token

tests/algorithms/test_EC.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
from jose.jwa import ECAlgorithm
2+
from jose.jwk import ECKey
33
from jose.exceptions import JOSEError
44

55
import ecdsa
@@ -8,7 +8,7 @@
88

99
@pytest.fixture
1010
def alg():
11-
return ECAlgorithm(ECAlgorithm.SHA256)
11+
return ECKey(ECKey.SHA256)
1212

1313
private_key = """-----BEGIN EC PRIVATE KEY-----
1414
MHQCAQEEIIAK499svJugZZfsTsgL2tc7kH/CpzQbkr4g55CEWQyPoAcGBSuBBAAK
@@ -28,10 +28,6 @@ def test_string_secret(self, alg):
2828
with pytest.raises(JOSEError):
2929
alg.prepare_key(key)
3030

31-
def test_string_unicode(self, alg):
32-
unicode_key = private_key.decode('utf-8')
33-
alg.prepare_key(unicode_key)
34-
3531
def test_object(self, alg):
3632
key = object()
3733
with pytest.raises(JOSEError):

tests/algorithms/test_HMAC.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11

2-
from jose.jwa import HMACAlgorithm
2+
from jose.jwk import HMACKey
33
from jose.exceptions import JOSEError
44

55
import pytest
66

77

88
@pytest.fixture
99
def alg():
10-
return HMACAlgorithm(HMACAlgorithm.SHA256)
10+
return HMACKey(HMACKey.SHA256)
1111

1212

1313
class TestHMACAlgorithm:
@@ -16,11 +16,6 @@ def test_non_string_key(self, alg):
1616
with pytest.raises(JOSEError):
1717
alg.prepare_key(object())
1818

19-
def test_unicode_encode(self, alg):
20-
key = u'secret'
21-
prepared_key = alg.prepare_key(key)
22-
assert key == prepared_key
23-
2419
def test_RSA_key(self, alg):
2520
key = "-----BEGIN PUBLIC KEY-----"
2621
with pytest.raises(JOSEError):

tests/algorithms/test_RSA.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
from jose.jwa import RSAAlgorithm
2+
from jose.jwk import RSAKey
33
from jose.exceptions import JOSEError
44

55
from Crypto.PublicKey import RSA
@@ -9,7 +9,7 @@
99

1010
@pytest.fixture
1111
def alg():
12-
return RSAAlgorithm(RSAAlgorithm.SHA256)
12+
return RSAKey(RSAKey.SHA256)
1313

1414

1515
private_key = """-----BEGIN RSA PRIVATE KEY-----
@@ -78,8 +78,6 @@ def test_cookbook_access_token(self, alg):
7878
"nt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI" \
7979
"-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg"
8080

81-
82-
8381
def test_RSA_key(self, alg):
8482
key = RSA.importKey(private_key)
8583
alg.prepare_key(key)
@@ -89,10 +87,6 @@ def test_string_secret(self, alg):
8987
with pytest.raises(JOSEError):
9088
alg.prepare_key(key)
9189

92-
def test_string_unicode(self, alg):
93-
unicode_key = private_key.decode('utf-8')
94-
alg.prepare_key(unicode_key)
95-
9690
def test_object(self, alg):
9791
key = object()
9892
with pytest.raises(JOSEError):

0 commit comments

Comments
 (0)