diff options
author | AlexSm <alex@ydb.tech> | 2024-01-09 18:56:40 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-09 18:56:40 +0100 |
commit | e95f266d2a3e48e62015220588a4fd73d5d5a5cb (patch) | |
tree | a8a784b6931fe52ad5f511cfef85af14e5f63991 /contrib/python/PyJWT/py3 | |
parent | 50a65e3b48a82d5b51f272664da389f2e0b0c99a (diff) | |
download | ydb-e95f266d2a3e48e62015220588a4fd73d5d5a5cb.tar.gz |
Library import 6 (#888)
Diffstat (limited to 'contrib/python/PyJWT/py3')
-rw-r--r-- | contrib/python/PyJWT/py3/.dist-info/METADATA | 3 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/LICENSE | 2 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/__init__.py | 6 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/algorithms.py | 45 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/api_jwk.py | 18 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/api_jws.py | 62 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/api_jwt.py | 28 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/exceptions.py | 2 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/help.py | 8 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/jwt/utils.py | 61 | ||||
-rw-r--r-- | contrib/python/PyJWT/py3/ya.make | 2 |
11 files changed, 174 insertions, 63 deletions
diff --git a/contrib/python/PyJWT/py3/.dist-info/METADATA b/contrib/python/PyJWT/py3/.dist-info/METADATA index f5fbdf64a5..65b6141fa1 100644 --- a/contrib/python/PyJWT/py3/.dist-info/METADATA +++ b/contrib/python/PyJWT/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: PyJWT -Version: 2.3.0 +Version: 2.4.0 Summary: JSON Web Token implementation in Python Home-page: https://github.com/jpadilla/pyjwt Author: Jose Padilla @@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Utilities Requires-Python: >=3.6 Description-Content-Type: text/x-rst diff --git a/contrib/python/PyJWT/py3/LICENSE b/contrib/python/PyJWT/py3/LICENSE index bdc7819ea1..fd0ecbc889 100644 --- a/contrib/python/PyJWT/py3/LICENSE +++ b/contrib/python/PyJWT/py3/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 José Padilla +Copyright (c) 2015-2022 José Padilla Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/contrib/python/PyJWT/py3/jwt/__init__.py b/contrib/python/PyJWT/py3/jwt/__init__.py index 3208c39f39..6b3f8ab160 100644 --- a/contrib/python/PyJWT/py3/jwt/__init__.py +++ b/contrib/python/PyJWT/py3/jwt/__init__.py @@ -25,19 +25,19 @@ from .exceptions import ( ) from .jwks_client import PyJWKClient -__version__ = "2.3.0" +__version__ = "2.4.0" __title__ = "PyJWT" __description__ = "JSON Web Token implementation in Python" __url__ = "https://pyjwt.readthedocs.io" __uri__ = __url__ -__doc__ = __description__ + " <" + __uri__ + ">" +__doc__ = f"{__description__} <{__uri__}>" __author__ = "José Padilla" __email__ = "hello@jpadilla.com" __license__ = "MIT" -__copyright__ = "Copyright 2015-2020 José Padilla" +__copyright__ = "Copyright 2015-2022 José Padilla" __all__ = [ diff --git a/contrib/python/PyJWT/py3/jwt/algorithms.py b/contrib/python/PyJWT/py3/jwt/algorithms.py index 1f8865afbd..46a1a532e7 100644 --- a/contrib/python/PyJWT/py3/jwt/algorithms.py +++ b/contrib/python/PyJWT/py3/jwt/algorithms.py @@ -9,6 +9,8 @@ from .utils import ( der_to_raw_signature, force_bytes, from_base64url_uint, + is_pem_format, + is_ssh_key, raw_to_der_signature, to_base64url_uint, ) @@ -183,14 +185,7 @@ class HMACAlgorithm(Algorithm): def prepare_key(self, key): key = force_bytes(key) - invalid_strings = [ - b"-----BEGIN PUBLIC KEY-----", - b"-----BEGIN CERTIFICATE-----", - b"-----BEGIN RSA PUBLIC KEY-----", - b"ssh-rsa", - ] - - if any(string_value in key for string_value in invalid_strings): + if is_pem_format(key) or is_ssh_key(key): raise InvalidKeyError( "The specified key is an asymmetric key or x509 certificate and" " should not be used as an HMAC secret." @@ -417,6 +412,12 @@ if has_crypto: except ValueError: key = load_pem_private_key(key, password=None) + # Explicit check the key to prevent confusing errors from cryptography + if not isinstance(key, (EllipticCurvePrivateKey, EllipticCurvePublicKey)): + raise InvalidKeyError( + "Expecting a EllipticCurvePrivateKey/EllipticCurvePublicKey. Wrong key provided for ECDSA algorithms" + ) + return key def sign(self, msg, key): @@ -545,26 +546,28 @@ if has_crypto: pass def prepare_key(self, key): - - if isinstance( - key, - (Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey), - ): - return key - if isinstance(key, (bytes, str)): if isinstance(key, str): key = key.encode("utf-8") str_key = key.decode("utf-8") if "-----BEGIN PUBLIC" in str_key: - return load_pem_public_key(key) - if "-----BEGIN PRIVATE" in str_key: - return load_pem_private_key(key, password=None) - if str_key[0:4] == "ssh-": - return load_ssh_public_key(key) + key = load_pem_public_key(key) + elif "-----BEGIN PRIVATE" in str_key: + key = load_pem_private_key(key, password=None) + elif str_key[0:4] == "ssh-": + key = load_ssh_public_key(key) + + # Explicit check the key to prevent confusing errors from cryptography + if not isinstance( + key, + (Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey), + ): + raise InvalidKeyError( + "Expecting a EllipticCurvePrivateKey/EllipticCurvePublicKey. Wrong key provided for EdDSA algorithms" + ) - raise TypeError("Expecting a PEM-formatted or OpenSSH key.") + return key def sign(self, msg, key): """ diff --git a/contrib/python/PyJWT/py3/jwt/api_jwk.py b/contrib/python/PyJWT/py3/jwt/api_jwk.py index a0f6364da0..31250d57f2 100644 --- a/contrib/python/PyJWT/py3/jwt/api_jwk.py +++ b/contrib/python/PyJWT/py3/jwt/api_jwk.py @@ -11,7 +11,7 @@ class PyJWK: kty = self._jwk_data.get("kty", None) if not kty: - raise InvalidKeyError("kty is not found: %s" % self._jwk_data) + raise InvalidKeyError(f"kty is not found: {self._jwk_data}") if not algorithm and isinstance(self._jwk_data, dict): algorithm = self._jwk_data.get("alg", None) @@ -29,25 +29,25 @@ class PyJWK: elif crv == "secp256k1": algorithm = "ES256K" else: - raise InvalidKeyError("Unsupported crv: %s" % crv) + raise InvalidKeyError(f"Unsupported crv: {crv}") elif kty == "RSA": algorithm = "RS256" elif kty == "oct": algorithm = "HS256" elif kty == "OKP": if not crv: - raise InvalidKeyError("crv is not found: %s" % self._jwk_data) + raise InvalidKeyError(f"crv is not found: {self._jwk_data}") if crv == "Ed25519": algorithm = "EdDSA" else: - raise InvalidKeyError("Unsupported crv: %s" % crv) + raise InvalidKeyError(f"Unsupported crv: {crv}") else: - raise InvalidKeyError("Unsupported kty: %s" % kty) + raise InvalidKeyError(f"Unsupported kty: {kty}") self.Algorithm = self._algorithms.get(algorithm) if not self.Algorithm: - raise PyJWKError("Unable to find a algorithm for key: %s" % self._jwk_data) + raise PyJWKError(f"Unable to find a algorithm for key: {self._jwk_data}") self.key = self.Algorithm.from_jwk(self._jwk_data) @@ -95,3 +95,9 @@ class PyJWKSet: def from_json(data): obj = json.loads(data) return PyJWKSet.from_dict(obj) + + def __getitem__(self, kid): + for key in self.keys: + if key.key_id == kid: + return key + raise KeyError(f"keyset has no key for kid: {kid}") diff --git a/contrib/python/PyJWT/py3/jwt/api_jws.py b/contrib/python/PyJWT/py3/jwt/api_jws.py index f85072e05e..cbf4f6f5b8 100644 --- a/contrib/python/PyJWT/py3/jwt/api_jws.py +++ b/contrib/python/PyJWT/py3/jwt/api_jws.py @@ -80,34 +80,54 @@ class PyJWS: algorithm: Optional[str] = "HS256", headers: Optional[Dict] = None, json_encoder: Optional[Type[json.JSONEncoder]] = None, + is_payload_detached: bool = False, ) -> str: segments = [] if algorithm is None: algorithm = "none" - # Prefer headers["alg"] if present to algorithm parameter. - if headers and "alg" in headers and headers["alg"]: - algorithm = headers["alg"] + # Prefer headers values if present to function parameters. + if headers: + headers_alg = headers.get("alg") + if headers_alg: + algorithm = headers["alg"] + + headers_b64 = headers.get("b64") + if headers_b64 is False: + is_payload_detached = True # Header - header = {"typ": self.header_typ, "alg": algorithm} + header = {"typ": self.header_typ, "alg": algorithm} # type: Dict[str, Any] if headers: self._validate_headers(headers) header.update(headers) - if not header["typ"]: - del header["typ"] + + if not header["typ"]: + del header["typ"] + + if is_payload_detached: + header["b64"] = False + elif "b64" in header: + # True is the standard value for b64, so no need for it + del header["b64"] json_header = json.dumps( header, separators=(",", ":"), cls=json_encoder ).encode() segments.append(base64url_encode(json_header)) - segments.append(base64url_encode(payload)) + + if is_payload_detached: + msg_payload = payload + else: + msg_payload = base64url_encode(payload) + segments.append(msg_payload) # Segments signing_input = b".".join(segments) + try: alg_obj = self._algorithms[algorithm] key = alg_obj.prepare_key(key) @@ -116,14 +136,15 @@ class PyJWS: except KeyError as e: if not has_crypto and algorithm in requires_cryptography: raise NotImplementedError( - "Algorithm '%s' could not be found. Do you have cryptography " - "installed?" % algorithm + f"Algorithm '{algorithm}' could not be found. Do you have cryptography installed?" ) from e - else: - raise NotImplementedError("Algorithm not supported") from e + raise NotImplementedError("Algorithm not supported") from e segments.append(base64url_encode(signature)) + # Don't put the payload content inside the encoded token when detached + if is_payload_detached: + segments[1] = b"" encoded_string = b".".join(segments) return encoded_string.decode("utf-8") @@ -132,8 +153,9 @@ class PyJWS: self, jwt: str, key: str = "", - algorithms: List[str] = None, - options: Dict = None, + algorithms: Optional[List[str]] = None, + options: Optional[Dict] = None, + detached_payload: Optional[bytes] = None, **kwargs, ) -> Dict[str, Any]: if options is None: @@ -148,6 +170,14 @@ class PyJWS: payload, signing_input, header, signature = self._load(jwt) + if header.get("b64", True) is False: + if detached_payload is None: + raise DecodeError( + 'It is required that you pass in a value for the "detached_payload" argument to decode a message having the b64 header set to false.' + ) + payload = detached_payload + signing_input = b".".join([signing_input.rsplit(b".", 1)[0], payload]) + if verify_signature: self._verify_signature(signing_input, header, signature, key, algorithms) @@ -161,8 +191,8 @@ class PyJWS: self, jwt: str, key: str = "", - algorithms: List[str] = None, - options: Dict = None, + algorithms: Optional[List[str]] = None, + options: Optional[Dict] = None, **kwargs, ) -> str: decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs) @@ -200,7 +230,7 @@ class PyJWS: try: header = json.loads(header_data) except ValueError as e: - raise DecodeError("Invalid header string: %s" % e) from e + raise DecodeError(f"Invalid header string: {e}") from e if not isinstance(header, Mapping): raise DecodeError("Invalid header string: must be a json object") diff --git a/contrib/python/PyJWT/py3/jwt/api_jwt.py b/contrib/python/PyJWT/py3/jwt/api_jwt.py index f3b55d360e..7d2177bf53 100644 --- a/contrib/python/PyJWT/py3/jwt/api_jwt.py +++ b/contrib/python/PyJWT/py3/jwt/api_jwt.py @@ -1,4 +1,5 @@ import json +import warnings from calendar import timegm from collections.abc import Iterable, Mapping from datetime import datetime, timedelta, timezone @@ -66,14 +67,23 @@ class PyJWT: self, jwt: str, key: str = "", - algorithms: List[str] = None, - options: Dict = None, + algorithms: Optional[List[str]] = None, + options: Optional[Dict] = None, **kwargs, ) -> Dict[str, Any]: - if options is None: - options = {"verify_signature": True} - else: - options.setdefault("verify_signature", True) + options = dict(options or {}) # shallow-copy or initialize an empty dict + options.setdefault("verify_signature", True) + + # If the user has set the legacy `verify` argument, and it doesn't match + # what the relevant `options` entry for the argument is, inform the user + # that they're likely making a mistake. + if "verify" in kwargs and kwargs["verify"] != options["verify_signature"]: + warnings.warn( + "The `verify` argument to `decode` does nothing in PyJWT 2.0 and newer. " + "The equivalent is setting `verify_signature` to False in the `options` dictionary. " + "This invocation has a mismatch between the kwarg and the option entry.", + category=DeprecationWarning, + ) if not options["verify_signature"]: options.setdefault("verify_exp", False) @@ -98,7 +108,7 @@ class PyJWT: try: payload = json.loads(decoded["payload"]) except ValueError as e: - raise DecodeError("Invalid payload string: %s" % e) + raise DecodeError(f"Invalid payload string: {e}") if not isinstance(payload, dict): raise DecodeError("Invalid payload string: must be a json object") @@ -112,8 +122,8 @@ class PyJWT: self, jwt: str, key: str = "", - algorithms: List[str] = None, - options: Dict = None, + algorithms: Optional[List[str]] = None, + options: Optional[Dict] = None, **kwargs, ) -> Dict[str, Any]: decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs) diff --git a/contrib/python/PyJWT/py3/jwt/exceptions.py b/contrib/python/PyJWT/py3/jwt/exceptions.py index 308899aa6a..ee201add1a 100644 --- a/contrib/python/PyJWT/py3/jwt/exceptions.py +++ b/contrib/python/PyJWT/py3/jwt/exceptions.py @@ -51,7 +51,7 @@ class MissingRequiredClaimError(InvalidTokenError): self.claim = claim def __str__(self): - return 'Token is missing the "%s" claim' % self.claim + return f'Token is missing the "{self.claim}" claim' class PyJWKError(PyJWTError): diff --git a/contrib/python/PyJWT/py3/jwt/help.py b/contrib/python/PyJWT/py3/jwt/help.py index d8f2302421..d5c3ebbfff 100644 --- a/contrib/python/PyJWT/py3/jwt/help.py +++ b/contrib/python/PyJWT/py3/jwt/help.py @@ -28,10 +28,10 @@ def info(): if implementation == "CPython": implementation_version = platform.python_version() elif implementation == "PyPy": - implementation_version = "{}.{}.{}".format( - sys.pypy_version_info.major, - sys.pypy_version_info.minor, - sys.pypy_version_info.micro, + implementation_version = ( + f"{sys.pypy_version_info.major}." + f"{sys.pypy_version_info.minor}." + f"{sys.pypy_version_info.micro}" ) if sys.pypy_version_info.releaselevel != "final": implementation_version = "".join( diff --git a/contrib/python/PyJWT/py3/jwt/utils.py b/contrib/python/PyJWT/py3/jwt/utils.py index 9dde10cf8e..8ab73b42d3 100644 --- a/contrib/python/PyJWT/py3/jwt/utils.py +++ b/contrib/python/PyJWT/py3/jwt/utils.py @@ -1,5 +1,6 @@ import base64 import binascii +import re from typing import Any, Union try: @@ -97,3 +98,63 @@ def raw_to_der_signature(raw_sig: bytes, curve: EllipticCurve) -> bytes: s = bytes_to_number(raw_sig[num_bytes:]) return encode_dss_signature(r, s) + + +# Based on https://github.com/hynek/pem/blob/7ad94db26b0bc21d10953f5dbad3acfdfacf57aa/src/pem/_core.py#L224-L252 +_PEMS = { + b"CERTIFICATE", + b"TRUSTED CERTIFICATE", + b"PRIVATE KEY", + b"PUBLIC KEY", + b"ENCRYPTED PRIVATE KEY", + b"OPENSSH PRIVATE KEY", + b"DSA PRIVATE KEY", + b"RSA PRIVATE KEY", + b"RSA PUBLIC KEY", + b"EC PRIVATE KEY", + b"DH PARAMETERS", + b"NEW CERTIFICATE REQUEST", + b"CERTIFICATE REQUEST", + b"SSH2 PUBLIC KEY", + b"SSH2 ENCRYPTED PRIVATE KEY", + b"X509 CRL", +} + +_PEM_RE = re.compile( + b"----[- ]BEGIN (" + + b"|".join(_PEMS) + + b""")[- ]----\r? +.+?\r? +----[- ]END \\1[- ]----\r?\n?""", + re.DOTALL, +) + + +def is_pem_format(key: bytes) -> bool: + return bool(_PEM_RE.search(key)) + + +# Based on https://github.com/pyca/cryptography/blob/bcb70852d577b3f490f015378c75cba74986297b/src/cryptography/hazmat/primitives/serialization/ssh.py#L40-L46 +_CERT_SUFFIX = b"-cert-v01@openssh.com" +_SSH_PUBKEY_RC = re.compile(br"\A(\S+)[ \t]+(\S+)") +_SSH_KEY_FORMATS = [ + b"ssh-ed25519", + b"ssh-rsa", + b"ssh-dss", + b"ecdsa-sha2-nistp256", + b"ecdsa-sha2-nistp384", + b"ecdsa-sha2-nistp521", +] + + +def is_ssh_key(key: bytes) -> bool: + if any(string_value in key for string_value in _SSH_KEY_FORMATS): + return True + + ssh_pubkey_match = _SSH_PUBKEY_RC.match(key) + if ssh_pubkey_match: + key_type = ssh_pubkey_match.group(1) + if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]: + return True + + return False diff --git a/contrib/python/PyJWT/py3/ya.make b/contrib/python/PyJWT/py3/ya.make index 0cbee2bb2e..3be98b643e 100644 --- a/contrib/python/PyJWT/py3/ya.make +++ b/contrib/python/PyJWT/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.3.0) +VERSION(2.4.0) LICENSE(MIT) |