Skip to content

Commit a41ba4c

Browse files
author
Michael Davis
committed
Add ECDSA support
1 parent 2c2bdd7 commit a41ba4c

File tree

10 files changed

+180
-15
lines changed

10 files changed

+180
-15
lines changed

ecdsa.pem

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MIHcAgEBBEIBzs13YUnYbLfYXTz4SG4DE4rPmsL3wBTdy34JcO+BDpI+NDZ0pqam
3+
UM/1sGZT+8hqUjSeQo6oz+Mx0VS6SJh31zygBwYFK4EEACOhgYkDgYYABACYencK
4+
8pm/iAeDVptaEZTZwNT0yW/muVwvvwkzS/D6GDCLsnLfI6e1FwEnTJF/GPFUlN5l
5+
9JSLxsbbFdM1muI+NgBE6ZLR1GZWjsNzu7BOB8RMy/mvSTokZwyIaWvWSn3hOF4i
6+
/4iczJnzJhUKDqHe5dJ//PLd7R3WVHxkvv7jFNTKYg==
7+
-----END EC PRIVATE KEY-----

ecdsa.pub

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAmHp3CvKZv4gHg1abWhGU2cDU9Mlv
3+
5rlcL78JM0vw+hgwi7Jy3yOntRcBJ0yRfxjxVJTeZfSUi8bG2xXTNZriPjYAROmS
4+
0dRmVo7Dc7uwTgfETMv5r0k6JGcMiGlr1kp94TheIv+InMyZ8yYVCg6h3uXSf/zy
5+
3e0d1lR8ZL7+4xTUymI=
6+
-----END PUBLIC KEY-----

jose/__init__.py

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

2-
__version__ = "0.1.8"
2+
__version__ = "0.2.0"
33
__author__ = 'Michael Davis'
44
__license__ = 'MIT'
55
__copyright__ = 'Copyright 2015 Michael Davis'

jose/algorithms/EC.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
import hashlib
3+
4+
import ecdsa
5+
6+
from .base import Algorithm
7+
from six import string_types, text_type
8+
9+
10+
class ECAlgorithm(Algorithm):
11+
"""
12+
Performs signing and verification operations using
13+
ECDSA and the specified hash function
14+
15+
This class requires the ecdsa package to be installed.
16+
17+
This is based off of the implementation in PyJWT 0.3.2
18+
"""
19+
SHA256 = hashlib.sha256
20+
SHA384 = hashlib.sha384
21+
SHA512 = hashlib.sha512
22+
23+
def __init__(self, hash_alg):
24+
self.hash_alg = hash_alg
25+
26+
def prepare_key(self, key):
27+
28+
if isinstance(key, ecdsa.SigningKey) or \
29+
isinstance(key, ecdsa.VerifyingKey):
30+
return key
31+
32+
if isinstance(key, string_types):
33+
if isinstance(key, text_type):
34+
key = key.encode('utf-8')
35+
36+
# Attempt to load key. We don't know if it's
37+
# a Signing Key or a Verifying Key, so we try
38+
# the Verifying Key first.
39+
try:
40+
key = ecdsa.VerifyingKey.from_pem(key)
41+
except ecdsa.der.UnexpectedDER:
42+
key = ecdsa.SigningKey.from_pem(key)
43+
44+
else:
45+
raise TypeError('Expecting a PEM-formatted key.')
46+
47+
return key
48+
49+
def sign(self, msg, key):
50+
return key.sign(msg, hashfunc=self.hash_alg,
51+
sigencode=ecdsa.util.sigencode_der)
52+
53+
def verify(self, msg, key, sig):
54+
try:
55+
return key.verify(sig, msg, hashfunc=self.hash_alg,
56+
sigdecode=ecdsa.util.sigdecode_der)
57+
except ecdsa.der.UnexpectedDER:
58+
return False

jose/algorithms/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .HMAC import HMACAlgorithm
66
from .RSA import RSAAlgorithm
7+
from .EC import ECAlgorithm
78

89

910
def get_algorithm_object(algorithm):
@@ -29,4 +30,13 @@ def get_algorithm_object(algorithm):
2930
if algorithm == ALGORITHMS.RS512:
3031
return RSAAlgorithm(RSAAlgorithm.SHA512)
3132

33+
if algorithm == ALGORITHMS.ES256:
34+
return ECAlgorithm(ECAlgorithm.SHA256)
35+
36+
if algorithm == ALGORITHMS.ES384:
37+
return ECAlgorithm(ECAlgorithm.SHA384)
38+
39+
if algorithm == ALGORITHMS.ES512:
40+
return ECAlgorithm(ECAlgorithm.SHA512)
41+
3242
raise JWSError('Algorithm not supported: %s' % algorithm)

jose/algorithms/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ def prepare_key(self, key):
88
"""
99
Performs necessary validation and conversions on the key and returns
1010
the key value in the proper format for sign() and verify().
11+
12+
Raises:
13+
TypeError: If an invalid key is attempted to be used.
1114
"""
1215
raise NotImplementedError
1316

jose/constants.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ class ALGORITHMS(object):
88
RS256 = 'RS256'
99
RS384 = 'RS384'
1010
RS512 = 'RS512'
11+
ES256 = 'ES256'
12+
ES384 = 'ES384'
13+
ES512 = 'ES512'
1114

1215
HMAC = (HS256, HS384, HS512)
1316
RSA = (RS256, RS384, RS512)
17+
EC = (ES256, ES384, ES512)
1418

15-
SUPPORTED = (HS256, HS384, HS512, RS256, RS384, RS512)
19+
SUPPORTED = HMAC + RSA + EC
1620

17-
ALL = (NONE, HS256, HS384, HS512, RS256, RS384, RS512)
21+
ALL = SUPPORTED + (NONE, )

jose/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ class JWSError(JOSEError):
88
pass
99

1010

11+
class JWSAlgorithmError(JWSError):
12+
pass
13+
14+
1115
class JWTError(JOSEError):
1216
pass
1317

tests/algorithms/test_EC.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
3+
from jose.algorithms import ECAlgorithm
4+
5+
import ecdsa
6+
import pytest
7+
8+
9+
@pytest.fixture
10+
def alg():
11+
return ECAlgorithm(ECAlgorithm.SHA256)
12+
13+
private_key = """-----BEGIN EC PRIVATE KEY-----
14+
MHQCAQEEIIAK499svJugZZfsTsgL2tc7kH/CpzQbkr4g55CEWQyPoAcGBSuBBAAK
15+
oUQDQgAEsOnVqWVPfjte2nI0Ay3oTZVehCUtH66nJM8z6flUluHxhLG8ZTTCkJAZ
16+
W6xQdXHfqGUy3Dx40NDhgTaM8xAdSw==
17+
-----END EC PRIVATE KEY-----"""
18+
19+
20+
class TestECAlgorithm:
21+
22+
def test_EC_key(self, alg):
23+
key = ecdsa.SigningKey.from_pem(private_key)
24+
alg.prepare_key(key)
25+
26+
def test_string_secret(self, alg):
27+
key = 'secret'
28+
with pytest.raises(TypeError):
29+
alg.prepare_key(key)
30+
31+
def test_string_unicode(self, alg):
32+
unicode_key = private_key.decode('utf-8')
33+
alg.prepare_key(unicode_key)
34+
35+
def test_object(self, alg):
36+
key = object()
37+
with pytest.raises(Exception):
38+
alg.prepare_key(key)

tests/test_jws.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def test_add_headers(self, claims):
9696
assert expected_headers == header
9797

9898

99-
private_key = """-----BEGIN RSA PRIVATE KEY-----
99+
rsa_private_key = """-----BEGIN RSA PRIVATE KEY-----
100100
MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7
101101
4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX
102102
ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M
@@ -148,7 +148,7 @@ def test_add_headers(self, claims):
148148
mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8=
149149
-----END RSA PRIVATE KEY-----"""
150150

151-
public_key = """-----BEGIN PUBLIC KEY-----
151+
rsa_public_key = """-----BEGIN PUBLIC KEY-----
152152
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtSKfSeI0fukRIX38AHlK
153153
B1YPpX8PUYN2JdvfM+XjNmLfU1M74N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/K
154154
gBZggAlS9Y0Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy
@@ -167,26 +167,61 @@ def test_add_headers(self, claims):
167167
class TestRSA:
168168

169169
def test_RSA256(self, claims):
170-
token = jws.sign(claims, private_key, algorithm=ALGORITHMS.RS256)
171-
assert jws.verify(token, public_key, ALGORITHMS.RS256) == claims
170+
token = jws.sign(claims, rsa_private_key, algorithm=ALGORITHMS.RS256)
171+
assert jws.verify(token, rsa_public_key, ALGORITHMS.RS256) == claims
172172

173173
def test_RSA384(self, claims):
174-
token = jws.sign(claims, private_key, algorithm=ALGORITHMS.RS384)
175-
assert jws.verify(token, public_key, ALGORITHMS.RS384) == claims
174+
token = jws.sign(claims, rsa_private_key, algorithm=ALGORITHMS.RS384)
175+
assert jws.verify(token, rsa_public_key, ALGORITHMS.RS384) == claims
176176

177177
def test_RSA512(self, claims):
178-
token = jws.sign(claims, private_key, algorithm=ALGORITHMS.RS512)
179-
assert jws.verify(token, public_key, ALGORITHMS.RS512) == claims
178+
token = jws.sign(claims, rsa_private_key, algorithm=ALGORITHMS.RS512)
179+
assert jws.verify(token, rsa_public_key, ALGORITHMS.RS512) == claims
180180

181181
def test_wrong_alg(self, claims):
182-
token = jws.sign(claims, private_key, algorithm=ALGORITHMS.RS256)
182+
token = jws.sign(claims, rsa_private_key, algorithm=ALGORITHMS.RS256)
183183
with pytest.raises(JWSError):
184-
jws.verify(token, public_key, ALGORITHMS.RS384)
184+
jws.verify(token, rsa_public_key, ALGORITHMS.RS384)
185185

186186
def test_wrong_key(self, claims):
187-
token = jws.sign(claims, private_key, algorithm=ALGORITHMS.RS256)
187+
token = jws.sign(claims, rsa_private_key, algorithm=ALGORITHMS.RS256)
188188
with pytest.raises(JWSError):
189-
jws.verify(token, public_key, ALGORITHMS.HS256)
189+
jws.verify(token, rsa_public_key, ALGORITHMS.HS256)
190+
191+
ec_private_key = """-----BEGIN EC PRIVATE KEY-----
192+
MIHcAgEBBEIBzs13YUnYbLfYXTz4SG4DE4rPmsL3wBTdy34JcO+BDpI+NDZ0pqam
193+
UM/1sGZT+8hqUjSeQo6oz+Mx0VS6SJh31zygBwYFK4EEACOhgYkDgYYABACYencK
194+
8pm/iAeDVptaEZTZwNT0yW/muVwvvwkzS/D6GDCLsnLfI6e1FwEnTJF/GPFUlN5l
195+
9JSLxsbbFdM1muI+NgBE6ZLR1GZWjsNzu7BOB8RMy/mvSTokZwyIaWvWSn3hOF4i
196+
/4iczJnzJhUKDqHe5dJ//PLd7R3WVHxkvv7jFNTKYg==
197+
-----END EC PRIVATE KEY-----"""
198+
199+
ec_public_key = """-----BEGIN PUBLIC KEY-----
200+
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAmHp3CvKZv4gHg1abWhGU2cDU9Mlv
201+
5rlcL78JM0vw+hgwi7Jy3yOntRcBJ0yRfxjxVJTeZfSUi8bG2xXTNZriPjYAROmS
202+
0dRmVo7Dc7uwTgfETMv5r0k6JGcMiGlr1kp94TheIv+InMyZ8yYVCg6h3uXSf/zy
203+
3e0d1lR8ZL7+4xTUymI=
204+
-----END PUBLIC KEY-----"""
205+
206+
207+
class TestEC:
208+
209+
def test_EC256(self, claims):
210+
token = jws.sign(claims, ec_private_key, algorithm=ALGORITHMS.ES256)
211+
assert jws.verify(token, ec_public_key, ALGORITHMS.ES256) == claims
212+
213+
def test_EC384(self, claims):
214+
token = jws.sign(claims, ec_private_key, algorithm=ALGORITHMS.ES384)
215+
assert jws.verify(token, ec_public_key, ALGORITHMS.ES384) == claims
216+
217+
def test_EC512(self, claims):
218+
token = jws.sign(claims, ec_private_key, algorithm=ALGORITHMS.ES512)
219+
assert jws.verify(token, ec_public_key, ALGORITHMS.ES512) == claims
220+
221+
def test_wrong_alg(self, claims):
222+
token = jws.sign(claims, ec_private_key, algorithm=ALGORITHMS.ES256)
223+
with pytest.raises(JWSError):
224+
jws.verify(token, rsa_public_key, ALGORITHMS.ES384)
190225

191226

192227
class TestLoad:

0 commit comments

Comments
 (0)