OLD | NEW |
(Empty) | |
| 1 """JSON Web Token""" |
| 2 import base64 |
| 3 import logging |
| 4 import re |
| 5 import struct |
| 6 import six |
| 7 |
| 8 try: |
| 9 from builtins import zip |
| 10 from builtins import hex |
| 11 from builtins import str |
| 12 except ImportError: |
| 13 pass |
| 14 |
| 15 from binascii import unhexlify |
| 16 |
| 17 __version__ = "1.0.9" |
| 18 |
| 19 logger = logging.getLogger(__name__) |
| 20 |
| 21 JWT_TYPES = (u"JWT", u"application/jws", u"JWS", u"JWE") |
| 22 |
| 23 JWT_CLAIMS = {"iss": str, "sub": str, "aud": str, "exp": int, "nbf": int, |
| 24 "iat": int, "jti": str, "typ": str} |
| 25 |
| 26 JWT_HEADERS = ["typ", "cty"] |
| 27 |
| 28 |
| 29 class JWKESTException(Exception): |
| 30 pass |
| 31 |
| 32 |
| 33 # XXX Should this be a subclass of ValueError? |
| 34 class Invalid(JWKESTException): |
| 35 """The JWT is invalid.""" |
| 36 |
| 37 |
| 38 class BadSyntax(Invalid): |
| 39 """The JWT could not be parsed because the syntax is invalid.""" |
| 40 |
| 41 def __init__(self, value, msg): |
| 42 Invalid.__init__(self) |
| 43 self.value = value |
| 44 self.msg = msg |
| 45 |
| 46 def __str__(self): |
| 47 return "%s: %r" % (self.msg, self.value) |
| 48 |
| 49 |
| 50 class BadSignature(Invalid): |
| 51 """The signature of the JWT is invalid.""" |
| 52 |
| 53 |
| 54 class Expired(Invalid): |
| 55 """The JWT claim has expired or is not yet valid.""" |
| 56 |
| 57 |
| 58 class UnknownAlgorithm(Invalid): |
| 59 """The JWT uses an unknown signing algorithm""" |
| 60 |
| 61 |
| 62 class BadType(Invalid): |
| 63 """The JWT has an unexpected "typ" value.""" |
| 64 |
| 65 |
| 66 class MissingKey(JWKESTException): |
| 67 """ No usable key """ |
| 68 |
| 69 |
| 70 class UnknownKeytype(Invalid): |
| 71 """An unknown key type""" |
| 72 |
| 73 |
| 74 # --------------------------------------------------------------------------- |
| 75 # Helper functions |
| 76 |
| 77 |
| 78 def intarr2bin(arr): |
| 79 return unhexlify(''.join(["%02x" % byte for byte in arr])) |
| 80 |
| 81 |
| 82 def long2hexseq(l): |
| 83 try: |
| 84 return unhexlify(hex(l)[2:]) |
| 85 except TypeError: |
| 86 return unhexlify(hex(l)[2:-1]) |
| 87 |
| 88 |
| 89 def intarr2long(arr): |
| 90 return int(''.join(["%02x" % byte for byte in arr]), 16) |
| 91 |
| 92 |
| 93 def long2intarr(long_int): |
| 94 _bytes = [] |
| 95 while long_int: |
| 96 long_int, r = divmod(long_int, 256) |
| 97 _bytes.insert(0, r) |
| 98 return _bytes |
| 99 |
| 100 |
| 101 def long_to_base64(n): |
| 102 bys = long2intarr(n) |
| 103 data = struct.pack('%sB' % len(bys), *bys) |
| 104 if not len(data): |
| 105 data = '\x00' |
| 106 s = base64.urlsafe_b64encode(data).rstrip(b'=') |
| 107 return s.decode("ascii") |
| 108 |
| 109 |
| 110 def base64_to_long(data): |
| 111 if isinstance(data, six.text_type): |
| 112 data = data.encode("ascii") |
| 113 |
| 114 # urlsafe_b64decode will happily convert b64encoded data |
| 115 _d = base64.urlsafe_b64decode(bytes(data) + b'==') |
| 116 return intarr2long(struct.unpack('%sB' % len(_d), _d)) |
| 117 |
| 118 |
| 119 def base64url_to_long(data): |
| 120 """ |
| 121 Stricter then base64_to_long since it really checks that it's |
| 122 base64url encoded |
| 123 |
| 124 :param data: The base64 string |
| 125 :return: |
| 126 """ |
| 127 _d = base64.urlsafe_b64decode(bytes(data) + b'==') |
| 128 # verify that it's base64url encoded and not just base64 |
| 129 # that is no '+' and '/' characters and not trailing "="s. |
| 130 if [e for e in [b'+', b'/', b'='] if e in data]: |
| 131 raise ValueError("Not base64url encoded") |
| 132 return intarr2long(struct.unpack('%sB' % len(_d), _d)) |
| 133 |
| 134 |
| 135 # ============================================================================= |
| 136 |
| 137 def b64e(b): |
| 138 """Base64 encode some bytes. |
| 139 |
| 140 Uses the url-safe - and _ characters, and doesn't pad with = characters.""" |
| 141 return base64.urlsafe_b64encode(b).rstrip(b"=") |
| 142 |
| 143 |
| 144 _b64_re = re.compile(b"^[A-Za-z0-9_-]*$") |
| 145 |
| 146 |
| 147 def add_padding(b): |
| 148 # add padding chars |
| 149 m = len(b) % 4 |
| 150 if m == 1: |
| 151 # NOTE: for some reason b64decode raises *TypeError* if the |
| 152 # padding is incorrect. |
| 153 raise BadSyntax(b, "incorrect padding") |
| 154 elif m == 2: |
| 155 b += b"==" |
| 156 elif m == 3: |
| 157 b += b"=" |
| 158 return b |
| 159 |
| 160 |
| 161 def b64d(b): |
| 162 """Decode some base64-encoded bytes. |
| 163 |
| 164 Raises BadSyntax if the string contains invalid characters or padding. |
| 165 |
| 166 :param b: bytes |
| 167 """ |
| 168 |
| 169 cb = b.rstrip(b"=") # shouldn't but there you are |
| 170 |
| 171 # Python's base64 functions ignore invalid characters, so we need to |
| 172 # check for them explicitly. |
| 173 if not _b64_re.match(cb): |
| 174 raise BadSyntax(cb, "base64-encoded data contains illegal characters") |
| 175 |
| 176 if cb == b: |
| 177 b = add_padding(b) |
| 178 |
| 179 return base64.urlsafe_b64decode(b) |
| 180 |
| 181 |
| 182 def b64e_enc_dec(str, encode, decode): |
| 183 return base64.urlsafe_b64encode(str.encode(encode)).decode(decode) |
| 184 |
| 185 |
| 186 def b64d_enc_dec(str, encode, decode): |
| 187 return base64.urlsafe_b64decode(str.encode(encode)).decode(decode) |
| 188 |
| 189 |
| 190 # 'Stolen' from Werkzeug |
| 191 def safe_str_cmp(a, b): |
| 192 """Compare two strings in constant time.""" |
| 193 if len(a) != len(b): |
| 194 return False |
| 195 r = 0 |
| 196 for c, d in zip(a, b): |
| 197 r |= ord(c) ^ ord(d) |
| 198 return r == 0 |
| 199 |
| 200 |
| 201 def constant_time_compare(a, b): |
| 202 """Compare two strings in constant time.""" |
| 203 if len(a) != len(b): |
| 204 return False |
| 205 r = 0 |
| 206 for c, d in zip(a, b): |
| 207 r |= c ^ d |
| 208 return r == 0 |
| 209 |
| 210 |
| 211 def as_bytes(s): |
| 212 """ |
| 213 Convert an unicode string to bytes. |
| 214 :param s: Unicode / bytes string |
| 215 :return: bytes string |
| 216 """ |
| 217 try: |
| 218 s = s.encode() |
| 219 except (AttributeError, UnicodeDecodeError): |
| 220 pass |
| 221 return s |
| 222 |
| 223 |
| 224 def as_unicode(b): |
| 225 """ |
| 226 Convert a byte string to a unicode string |
| 227 :param b: byte string |
| 228 :return: unicode string |
| 229 """ |
| 230 try: |
| 231 b = b.decode() |
| 232 except (AttributeError, UnicodeDecodeError): |
| 233 pass |
| 234 return b |
OLD | NEW |