Skip to content

Commit 634710e

Browse files
committed
Fix: add an algorithms parameter to create_signature_verifier function to security measures.
1 parent f915ff2 commit 634710e

File tree

3 files changed

+33
-20
lines changed

3 files changed

+33
-20
lines changed

src/a2a/utils/signing.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from jose.utils import base64url_decode, base64url_encode
1212
except ImportError as e:
1313
raise ImportError(
14-
'A2AUtilsSigning requires python-jose to be installed. '
14+
'A2A Signing requires python-jose to be installed. '
1515
'Install with: '
1616
"'pip install a2a-sdk[signing]'"
1717
) from e
@@ -70,7 +70,7 @@ def create_agent_card_signer(
7070
signing_key: The private key for signing.
7171
kid: Key ID for the signing key.
7272
alg: The algorithm to use (e.g., "ES256", "RS256").
73-
jku: Optional URL to the JWKS.
73+
jku: Optional URL to the JSON Web Keys.
7474
7575
Returns:
7676
A callable that takes an AgentCard and returns the modified AgentCard with a signature.
@@ -111,11 +111,13 @@ def create_signature_verifier(
111111
key_provider: Callable[
112112
[str | None, str | None], str | bytes | dict[str, Any] | Key
113113
],
114+
algorithms: list[str],
114115
) -> Callable[[AgentCard], None]:
115116
"""Creates a function that verifies AgentCard signatures.
116117
117118
Args:
118-
key_provider: A callable that takes key-id (kid) and JSON web key url (jku) and returns the verification key.
119+
key_provider: A callable that takes key-id and JSON web key url and returns the verification key.
120+
algorithms: List of acceptable algorithms for verification used to prevent algorithm confusion attacks.
119121
120122
Returns:
121123
A callable that takes an AgentCard, and raises an error if none of the signatures are valid.
@@ -138,7 +140,6 @@ def signature_verifier(
138140
protected_header = json.loads(protected_header_json)
139141
kid = protected_header.get('kid')
140142
jku = protected_header.get('jku')
141-
alg = protected_header.get('alg')
142143
verification_key = key_provider(kid, jku)
143144

144145
canonical_payload = canonicalize_agent_card(agent_card)
@@ -150,7 +151,7 @@ def signature_verifier(
150151
jws.verify(
151152
token=token,
152153
key=verification_key,
153-
algorithms=[alg] if alg else None,
154+
algorithms=algorithms,
154155
)
155156
# Found a valid signature, exit the loop and function
156157
break

tests/integration/test_client_server_integration.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,9 @@ async def test_json_transport_get_signed_base_card_no_initial(
878878
)
879879

880880
# Get the card, this will trigger verification in get_card
881-
signature_verifier = create_signature_verifier(create_key_provider(key))
881+
signature_verifier = create_signature_verifier(
882+
create_key_provider(key), ['HS384']
883+
)
882884
result = await transport.get_card(signature_verifier=signature_verifier)
883885
assert result.name == agent_card.name
884886
assert result.signatures is not None
@@ -929,7 +931,7 @@ async def test_json_transport_get_signed_extended_card(
929931

930932
# Get the card, this will trigger verification in get_card
931933
signature_verifier = create_signature_verifier(
932-
create_key_provider(public_key)
934+
create_key_provider(public_key), ['HS384', 'ES256']
933935
)
934936
result = await transport.get_card(signature_verifier=signature_verifier)
935937
assert result.name == extended_agent_card.name
@@ -985,7 +987,7 @@ async def test_json_transport_get_signed_base_and_extended_cards(
985987

986988
# Get the card, this will trigger verification in get_card
987989
signature_verifier = create_signature_verifier(
988-
create_key_provider(public_key)
990+
create_key_provider(public_key), ['HS384', 'ES256', 'RS256']
989991
)
990992
result = await transport.get_card(signature_verifier=signature_verifier)
991993
assert result.name == extended_agent_card.name
@@ -1039,8 +1041,9 @@ async def test_rest_transport_get_signed_card(
10391041
)
10401042

10411043
# Get the card, this will trigger verification in get_card
1042-
signature_verifier = create_signature_verifier(
1043-
create_key_provider(public_key)
1044+
signature_verifier = (
1045+
create_key_provider(public_key),
1046+
['HS384', 'ES256', 'RS256'],
10441047
)
10451048
result = await transport.get_card(signature_verifier=signature_verifier)
10461049
assert result.name == extended_agent_card.name
@@ -1093,7 +1096,7 @@ def channel_factory(address: str) -> Channel:
10931096

10941097
# Get the card, this will trigger verification in get_card
10951098
signature_verifier = create_signature_verifier(
1096-
create_key_provider(public_key)
1099+
create_key_provider(public_key), ['HS384', 'ES256', 'RS256']
10971100
)
10981101
result = await transport.get_card(signature_verifier=signature_verifier)
10991102
assert result.signatures is not None

tests/utils/test_signing.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,17 @@ def test_signer_and_verifier_symmetric(sample_agent_card: AgentCard):
7474
assert signature.signature is not None
7575

7676
# Verify the signature
77-
verifier = create_signature_verifier(create_key_provider(key))
77+
verifier = create_signature_verifier(
78+
create_key_provider(key), ['HS256', 'HS384', 'ES256', 'RS256']
79+
)
7880
try:
7981
verifier(signed_card)
8082
except JOSEError:
8183
pytest.fail('Signature verification failed with correct key')
8284

8385
# Verify with wrong key
8486
verifier_wrong_key = create_signature_verifier(
85-
create_key_provider(wrong_key)
87+
create_key_provider(wrong_key), ['HS256', 'HS384', 'ES256', 'RS256']
8688
)
8789
with pytest.raises(JOSEError):
8890
verifier_wrong_key(signed_card)
@@ -114,15 +116,17 @@ def test_signer_and_verifier_symmetric_multiple_signatures(
114116
assert signature.signature is not None
115117

116118
# Verify the signature
117-
verifier = create_signature_verifier(create_key_provider(key))
119+
verifier = create_signature_verifier(
120+
create_key_provider(key), ['HS256', 'HS384', 'ES256', 'RS256']
121+
)
118122
try:
119123
verifier(signed_card)
120124
except JOSEError:
121125
pytest.fail('Signature verification failed with correct key')
122126

123127
# Verify with wrong key
124128
verifier_wrong_key = create_signature_verifier(
125-
create_key_provider(wrong_key)
129+
create_key_provider(wrong_key), ['HS256', 'HS384', 'ES256', 'RS256']
126130
)
127131
with pytest.raises(JOSEError):
128132
verifier_wrong_key(signed_card)
@@ -150,15 +154,18 @@ def test_signer_and_verifier_asymmetric(sample_agent_card: AgentCard):
150154
assert signature.protected is not None
151155
assert signature.signature is not None
152156

153-
verifier = create_signature_verifier(create_key_provider(public_key))
157+
verifier = create_signature_verifier(
158+
create_key_provider(public_key), ['HS256', 'HS384', 'ES256', 'RS256']
159+
)
154160
try:
155161
verifier(signed_card)
156162
except JOSEError:
157163
pytest.fail('Signature verification failed with correct key')
158164

159165
# Verify with wrong key
160166
verifier_wrong_key = create_signature_verifier(
161-
create_key_provider(public_key_error)
167+
create_key_provider(public_key_error),
168+
['HS256', 'HS384', 'ES256', 'RS256'],
162169
)
163170
with pytest.raises(JOSEError):
164171
verifier_wrong_key(signed_card)
@@ -173,9 +180,11 @@ def test_canonicalize_agent_card(
173180
- protocolVersion is included because it's always added by canonicalize_agent_card.
174181
- signatures should be omitted.
175182
"""
176-
sample_agent_card.signatures = (
177-
[{'protected': 'protected_header', 'signature': 'test_signature'}],
178-
)
183+
sample_agent_card.signatures = [
184+
AgentCardSignature(
185+
protected='protected_header', signature='test_signature'
186+
)
187+
]
179188
expected_jcs = (
180189
'{"capabilities":{"pushNotifications":true},'
181190
'"defaultInputModes":["text/plain"],"defaultOutputModes":["text/plain"],'

0 commit comments

Comments
 (0)