Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-joserfc for openSUSE:Factory 
checked in at 2026-10-20 12:37:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-joserfc (Old)
 and      /work/SRC/openSUSE:Factory/.python-joserfc.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-joserfc"

Tue Oct 20 12:37:18 2026 rev:16 rq:1386102 version:1.7.7

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-joserfc/python-joserfc.changes    
2026-09-02 17:01:03.899835628 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-joserfc.new.3698736/python-joserfc.changes   
    2026-10-20 12:37:58.869668923 +0200
@@ -1,0 +2,17 @@
+Mon Oct 19 01:02:59 UTC 2026 - Steve Kowalik <[email protected]>
+
+- Update to 1.7.7:
+  * JWK: Reject recognized DER, PEM, and SSH asymmetric public keys when
+    importing octkeys, including public key bytes embedded in an oct JWK.
+    Preserve warnings for input that only resembles an asymmetric key.
+  * Replace legacy union type hints with | syntax.
+  * JWE: Limit base64url-encoded additional authenticated data to 65,536
+    bytes before copying or decoding it in JSON serialization. Configure the
+    limit with JWERegistry(max_aad_length=...).
+  * JWT: Reject non-string jti claims, including arrays containing an
+    expected token ID, even when no claim options are configured.
+  * JWT: Report the correct claim name for invalid iss values.
+  * JWS & JWE: Validate compact token size before splitting it into segments.
+  * JWK: Handle key set equality comparisons with unrelated types.
+
+-------------------------------------------------------------------
@@ -14 +31 @@
-    empty.
+    empty. (CVE-2026-102946, bsc#1285840)

Old:
----
  joserfc-1.7.5.tar.gz

New:
----
  joserfc-1.7.7.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-joserfc.spec ++++++
--- /var/tmp/diff_new_pack.2CwMgQ/_old  2026-10-20 12:38:00.276727906 +0200
+++ /var/tmp/diff_new_pack.2CwMgQ/_new  2026-10-20 12:38:00.279728031 +0200
@@ -18,12 +18,13 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-joserfc
-Version:        1.7.5
+Version:        1.7.7
 Release:        0
 Summary:        The ultimate Python library for JOSE RFCs
 License:        BSD-3-Clause
 URL:            https://github.com/authlib/joserfc
 Source:         
https://files.pythonhosted.org/packages/source/j/joserfc/joserfc-%{version}.tar.gz
+BuildRequires:  %{python_module base >= 3.10}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}

++++++ joserfc-1.7.5.tar.gz -> joserfc-1.7.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/PKG-INFO new/joserfc-1.7.7/PKG-INFO
--- old/joserfc-1.7.5/PKG-INFO  2026-08-29 15:05:19.208686000 +0200
+++ new/joserfc-1.7.7/PKG-INFO  2026-10-19 02:30:02.603546100 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: joserfc
-Version: 1.7.5
+Version: 1.7.7
 Summary: The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, 
JWA, JWT
 Author-email: Hsiaoming Yang <[email protected]>
 License: BSD-3-Clause
@@ -21,6 +21,7 @@
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Security
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/docs/changelog.rst 
new/joserfc-1.7.7/docs/changelog.rst
--- old/joserfc-1.7.5/docs/changelog.rst        2026-08-29 15:05:08.757169700 
+0200
+++ new/joserfc-1.7.7/docs/changelog.rst        2026-10-19 02:29:58.466911300 
+0200
@@ -12,6 +12,30 @@
 .. module:: joserfc
     :noindex:
 
+1.7.7
+-----
+
+**Released on October 19, 2026**
+
+- **JWK**: Reject recognized DER, PEM, and SSH asymmetric public keys when
+  importing oct keys, including public key bytes embedded in an oct JWK.
+  Preserve warnings for input that only resembles an asymmetric key.
+- Replace legacy union type hints with ``|`` syntax.
+
+1.7.6
+-----
+
+**Released on October 19, 2026**
+
+- **JWE**: Limit base64url-encoded additional authenticated data to 65,536 
bytes
+  before copying or decoding it in JSON serialization. Configure the limit with
+  ``JWERegistry(max_aad_length=...)``.
+- **JWT**: Reject non-string ``jti`` claims, including arrays containing an 
expected
+  token ID, even when no claim options are configured.
+- **JWT**: Report the correct claim name for invalid ``iss`` values.
+- **JWS & JWE**: Validate compact token size before splitting it into segments.
+- **JWK**: Handle key set equality comparisons with unrelated types.
+
 1.7.5
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/docs/guide/jwk.rst 
new/joserfc-1.7.7/docs/guide/jwk.rst
--- old/joserfc-1.7.5/docs/guide/jwk.rst        2026-08-29 15:05:08.757520200 
+0200
+++ new/joserfc-1.7.7/docs/guide/jwk.rst        2026-10-19 02:29:58.467604200 
+0200
@@ -63,6 +63,10 @@
 ~~~~~~~~~~~~~~~~~~~
 
 You can import an ``OctKey`` from string, bytes and a JWK (in dict).
+Recognized DER, PEM, and SSH asymmetric public keys are rejected with
+``ValueError``, including when their bytes are embedded in an oct JWK's ``k``
+parameter. Use a shared secret for HMAC algorithms and import asymmetric public
+keys with their corresponding key type, such as ``RSAKey`` for RSA signatures.
 
 .. code-block:: python
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/pyproject.toml 
new/joserfc-1.7.7/pyproject.toml
--- old/joserfc-1.7.5/pyproject.toml    2026-08-29 15:05:08.759468000 +0200
+++ new/joserfc-1.7.7/pyproject.toml    2026-10-19 02:29:58.471396700 +0200
@@ -23,6 +23,7 @@
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
     "Programming Language :: Python :: 3.14",
+    "Programming Language :: Python :: 3.15",
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
     "Topic :: Security",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/__init__.py 
new/joserfc-1.7.7/src/joserfc/__init__.py
--- old/joserfc-1.7.5/src/joserfc/__init__.py   2026-08-29 15:05:08.759605600 
+0200
+++ new/joserfc-1.7.7/src/joserfc/__init__.py   2026-10-19 02:29:58.471770500 
+0200
@@ -1,4 +1,4 @@
-__version__ = "1.7.5"
+__version__ = "1.7.7"
 __homepage__ = "https://jose.authlib.org/en/";
 __author__ = "Hsiaoming Yang <[email protected]>"
 __license__ = "BSD-3-Clause"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_keys.py 
new/joserfc-1.7.7/src/joserfc/_keys.py
--- old/joserfc-1.7.5/src/joserfc/_keys.py      2026-08-29 15:05:08.759626900 
+0200
+++ new/joserfc-1.7.7/src/joserfc/_keys.py      2026-10-19 02:29:58.471852300 
+0200
@@ -24,7 +24,7 @@
     "KeySetSerialization",
 ]
 
-Key = t.Union[OctKey, RSAKey, ECKey, OKPKey]
+Key = OctKey | RSAKey | ECKey | OKPKey
 
 
 class JWKRegistry:
@@ -124,7 +124,8 @@
         return bool(self.keys)
 
     def __eq__(self, other: t.Any) -> bool:
-        assert isinstance(other, KeySet)
+        if not isinstance(other, KeySet):
+            return False
         return self.keys == other.keys
 
     def as_dict(self, private: bool = False, **params: t.Any) -> 
KeySetSerialization:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7515/registry.py 
new/joserfc-1.7.7/src/joserfc/_rfc7515/registry.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7515/registry.py  2026-08-29 
15:05:08.759683600 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7515/registry.py  2026-10-19 
02:29:58.471998200 +0200
@@ -100,6 +100,11 @@
         if self.strict_check_header:
             check_supported_header(self.header_registry, header)
 
+    def validate_compact_value_size(self, value: bytes) -> None:
+        max_length = self.max_header_length + self.max_payload_length + 
self.max_signature_length
+        if value and len(value) > max_length:
+            raise ExceededSizeError(f"Value size exceeds {max_length} bytes.")
+
     def validate_header_size(self, header: bytes) -> None:
         if header and len(header) > self.max_header_length:
             raise ExceededSizeError(f"Header size exceeds 
{self.max_header_length} bytes.")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7516/compact.py 
new/joserfc-1.7.7/src/joserfc/_rfc7516/compact.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7516/compact.py   2026-08-29 
15:05:08.759827400 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7516/compact.py   2026-10-19 
02:29:58.472337500 +0200
@@ -34,6 +34,8 @@
 
 
 def extract_compact(value: bytes, registry: JWERegistry) -> CompactEncryption:
+    registry.validate_compact_value_size(value)
+
     parts = value.split(b".")
     if len(parts) != 5:
         raise ValueError("Invalid JSON Web Encryption")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7516/json.py 
new/joserfc-1.7.7/src/joserfc/_rfc7516/json.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7516/json.py      2026-08-29 
15:05:08.759827400 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7516/json.py      2026-10-19 
02:29:58.472337500 +0200
@@ -121,6 +121,7 @@
         "tag": urlsafe_b64decode(base64_segments["tag"]),
     }
     if "aad" in data:
+        registry.validate_aad_size(data["aad"])
         base64_segments["aad"] = to_bytes(data["aad"])
         aad = urlsafe_b64decode(base64_segments["aad"])
         bytes_segments["aad"] = aad
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7516/message.py 
new/joserfc-1.7.7/src/joserfc/_rfc7516/message.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7516/message.py   2026-08-29 
15:05:08.759827400 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7516/message.py   2026-10-19 
02:29:58.472337500 +0200
@@ -1,5 +1,5 @@
 import secrets
-from typing import Any, Union
+from typing import Any
 
 from .models import (
     CompactEncryption,
@@ -33,7 +33,7 @@
     "perform_decrypt",
 ]
 
-EncryptionData = Union[CompactEncryption, GeneralJSONEncryption, 
FlattenedJSONEncryption]
+EncryptionData = CompactEncryption | GeneralJSONEncryption | 
FlattenedJSONEncryption
 
 
 def perform_encrypt(obj: EncryptionData, registry: JWERegistry) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7516/models.py 
new/joserfc-1.7.7/src/joserfc/_rfc7516/models.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7516/models.py    2026-08-29 
15:05:08.759827400 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7516/models.py    2026-10-19 
02:29:58.472337500 +0200
@@ -27,7 +27,7 @@
 class Recipient(t.Generic[KeyType]):
     def __init__(
         self,
-        parent: t.Union["CompactEncryption", "GeneralJSONEncryption", 
"FlattenedJSONEncryption"],
+        parent: "CompactEncryption | GeneralJSONEncryption | 
FlattenedJSONEncryption",
         header: Header | None = None,
         recipient_key: KeyType | None = None,
     ):
@@ -103,11 +103,11 @@
     #: protected header in dict
     protected: Header
     #: the plaintext in bytes
-    plaintext: t.Optional[bytes]
+    plaintext: bytes | None
     #: unprotected header in dict
-    unprotected: t.Optional[Header]
+    unprotected: Header | None
     #: an optional additional authenticated data
-    aad: t.Optional[bytes]
+    aad: bytes | None
     #: a list of recipients
     recipients: list[Recipient[t.Any]]
 
@@ -308,7 +308,7 @@
 class JWEKeyAgreement(KeyManagement, metaclass=ABCMeta):
     key_types = ["EC", "OKP"]
     tag_aware: bool = False
-    key_wrapping: t.Optional[JWEKeyWrapping]
+    key_wrapping: JWEKeyWrapping | None
 
     def prepare_ephemeral_key(self, recipient: Recipient[ECKey]) -> None:
         recipient_key = recipient.recipient_key
@@ -342,4 +342,4 @@
         raise NotImplementedError()
 
 
-JWEAlgModel = t.Union[JWEKeyEncryption, JWEKeyWrapping, JWEKeyAgreement, 
JWEDirectEncryption]
+JWEAlgModel = JWEKeyEncryption | JWEKeyWrapping | JWEKeyAgreement | 
JWEDirectEncryption
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7516/registry.py 
new/joserfc-1.7.7/src/joserfc/_rfc7516/registry.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7516/registry.py  2026-08-29 
15:05:08.759827400 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7516/registry.py  2026-10-19 
02:29:58.472337500 +0200
@@ -22,7 +22,7 @@
     "default_registry",
 ]
 
-JWEAlgorithm = t.Union[JWEAlgModel, JWEEncModel, JWEZipModel]
+JWEAlgorithm = JWEAlgModel | JWEEncModel | JWEZipModel
 
 AlgorithmsDict = t.TypedDict(
     "AlgorithmsDict",
@@ -44,6 +44,7 @@
     :param verify_all_recipients: validating all recipients in a JSON 
serialization
     :param strict_check_header: only allow header key in the registry to be 
used
     :param max_recipients: max number of recipients in JWE General JSON 
Serialization
+    :param max_aad_length: max base64url-encoded additional authenticated data 
size in bytes
     """
 
     algorithms: AlgorithmsDict = {
@@ -63,6 +64,8 @@
     max_ciphertext_length: int = 65536  # 64KB
     #: max auth tag's size in bytes
     max_auth_tag_length: int = 64
+    #: max base64url-encoded additional authenticated data size in bytes
+    max_aad_length: int = 65536  # 64KB
     #: max number of recipients in JWE General JSON Serialization
     max_recipients: int = 16
 
@@ -73,6 +76,7 @@
         verify_all_recipients: bool = True,
         strict_check_header: bool = True,
         max_recipients: int | None = None,
+        max_aad_length: int | None = None,
     ):
         self.header_registry: HeaderRegistryDict = {}
         self.header_registry.update(JWE_HEADER_REGISTRY)
@@ -83,6 +87,8 @@
         self.strict_check_header = strict_check_header
         if max_recipients is not None:
             self.max_recipients = max_recipients
+        if max_aad_length is not None:
+            self.max_aad_length = max_aad_length
 
     @classmethod
     def register(cls, model: JWEAlgorithm) -> None:
@@ -106,6 +112,17 @@
         elif self.strict_check_header:
             check_supported_header(self.header_registry, header)
 
+    def validate_compact_value_size(self, value: bytes) -> None:
+        max_length = (
+            self.max_protected_header_length
+            + self.max_encrypted_key_length
+            + self.max_initialization_vector_length
+            + self.max_ciphertext_length
+            + self.max_auth_tag_length
+        )
+        if value and len(value) > max_length:
+            raise ExceededSizeError(f"Value size exceeds {max_length} bytes.")
+
     def validate_protected_header_size(self, header: bytes) -> None:
         if header and len(header) > self.max_protected_header_length:
             raise ExceededSizeError(f"Header size exceeds 
{self.max_protected_header_length} bytes.")
@@ -128,6 +145,10 @@
         if tag and len(tag) > self.max_auth_tag_length:
             raise ExceededSizeError(f"Auth tag size exceeds 
{self.max_auth_tag_length} bytes.")
 
+    def validate_aad_size(self, aad: str | bytes) -> None:
+        if len(aad) > self.max_aad_length:
+            raise ExceededSizeError(f"AAD size exceeds {self.max_aad_length} 
bytes.")
+
     def validate_recipient_count(self, recipients: Collection[t.Any]) -> None:
         if len(recipients) > self.max_recipients:
             raise ExceededSizeError(f"Recipient count exceeds 
{self.max_recipients}.")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7517/models.py 
new/joserfc-1.7.7/src/joserfc/_rfc7517/models.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7517/models.py    2026-08-29 
15:05:08.760023000 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7517/models.py    2026-10-19 
02:29:58.472815000 +0200
@@ -126,12 +126,12 @@
     @property
     def kid(self) -> str | None:
         """The "kid" value of the JSON Web Key."""
-        return t.cast(t.Optional[str], self.get("kid"))
+        return t.cast(str | None, self.get("kid"))
 
     @property
     def alg(self) -> str | None:
         """The "alg" value of the JSON Web Key."""
-        return t.cast(t.Optional[str], self.get("alg"))
+        return t.cast(str | None, self.get("alg"))
 
     @property
     def raw_value(self) -> t.Any:
@@ -316,7 +316,7 @@
 
 class AsymmetricKey(BaseKey[NativePrivateKey, NativePublicKey], 
metaclass=ABCMeta):
     @property
-    def raw_value(self) -> t.Union[NativePublicKey, NativePrivateKey]:
+    def raw_value(self) -> NativePublicKey | NativePrivateKey:
         return self._raw_value
 
     def as_bytes(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7517/types.py 
new/joserfc-1.7.7/src/joserfc/_rfc7517/types.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7517/types.py     2026-08-29 
15:05:08.760023000 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7517/types.py     2026-10-19 
02:29:58.472815000 +0200
@@ -3,10 +3,10 @@
 __all__ = ["DictKey", "AnyKey", "KeyParameters"]
 
 #: JSON Web Key in dict
-DictKey = dict[str, t.Union[str, list[str]]]
+DictKey = dict[str, str | list[str]]
 
 #: Key in str, bytes and dict
-AnyKey = t.Union[str, bytes, DictKey]
+AnyKey = str | bytes | DictKey
 
 #: extra key parameters for JWK
 KeyParameters = t.TypedDict(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7518/oct_key.py 
new/joserfc-1.7.7/src/joserfc/_rfc7518/oct_key.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7518/oct_key.py   2026-08-29 
15:05:08.760132800 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7518/oct_key.py   2026-10-19 
02:29:58.473082800 +0200
@@ -2,6 +2,12 @@
 from typing import Any
 import secrets
 import warnings
+from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography.hazmat.primitives.serialization import (
+    load_der_public_key,
+    load_pem_public_key,
+    load_ssh_public_key,
+)
 from ..errors import SecurityWarning
 from ..util import (
     to_bytes,
@@ -31,11 +37,29 @@
 
     @classmethod
     def import_from_dict(cls, value: DictKey) -> bytes:
-        return urlsafe_b64decode(to_bytes(value["k"]))
+        return cls.import_from_bytes(urlsafe_b64decode(to_bytes(value["k"])))
 
     @classmethod
     def import_from_bytes(cls, value: bytes, password: Any | None = None) -> 
bytes:
-        # security check
+        # Reject recognized public keys, while retaining warnings for input
+        # that only resembles an asymmetric key (see issue #32).
+        if value.startswith(b"0"):
+            loader = load_der_public_key
+        elif value.startswith(b"-----BEGIN "):
+            loader = load_pem_public_key
+        elif value.startswith((b"ssh-", b"ecdsa-sha2-")):
+            loader = load_ssh_public_key
+        else:
+            loader = None
+
+        if loader is not None:
+            try:
+                loader(value)
+            except (ValueError, UnsupportedAlgorithm):
+                pass
+            else:
+                raise ValueError("Asymmetric public keys must not be used as 
oct keys")
+
         if value.startswith(POSSIBLE_UNSAFE_KEYS):
             warnings.warn("This key should not be used as an oct key", 
SecurityWarning)
         return value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7519/claims.py 
new/joserfc-1.7.7/src/joserfc/_rfc7519/claims.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7519/claims.py    2026-08-29 
15:05:08.760463700 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7519/claims.py    2026-10-19 
02:29:58.473762500 +0200
@@ -127,7 +127,7 @@
         value.  Use of this claim is OPTIONAL.
         """
         if not isinstance(value, str):
-            raise InvalidClaimError("str", "Claim 'str' must be a StringOrURI 
value")
+            raise InvalidClaimError("iss", "Claim 'iss' must be a StringOrURI 
value")
         self.check_value("iss", value)
 
     def validate_sub(self, value: str) -> None:
@@ -143,6 +143,14 @@
             raise InvalidClaimError("sub", "Claim 'sub' must be a StringOrURI 
value")
         self.check_value("sub", value)
 
+    def validate_jti(self, value: str) -> None:
+        """The "jti" (JWT ID) claim provides a unique identifier for the JWT.
+        Its value MUST be a case-sensitive string. Use of this claim is 
OPTIONAL.
+        """
+        if not isinstance(value, str):
+            raise InvalidClaimError("jti", "Claim 'jti' must be a string")
+        self.check_value("jti", value)
+
     def validate_aud(self, value: str | list[str]) -> None:
         """The "aud" (audience) claim identifies the recipients that the JWT is
         intended for.  Each principal intended to process the JWT MUST
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc7797/compact.py 
new/joserfc-1.7.7/src/joserfc/_rfc7797/compact.py
--- old/joserfc-1.7.5/src/joserfc/_rfc7797/compact.py   2026-08-29 
15:05:08.760578600 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc7797/compact.py   2026-10-19 
02:29:58.474037200 +0200
@@ -37,13 +37,15 @@
     :param registry: optional JWSRegistry instance
     :raise DecodeError: when decoding fails
     """
+    if registry is None:
+        registry = default_registry
+
+    registry.validate_compact_value_size(value)
+
     parts = value.split(b".")
     if len(parts) != 3:
         raise DecodeError("Invalid JSON Web Signature")
 
-    if registry is None:
-        registry = default_registry
-
     header_segment, payload_segment, signature_segment = parts
 
     registry.validate_header_size(header_segment)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc8037/okp_key.py 
new/joserfc-1.7.7/src/joserfc/_rfc8037/okp_key.py
--- old/joserfc-1.7.5/src/joserfc/_rfc8037/okp_key.py   2026-08-29 
15:05:08.760650400 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc8037/okp_key.py   2026-10-19 
02:29:58.474254600 +0200
@@ -22,8 +22,8 @@
 
 
 LiteralCurves = t.Literal["Ed25519", "Ed448", "X25519", "X448"]
-PublicOKPKey = t.Union[Ed25519PublicKey, Ed448PublicKey, X25519PublicKey, 
X448PublicKey]
-PrivateOKPKey = t.Union[Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, 
X448PrivateKey]
+PublicOKPKey = Ed25519PublicKey | Ed448PublicKey | X25519PublicKey | 
X448PublicKey
+PrivateOKPKey = Ed25519PrivateKey | Ed448PrivateKey | X25519PrivateKey | 
X448PrivateKey
 OKPDictKey = t.TypedDict(
     "OKPDictKey",
     {
@@ -126,7 +126,7 @@
 
     def exchange_derive_key(self, key: "OKPKey") -> bytes:
         # used in ECDH-ES Algorithms
-        pubkey = t.cast(t.Union[X25519PublicKey, X448PublicKey], 
key.get_op_key("deriveKey"))
+        pubkey = t.cast(X25519PublicKey | X448PublicKey, 
key.get_op_key("deriveKey"))
 
         # this if else logic is used for type hints
         if isinstance(self.private_key, X25519PrivateKey) and 
isinstance(pubkey, X25519PublicKey):
@@ -297,7 +297,7 @@
         return _wrap_key(cls, raw_key, private, auto_kid, parameters)
 
 
-def get_key_curve(key: t.Union[PublicOKPKey, PrivateOKPKey]) -> LiteralCurves:
+def get_key_curve(key: PublicOKPKey | PrivateOKPKey) -> LiteralCurves:
     if isinstance(key, (Ed25519PublicKey, Ed25519PrivateKey)):
         return "Ed25519"
     elif isinstance(key, (Ed448PublicKey, Ed448PrivateKey)):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/_rfc9864/jws_eddsa.py 
new/joserfc-1.7.7/src/joserfc/_rfc9864/jws_eddsa.py
--- old/joserfc-1.7.5/src/joserfc/_rfc9864/jws_eddsa.py 2026-08-29 
15:05:08.760749800 +0200
+++ new/joserfc-1.7.7/src/joserfc/_rfc9864/jws_eddsa.py 2026-10-19 
02:29:58.474574300 +0200
@@ -21,11 +21,11 @@
             raise InvalidKeyCurveError(f"Key for '{self.name}' not supported, 
only '{self.curve}' allowed")
 
     def sign(self, msg: bytes, key: OKPKey) -> bytes:
-        op_key = t.cast(t.Union[Ed25519PrivateKey, Ed448PrivateKey], 
key.get_op_key("sign"))
+        op_key = t.cast(Ed25519PrivateKey | Ed448PrivateKey, 
key.get_op_key("sign"))
         return op_key.sign(msg)
 
     def verify(self, msg: bytes, sig: bytes, key: OKPKey) -> bool:
-        op_key = t.cast(t.Union[Ed25519PublicKey, Ed448PublicKey], 
key.get_op_key("verify"))
+        op_key = t.cast(Ed25519PublicKey | Ed448PublicKey, 
key.get_op_key("verify"))
         try:
             op_key.verify(sig, msg)
             return True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/jwe.py 
new/joserfc-1.7.7/src/joserfc/jwe.py
--- old/joserfc-1.7.5/src/joserfc/jwe.py        2026-08-29 15:05:08.760791000 
+0200
+++ new/joserfc-1.7.7/src/joserfc/jwe.py        2026-10-19 02:29:58.474686100 
+0200
@@ -1,4 +1,4 @@
-from typing import cast, overload, Union
+from typing import cast, overload
 from collections.abc import Collection
 from ._rfc7516.types import (
     GeneralJSONSerialization,
@@ -266,11 +266,11 @@
     headers = recipient.headers()
     skid = headers.get("skid")
     if skid:
-        skey = cast(Union[ECKey, OKPKey], key.get_by_kid(skid))
+        skey = cast(ECKey | OKPKey, key.get_by_kid(skid))
         return skey
 
     if use_random:
-        skey = cast(Union[ECKey, OKPKey], key.pick_random_key(headers["alg"]))
+        skey = cast(ECKey | OKPKey, key.pick_random_key(headers["alg"]))
         if skey is not None:
             recipient.add_header("skid", skey.kid)
             return skey
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/jwk.py 
new/joserfc-1.7.7/src/joserfc/jwk.py
--- old/joserfc-1.7.5/src/joserfc/jwk.py        2026-08-29 15:05:08.760791000 
+0200
+++ new/joserfc-1.7.7/src/joserfc/jwk.py        2026-10-19 02:29:58.474686100 
+0200
@@ -1,5 +1,5 @@
 import warnings
-from typing import cast, overload, Literal, Union, Callable, Protocol
+from typing import cast, overload, Literal, Callable, Protocol
 from ._keys import (
     JWKRegistry,
     KeySet,
@@ -54,9 +54,9 @@
     def set_kid(self, kid: str) -> None: ...
 
 
-KeyBase = Union[Key, KeySet]
+KeyBase = Key | KeySet
 KeyCallable = Callable[[GuestProtocol], KeyBase]
-KeyFlexible = Union[KeyBase, KeyCallable]
+KeyFlexible = KeyBase | KeyCallable
 
 
 def guess_key(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc/registry.py 
new/joserfc-1.7.7/src/joserfc/registry.py
--- old/joserfc-1.7.5/src/joserfc/registry.py   2026-08-29 15:05:08.760791000 
+0200
+++ new/joserfc-1.7.7/src/joserfc/registry.py   2026-10-19 02:29:58.474686100 
+0200
@@ -1,4 +1,4 @@
-from typing import Any, Callable, Union
+from typing import Any, Callable
 from .errors import (
     MissingHeaderError,
     MissingCritHeaderError,
@@ -43,7 +43,7 @@
         raise ValueError("must be a JWK")
 
 
-def in_choices(choices: list[str]) -> Callable[[Union[str, list[str]]], None]:
+def in_choices(choices: list[str]) -> Callable[[str | list[str]], None]:
     def _is_one_of(value: str | list[str]) -> None:
         if isinstance(value, list):
             if not all(v in choices for v in value):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/src/joserfc.egg-info/PKG-INFO 
new/joserfc-1.7.7/src/joserfc.egg-info/PKG-INFO
--- old/joserfc-1.7.5/src/joserfc.egg-info/PKG-INFO     2026-08-29 
15:05:19.177635200 +0200
+++ new/joserfc-1.7.7/src/joserfc.egg-info/PKG-INFO     2026-10-19 
02:30:02.547517800 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: joserfc
-Version: 1.7.5
+Version: 1.7.7
 Summary: The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, 
JWA, JWT
 Author-email: Hsiaoming Yang <[email protected]>
 License: BSD-3-Clause
@@ -21,6 +21,7 @@
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Security
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/tests/jwe/test_json.py 
new/joserfc-1.7.7/tests/jwe/test_json.py
--- old/joserfc-1.7.5/tests/jwe/test_json.py    2026-08-29 15:05:08.761375000 
+0200
+++ new/joserfc-1.7.7/tests/jwe/test_json.py    2026-10-19 02:29:58.475882300 
+0200
@@ -68,6 +68,42 @@
         obj1 = jwe.decrypt_json(value, self.rsa_key)
         self.assertEqual(obj1.aad, b"foo")
 
+    def test_aad_size_limit(self):
+        key = OctKey.generate_key(128)
+        limit = jwe.JWERegistry.max_aad_length
+        for model in (GeneralJSONEncryption, FlattenedJSONEncryption):
+            for size in (0, limit * 3 // 4, limit * 3 // 4 + 1):
+                with self.subTest(model=model.__name__, size=size):
+                    aad = b"a" * size
+                    obj = model({"enc": "A128GCM"}, b"hello", aad=aad)
+                    obj.add_recipient({"alg": "A128KW"}, key)
+                    value = jwe.encrypt_json(obj, None)
+                    if size == 0:
+                        value["aad"] = ""
+                    if len(value["aad"]) <= limit:
+                        result = jwe.decrypt_json(value, key)
+                        self.assertEqual(result.aad, aad)
+                        self.assertEqual(result.plaintext, b"hello")
+                    else:
+                        with self.assertRaisesRegex(ExceededSizeError, "AAD 
size exceeds"):
+                            jwe.decrypt_json(value, key)
+                        registry = 
jwe.JWERegistry(max_aad_length=len(value["aad"]))
+                        result = jwe.decrypt_json(value, key, 
registry=registry)
+                        self.assertEqual(result.aad, aad)
+                        self.assertEqual(result.plaintext, b"hello")
+
+    def test_oversized_aad_rejected_before_decoding(self):
+        key = OctKey.generate_key(128)
+        for model in (GeneralJSONEncryption, FlattenedJSONEncryption):
+            with self.subTest(model=model.__name__):
+                obj = model({"enc": "A128GCM"}, b"hello")
+                obj.add_recipient({"alg": "A128KW"}, key)
+                value = jwe.encrypt_json(obj, None)
+                value["aad"] = "!" * 5
+                registry = jwe.JWERegistry(max_aad_length=4)
+                with self.assertRaisesRegex(ExceededSizeError, "AAD size 
exceeds 4 bytes"):
+                    jwe.decrypt_json(value, key, registry=registry)
+
     def test_decode_multiple_recipients(self):
         obj = GeneralJSONEncryption({"enc": "A128CBC-HS256"}, b"i")
         obj.add_recipient({"alg": "RSA-OAEP"}, self.rsa_key)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/tests/jwk/test_jwk_set.py 
new/joserfc-1.7.7/tests/jwk/test_jwk_set.py
--- old/joserfc-1.7.5/tests/jwk/test_jwk_set.py 2026-08-29 15:05:08.761608600 
+0200
+++ new/joserfc-1.7.7/tests/jwk/test_jwk_set.py 2026-10-19 02:29:58.476305200 
+0200
@@ -103,3 +103,8 @@
         key_set2 = KeySet([RSAKey.import_key(k.as_dict(private=True)) for k in 
key_set1])
         self.assertIsNot(key_set1, key_set2)
         self.assertEqual(key_set1, key_set2)
+
+    def test_key_set_eq_with_unrelated_type(self):
+        key_set = KeySet.generate_key_set("oct", 8, count=1)
+        self.assertFalse(key_set == "foo")
+        self.assertNotEqual(key_set, "foo")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/tests/jwk/test_oct_key.py 
new/joserfc-1.7.7/tests/jwk/test_oct_key.py
--- old/joserfc-1.7.5/tests/jwk/test_oct_key.py 2026-08-29 15:05:08.761608600 
+0200
+++ new/joserfc-1.7.7/tests/jwk/test_oct_key.py 2026-10-19 02:29:58.476305200 
+0200
@@ -1,5 +1,7 @@
 from unittest import TestCase
-from joserfc.jwk import OctKey
+from joserfc.jwk import OctKey, RSAKey, ECKey, OKPKey
+from joserfc.util import urlsafe_b64encode
+from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
 from joserfc.errors import SecurityWarning, KeyParameterError
 from tests.keys import read_key
 
@@ -85,9 +87,36 @@
         }
         self.assertRaises(KeyParameterError, OctKey.import_key, data)
 
-    def test_import_pem_key(self):
-        public_pem = read_key("ec-p256-public.pem")
-        self.assertWarns(SecurityWarning, OctKey.import_key, public_pem)
+    def test_import_asymmetric_public_key_rejected(self):
+        keys = (RSAKey.generate_key(), ECKey.generate_key(), 
OKPKey.generate_key())
+        for key in keys:
+            formats = [PublicFormat.SubjectPublicKeyInfo]
+            if isinstance(key, RSAKey):
+                formats.append(PublicFormat.PKCS1)
+            for encoding in (Encoding.DER, Encoding.PEM):
+                for public_format in formats:
+                    raw = key.public_key.public_bytes(encoding, public_format)
+                    values = [raw, {"kty": "oct", "k": 
urlsafe_b64encode(raw).decode("ascii")}]
+                    if encoding == Encoding.PEM:
+                        values.append(raw.decode("ascii"))
+                    for value in values:
+                        with self.subTest(key=key.key_type, encoding=encoding, 
public_format=public_format):
+                            self.assertRaisesRegex(ValueError, "Asymmetric 
public keys", OctKey.import_key, value)
+
+    def test_import_ssh_public_key_rejected(self):
+        for filename in ("ssh-rsa-public.pem", "ssh-ecdsa-public.pem", 
"ssh-ed25519-public.pem"):
+            raw = read_key(filename)
+            for value in (raw, {"kty": "oct", "k": 
urlsafe_b64encode(raw).decode("ascii")}):
+                with self.subTest(filename=filename):
+                    self.assertRaisesRegex(ValueError, "Asymmetric public 
keys", OctKey.import_key, value)
+
+    def test_import_ambiguous_key_material(self):
+        # A DER prefix alone does not make a random symmetric key unsafe.
+        raw = b"0" + b"x" * 31
+        self.assertEqual(OctKey.import_key(raw).raw_value, raw)
+        self.assertEqual(OctKey.import_key({"kty": "oct", "k": 
urlsafe_b64encode(raw).decode("ascii")}).raw_value, raw)
+        self.assertWarns(SecurityWarning, OctKey.import_key, b"-----BEGIN 
not-a-public-key")
+        self.assertWarns(SecurityWarning, OctKey.import_key, b"ssh-rsa 
not-a-public-key")
 
     def test_generate_key(self):
         key = OctKey.generate_key()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/joserfc-1.7.5/tests/jwt/test_claims.py 
new/joserfc-1.7.7/tests/jwt/test_claims.py
--- old/joserfc-1.7.5/tests/jwt/test_claims.py  2026-08-29 15:05:08.762014600 
+0200
+++ new/joserfc-1.7.7/tests/jwt/test_claims.py  2026-10-19 02:29:58.477086000 
+0200
@@ -51,11 +51,15 @@
     def test_claim_iss(self):
         claims_requests = jwt.JWTClaimsRegistry(iss={"essential": True})
         claims_requests.validate({"iss": "a"})
-        self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": 
1})
         self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": 
True})
         self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": 
["a"]})
         self.assertRaises(InvalidClaimError, claims_requests.validate, {"iss": 
{"a": "b"}})
 
+        with self.assertRaises(InvalidClaimError) as cm:
+            claims_requests.validate({"iss": 1})
+        self.assertEqual(cm.exception.claim, "iss")
+        self.assertIn("'iss'", cm.exception.description)
+
     def test_claim_sub(self):
         claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True})
         claims_requests.validate({"sub": "a"})
@@ -64,6 +68,34 @@
         self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": 
["a"]})
         self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": 
{"a": "b"}})
 
+    def test_claim_jti(self):
+        claims_requests = jwt.JWTClaimsRegistry()
+        claims_requests.validate({"jti": "expected"})
+        self.assertRaises(InvalidClaimError, claims_requests.validate, {"jti": 
["expected"]})
+        self.assertRaises(InvalidClaimError, claims_requests.validate, {"jti": 
1})
+
+        for options in ({}, {"value": "expected"}, {"values": ["expected", 
"other"]}):
+            claims_requests = jwt.JWTClaimsRegistry(jti=options)
+            claims_requests.validate({})
+            claims_requests.validate({"jti": "expected"})
+            for value in (None, 1, True, ["other", "expected"], {"jti": 
"expected"}):
+                with self.subTest(options=options, value=value):
+                    with self.assertRaises(InvalidClaimError) as cm:
+                        claims_requests.validate({"jti": value})
+                    self.assertEqual(cm.exception.claim, "jti")
+                    self.assertIn("'jti'", cm.exception.description)
+
+        for options in ({"value": "expected"}, {"values": ["expected", 
"other"]}):
+            claims_requests = jwt.JWTClaimsRegistry(jti=options)
+            self.assertRaises(InvalidClaimError, claims_requests.validate, 
{"jti": "wrong"})
+
+        claims_requests = jwt.JWTClaimsRegistry(jti={"essential": True})
+        self.assertRaises(MissingClaimError, claims_requests.validate, {})
+        self.assertRaises(InvalidClaimError, claims_requests.validate, {"jti": 
""})
+        claims_requests = jwt.JWTClaimsRegistry(jti={"allow_blank": True})
+        claims_requests.validate({"jti": ""})
+        self.assertRaises(InvalidClaimError, claims_requests.validate, {"jti": 
[]})
+
     def test_claim_aud(self):
         claims_requests = jwt.JWTClaimsRegistry(aud={"essential": True})
         claims_requests.validate({"aud": "a"})

Reply via email to