Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:python-authlib
User: [email protected]
Usertags: pu

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

[ Reason ]

This update attempts to fix all open CVEs for python-authlib and close the gap
between Bullseye and Sid/Forky.

[ Impact ]

If the update isn't approved, users will continue to be vulnerable to
CVE-2024-37568, CVE-2025-59420, CVE-2025-61920, CVE-2025-62706, and
CVE-2025-68158. Bullseye users upgrading to Bookworm will become vulnerable
again.

[ Tests ]

I've enabled some more tests during the autopkgtest phase to cover the newly
introduced tests. Some of the changes include new or changed tests as well. All
tests run successfully.

[ Risks ]

Some of the changes are trivial, but some are more complex. In general, there
is the risk of regressions. Running the tests should hopefully prevent that,
though.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]

CVE-2024-37568.patch just introduces a blocklist of public key formats when
verifying with HMAC.

CVE-2025-59420.patch establishes an integrity protection for crit headers and
rejects 'crit' in unprotected headers. The patch adds some tests for the new
behavior.

CVE-2025-61920.patch adds some input size limitations and a few tests.

CVE-2025-62706.patch adds a size limitation for JWE zip=DEF decompression.

CVE-2025-68158.patch makes sure that an expiry marker is stored in the session
alongside the cached data, and that the marker is to be present before the
state data is considered valid.

[ Other info ]

A PU for Trixie is coming as well.

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEvu1N7VVEpMA+KD3HS80FZ8KW0F0FAmmiYKcACgkQS80FZ8KW
0F1cEA//ZO0in0sXihGQrfJg/HRvhIQjy9Fj3Y7fnm9/YLMprrl9PCU6c+K50CiP
qLWem5mPGEeKqGmxjyb0U06tHiugnSQpAK9NbM2mP6GBZu66YqMmebHT5p1iGH7h
zjENyJw86X91mlDt37DOUzSthNtbhdw50d38XMevX91Nkknz1TJGZS8PTD9o3671
rpuYolBf1FHHQBqlq6NM8/snEqmLOfdN2NHLa7WQJSQWJlDMpR42CHYFCtXyNEWx
bjZNNQitpkwGMyE0ulDMaFx1CpxPYWQQbKdlVwxcPfjSjjB4LPLv0Wro5S5RMZK6
cBvEiVExvohovUgTaGyZNgKK4fZFSZ5rcA/hFH4tmJWzJbtudd1qMN/FvZ02LjHa
fASAjfazzqBJrNBOdb3dXmZUkNsb5tS+9Y28BBJyyG8YRxwBY1xy3FomvRPKtHgi
IUX1SF/+BzJ5jzZnwIBrPPIr48YZ6w2Ff2GMcFWD+eiy6JO2DDWbVtaQbe/Y1j+8
X6I4Mj/r97TGUH5inJUTxVqQqRyK8KLZ5yie+6jf0MR4NTadzDDuRqxo/jmkiYhk
OWHkyPlBTmUkKXM/Q0AnrIdsJKYxIu3oIawRrX2xDdeUXqwvjHq25s0jRcbdcw6r
cRXiHmyH8+BZ7fge4Waq6+bgTLVTTyq5wOxY/Mll85BzqY3m5I4=
=GxF1
-----END PGP SIGNATURE-----
diff -Nru python-authlib-1.2.0/debian/changelog 
python-authlib-1.2.0/debian/changelog
--- python-authlib-1.2.0/debian/changelog       2022-12-09 23:08:37.000000000 
+0100
+++ python-authlib-1.2.0/debian/changelog       2026-02-28 03:41:12.000000000 
+0100
@@ -1,3 +1,30 @@
+python-authlib (1.2.0-1+deb12u1) bookworm; urgency=medium
+
+  * Non-maintainer upload by the Debian LTS team.
+  * d/patches/CVE-2025-68158.patch: Add patch to fix CVE-2025-68158.
+    - The cache-backed state/request-token storage is not tied to the
+      initiating user session, so CSRF is possible for any attacker that has
+      a valid state.
+  * d/patches/CVE-2025-62706.patch: Add patch to fix CVE-2025-62706.
+    - Authlib’s JWE zip=DEF path performs unbounded DEFLATE decompression
+      which can lead to a DoS.
+  * d/patches/CVE-2025-61920.patch: Add patch to fix CVE-2025-61920.
+    - Authlib’s JOSE implementation accepts unbounded JWS/JWT header and
+      signature segments which can lead to a DoS during verification.
+  * d/patches/CVE-2025-59420.patch: Add patch to fix CVE-2025-59420.
+    - Authlib’s JWS verification accepts tokens that declare unknown critical
+      header parameters (crit), violating RFC 7515 “must‑understand” semantics.
+      An attacker can craft a signed token with a critical header that strict
+      verifiers reject but Authlib accepts. In mixed‑language fleets, this
+      enables split‑brain verification and can lead to policy bypass, replay,
+      or privilege escalation.
+  * d/patches/CVE-2024-37568.patch: Add patch to fix CVE-2024-37568.
+    - Unless an algorithm is specified in a jwt.decode call, HMAC verification
+      is allowed with any asymmetric public key.
+  * debian/tests/control, debian/tests/unittests3: Enable client and jose 
tests.
+
+ -- Daniel Leidert <[email protected]>  Sat, 28 Feb 2026 03:41:12 +0100
+
 python-authlib (1.2.0-1) unstable; urgency=medium
 
   * New upstream release.
diff -Nru python-authlib-1.2.0/debian/gbp.conf 
python-authlib-1.2.0/debian/gbp.conf
--- python-authlib-1.2.0/debian/gbp.conf        2022-12-09 23:08:37.000000000 
+0100
+++ python-authlib-1.2.0/debian/gbp.conf        2026-02-28 03:41:12.000000000 
+0100
@@ -1,6 +1,6 @@
 [DEFAULT]
 upstream-branch = upstream
-debian-branch = debian/master
+debian-branch = debian/bookworm
 upstream-tag = upstream/%(version)s
 debian-tag = debian/%(version)s
 sign-tags = True
diff -Nru python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch 
python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch
--- python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch    1970-01-01 
01:00:00.000000000 +0100
+++ python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch    2026-02-28 
03:41:12.000000000 +0100
@@ -0,0 +1,48 @@
+From: Hsiaoming Yang <[email protected]>
+Date: Tue, 4 Jun 2024 11:34:43 +0900
+Subject: fix: prevent OctKey to import ssh/rsa/pem keys
+
+https://github.com/lepture/authlib/issues/654
+
+Reviewed-By: Daniel Leidert <[email protected]>
+Origin: 
https://github.com/lepture/authlib/commit/3bea812acefebc9ee108aa24557be3ba8971daf1
+Bug: https://github.com/lepture/authlib/issues/654
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2024-37568
+Bug-Freexian-Security: 
https://deb.freexian.com/extended-lts/tracker/CVE-2024-37568
+---
+ authlib/jose/rfc7518/oct_key.py | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/authlib/jose/rfc7518/oct_key.py b/authlib/jose/rfc7518/oct_key.py
+index c2e16b1..fddb45d 100644
+--- a/authlib/jose/rfc7518/oct_key.py
++++ b/authlib/jose/rfc7518/oct_key.py
+@@ -6,6 +6,16 @@ from authlib.common.security import generate_token
+ from ..rfc7517 import Key
+ 
+ 
++POSSIBLE_UNSAFE_KEYS = (
++    b"-----BEGIN ",
++    b"---- BEGIN ",
++    b"ssh-rsa ",
++    b"ssh-dss ",
++    b"ssh-ed25519 ",
++    b"ecdsa-sha2-",
++)
++
++
+ class OctKey(Key):
+     """Key class of the ``oct`` key type."""
+ 
+@@ -65,6 +75,11 @@ class OctKey(Key):
+             key._dict_data = raw
+         else:
+             raw_key = to_bytes(raw)
++
++            # security check
++            if raw_key.startswith(POSSIBLE_UNSAFE_KEYS):
++                raise ValueError("This key may not be safe to import")
++
+             key = cls(raw_key=raw_key, options=options)
+         return key
+ 
diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch 
python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch
--- python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch    1970-01-01 
01:00:00.000000000 +0100
+++ python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch    2026-02-28 
03:41:12.000000000 +0100
@@ -0,0 +1,203 @@
+From: Hsiaoming Yang <[email protected]>
+Date: Tue, 9 Sep 2025 14:47:14 +0900
+Subject: [PATCH 1/3] fix(jose): validate crit header parameters
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+
+From: Hsiaoming Yang <[email protected]>
+Date: Wed, 10 Sep 2025 18:03:10 +0900
+Subject: [PATCH 2/3] fix(jose): validate crit header when deserialize
+
+
+From: Muhammad Noman Ilyas <[email protected]>
+Date: Sun, 14 Sep 2025 19:41:50 +0500
+Subject: [PATCH 3/3] fix(jose): Reject unprotected ‘crit’ and enforce type; 
add tests (#823)
+
+
+Reviewed-By: Daniel Leidert <[email protected]>
+Origin: 
https://github.com/authlib/authlib/commit/6b1813e4392eb7c168c276099ff7783b176479df
+Bug: https://github.com/authlib/authlib/security/advisories/GHSA-9ggr-2464-2j32
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-59420
+Bug-Freexian-Security: 
https://deb.freexian.com/extended-lts/tracker/CVE-2025-59420
+---
+ authlib/jose/errors.py      |  9 +++++++
+ authlib/jose/rfc7515/jws.py | 39 +++++++++++++++++++++++++++++-
+ tests/jose/test_jws.py      | 58 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 105 insertions(+), 1 deletion(-)
+
+diff --git a/authlib/jose/errors.py b/authlib/jose/errors.py
+index b93523f..6d6e722 100644
+--- a/authlib/jose/errors.py
++++ b/authlib/jose/errors.py
+@@ -34,6 +34,15 @@ class InvalidHeaderParameterNameError(JoseError):
+             description=description)
+ 
+ 
++class InvalidCritHeaderParameterNameError(JoseError):
++    error = "invalid_crit_header_parameter_name"
++
++    def __init__(self, name):
++        description = f"Invalid Header Parameter Name: {name}"
++        super(InvalidCritHeaderParameterNameError, self).__init__(
++            description=description)
++
++
+ class InvalidEncryptionAlgorithmForECDH1PUWithKeyWrappingError(JoseError):
+     error = 'invalid_encryption_algorithm_for_ECDH_1PU_with_key_wrapping'
+ 
+diff --git a/authlib/jose/rfc7515/jws.py b/authlib/jose/rfc7515/jws.py
+index 7e1cada..e2abc38 100644
+--- a/authlib/jose/rfc7515/jws.py
++++ b/authlib/jose/rfc7515/jws.py
+@@ -10,6 +10,7 @@ from authlib.jose.util import (
+ )
+ from authlib.jose.errors import (
+     DecodeError,
++    InvalidCritHeaderParameterNameError,
+     MissingAlgorithmError,
+     UnsupportedAlgorithmError,
+     BadSignatureError,
+@@ -61,6 +62,7 @@ class JsonWebSignature(object):
+         """
+         jws_header = JWSHeader(protected, None)
+         self._validate_private_headers(protected)
++        self._validate_crit_headers(protected)
+         algorithm, key = self._prepare_algorithm_key(protected, payload, key)
+ 
+         protected_segment = json_b64encode(jws_header.protected)
+@@ -95,6 +97,7 @@ class JsonWebSignature(object):
+             raise DecodeError('Not enough segments')
+ 
+         protected = _extract_header(protected_segment)
++        self._validate_crit_headers(protected)
+         jws_header = JWSHeader(protected, None)
+ 
+         payload = _extract_payload(payload_segment)
+@@ -132,6 +135,11 @@ class JsonWebSignature(object):
+ 
+         def _sign(jws_header):
+             self._validate_private_headers(jws_header)
++            # RFC 7515 §4.1.11: 'crit' MUST be integrity-protected.
++            # Reject if present in unprotected header, and validate only
++            # against the protected header parameters.
++            self._reject_unprotected_crit(jws_header.header)
++            self._validate_crit_headers(jws_header.protected)
+             _alg, _key = self._prepare_algorithm_key(jws_header, payload, key)
+ 
+             protected_segment = json_b64encode(jws_header.protected)
+@@ -273,6 +281,28 @@ class JsonWebSignature(object):
+                 if k not in names:
+                     raise InvalidHeaderParameterNameError(k)
+ 
++    def _reject_unprotected_crit(self, unprotected_header):
++        """Reject 'crit' when found in the unprotected header (RFC 7515 
§4.1.11)."""
++        if unprotected_header and "crit" in unprotected_header:
++            raise InvalidHeaderParameterNameError("crit")
++
++    def _validate_crit_headers(self, header):
++        if "crit" in header:
++            crit_headers = header["crit"]
++            # Type enforcement for robustness and predictable errors
++            if not isinstance(crit_headers, list) or not all(
++                isinstance(x, str) for x in crit_headers
++            ):
++                raise InvalidHeaderParameterNameError("crit")
++            names = self.REGISTERED_HEADER_PARAMETER_NAMES.copy()
++            if self._private_headers:
++                names = names.union(self._private_headers)
++            for k in crit_headers:
++                if k not in names:
++                    raise InvalidCritHeaderParameterNameError(k)
++                elif k not in header:
++                    raise InvalidCritHeaderParameterNameError(k)
++
+     def _validate_json_jws(self, payload_segment, payload, header_obj, key):
+         protected_segment = header_obj.get('protected')
+         if not protected_segment:
+@@ -287,7 +317,14 @@ class JsonWebSignature(object):
+         header = header_obj.get('header')
+         if header and not isinstance(header, dict):
+             raise DecodeError('Invalid "header" value')
+-
++        # RFC 7515 §4.1.11: 'crit' MUST be integrity-protected. If present in
++        # the unprotected header object, reject the JWS.
++        self._reject_unprotected_crit(header)
++
++        # Enforce must-understand semantics for names listed in protected
++        # 'crit'. This will also ensure each listed name is present in the
++        # protected header.
++        self._validate_crit_headers(protected)
+         jws_header = JWSHeader(protected, header)
+         algorithm, key = self._prepare_algorithm_key(jws_header, payload, key)
+         signing_input = b'.'.join([protected_segment, payload_segment])
+diff --git a/tests/jose/test_jws.py b/tests/jose/test_jws.py
+index 4df832f..a0df918 100644
+--- a/tests/jose/test_jws.py
++++ b/tests/jose/test_jws.py
+@@ -184,6 +184,64 @@ class JWSTest(unittest.TestCase):
+         s = jws.serialize(header, b'hello', 'secret')
+         self.assertIsInstance(s, dict)
+ 
++    def test_validate_crit_header_with_serialize(self):
++        jws = JsonWebSignature()
++        protected = {'alg': 'HS256', 'kid': '1', 'crit': ['kid']}
++        jws.serialize(protected, b'hello', 'secret')
++
++        protected = {'alg': 'HS256', 'crit': ['kid']}
++        self.assertRaises(
++              errors.InvalidCritHeaderParameterNameError,
++              jws.serialize, protected, b'hello', 'secret'
++        )
++
++        protected = {'alg': 'HS256', 'invalid': '1', 'crit': ['invalid']}
++        self.assertRaises(
++              errors.InvalidCritHeaderParameterNameError,
++              jws.serialize, protected, b'hello', 'secret'
++        )
++
++    def test_validate_crit_header_with_deserialize(self):
++        jws = JsonWebSignature()
++
++        case1 = 
"eyJhbGciOiJIUzI1NiIsImNyaXQiOlsia2lkIl19.aGVsbG8.RVimhJH2LRGAeHy0ZcbR9xsgKhzhxIBkHs7S_TDgWvc"
++        self.assertRaises(
++            errors.InvalidCritHeaderParameterNameError,
++            jws.deserialize, case1, 'secret'
++        )
++
++        case2 = (
++            
"eyJhbGciOiJIUzI1NiIsImludmFsaWQiOiIxIiwiY3JpdCI6WyJpbnZhbGlkIl19."
++            "aGVsbG8.ifW_D1AQWzggrpd8npcnmpiwMD9dp5FTX66lCkYFENM"
++        )
++        self.assertRaises(
++            errors.InvalidCritHeaderParameterNameError,
++            jws.deserialize, case2, 'secret'
++        )
++
++    def test_unprotected_crit_rejected_in_json_serialize(self):
++        jws = JsonWebSignature()
++        protected = {'alg': 'HS256', 'kid': 'a'}
++        # Place 'crit' in unprotected header; must be rejected
++        header = {'protected': protected, 'header': {'kid': 'a', 'crit': 
['kid']}}
++        self.assertRaises(
++              errors.InvalidHeaderParameterNameError,
++              jws.serialize_json, header, b'hello', 'secret'
++        )
++
++    def test_unprotected_crit_rejected_in_json_deserialize(self):
++        jws = JsonWebSignature()
++        protected = {'alg': 'HS256', 'kid': 'a'}
++        header = {'protected': protected, 'header': {'kid': 'a'}}
++        data = jws.serialize_json(header, b'hello', 'secret')
++        # Tamper by adding 'crit' into the unprotected header; must be 
rejected
++        data_tampered = dict(data)
++        data_tampered['header'] = {'kid': 'a', 'crit': ['kid']}
++        self.assertRaises(
++            errors.InvalidHeaderParameterNameError,
++            jws.deserialize_json, data_tampered, 'secret'
++        )
++
+     def test_ES512_alg(self):
+         jws = JsonWebSignature()
+         private_key = read_file_path('secp521r1-private.json')
diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch 
python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch
--- python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch    1970-01-01 
01:00:00.000000000 +0100
+++ python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch    2026-02-28 
03:41:12.000000000 +0100
@@ -0,0 +1,95 @@
+From: Hsiaoming Yang <[email protected]>
+Date: Thu, 2 Oct 2025 22:26:41 +0900
+Subject: [PATCH] fix(jose): add size limitation to prevent DoS
+
+Reviewed-By: Daniel Leidert <[email protected]>
+Origin: 
https://github.com/authlib/authlib/commit/867e3f87b072347a1ae9cf6983cc8bbf88447e5e
+Bug: https://github.com/authlib/authlib/security/advisories/GHSA-pq5p-34cr-23v9
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-61920
+Bug-Freexian-Security: 
https://deb.freexian.com/extended-lts/tracker/CVE-2025-61920
+---
+ authlib/jose/rfc7515/jws.py |  5 +++++
+ authlib/jose/util.py        |  6 ++++++
+ tests/jose/test_jws.py      | 25 +++++++++++++++++++++++++
+ 3 files changed, 36 insertions(+)
+
+diff --git a/authlib/jose/rfc7515/jws.py b/authlib/jose/rfc7515/jws.py
+index faaa740..7e1cada 100644
+--- a/authlib/jose/rfc7515/jws.py
++++ b/authlib/jose/rfc7515/jws.py
+@@ -27,6 +27,8 @@ class JsonWebSignature(object):
+         'typ', 'cty', 'crit'
+     ])
+ 
++    MAX_CONTENT_LENGTH: int = 256000
++
+     #: Defined available JWS algorithms in the registry
+     ALGORITHMS_REGISTRY = {}
+ 
+@@ -82,6 +84,9 @@ class JsonWebSignature(object):
+ 
+         .. _`Section 7.1`: https://tools.ietf.org/html/rfc7515#section-7.1
+         """
++        if len(s) > self.MAX_CONTENT_LENGTH:
++            raise ValueError("Serialization is too long.")
++
+         try:
+             s = to_bytes(s)
+             signing_input, signature_segment = s.rsplit(b'.', 1)
+diff --git a/authlib/jose/util.py b/authlib/jose/util.py
+index adc8ad8..3c86433 100644
+--- a/authlib/jose/util.py
++++ b/authlib/jose/util.py
+@@ -4,6 +4,9 @@ from authlib.jose.errors import DecodeError
+ 
+ 
+ def extract_header(header_segment, error_cls):
++    if len(header_segment) > 256000:
++        raise ValueError("Value of header is too long")
++
+     header_data = extract_segment(header_segment, error_cls, 'header')
+ 
+     try:
+@@ -17,6 +20,9 @@ def extract_header(header_segment, error_cls):
+ 
+ 
+ def extract_segment(segment, error_cls, name='payload'):
++    if len(segment) > 256000:
++        raise ValueError(f"Value of {name} is too long")
++
+     try:
+         return urlsafe_b64decode(segment)
+     except (TypeError, binascii.Error):
+diff --git a/tests/jose/test_jws.py b/tests/jose/test_jws.py
+index e531e5c..4df832f 100644
+--- a/tests/jose/test_jws.py
++++ b/tests/jose/test_jws.py
+@@ -204,3 +204,28 @@ class JWSTest(unittest.TestCase):
+         header, payload = data['header'], data['payload']
+         self.assertEqual(payload, b'hello')
+         self.assertEqual(header['alg'], 'ES256K')
++
++    def test_deserialize_exceeds_length(self):
++        jws = JsonWebSignature()
++        value = "aa" * 256000
++
++        # header exceeds length
++        s = value + '.' + value + '.' + value
++        self.assertRaises(
++              ValueError,
++              jws.deserialize, s, ''
++        )
++
++        # payload exceeds length
++        s = 'eyJhbGciOiJIUzI1NiJ9.' + value + '.' + value
++        self.assertRaises(
++              ValueError,
++              jws.deserialize, s, ''
++        )
++
++        # signature exceeds length
++        s = 'eyJhbGciOiJIUzI1NiJ9.YQ.' + value
++        self.assertRaises(
++              ValueError,
++              jws.deserialize, s, ''
++        )
diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch 
python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch
--- python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch    1970-01-01 
01:00:00.000000000 +0100
+++ python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch    2026-02-28 
03:41:12.000000000 +0100
@@ -0,0 +1,53 @@
+From: Hsiaoming Yang <[email protected]>
+Date: Wed, 24 Sep 2025 21:38:45 +0900
+Subject: [PATCH] fix(jose): add max size for JWE zip=DEF decompression
+
+Reviewed-By: Daniel Leidert <[email protected]>
+Origin: 
https://github.com/authlib/authlib/commit/4b5b5703394608124cd39e547cc7829feda05a13
+Bug: https://github.com/authlib/authlib/security/advisories/GHSA-g7f3-828f-7h7m
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-62706
+Bug-Freexian-Security: 
https://deb.freexian.com/extended-lts/tracker/CVE-2025-62706
+---
+ authlib/jose/rfc7518/jwe_zips.py | 20 ++++++++++++++++----
+ 1 file changed, 16 insertions(+), 4 deletions(-)
+
+diff --git a/authlib/jose/rfc7518/jwe_zips.py 
b/authlib/jose/rfc7518/jwe_zips.py
+index 2396861..99dcf68 100644
+--- a/authlib/jose/rfc7518/jwe_zips.py
++++ b/authlib/jose/rfc7518/jwe_zips.py
+@@ -2,19 +2,31 @@ import zlib
+ from ..rfc7516 import JWEZipAlgorithm, JsonWebEncryption
+ 
+ 
++GZIP_HEAD = bytes([120, 156])
++MAX_SIZE = 250 * 1024
++
++
+ class DeflateZipAlgorithm(JWEZipAlgorithm):
+     name = 'DEF'
+     description = 'DEFLATE'
+ 
+-    def compress(self, s):
++    def compress(self, s: bytes) -> bytes:
+         """Compress bytes data with DEFLATE algorithm."""
+         data = zlib.compress(s)
+-        # drop gzip headers and tail
++        # https://datatracker.ietf.org/doc/html/rfc1951
++        # since DEF is always gzip, we can drop gzip headers and tail
+         return data[2:-4]
+ 
+-    def decompress(self, s):
++    def decompress(self, s: bytes) -> bytes:
+         """Decompress DEFLATE bytes data."""
+-        return zlib.decompress(s, -zlib.MAX_WBITS)
++        if s.startswith(GZIP_HEAD):
++            decompressor = zlib.decompressobj()
++        else:
++            decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
++        value = decompressor.decompress(s, MAX_SIZE)
++        if decompressor.unconsumed_tail:
++            raise ValueError(f"Decompressed string exceeds {MAX_SIZE} bytes")
++        return value
+ 
+ 
+ def register_jwe_rfc7518():
diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch 
python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch
--- python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch    1970-01-01 
01:00:00.000000000 +0100
+++ python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch    2026-02-28 
03:41:12.000000000 +0100
@@ -0,0 +1,151 @@
+From: Hsiaoming Yang <[email protected]>
+Date: Fri, 12 Dec 2025 16:37:44 +0900
+Subject: [PATCH] Merge commit from fork
+
+Reviewed-By: Daniel Leidert <[email protected]>
+Origin: 
https://github.com/authlib/authlib/commit/2808378611dd6fb2532b189a9087877d8f0c0489
+Bug: https://github.com/authlib/authlib/security/advisories/GHSA-fg6f-75jq-6523
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-68158
+Bug-Freexian-Security: 
https://deb.freexian.com/extended-lts/tracker/CVE-2025-68158
+---
+ .../base_client/framework_integration.py           | 25 ++++++------
+ tests/clients/test_flask/test_oauth_client.py      | 47 +++++++++++++++++++++-
+ 2 files changed, 58 insertions(+), 14 deletions(-)
+
+diff --git a/authlib/integrations/base_client/framework_integration.py 
b/authlib/integrations/base_client/framework_integration.py
+index 91028b8..f20f98c 100644
+--- a/authlib/integrations/base_client/framework_integration.py
++++ b/authlib/integrations/base_client/framework_integration.py
+@@ -20,11 +20,9 @@ class FrameworkIntegration(object):
+ 
+     def _clear_session_state(self, session):
+         now = time.time()
++        prefix = f"_state_{self.name}"
+         for key in dict(session):
+-            if '_authlib_' in key:
+-                # TODO: remove in future
+-                session.pop(key)
+-            elif key.startswith('_state_'):
++            if key.startswith(prefix):
+                 value = session[key]
+                 exp = value.get('exp')
+                 if not exp or exp < now:
+@@ -32,29 +30,32 @@ class FrameworkIntegration(object):
+ 
+     def get_state_data(self, session, state):
+         key = f'_state_{self.name}_{state}'
++        session_data = session.get(key)
++        if not session_data:
++            return None
+         if self.cache:
+-            value = self._get_cache_data(key)
++            cached_value = self._get_cache_data(key)
+         else:
+-            value = session.get(key)
+-        if value:
+-            return value.get('data')
++            cached_value = session_data
++        if cached_value:
++            return cached_value.get("data")
+         return None
+ 
+     def set_state_data(self, session, state, data):
+         key = f'_state_{self.name}_{state}'
++        now = time.time()
+         if self.cache:
+             self.cache.set(key, json.dumps({'data': data}), self.expires_in)
++            session[key] = {"exp": now + self.expires_in}
+         else:
+-            now = time.time()
+             session[key] = {'data': data, 'exp': now + self.expires_in}
+ 
+     def clear_state_data(self, session, state):
+         key = f'_state_{self.name}_{state}'
+         if self.cache:
+             self.cache.delete(key)
+-        else:
+-            session.pop(key, None)
+-            self._clear_session_state(session)
++        session.pop(key, None)
++        self._clear_session_state(session)
+ 
+     def update_token(self, token, refresh_token=None, access_token=None):
+         raise NotImplementedError()
+diff --git a/tests/clients/test_flask/test_oauth_client.py 
b/tests/clients/test_flask/test_oauth_client.py
+index 0789822..aa517b6 100644
+--- a/tests/clients/test_flask/test_oauth_client.py
++++ b/tests/clients/test_flask/test_oauth_client.py
+@@ -132,9 +132,13 @@ class FlaskOAuthTest(TestCase):
+                 self.assertEqual(resp.status_code, 302)
+                 url = resp.headers.get('Location')
+                 self.assertIn('oauth_token=foo', url)
++                session_data = session["_state_dev_foo"]
++                self.assertIn('exp', session_data)
++                self.assertNotIn('data', session_data)
+ 
+         with app.test_request_context('/?oauth_token=foo'):
+             with mock.patch('requests.sessions.Session.send') as send:
++                session["_state_dev_foo"] = session_data
+                 send.return_value = 
mock_send_value('oauth_token=a&oauth_token_secret=b')
+                 token = client.authorize_access_token()
+                 self.assertEqual(token['oauth_token'], 'a')
+@@ -186,7 +190,43 @@ class FlaskOAuthTest(TestCase):
+         session = oauth.dev._get_oauth_client()
+         self.assertIsNotNone(session.update_token)
+ 
+-    def test_oauth2_authorize(self):
++    def test_oauth2_authorize_cache(self):
++        app = Flask(__name__)
++        app.secret_key = "!"
++        cache = SimpleCache()
++        oauth = OAuth(app, cache=cache)
++        client = oauth.register(
++            "dev",
++            client_id="dev",
++            client_secret="dev",
++            api_base_url="https://resource.test/api";,
++            access_token_url="https://provider.test/token";,
++            authorize_url="https://provider.test/authorize";,
++        )
++        with app.test_request_context():
++            resp = client.authorize_redirect("https://client.test/callback";)
++            self.assertEqual(resp.status_code, 302)
++            url = resp.headers.get("Location")
++            self.assertIn('state=', url)
++            state = dict(url_decode(urlparse.urlparse(url).query))["state"]
++            self.assertIsNotNone(state)
++            session_data = session[f"_state_dev_{state}"]
++            self.assertIn('exp', session_data)
++            self.assertNotIn('data', session_data)
++
++        with app.test_request_context(path=f"/?code=a&state={state}"):
++            # session is cleared in tests
++            session[f"_state_dev_{state}"] = session_data
++
++            with mock.patch("requests.sessions.Session.send") as send:
++                send.return_value = mock_send_value(get_bearer_token())
++                token = client.authorize_access_token()
++                self.assertEqual(token['access_token'], 'a')
++
++        with app.test_request_context():
++            self.assertEqual(client.token, None)
++
++    def test_oauth2_authorize_session(self):
+         app = Flask(__name__)
+         app.secret_key = '!'
+         oauth = OAuth(app)
+@@ -207,10 +247,13 @@ class FlaskOAuthTest(TestCase):
+             state = dict(url_decode(urlparse.urlparse(url).query))['state']
+             self.assertIsNotNone(state)
+             data = session[f'_state_dev_{state}']
++            session_data = session[f"_state_dev_{state}"]
++            self.assertIn('exp', session_data)
++            self.assertIn('data', session_data)
+ 
+         with app.test_request_context(path=f'/?code=a&state={state}'):
+             # session is cleared in tests
+-            session[f'_state_dev_{state}'] = data
++            session[f"_state_dev_{state}"] = session_data
+ 
+             with mock.patch('requests.sessions.Session.send') as send:
+                 send.return_value = mock_send_value(get_bearer_token())
diff -Nru python-authlib-1.2.0/debian/patches/series 
python-authlib-1.2.0/debian/patches/series
--- python-authlib-1.2.0/debian/patches/series  2022-12-09 23:08:37.000000000 
+0100
+++ python-authlib-1.2.0/debian/patches/series  2026-02-28 03:41:12.000000000 
+0100
@@ -1,2 +1,7 @@
 sphinx-default-theme
 sphinx-3rdparty-assets
+CVE-2025-62706.patch
+CVE-2025-61920.patch
+CVE-2025-59420.patch
+CVE-2025-68158.patch
+CVE-2024-37568.patch
diff -Nru python-authlib-1.2.0/debian/tests/control 
python-authlib-1.2.0/debian/tests/control
--- python-authlib-1.2.0/debian/tests/control   2022-12-09 23:08:37.000000000 
+0100
+++ python-authlib-1.2.0/debian/tests/control   2026-02-28 03:41:12.000000000 
+0100
@@ -2,12 +2,16 @@
 Depends:
  python3-all,
  python3-authlib,
+ python3-cachelib,
  python3-cryptography,
  python3-django,
  python3-flask,
  python3-flask-sqlalchemy,
+ python3-httpx,
  python3-itsdangerous,
  python3-mock,
  python3-pytest,
+ python3-pytest-asyncio,
  python3-pytest-django,
- python3-requests
+ python3-requests,
+ python3-starlette,
diff -Nru python-authlib-1.2.0/debian/tests/unittests3 
python-authlib-1.2.0/debian/tests/unittests3
--- python-authlib-1.2.0/debian/tests/unittests3        2022-12-09 
23:08:37.000000000 +0100
+++ python-authlib-1.2.0/debian/tests/unittests3        2026-02-28 
03:41:12.000000000 +0100
@@ -4,6 +4,7 @@
 pys="$(py3versions -r 2> /dev/null)"
 
 cp -a setup.cfg tests "$AUTOPKGTEST_TMP"
+echo "asyncio_mode = auto" >> "$AUTOPKGTEST_TMP/setup.cfg"
 
 cd "$AUTOPKGTEST_TMP"
 
@@ -12,4 +13,6 @@
        $py -m pytest tests/core 2>&1
        DJANGO_SETTINGS_MODULE=tests.django.settings $py -m pytest tests/django 
2>&1
        $py -m pytest tests/flask 2>&1
+       DJANGO_SETTINGS_MODULE=tests.clients.test_django.settings $py -m pytest 
tests/clients 2>&1
+       $py -m pytest tests/jose 2>&1
 done

Reply via email to