4
4
import json
5
5
from time import time
6
6
7
+ # Optionally depend on https://github.com/dmazzella/ucryptography
8
+ try :
9
+ # Try importing from ucryptography port.
10
+ import cryptography
11
+ from cryptography import hashes , ec , serialization , utils
12
+
13
+ _ec_supported = True
14
+ except ImportError :
15
+ # No cryptography library available, no EC256 support.
16
+ _ec_supported = False
17
+
7
18
8
19
def _to_b64url (data ):
9
20
return (
@@ -19,6 +30,28 @@ def _from_b64url(data):
19
30
return binascii .a2b_base64 (data .replace (b"-" , b"+" ).replace (b"_" , b"/" ) + b"===" )
20
31
21
32
33
+ def _sig_der_to_jws (signed ):
34
+ """Accept a DER signature and convert to JSON Web Signature bytes.
35
+
36
+ `cryptography` produces signatures encoded in DER ASN.1 binary format.
37
+ JSON Web Algorithm instead encodes the signature as the point coordinates
38
+ as bigendian byte strings concatenated.
39
+
40
+ See https://datatracker.ietf.org/doc/html/rfc7518#section-3.4
41
+ """
42
+ r , s = utils .decode_dss_signature (signed )
43
+ return r .to_bytes (32 , "big" ) + s .to_bytes (32 , "big" )
44
+
45
+
46
+ def _sig_jws_to_der (signed ):
47
+ """Accept a JSON Web Signature and convert to a DER signature.
48
+
49
+ See `_sig_der_to_jws()`
50
+ """
51
+ r , s = int .from_bytes (signed [0 :32 ], "big" ), int .from_bytes (signed [32 :], "big" )
52
+ return utils .encode_dss_signature (r , s )
53
+
54
+
22
55
class exceptions :
23
56
class PyJWTError (Exception ):
24
57
pass
@@ -37,19 +70,32 @@ class ExpiredSignatureError(PyJWTError):
37
70
38
71
39
72
def encode (payload , key , algorithm = "HS256" ):
40
- if algorithm != "HS256" :
73
+ if algorithm != "HS256" and algorithm != "ES256" :
41
74
raise exceptions .InvalidAlgorithmError
42
75
43
- if isinstance (key , str ):
44
- key = key .encode ()
45
76
header = _to_b64url (json .dumps ({"typ" : "JWT" , "alg" : algorithm }).encode ())
46
77
payload = _to_b64url (json .dumps (payload ).encode ())
47
- signature = _to_b64url (hmac .new (key , header + b"." + payload , hashlib .sha256 ).digest ())
78
+
79
+ if algorithm == "HS256" :
80
+ if isinstance (key , str ):
81
+ key = key .encode ()
82
+ signature = _to_b64url (hmac .new (key , header + b"." + payload , hashlib .sha256 ).digest ())
83
+ elif algorithm == "ES256" :
84
+ if not _ec_supported :
85
+ raise exceptions .InvalidAlgorithmError (
86
+ "Required dependencies for ES256 are not available"
87
+ )
88
+ if isinstance (key , int ):
89
+ key = ec .derive_private_key (key , ec .SECP256R1 ())
90
+ signature = _to_b64url (
91
+ _sig_der_to_jws (key .sign (header + b"." + payload , ec .ECDSA (hashes .SHA256 ())))
92
+ )
93
+
48
94
return (header + b"." + payload + b"." + signature ).decode ()
49
95
50
96
51
- def decode (token , key , algorithms = ["HS256" ]):
52
- if "HS256" not in algorithms :
97
+ def decode (token , key , algorithms = ["HS256" , "ES256" ]):
98
+ if "HS256" not in algorithms and "ES256" not in algorithms :
53
99
raise exceptions .InvalidAlgorithmError
54
100
55
101
parts = token .encode ().split (b"." )
@@ -63,14 +109,31 @@ def decode(token, key, algorithms=["HS256"]):
63
109
except Exception :
64
110
raise exceptions .InvalidTokenError
65
111
66
- if header ["alg" ] not in algorithms or header ["alg" ] != "HS256" :
112
+ if header ["alg" ] not in algorithms or ( header ["alg" ] != "HS256" and header [ "alg" ] != "ES256" ) :
67
113
raise exceptions .InvalidAlgorithmError
68
114
69
- if isinstance (key , str ):
70
- key = key .encode ()
71
- calculated_signature = hmac .new (key , parts [0 ] + b"." + parts [1 ], hashlib .sha256 ).digest ()
72
- if signature != calculated_signature :
73
- raise exceptions .InvalidSignatureError
115
+ if header ["alg" ] == "HS256" :
116
+ if isinstance (key , str ):
117
+ key = key .encode ()
118
+ calculated_signature = hmac .new (key , parts [0 ] + b"." + parts [1 ], hashlib .sha256 ).digest ()
119
+ if signature != calculated_signature :
120
+ raise exceptions .InvalidSignatureError
121
+ elif header ["alg" ] == "ES256" :
122
+ if not _ec_supported :
123
+ raise exceptions .InvalidAlgorithmError (
124
+ "Required dependencies for ES256 are not available"
125
+ )
126
+
127
+ if isinstance (key , bytes ):
128
+ key = ec .EllipticCurvePublicKey .from_encoded_point (key , ec .SECP256R1 ())
129
+ try :
130
+ key .verify (
131
+ _sig_jws_to_der (signature ),
132
+ parts [0 ] + b"." + parts [1 ],
133
+ ec .ECDSA (hashes .SHA256 ()),
134
+ )
135
+ except cryptography .exceptions .InvalidSignature :
136
+ raise exceptions .InvalidSignatureError
74
137
75
138
if "exp" in payload :
76
139
if time () > payload ["exp" ]:
0 commit comments