Skip to content

Commit 01b7fb0

Browse files
committed
Fix the ECDSA signature serialization format when using cryptography
1 parent b8771db commit 01b7fb0

File tree

5 files changed

+64
-20
lines changed

5 files changed

+64
-20
lines changed

jwt/algorithms.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from .compat import constant_time_compare, string_types, text_type
55
from .exceptions import InvalidKeyError
6+
from .utils import raw_to_der_signature, der_to_raw_signature
67

78
try:
89
from cryptography.hazmat.primitives import hashes
@@ -233,10 +234,17 @@ def sign(self, msg, key):
233234
signer = key.signer(ec.ECDSA(self.hash_alg()))
234235

235236
signer.update(msg)
236-
return signer.finalize()
237+
der_sig = signer.finalize()
238+
239+
return der_to_raw_signature(der_sig, key.curve)
237240

238241
def verify(self, msg, key, sig):
239-
verifier = key.verifier(sig, ec.ECDSA(self.hash_alg()))
242+
try:
243+
der_sig = raw_to_der_signature(sig, key.curve)
244+
except ValueError:
245+
return False
246+
247+
verifier = key.verifier(der_sig, ec.ECDSA(self.hash_alg()))
240248

241249
verifier.update(msg)
242250

jwt/utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
import base64
2+
import binascii
3+
4+
try:
5+
from cryptography.hazmat.primitives.asymmetric.utils import (
6+
decode_rfc6979_signature, encode_rfc6979_signature
7+
)
8+
except ImportError:
9+
pass
210

311

412
def base64url_decode(input):
@@ -25,3 +33,35 @@ def merge_dict(original, updates):
2533
raise TypeError('original and updates must be a dictionary: %s' % e)
2634

2735
return merged_options
36+
37+
38+
def number_to_bytes(num, num_bytes):
39+
padded_hex = '%0*x' % (2 * num_bytes, num)
40+
big_endian = binascii.a2b_hex(padded_hex.encode('ascii'))
41+
return big_endian
42+
43+
44+
def bytes_to_number(string):
45+
return int(binascii.b2a_hex(string), 16)
46+
47+
48+
def der_to_raw_signature(der_sig, curve):
49+
num_bits = curve.key_size
50+
num_bytes = (num_bits + 7) / 8
51+
52+
r, s = decode_rfc6979_signature(der_sig)
53+
54+
return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes)
55+
56+
57+
def raw_to_der_signature(raw_sig, curve):
58+
num_bits = curve.key_size
59+
num_bytes = (num_bits + 7) / 8
60+
61+
if len(raw_sig) != 2 * num_bytes:
62+
raise ValueError('Invalid signature')
63+
64+
r = bytes_to_number(raw_sig[:num_bytes])
65+
s = bytes_to_number(raw_sig[num_bytes:])
66+
67+
return encode_rfc6979_signature(r, s)

tests/test_algorithms.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,9 @@ def test_ec_verify_should_return_false_if_signature_invalid(self):
171171

172172
# Mess up the signature by replacing a known byte
173173
sig = base64.b64decode(ensure_bytes(
174-
'MIGIAkIB9vYz+inBL8aOTA4auYz/zVuig7TT1bQgKROIQX9YpViHkFa4DT5'
175-
'5FuFKn9XzVlk90p6ldEj42DC9YecXHbC2t+cCQgCicY+8f3f/KCNtWK7cif'
176-
'6vdsVwm6Lrjs0Ag6ZqCf+olN11hVt1qKBC4lXppqB1gNWEmNQaiz1z2QRyc'
177-
'zJ8hSJmbw=='.replace('r', 's')))
174+
'AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M'
175+
'mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw'
176+
'LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65'.replace('r', 's')))
178177

179178
with open(key_path('testkey_ec.pub'), 'r') as keyfile:
180179
pub_key = algo.prepare_key(keyfile.read())
@@ -189,10 +188,9 @@ def test_ec_verify_should_return_true_if_signature_valid(self):
189188
message = ensure_bytes('Hello World!')
190189

191190
sig = base64.b64decode(ensure_bytes(
192-
'MIGIAkIB9vYz+inBL8aOTA4auYz/zVuig7TT1bQgKROIQX9YpViHkFa4DT5'
193-
'5FuFKn9XzVlk90p6ldEj42DC9YecXHbC2t+cCQgCicY+8f3f/KCNtWK7cif'
194-
'6vdsVwm6Lrjs0Ag6ZqCf+olN11hVt1qKBC4lXppqB1gNWEmNQaiz1z2QRyc'
195-
'zJ8hSJmbw=='))
191+
'AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M'
192+
'mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw'
193+
'LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65'))
196194

197195
with open(key_path('testkey_ec.pub'), 'r') as keyfile:
198196
pub_key = algo.prepare_key(keyfile.read())

tests/test_api_jws.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,10 @@ def test_decodes_valid_es384_jws(self, jws):
186186
example_jws = (
187187
b'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9'
188188
b'.eyJoZWxsbyI6IndvcmxkIn0'
189-
b'.MIGHAkEdh2kR7IRu5w0tGuY6Xz3Vqa7PHHY2DgXWeee'
190-
b'LXotEqpn9udp2NfVL-XFG0TDoCakzXbIGAWg42S69GFl'
191-
b'KZzxhXAJCAPLPuJoKyAixFnXPBkvkti-UzSIj4s6DePe'
192-
b'uTu7102G_QIXiijY5bx6mdmZa3xUuKeu-zobOIOqR8Zw'
193-
b'FqGjBLZum')
189+
b'.AGtlemKghaIaYh1yeeekFH9fRuNY7hCaw5hUgZ5aG1N'
190+
b'2F8FIbiKLaZKr8SiFdTimXFVTEmxpBQ9sRmdsDsnrM-1'
191+
b'HAG0_zxxu0JyINOFT2iqF3URYl9HZ8kZWMeZAtXmn6Cw'
192+
b'PXRJD2f7N-f7bJ5JeL9VT5beI2XD3FlK3GgRvI-eE-2Ik')
194193
decoded_payload = jws.decode(example_jws, example_pubkey)
195194
json_payload = json.loads(ensure_unicode(decoded_payload))
196195

tests/test_api_jwt.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,10 @@ def test_decodes_valid_es384_jwt(self, jwt):
190190
example_jwt = (
191191
b'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9'
192192
b'.eyJoZWxsbyI6IndvcmxkIn0'
193-
b'.MIGHAkEdh2kR7IRu5w0tGuY6Xz3Vqa7PHHY2DgXWeee'
194-
b'LXotEqpn9udp2NfVL-XFG0TDoCakzXbIGAWg42S69GFl'
195-
b'KZzxhXAJCAPLPuJoKyAixFnXPBkvkti-UzSIj4s6DePe'
196-
b'uTu7102G_QIXiijY5bx6mdmZa3xUuKeu-zobOIOqR8Zw'
197-
b'FqGjBLZum')
193+
b'.AddMgkmRhzqptDYqlmy_f2dzM6O9YZmVo-txs_CeAJD'
194+
b'NoD8LN7YiPeLmtIhkO5_VZeHHKvtQcGc4lsq-Y72c4dK'
195+
b'pANr1f6HEYhjpBc03u_bv06PYMcr5N2-9k97-qf-JCSb'
196+
b'zqW6R250Q7gNCX5R7NrCl7MTM4DTBZkGbUlqsFUleiGlj')
198197
decoded_payload = jwt.decode(example_jwt, example_pubkey)
199198

200199
assert decoded_payload == example_payload

0 commit comments

Comments
 (0)