Skip to content

Commit 4b59bb7

Browse files
author
Gasper Zejn
committed
Move ecdsa python implementation into separate backend. Key.to_pem returns six.binary_type - str on PY2, bytes on PY3.
1 parent fbd122d commit 4b59bb7

File tree

7 files changed

+124
-109
lines changed

7 files changed

+124
-109
lines changed

jose/backends/__init__.py

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

22
try:
3-
from .pycrypto_backend import RSAKey
3+
from jose.backends.pycrypto_backend import RSAKey
44
except ImportError:
5-
from .cryptography_backend import CryptographyRSAKey as RSAKey
5+
from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey
6+
7+
try:
8+
from jose.backends.ecdsa_backend import ECKey
9+
except ImportError:
10+
from jose.backends.cryptography_backend import CryptographyECKey as ECKey

jose/backends/cryptography_backend.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):
5252
return
5353

5454
if isinstance(key, six.string_types):
55-
if isinstance(key, six.text_type):
56-
key = key.encode('utf-8')
55+
key = key.encode('utf-8')
5756

57+
if isinstance(key, six.binary_type):
5858
# Attempt to load key. We don't know if it's
5959
# a Public Key or a Private Key, so we try
6060
# the Public Key first.
@@ -109,13 +109,13 @@ def to_pem(self):
109109
encoding=serialization.Encoding.PEM,
110110
format=serialization.PublicFormat.SubjectPublicKeyInfo
111111
)
112-
return pem.decode('utf-8')
112+
return pem
113113
pem = self.prepared_key.private_bytes(
114114
encoding=serialization.Encoding.PEM,
115115
format=serialization.PrivateFormat.TraditionalOpenSSL,
116116
encryption_algorithm=serialization.NoEncryption()
117117
)
118-
return pem.decode('utf-8')
118+
return pem
119119

120120

121121
class CryptographyRSAKey(Key):
@@ -146,9 +146,9 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):
146146
return
147147

148148
if isinstance(key, six.string_types):
149-
if isinstance(key, six.text_type):
150-
key = key.encode('utf-8')
149+
key = key.encode('utf-8')
151150

151+
if isinstance(key, six.binary_type):
152152
try:
153153
try:
154154
key = load_pem_public_key(key, self.cryptography_backend())
@@ -203,10 +203,10 @@ def to_pem(self):
203203
return self.prepared_key.public_bytes(
204204
encoding=serialization.Encoding.PEM,
205205
format=serialization.PublicFormat.SubjectPublicKeyInfo
206-
).decode('utf-8')
206+
)
207207

208208
return self.prepared_key.private_bytes(
209209
encoding=serialization.Encoding.PEM,
210210
format=serialization.PrivateFormat.TraditionalOpenSSL,
211211
encryption_algorithm=serialization.NoEncryption()
212-
).decode('utf-8')
212+
)

jose/backends/ecdsa_backend.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import hashlib
2+
import six
3+
4+
from jose.backends.base import Key
5+
import ecdsa
6+
7+
from jose.constants import ALGORITHMS
8+
from jose.exceptions import JWKError
9+
from jose.utils import base64_to_long
10+
11+
12+
class ECKey(Key):
13+
"""
14+
Performs signing and verification operations using
15+
ECDSA and the specified hash function
16+
17+
This class requires the ecdsa package to be installed.
18+
19+
This is based off of the implementation in PyJWT 0.3.2
20+
"""
21+
SHA256 = hashlib.sha256
22+
SHA384 = hashlib.sha384
23+
SHA512 = hashlib.sha512
24+
25+
CURVE_MAP = {
26+
SHA256: ecdsa.curves.NIST256p,
27+
SHA384: ecdsa.curves.NIST384p,
28+
SHA512: ecdsa.curves.NIST521p,
29+
}
30+
31+
def __init__(self, key, algorithm):
32+
if algorithm not in ALGORITHMS.EC:
33+
raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm)
34+
35+
self.hash_alg = {
36+
ALGORITHMS.ES256: self.SHA256,
37+
ALGORITHMS.ES384: self.SHA384,
38+
ALGORITHMS.ES512: self.SHA512
39+
}.get(algorithm)
40+
self._algorithm = algorithm
41+
42+
self.curve = self.CURVE_MAP.get(self.hash_alg)
43+
44+
if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
45+
self.prepared_key = key
46+
return
47+
48+
if isinstance(key, dict):
49+
self.prepared_key = self._process_jwk(key)
50+
return
51+
52+
if isinstance(key, six.string_types):
53+
key = key.encode('utf-8')
54+
55+
if isinstance(key, six.binary_type):
56+
# Attempt to load key. We don't know if it's
57+
# a Signing Key or a Verifying Key, so we try
58+
# the Verifying Key first.
59+
try:
60+
key = ecdsa.VerifyingKey.from_pem(key)
61+
except ecdsa.der.UnexpectedDER:
62+
key = ecdsa.SigningKey.from_pem(key)
63+
except Exception as e:
64+
raise JWKError(e)
65+
66+
self.prepared_key = key
67+
return
68+
69+
raise JWKError('Unable to parse an ECKey from key: %s' % key)
70+
71+
def _process_jwk(self, jwk_dict):
72+
if not jwk_dict.get('kty') == 'EC':
73+
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))
74+
75+
x = base64_to_long(jwk_dict.get('x'))
76+
y = base64_to_long(jwk_dict.get('y'))
77+
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))
80+
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)
83+
84+
return verifying_key
85+
86+
def sign(self, msg):
87+
return self.prepared_key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string)
88+
89+
def verify(self, msg, sig):
90+
try:
91+
return self.prepared_key.verify(sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string)
92+
except:
93+
return False
94+
95+
def public_key(self):
96+
if isinstance(self.prepared_key, ecdsa.VerifyingKey):
97+
return self
98+
return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm)
99+
100+
def to_pem(self):
101+
return self.prepared_key.to_pem()

jose/backends/pycrypto_backend.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from Crypto.Util.asn1 import DerSequence
1010

1111
from jose.backends.base import Key
12-
from jose.jwk import base64_to_long
12+
from jose.utils import base64_to_long
1313
from jose.constants import ALGORITHMS
1414
from jose.exceptions import JWKError
1515
from jose.utils import base64url_decode
@@ -55,9 +55,9 @@ def __init__(self, key, algorithm):
5555
return
5656

5757
if isinstance(key, six.string_types):
58-
if isinstance(key, six.text_type):
59-
key = key.encode('utf-8')
58+
key = key.encode('utf-8')
6059

60+
if isinstance(key, six.binary_type):
6161
if key.startswith(b'-----BEGIN CERTIFICATE-----'):
6262
try:
6363
self._process_cert(key)

jose/jwk.py

Lines changed: 2 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
import hmac
44
import six
55

6-
import ecdsa
7-
86
from jose.constants import ALGORITHMS
97
from jose.exceptions import JWKError
10-
from jose.utils import base64url_decode, base64_to_long
8+
from jose.utils import base64url_decode
119
from jose.utils import constant_time_string_compare
1210
from jose.backends.base import Key
13-
from jose.backends import RSAKey
11+
from jose.backends import RSAKey, ECKey
1412

1513

1614
def get_key(algorithm):
@@ -122,90 +120,3 @@ def sign(self, msg):
122120

123121
def verify(self, msg, sig):
124122
return constant_time_string_compare(sig, self.sign(msg))
125-
126-
127-
class ECKey(Key):
128-
"""
129-
Performs signing and verification operations using
130-
ECDSA and the specified hash function
131-
132-
This class requires the ecdsa package to be installed.
133-
134-
This is based off of the implementation in PyJWT 0.3.2
135-
"""
136-
SHA256 = hashlib.sha256
137-
SHA384 = hashlib.sha384
138-
SHA512 = hashlib.sha512
139-
140-
CURVE_MAP = {
141-
SHA256: ecdsa.curves.NIST256p,
142-
SHA384: ecdsa.curves.NIST384p,
143-
SHA512: ecdsa.curves.NIST521p,
144-
}
145-
146-
def __init__(self, key, algorithm):
147-
if algorithm not in ALGORITHMS.EC:
148-
raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm)
149-
self.hash_alg = get_algorithm_object(algorithm)
150-
self._algorithm = algorithm
151-
152-
self.curve = self.CURVE_MAP.get(self.hash_alg)
153-
154-
if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
155-
self.prepared_key = key
156-
return
157-
158-
if isinstance(key, dict):
159-
self.prepared_key = self._process_jwk(key)
160-
return
161-
162-
if isinstance(key, six.string_types):
163-
if isinstance(key, six.text_type):
164-
key = key.encode('utf-8')
165-
166-
# Attempt to load key. We don't know if it's
167-
# a Signing Key or a Verifying Key, so we try
168-
# the Verifying Key first.
169-
try:
170-
key = ecdsa.VerifyingKey.from_pem(key)
171-
except ecdsa.der.UnexpectedDER:
172-
key = ecdsa.SigningKey.from_pem(key)
173-
except Exception as e:
174-
raise JWKError(e)
175-
176-
self.prepared_key = key
177-
return
178-
179-
raise JWKError('Unable to parse an ECKey from key: %s' % key)
180-
181-
def _process_jwk(self, jwk_dict):
182-
if not jwk_dict.get('kty') == 'EC':
183-
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))
184-
185-
x = base64_to_long(jwk_dict.get('x'))
186-
y = base64_to_long(jwk_dict.get('y'))
187-
188-
if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
189-
raise JWKError("Point: %s, %s is not a valid point" % (x, y))
190-
191-
point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order)
192-
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, self.curve)
193-
194-
return verifying_key
195-
196-
def sign(self, msg):
197-
return self.prepared_key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string)
198-
199-
def verify(self, msg, sig):
200-
try:
201-
return self.prepared_key.verify(sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string)
202-
except:
203-
return False
204-
205-
def public_key(self):
206-
if isinstance(self.prepared_key, ecdsa.VerifyingKey):
207-
return self
208-
return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm)
209-
210-
def to_pem(self):
211-
return self.prepared_key.to_pem()

tests/algorithms/test_RSA.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def test_RSA_key_instance(self):
108108

109109
pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256)
110110
pem = pubkey.to_pem()
111-
assert pem.startswith('-----BEGIN PUBLIC KEY-----')
111+
assert pem.startswith(b'-----BEGIN PUBLIC KEY-----')
112112

113113
def test_RSA_jwk(self):
114114
d = {

tests/algorithms/test_cryptography_EC.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import ecdsa
88
import pytest
99

10-
private_key = """-----BEGIN EC PRIVATE KEY-----
10+
private_key = b"""-----BEGIN EC PRIVATE KEY-----
1111
MHQCAQEEIIAK499svJugZZfsTsgL2tc7kH/CpzQbkr4g55CEWQyPoAcGBSuBBAAK
1212
oUQDQgAEsOnVqWVPfjte2nI0Ay3oTZVehCUtH66nJM8z6flUluHxhLG8ZTTCkJAZ
1313
W6xQdXHfqGUy3Dx40NDhgTaM8xAdSw==
@@ -20,8 +20,6 @@ def test_EC_key(self):
2020
key = ecdsa.SigningKey.from_pem(private_key)
2121
k = CryptographyECKey(key, ALGORITHMS.ES256)
2222

23-
print(repr(k.to_pem().strip()))
24-
print(repr(private_key.strip()))
2523
assert k.to_pem().strip() == private_key.strip()
2624
public_pem = k.public_key().to_pem()
2725
public_key = CryptographyECKey(public_pem, ALGORITHMS.ES256)
@@ -42,7 +40,7 @@ def test_cryptography_EC_key(self):
4240

4341
def test_signing_parity(self):
4442
key1 = ECKey(private_key, ALGORITHMS.ES256)
45-
public_key = key1.prepared_key.get_verifying_key().to_pem().decode('utf-8')
43+
public_key = key1.public_key().to_pem()
4644
vkey1 = ECKey(public_key, ALGORITHMS.ES256)
4745
key2 = CryptographyECKey(private_key, ALGORITHMS.ES256)
4846
vkey2 = CryptographyECKey(public_key, ALGORITHMS.ES256)

0 commit comments

Comments
 (0)