From 268f4bc5d46e05393fecbd2854074b11ab20cd65 Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Mon, 31 Aug 2026 16:12:54 -0400
Subject: [PATCH] Use checked headers to find kid in JWKSet

When verifying a JWS against a JWKSet, check for the 'kid'
parameter in chk_hdrs instead of self.jose_header. This
ensures that key lookup works properly for signatures that
define 'kid' in unprotected headers or when processing
multiple signatures.

Assisted-by: Gemini:Gemini 3.7 Flash
Signed-off-by: Simo Sorce <simo@redhat.com>
---
 jwcrypto/jws.py   |  6 +++---
 jwcrypto/tests.py | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 3 deletions(-)

Index: jwcrypto-1.5.7/jwcrypto/jws.py
===================================================================
--- jwcrypto-1.5.7.orig/jwcrypto/jws.py
+++ jwcrypto-1.5.7/jwcrypto/jws.py
@@ -297,11 +297,11 @@ class JWS:
             self.verifylog.append("Success")
         elif isinstance(key, JWKSet):
             keys = key
-            if 'kid' in self.jose_header:
-                kid_keys = key.get_keys(self.jose_header['kid'])
+            if 'kid' in chk_hdrs:
+                kid_keys = key.get_keys(chk_hdrs['kid'])
                 if not kid_keys:
                     raise JWKeyNotFound('Key ID {} not in key set'.format(
-                                        self.jose_header['kid']))
+                                        chk_hdrs['kid']))
                 keys = kid_keys
 
             for k in keys:
Index: jwcrypto-1.5.7/jwcrypto/tests.py
===================================================================
--- jwcrypto-1.5.7.orig/jwcrypto/tests.py
+++ jwcrypto-1.5.7/jwcrypto/tests.py
@@ -1194,6 +1194,42 @@ class TestJWS(unittest.TestCase):
         with self.assertRaises(JWKeyNotFound):
             s4.deserialize(s3.serialize(), ks)
 
+    def test_multiple_signatures_keyset(self):
+        ks = jwk.JWKSet()
+        key1 = jwk.JWK.generate(kty='oct', alg='HS256', kid='key1')
+        key2 = jwk.JWK.generate(kty='oct', alg='HS256', kid='key2')
+        ks.add(key1)
+        ks.add(key2)
+
+        payload = b'JSON Serialization JWS with multiple signatures'
+        s = jws.JWS(payload=payload)
+        s.add_signature(key1, protected={'alg': 'HS256', 'kid': 'key1'})
+        s.add_signature(key2, protected={'alg': 'HS256'},
+                        header={'kid': 'key2'})
+
+        serialized = s.serialize()
+        decoded = json_decode(serialized)
+        self.assertIn('signatures', decoded)
+        self.assertEqual(len(decoded['signatures']), 2)
+
+        s2 = jws.JWS()
+        s2.deserialize(serialized, ks)
+        self.assertTrue(s2.is_valid)
+        self.assertEqual(s2.payload, payload)
+
+        # Test with keys swapped
+        key1_dict = key1.export(as_dict=True)
+        key1_dict['kid'] = 'key2'
+        key2_dict = key2.export(as_dict=True)
+        key2_dict['kid'] = 'key1'
+        ks_swapped = jwk.JWKSet()
+        ks_swapped.add(jwk.JWK(**key1_dict))
+        ks_swapped.add(jwk.JWK(**key2_dict))
+
+        s3 = jws.JWS()
+        with self.assertRaises(JWKeyNotFound):
+            s3.deserialize(serialized, ks_swapped)
+
 
 E_A1_plaintext = \
     [84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32,
