Summary
adcp.signing.verifier.verify_request_signature (adcp 6.6.0, Python 3.12) accepts a request whose Signature-Input header contains the same label twice — e.g. sig1=(...), sig1=(...). The conformance vector negative/021-duplicate-signature-input-label (dist/compliance/3.1.13/test-vectors/request-signing/) expects rejection with request_signature_header_malformed.
RFC 8941 §3.2 permits parsers to reject duplicate dictionary keys, and the vector's $comment cites the AdCP profile's downgrade-protection rule requiring it: with last-wins parsing, two different signature bases can hide behind one label — the verifier validates one of them while a downstream consumer may read the other.
We found this while grading our seller against the vectors: the live failure initially surfaced as request_signature_window_invalid (the vector's frozen timestamps fail the window check first), which masked the parser leniency. The repro below uses a fresh window and a signature that genuinely verifies — the duplicate label is the only defect, and the verifier accepts it.
Likely the same parser layer affects negative/022-multi-valued-content-type and negative/023-multi-valued-content-digest (we haven't isolated those to the parser as cleanly; the duplicate-label case is proven).
Repro (self-contained, adcp 6.6.0)
import time
from adcp.signing import crypto, keygen
from adcp.signing.signer import sign_request
from adcp.signing.verifier import (
VerifierCapability, VerifyOptions, verify_request_signature,
)
URL = "https://seller.example.com/adcp/create_media_buy"
BODY = b'{"plan_id":"plan_001"}'
pem, jwk = keygen.generate_ed25519("dup-kid", adcp_use="request-signing")
pk = crypto.load_private_key_pem(pem)
headers = {"content-type": "application/json"}
sh = sign_request(method="POST", url=URL, headers=headers, body=BODY,
private_key=pk, key_id="dup-kid", alg="ed25519")
merged = dict(headers)
merged.update(sh.as_dict())
key = next(k for k in merged if k.lower() == "signature-input")
merged[key] = merged[key] + ", " + merged[key] # duplicate the sig1 label
opts = VerifyOptions(
now=time.time(),
capability=VerifierCapability(
supported=True, covers_content_digest="either",
required_for=frozenset(), supported_for=frozenset({"create_media_buy"}),
),
operation="create_media_buy",
jwks_resolver=lambda keyid: jwk if keyid == "dup-kid" else None,
expected_tag="adcp/request-signing/v1",
expected_adcp_use="request-signing",
allowed_algs=frozenset({"ed25519"}),
signing_purpose="request-signing",
)
signer = verify_request_signature(
method="POST", url=URL, headers=merged, body=BODY, options=opts)
print("ACCEPTED - signer keyid:", signer.key_id)
# prints: ACCEPTED - signer keyid: dup-kid
# expected: SignatureVerificationError(request_signature_header_malformed)
Expected
SignatureVerificationError with code request_signature_header_malformed, per the vector and the profile's step-6 strict-parse requirement.
We've added an application-side pre-check as defense in depth, so this isn't blocking us — filing so the SDK's parser matches the conformance vectors it ships with. Happy to contribute the fix if useful.
Summary
adcp.signing.verifier.verify_request_signature(adcp 6.6.0, Python 3.12) accepts a request whoseSignature-Inputheader contains the same label twice — e.g.sig1=(...), sig1=(...). The conformance vectornegative/021-duplicate-signature-input-label(dist/compliance/3.1.13/test-vectors/request-signing/) expects rejection withrequest_signature_header_malformed.RFC 8941 §3.2 permits parsers to reject duplicate dictionary keys, and the vector's
$commentcites the AdCP profile's downgrade-protection rule requiring it: with last-wins parsing, two different signature bases can hide behind one label — the verifier validates one of them while a downstream consumer may read the other.We found this while grading our seller against the vectors: the live failure initially surfaced as
request_signature_window_invalid(the vector's frozen timestamps fail the window check first), which masked the parser leniency. The repro below uses a fresh window and a signature that genuinely verifies — the duplicate label is the only defect, and the verifier accepts it.Likely the same parser layer affects
negative/022-multi-valued-content-typeandnegative/023-multi-valued-content-digest(we haven't isolated those to the parser as cleanly; the duplicate-label case is proven).Repro (self-contained, adcp 6.6.0)
Expected
SignatureVerificationErrorwith coderequest_signature_header_malformed, per the vector and the profile's step-6 strict-parse requirement.We've added an application-side pre-check as defense in depth, so this isn't blocking us — filing so the SDK's parser matches the conformance vectors it ships with. Happy to contribute the fix if useful.