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"})