您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

269 行
7.7 KiB

  1. import binascii
  2. import json
  3. try:
  4. from collections.abc import Iterable, Mapping
  5. except ImportError:
  6. from collections import Mapping, Iterable
  7. from jose import jwk
  8. from jose.backends.base import Key
  9. from jose.constants import ALGORITHMS
  10. from jose.exceptions import JWSError, JWSSignatureError
  11. from jose.utils import base64url_decode, base64url_encode
  12. def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256):
  13. """Signs a claims set and returns a JWS string.
  14. Args:
  15. payload (str or dict): A string to sign
  16. key (str or dict): The key to use for signing the claim set. Can be
  17. individual JWK or JWK set.
  18. headers (dict, optional): A set of headers that will be added to
  19. the default headers. Any headers that are added as additional
  20. headers will override the default headers.
  21. algorithm (str, optional): The algorithm to use for signing the
  22. the claims. Defaults to HS256.
  23. Returns:
  24. str: The string representation of the header, claims, and signature.
  25. Raises:
  26. JWSError: If there is an error signing the token.
  27. Examples:
  28. >>> jws.sign({'a': 'b'}, 'secret', algorithm='HS256')
  29. 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8'
  30. """
  31. if algorithm not in ALGORITHMS.SUPPORTED:
  32. raise JWSError("Algorithm %s not supported." % algorithm)
  33. encoded_header = _encode_header(algorithm, additional_headers=headers)
  34. encoded_payload = _encode_payload(payload)
  35. signed_output = _sign_header_and_claims(encoded_header, encoded_payload, algorithm, key)
  36. return signed_output
  37. def verify(token, key, algorithms, verify=True):
  38. """Verifies a JWS string's signature.
  39. Args:
  40. token (str): A signed JWS to be verified.
  41. key (str or dict): A key to attempt to verify the payload with. Can be
  42. individual JWK or JWK set.
  43. algorithms (str or list): Valid algorithms that should be used to verify the JWS.
  44. Returns:
  45. str: The str representation of the payload, assuming the signature is valid.
  46. Raises:
  47. JWSError: If there is an exception verifying a token.
  48. Examples:
  49. >>> token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8'
  50. >>> jws.verify(token, 'secret', algorithms='HS256')
  51. """
  52. header, payload, signing_input, signature = _load(token)
  53. if verify:
  54. _verify_signature(signing_input, header, signature, key, algorithms)
  55. return payload
  56. def get_unverified_header(token):
  57. """Returns the decoded headers without verification of any kind.
  58. Args:
  59. token (str): A signed JWS to decode the headers from.
  60. Returns:
  61. dict: The dict representation of the token headers.
  62. Raises:
  63. JWSError: If there is an exception decoding the token.
  64. """
  65. header, claims, signing_input, signature = _load(token)
  66. return header
  67. def get_unverified_headers(token):
  68. """Returns the decoded headers without verification of any kind.
  69. This is simply a wrapper of get_unverified_header() for backwards
  70. compatibility.
  71. Args:
  72. token (str): A signed JWS to decode the headers from.
  73. Returns:
  74. dict: The dict representation of the token headers.
  75. Raises:
  76. JWSError: If there is an exception decoding the token.
  77. """
  78. return get_unverified_header(token)
  79. def get_unverified_claims(token):
  80. """Returns the decoded claims without verification of any kind.
  81. Args:
  82. token (str): A signed JWS to decode the headers from.
  83. Returns:
  84. str: The str representation of the token claims.
  85. Raises:
  86. JWSError: If there is an exception decoding the token.
  87. """
  88. header, claims, signing_input, signature = _load(token)
  89. return claims
  90. def _encode_header(algorithm, additional_headers=None):
  91. header = {"typ": "JWT", "alg": algorithm}
  92. if additional_headers:
  93. header.update(additional_headers)
  94. json_header = json.dumps(
  95. header,
  96. separators=(",", ":"),
  97. sort_keys=True,
  98. ).encode("utf-8")
  99. return base64url_encode(json_header)
  100. def _encode_payload(payload):
  101. if isinstance(payload, Mapping):
  102. try:
  103. payload = json.dumps(
  104. payload,
  105. separators=(",", ":"),
  106. ).encode("utf-8")
  107. except ValueError:
  108. pass
  109. return base64url_encode(payload)
  110. def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key):
  111. signing_input = b".".join([encoded_header, encoded_claims])
  112. try:
  113. if not isinstance(key, Key):
  114. key = jwk.construct(key, algorithm)
  115. signature = key.sign(signing_input)
  116. except Exception as e:
  117. raise JWSError(e)
  118. encoded_signature = base64url_encode(signature)
  119. encoded_string = b".".join([encoded_header, encoded_claims, encoded_signature])
  120. return encoded_string.decode("utf-8")
  121. def _load(jwt):
  122. if isinstance(jwt, str):
  123. jwt = jwt.encode("utf-8")
  124. try:
  125. signing_input, crypto_segment = jwt.rsplit(b".", 1)
  126. header_segment, claims_segment = signing_input.split(b".", 1)
  127. header_data = base64url_decode(header_segment)
  128. except ValueError:
  129. raise JWSError("Not enough segments")
  130. except (TypeError, binascii.Error):
  131. raise JWSError("Invalid header padding")
  132. try:
  133. header = json.loads(header_data.decode("utf-8"))
  134. except ValueError as e:
  135. raise JWSError("Invalid header string: %s" % e)
  136. if not isinstance(header, Mapping):
  137. raise JWSError("Invalid header string: must be a json object")
  138. try:
  139. payload = base64url_decode(claims_segment)
  140. except (TypeError, binascii.Error):
  141. raise JWSError("Invalid payload padding")
  142. try:
  143. signature = base64url_decode(crypto_segment)
  144. except (TypeError, binascii.Error):
  145. raise JWSError("Invalid crypto padding")
  146. return (header, payload, signing_input, signature)
  147. def _sig_matches_keys(keys, signing_input, signature, alg):
  148. for key in keys:
  149. if not isinstance(key, Key):
  150. key = jwk.construct(key, alg)
  151. try:
  152. if key.verify(signing_input, signature):
  153. return True
  154. except Exception:
  155. pass
  156. return False
  157. def _get_keys(key):
  158. if isinstance(key, Key):
  159. return (key,)
  160. try:
  161. key = json.loads(key, parse_int=str, parse_float=str)
  162. except Exception:
  163. pass
  164. if isinstance(key, Mapping):
  165. if "keys" in key:
  166. # JWK Set per RFC 7517
  167. return key["keys"]
  168. elif "kty" in key:
  169. # Individual JWK per RFC 7517
  170. return (key,)
  171. else:
  172. # Some other mapping. Firebase uses just dict of kid, cert pairs
  173. values = key.values()
  174. if values:
  175. return values
  176. return (key,)
  177. # Iterable but not text or mapping => list- or tuple-like
  178. elif isinstance(key, Iterable) and not (isinstance(key, str) or isinstance(key, bytes)):
  179. return key
  180. # Scalar value, wrap in tuple.
  181. else:
  182. return (key,)
  183. def _verify_signature(signing_input, header, signature, key="", algorithms=None):
  184. alg = header.get("alg")
  185. if not alg:
  186. raise JWSError("No algorithm was specified in the JWS header.")
  187. if algorithms is not None and alg not in algorithms:
  188. raise JWSError("The specified alg value is not allowed")
  189. keys = _get_keys(key)
  190. try:
  191. if not _sig_matches_keys(keys, signing_input, signature, alg):
  192. raise JWSSignatureError()
  193. except JWSSignatureError:
  194. raise JWSError("Signature verification failed.")
  195. except JWSError:
  196. raise JWSError("Invalid or unsupported algorithm: %s" % alg)