Skip to content

Commit

Permalink
Convert JWT signatures to the correct format.
Browse files Browse the repository at this point in the history
`key.sign` returns an ASN.1-encoded, DER-formatted signature, but
RFC 7518, section 3.4 says we should include R and S directly, in that
order, and in big-endian form.
  • Loading branch information
Kit Cambridge committed May 7, 2017
1 parent bddefbe commit 5eba0bd
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 31 deletions.
16 changes: 8 additions & 8 deletions python/py_vapid/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.primitives import hashes

from py_vapid.utils import b64urldecode, b64urlencode
from py_vapid.utils import b64urldecode, b64urlencode, num_to_bytes


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

encoded = utils.encode_dss_signature(
s=int(binascii.hexlify(sig[32:]), 16),
Expand All @@ -35,8 +36,6 @@ def extract_signature(auth):
def decode(token, key):
"""Decode a web token into an assertion dictionary
This attempts to rectify both ecdsa and openssl generated signatures.
:param token: VAPID auth token
:type token: str
:param key: bitarray containing the public key
Expand Down Expand Up @@ -84,5 +83,6 @@ def sign(claims, key):
separators=(',', ':')).encode('utf8'))
token = "{}.{}".format(header, claims)
rsig = key.sign(token.encode('utf8'), ec.ECDSA(hashes.SHA256()))
sig = b64urlencode(rsig)
(r, s) = utils.decode_dss_signature(rsig)
sig = b64urlencode(num_to_bytes(r) + num_to_bytes(s))
return "{}.{}".format(token, sig)
24 changes: 1 addition & 23 deletions python/py_vapid/tests/test_vapid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
from nose.tools import eq_, ok_
from mock import patch, Mock

from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes

from py_vapid import Vapid01, Vapid02, VapidException
from py_vapid.jwt import decode
from py_vapid.utils import b64urldecode

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

def test_alt_sign(self):
"""ecdsa uses a raw key pair to sign, openssl uses a DER."""
v = Vapid01.from_file("/tmp/private")
claims = {"aud": "https://example.com",
"sub": "mailto:[email protected]",
"foo": "extra value"}
# Get a signed token.
result = v.sign(claims)
# Convert the dss into raw.
auth, sig = result.get('Authorization').split(' ')[1].rsplit('.', 1)
ss = utils.decode_dss_signature(b64urldecode(sig.encode('utf8')))
new_sig = binascii.b2a_base64(
binascii.unhexlify("%064x%064x" % ss)
).strip().strip(b'=').decode()
new_auth = auth + '.' + new_sig
# phew, all that done, now check
pkey = result.get("Crypto-Key").split('=')[1]
items = decode(new_auth, pkey)

eq_(items, claims)

def test_bad_sign(self):
v = Vapid01.from_file("/tmp/private")
self.assertRaises(VapidException,
Expand Down
14 changes: 14 additions & 0 deletions python/py_vapid/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
import binascii


def b64urldecode(data):
Expand All @@ -23,3 +24,16 @@ def b64urlencode(data):
"""
return base64.urlsafe_b64encode(data).replace(b'=', b'').decode('utf8')


def num_to_bytes(n):
"""Returns the byte representation of an integer, in big-endian order.
:param n: The integer to encode.
:type n: int
:returns bytes
"""
h = '%x' % n
return binascii.unhexlify('0' * (len(h) % 2) + h)

0 comments on commit 5eba0bd

Please sign in to comment.