Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-jwcrypto for openSUSE:Factory
checked in at 2026-06-29 17:29:31
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jwcrypto (Old)
and /work/SRC/openSUSE:Factory/.python-jwcrypto.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jwcrypto"
Mon Jun 29 17:29:31 2026 rev:20 rq:1362137 version:1.5.8
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-jwcrypto/python-jwcrypto.changes
2026-04-18 21:30:52.012120422 +0200
+++
/work/SRC/openSUSE:Factory/.python-jwcrypto.new.11887/python-jwcrypto.changes
2026-06-29 17:29:53.261057622 +0200
@@ -1,0 +2,9 @@
+Sun Jun 28 10:10:57 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.5.8:
+ * Fix list iteration in claim format validation
+ * fix: bump minimum cryptography dependency to >= 39.0.0
+ * Wrap JWKSet parsing errors in InvalidJWKValue
+ * jwt: add opt-in strict_serialization to enforce compact form
+
+-------------------------------------------------------------------
Old:
----
jwcrypto-1.5.7.tar.gz
New:
----
jwcrypto-1.5.8.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jwcrypto.spec ++++++
--- /var/tmp/diff_new_pack.3NWRa4/_old 2026-06-29 17:29:54.105086309 +0200
+++ /var/tmp/diff_new_pack.3NWRa4/_new 2026-06-29 17:29:54.105086309 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-jwcrypto
-Version: 1.5.7
+Version: 1.5.8
Release: 0
Summary: Python module package implementing JOSE Web standards
License: LGPL-3.0-only
++++++ jwcrypto-1.5.7.tar.gz -> jwcrypto-1.5.8.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/PKG-INFO new/jwcrypto-1.5.8/PKG-INFO
--- old/jwcrypto-1.5.7/PKG-INFO 2026-04-07 02:35:27.992449300 +0200
+++ new/jwcrypto-1.5.8/PKG-INFO 2026-06-24 21:36:41.927917500 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: jwcrypto
-Version: 1.5.7
+Version: 1.5.8
Summary: Implementation of JOSE Web standards
Home-page: https://github.com/latchset/jwcrypto
Maintainer: JWCrypto Project Contributors
@@ -17,7 +17,7 @@
Requires-Python: >= 3.8
Description-Content-Type: text/markdown
License-File: LICENSE
-Requires-Dist: cryptography>=3.4
+Requires-Dist: cryptography>=39.0.0
Requires-Dist: typing_extensions>=4.5.0
Dynamic: home-page
Dynamic: license-file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/jwcrypto/jwk.py
new/jwcrypto-1.5.8/jwcrypto/jwk.py
--- old/jwcrypto-1.5.7/jwcrypto/jwk.py 2026-04-07 02:35:02.000000000 +0200
+++ new/jwcrypto-1.5.8/jwcrypto/jwk.py 2026-06-24 21:35:53.000000000 +0200
@@ -1356,19 +1356,18 @@
"""
try:
jwkset = json_decode(keyset)
+ if 'keys' not in jwkset:
+ raise ValueError("'keys' not in set")
+
+ for k, v in jwkset.items():
+ if k == 'keys':
+ for jwk in v:
+ self['keys'].add(JWK(**jwk))
+ else:
+ self[k] = v
except Exception as e: # pylint: disable=broad-except
raise InvalidJWKValue from e
- if 'keys' not in jwkset:
- raise InvalidJWKValue
-
- for k, v in jwkset.items():
- if k == 'keys':
- for jwk in v:
- self['keys'].add(JWK(**jwk))
- else:
- self[k] = v
-
@classmethod
def from_json(cls, keyset):
"""Creates a RFC 7517 key set from the standard JSON format.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/jwcrypto/jwt.py
new/jwcrypto-1.5.8/jwcrypto/jwt.py
--- old/jwcrypto-1.5.7/jwcrypto/jwt.py 2026-04-07 02:35:02.000000000 +0200
+++ new/jwcrypto-1.5.8/jwcrypto/jwt.py 2026-06-24 21:35:53.000000000 +0200
@@ -168,7 +168,7 @@
def __init__(self, header=None, claims=None, jwt=None, key=None,
algs=None, default_claims=None, check_claims=None,
- expected_type=None):
+ expected_type=None, strict_serialization=False):
"""Creates a JWT object.
:param header: A dict or a JSON string with the JWT Header data.
@@ -190,6 +190,13 @@
If left to None the code will try to detect what the expected
type is based on other parameters like 'algs' and will default
to JWS if no hints are found. It has no effect on token creation.
+ :param strict_serialization: An optional boolean (default False).
+ RFC 7519 mandates that a JWT uses the JWS/JWE Compact
+ Serialization. When set to True any token presented in the
+ JWS/JWE JSON Serialization is rejected at deserialization time,
+ so only the compact representation is accepted. The default is
+ False to preserve backwards compatibility. It has no effect on
+ token creation, which always produces the compact serialization.
Note: either the header,claims or jwt,key parameters should be
provided as a deserialization operation (which occurs if the jwt
@@ -212,6 +219,7 @@
self._validity = 600 # 10 minutes validity (up to 11 with leeway)
self.deserializelog = None
self._expected_type = expected_type
+ self._strict_serialization = strict_serialization
if header:
self.header = header
@@ -433,7 +441,7 @@
if name not in claims or claims[name] is None:
return
if isinstance(claims[name], list):
- if any(not isinstance(claim, str) for claim in claims):
+ if any(not isinstance(claim, str) for claim in claims[name]):
raise JWTInvalidClaimFormat(
"Claim %s contains non StringOrURI types" % (name, ))
elif not isinstance(claims[name], str):
@@ -670,6 +678,18 @@
self.claims = payload
self._check_provided_claims()
+ @staticmethod
+ def _is_json_serialization(jwt):
+ # The JWS/JWE Compact Serialization is a sequence of base64url
+ # encoded parts joined by dots, so it never decodes as a JSON
+ # object. The JWS/JWE JSON Serialization always is a JSON object.
+ # This mirrors how JWS.deserialize and JWE.deserialize themselves
+ # tell the two representations apart.
+ try:
+ return isinstance(json_decode(jwt), dict)
+ except ValueError:
+ return False
+
def deserialize(self, jwt, key=None):
"""Deserialize a JWT token.
@@ -680,7 +700,16 @@
:param key: A (:class:`jwcrypto.jwk.JWK`) verification or
decryption key, or a (:class:`jwcrypto.jwk.JWKSet`) that
contains a key indexed by the 'kid' header.
+
+ :raises ValueError: if strict_serialization is enabled and the
+ token is not in the JWS/JWE Compact Serialization (for example
+ when a JSON Serialization is provided).
"""
+ if self._strict_serialization and self._is_json_serialization(jwt):
+ raise ValueError("Only the JWS/JWE Compact Serialization is "
+ "allowed for JWTs (RFC 7519), but a JSON "
+ "Serialization was provided")
+
data = jwt.count('.')
if data == 2:
self.token = JWS()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/jwcrypto/tests.py
new/jwcrypto-1.5.8/jwcrypto/tests.py
--- old/jwcrypto-1.5.7/jwcrypto/tests.py 2026-04-07 02:35:02.000000000
+0200
+++ new/jwcrypto-1.5.8/jwcrypto/tests.py 2026-06-24 21:35:53.000000000
+0200
@@ -562,6 +562,21 @@
self.assertEqual(len(ks['keys']), 2)
self.assertEqual(len(ks['keys']), len(ks2['keys']))
+ def test_import_keyset_invalid(self):
+ ks = jwk.JWKSet()
+ invalid_inputs = [
+ '',
+ 'null',
+ '[]',
+ '{}',
+ '{"keys": 1}',
+ '{"keys": [1]}',
+ '{"keys": [{"kty": "invalid"}]}'
+ ]
+ for inp in invalid_inputs:
+ with self.assertRaises(jwk.InvalidJWKValue):
+ ks.import_keyset(inp)
+
def test_thumbprint(self):
for i in range(0, len(PublicKeys['keys'])):
k = jwk.JWK(**PublicKeys['keys'][i])
@@ -1895,6 +1910,18 @@
jwt=sertok, check_claims={"aud": ["nomatch",
"failmatch"]})
+ def test_claim_aud(self):
+ claims = {"aud": "www.example.com"}
+ key = jwk.JWK(generate='oct', size=256)
+ token = jwt.JWT(header={"alg": "HS256"}, claims=claims)
+ token.make_signed_token(key)
+ sertok = token.serialize()
+ check_claim = {"aud": ["www.example.com", 123]}
+ self.assertRaises(jwt.JWTInvalidClaimFormat, jwt.JWT, key=key,
+ jwt=sertok, check_claims=check_claim)
+ check_claim = {"aud": ["www.example.com", "123"]}
+ jwt.JWT(key=key, jwt=sertok, check_claims=check_claim)
+
def test_unexpected(self):
key = jwk.JWK(generate='oct', size=256)
claims = {"testclaim": "test"}
@@ -2510,6 +2537,74 @@
'algs=None, default_claims=None, check_claims=None)'
self.assertEqual(repr(token), reprrep)
+ def test_jwt_strict_serialization_jws(self):
+ # RFC 7519 mandates the Compact Serialization for JWTs. With the
+ # opt-in strict_serialization flag a JSON-serialized JWS must be
+ # rejected, while the compact one keeps working. Without the flag
+ # the previous (lenient) behavior is preserved.
+ key = jwk.JWK.generate(kty='oct', size=256)
+ signer = jws.JWS(payload='{"sub":"alice"}')
+ # The unprotected header carries a dotted value so that the JSON
+ # serialization happens to contain exactly two '.' characters and
+ # is therefore routed to the JWS branch by the legacy dot-count
+ # heuristic. This is the case the lenient path used to accept.
+ signer.add_signature(key, alg='HS256',
+ protected='{"alg":"HS256"}',
+ header={'kid': 'a.b.c'})
+ json_token = signer.serialize(compact=False)
+ compact_token = signer.serialize(compact=True)
+
+ # Default behavior is unchanged: the JSON serialization is accepted.
+ lenient = jwt.JWT()
+ lenient.deserialize(json_token, key)
+ self.assertEqual(json_decode(lenient.claims)['sub'], 'alice')
+
+ # Strict mode rejects the JSON serialization.
+ strict = jwt.JWT(strict_serialization=True)
+ with self.assertRaises(ValueError):
+ strict.deserialize(json_token, key)
+
+ # Strict mode also rejects it through the constructor jwt= path.
+ with self.assertRaises(ValueError):
+ jwt.JWT(jwt=json_token, key=key, check_claims=False,
+ strict_serialization=True)
+
+ # The compact serialization works in both modes.
+ strict_ok = jwt.JWT(strict_serialization=True)
+ strict_ok.deserialize(compact_token, key)
+ self.assertEqual(json_decode(strict_ok.claims)['sub'], 'alice')
+ lenient_ok = jwt.JWT()
+ lenient_ok.deserialize(compact_token, key)
+ self.assertEqual(json_decode(lenient_ok.claims)['sub'], 'alice')
+
+ def test_jwt_strict_serialization_jwe(self):
+ key = jwk.JWK.generate(kty='oct', size=256)
+ algs = ['A256KW', 'A256CBC-HS512']
+ enc = jwe.JWE(plaintext='{"sub":"bob"}',
+ protected='{"enc":"A256CBC-HS512"}')
+ # A per-recipient dotted header value yields a JSON serialization
+ # with exactly four '.' characters, routed to the JWE branch.
+ enc.add_recipient(key, header={'alg': 'A256KW', 'kid': 'a.b.c.d.e'})
+ json_token = enc.serialize(compact=False)
+
+ enc2 = jwe.JWE(plaintext='{"sub":"bob"}',
+ protected='{"alg":"A256KW","enc":"A256CBC-HS512"}')
+ enc2.add_recipient(key)
+ compact_token = enc2.serialize(compact=True)
+
+ # Default behavior is unchanged: JSON serialization is accepted.
+ lenient = jwt.JWT(algs=algs)
+ lenient.deserialize(json_token, key)
+ self.assertEqual(json_decode(lenient.claims)['sub'], 'bob')
+
+ # Strict mode rejects the JSON serialization but accepts compact.
+ strict = jwt.JWT(algs=algs, strict_serialization=True)
+ with self.assertRaises(ValueError):
+ strict.deserialize(json_token, key)
+ strict_ok = jwt.JWT(algs=algs, strict_serialization=True)
+ strict_ok.deserialize(compact_token, key)
+ self.assertEqual(json_decode(strict_ok.claims)['sub'], 'bob')
+
class TestRfc9864(unittest.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/jwcrypto/version.py
new/jwcrypto-1.5.8/jwcrypto/version.py
--- old/jwcrypto-1.5.7/jwcrypto/version.py 2026-04-07 02:35:02.000000000
+0200
+++ new/jwcrypto-1.5.8/jwcrypto/version.py 2026-06-24 21:35:53.000000000
+0200
@@ -1 +1 @@
-__version__ = "1.5.7"
+__version__ = "1.5.8"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/jwcrypto.egg-info/PKG-INFO
new/jwcrypto-1.5.8/jwcrypto.egg-info/PKG-INFO
--- old/jwcrypto-1.5.7/jwcrypto.egg-info/PKG-INFO 2026-04-07
02:35:27.000000000 +0200
+++ new/jwcrypto-1.5.8/jwcrypto.egg-info/PKG-INFO 2026-06-24
21:36:41.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: jwcrypto
-Version: 1.5.7
+Version: 1.5.8
Summary: Implementation of JOSE Web standards
Home-page: https://github.com/latchset/jwcrypto
Maintainer: JWCrypto Project Contributors
@@ -17,7 +17,7 @@
Requires-Python: >= 3.8
Description-Content-Type: text/markdown
License-File: LICENSE
-Requires-Dist: cryptography>=3.4
+Requires-Dist: cryptography>=39.0.0
Requires-Dist: typing_extensions>=4.5.0
Dynamic: home-page
Dynamic: license-file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/jwcrypto.egg-info/requires.txt
new/jwcrypto-1.5.8/jwcrypto.egg-info/requires.txt
--- old/jwcrypto-1.5.7/jwcrypto.egg-info/requires.txt 2026-04-07
02:35:27.000000000 +0200
+++ new/jwcrypto-1.5.8/jwcrypto.egg-info/requires.txt 2026-06-24
21:36:41.000000000 +0200
@@ -1,2 +1,2 @@
-cryptography>=3.4
+cryptography>=39.0.0
typing_extensions>=4.5.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/pyproject.toml
new/jwcrypto-1.5.8/pyproject.toml
--- old/jwcrypto-1.5.7/pyproject.toml 2026-04-07 02:35:02.000000000 +0200
+++ new/jwcrypto-1.5.8/pyproject.toml 2026-06-24 21:35:53.000000000 +0200
@@ -22,7 +22,7 @@
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
- "cryptography >= 3.4",
+ "cryptography >= 39.0.0",
"typing_extensions >= 4.5.0",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-1.5.7/tox.ini new/jwcrypto-1.5.8/tox.ini
--- old/jwcrypto-1.5.7/tox.ini 2026-04-07 02:35:02.000000000 +0200
+++ new/jwcrypto-1.5.8/tox.ini 2026-06-24 21:35:53.000000000 +0200
@@ -1,5 +1,5 @@
[tox]
-envlist = lint,py38,py39,py310,py311,py312,py313,pep8,doc,sphinx,doctest
+envlist = lint,py38,py39,py310,py311,py312,py313,py314,pep8,doc,sphinx,doctest
skip_missing_interpreters = true
[testenv]
@@ -16,7 +16,7 @@
{envpython} -m coverage report -m
[testenv:lint]
-basepython = python3.13
+basepython = python3.14
deps =
pylint
#sitepackages = True
@@ -24,7 +24,7 @@
{envpython} -m pylint -d c,r,i,W0613 -r n -f colorized --notes=
--disable=star-args ./jwcrypto
[testenv:pep8]
-basepython = python3.13
+basepython = python3.14
deps =
flake8
flake8-import-order
@@ -37,13 +37,13 @@
doc8
docutils
markdown
-basepython = python3.13
+basepython = python3.14
commands =
doc8 --allow-long-titles README.md
markdown_py README.md -f {toxworkdir}/README.md.html
[testenv:sphinx]
-basepython = python3.13
+basepython = python3.14
changedir = docs/source
deps =
sphinx
@@ -52,7 +52,7 @@
sphinx-build -n -v -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
[testenv:doctest]
-basepython = python3.13
+basepython = python3.14
changedir = docs/source
deps =
sphinx
@@ -60,7 +60,7 @@
sphinx-build -v -W -b doctest -d {envtmpdir}/doctrees . {envtmpdir}/doctest
[testenv:codespell]
-basepython = python3.13
+basepython = python3.14
deps =
codespell
commands =