File: jwt.py

package info (click to toggle)
python-py-vapid 1.9.2-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 236 kB
  • sloc: python: 687; makefile: 2
file content (87 lines) | stat: -rw-r--r-- 2,547 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import binascii
import json

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

from py_vapid.utils import b64urldecode, b64urlencode, num_to_bytes


def extract_signature(auth):
    """Extracts the payload and signature from a JWT, converting from RFC7518
    to RFC 3279

    :param auth: A JWT Authorization Token.
    :type auth: str

    :return tuple containing the signature material and signature

    """
    payload, asig = auth.encode('utf8').rsplit(b'.', 1)
    sig = b64urldecode(asig)
    if len(sig) != 64:
        raise InvalidSignature()

    encoded = utils.encode_dss_signature(
        s=int(binascii.hexlify(sig[32:]), 16),
        r=int(binascii.hexlify(sig[:32]), 16)
    )
    return payload, encoded


def decode(token, key):
    """Decode a web token into an assertion dictionary

    :param token: VAPID auth token
    :type token: str
    :param key: bitarray containing the public key
    :type key: str

    :return dict of the VAPID claims

    :raise InvalidSignature

    """
    try:
        sig_material, signature = extract_signature(token)
        dkey = b64urldecode(key.encode('utf8'))
        pkey = ec.EllipticCurvePublicKey.from_encoded_point(
            ec.SECP256R1(),
            dkey,
        )
        pkey.verify(
            signature,
            sig_material,
            ec.ECDSA(hashes.SHA256())
        )
        return json.loads(
            b64urldecode(sig_material.split(b'.')[1]).decode('utf8')
        )
    except InvalidSignature:
        raise
    except(ValueError, TypeError, binascii.Error):
        raise InvalidSignature()


def sign(claims, key):
    """Sign the claims

    :param claims: list of JWS claims
    :type claims: dict
    :param key: Private key for signing
    :type key: ec.EllipticCurvePrivateKey
    :param algorithm: JWT "alg" descriptor
    :type algorithm: str

    """
    header = b64urlencode(b"""{"typ":"JWT","alg":"ES256"}""")
    # Unfortunately, chrome seems to require the claims to be sorted.
    claims = b64urlencode(json.dumps(claims,
                                     separators=(',', ':'),
                                     sort_keys=True).encode('utf8'))
    token = "{}.{}".format(header, claims)
    rsig = key.sign(token.encode('utf8'), ec.ECDSA(hashes.SHA256()))
    (r, s) = utils.decode_dss_signature(rsig)
    sig = b64urlencode(num_to_bytes(r, 32) + num_to_bytes(s, 32))
    return "{}.{}".format(token, sig)