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 =

Reply via email to