Skip to content

Commit 0a49b84

Browse files
authored
Merge pull request #44 from kitcambridge/kit/sigform
Convert JWT signatures to the correct format
2 parents bddefbe + 5eba0bd commit 0a49b84

File tree

3 files changed

+23
-31
lines changed

3 files changed

+23
-31
lines changed

python/py_vapid/jwt.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
from cryptography.hazmat.primitives.asymmetric import ec, utils
77
from cryptography.hazmat.primitives import hashes
88

9-
from py_vapid.utils import b64urldecode, b64urlencode
9+
from py_vapid.utils import b64urldecode, b64urlencode, num_to_bytes
1010

1111

1212
def extract_signature(auth):
13-
"""Fix the JWT auth token
14-
15-
convert a ecdsa integer pair into an OpenSSL DER pair.
13+
"""
14+
Extracts the payload and signature from a JWT, converting the
15+
signature from the JOSE format (RFC 7518, section 3.4) to the
16+
RFC 3279 format that Cryptography requires.
1617
1718
:param auth: A JWT Authorization Token.
1819
:type auth: str
@@ -23,7 +24,7 @@ def extract_signature(auth):
2324
payload, asig = auth.encode('utf8').rsplit(b'.', 1)
2425
sig = b64urldecode(asig)
2526
if len(sig) != 64:
26-
return payload, sig
27+
raise InvalidSignature()
2728

2829
encoded = utils.encode_dss_signature(
2930
s=int(binascii.hexlify(sig[32:]), 16),
@@ -35,8 +36,6 @@ def extract_signature(auth):
3536
def decode(token, key):
3637
"""Decode a web token into an assertion dictionary
3738
38-
This attempts to rectify both ecdsa and openssl generated signatures.
39-
4039
:param token: VAPID auth token
4140
:type token: str
4241
:param key: bitarray containing the public key
@@ -84,5 +83,6 @@ def sign(claims, key):
8483
separators=(',', ':')).encode('utf8'))
8584
token = "{}.{}".format(header, claims)
8685
rsig = key.sign(token.encode('utf8'), ec.ECDSA(hashes.SHA256()))
87-
sig = b64urlencode(rsig)
86+
(r, s) = utils.decode_dss_signature(rsig)
87+
sig = b64urlencode(num_to_bytes(r) + num_to_bytes(s))
8888
return "{}.{}".format(token, sig)

python/py_vapid/tests/test_vapid.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
from nose.tools import eq_, ok_
77
from mock import patch, Mock
88

9-
from cryptography.hazmat.primitives.asymmetric import ec, utils
9+
from cryptography.hazmat.primitives.asymmetric import ec
1010
from cryptography.hazmat.primitives import hashes
1111

1212
from py_vapid import Vapid01, Vapid02, VapidException
1313
from py_vapid.jwt import decode
14-
from py_vapid.utils import b64urldecode
1514

1615
# This is a private key in DER form.
1716
T_DER = """
@@ -174,27 +173,6 @@ def test_sign_02(self):
174173
for k in claims:
175174
eq_(t_val[k], claims[k])
176175

177-
def test_alt_sign(self):
178-
"""ecdsa uses a raw key pair to sign, openssl uses a DER."""
179-
v = Vapid01.from_file("/tmp/private")
180-
claims = {"aud": "https://example.com",
181-
"sub": "mailto:[email protected]",
182-
"foo": "extra value"}
183-
# Get a signed token.
184-
result = v.sign(claims)
185-
# Convert the dss into raw.
186-
auth, sig = result.get('Authorization').split(' ')[1].rsplit('.', 1)
187-
ss = utils.decode_dss_signature(b64urldecode(sig.encode('utf8')))
188-
new_sig = binascii.b2a_base64(
189-
binascii.unhexlify("%064x%064x" % ss)
190-
).strip().strip(b'=').decode()
191-
new_auth = auth + '.' + new_sig
192-
# phew, all that done, now check
193-
pkey = result.get("Crypto-Key").split('=')[1]
194-
items = decode(new_auth, pkey)
195-
196-
eq_(items, claims)
197-
198176
def test_bad_sign(self):
199177
v = Vapid01.from_file("/tmp/private")
200178
self.assertRaises(VapidException,

python/py_vapid/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import base64
2+
import binascii
23

34

45
def b64urldecode(data):
@@ -23,3 +24,16 @@ def b64urlencode(data):
2324
2425
"""
2526
return base64.urlsafe_b64encode(data).replace(b'=', b'').decode('utf8')
27+
28+
29+
def num_to_bytes(n):
30+
"""Returns the byte representation of an integer, in big-endian order.
31+
32+
:param n: The integer to encode.
33+
:type n: int
34+
35+
:returns bytes
36+
37+
"""
38+
h = '%x' % n
39+
return binascii.unhexlify('0' * (len(h) % 2) + h)

0 commit comments

Comments
 (0)