feat(signing): L1 signing — RFC 9421, KMS providers, JWKS, webhook verification#32
feat(signing): L1 signing — RFC 9421, KMS providers, JWKS, webhook verification#32MichielDean wants to merge 21 commits into
Conversation
…ntext, VerificationKeyResolver)
…verification round-trip tests Track 4 (L1 Signing) implementation: - InProcessSigningProvider: JCA Ed25519/ES256/ES384 signing via SigningProvider SPI, delegates to Rfc9421Canonicalizer for signature base construction - InProcessKeyGenerator: test keypair generation utility (Ed25519, ES256, ES384) for development/testing - InProcessVerificationProvider: verification path using Rfc9421Verifier with AdCP profile validation - WebhookSigner/DefaultWebhookSigner: seam interface for Track 6 (async-l3) webhook signing, applies AdCP webhook-signing profile - WebhookSigningResult: record for signed webhook headers - Tests: signing/verification round-trip for Ed25519 and ES256, key generation, webhook signer seam
…wire, and ECDSA DER conversion
…wire, and ECDSA DER conversion
…lidation, and brand.json walk - JwkParser: parse Ed25519, EC (P-256/P-384), and RSA JWKs into VerificationKey - StaticJwksResolver: in-memory kid lookup from pre-loaded JWKS - CachingJwksResolver: HTTP-fetching with 30s cooldown, single-flight dedup - BrandJsonJwksResolver: 3-hop resolution (brand.json → agent → jwks_uri) - SsrfJwksUriValidator: HTTPS enforcement, private IP rejection, redirect:manual - JwksDocument: parsed JWKS with per-kid index and lastFetched timestamp - JwksResolutionException: unchecked exception for JWKS fetch failures - SsrfBlockedException: made constructor public for JWKS validator tests
…r nonce dedup and key revocation
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 34069021 | Triggered | Generic High Entropy Secret | f3d9c7b | adcp-server/src/test/java/org/adcontextprotocol/adcp/server/signing/webhook/StandardWebhooksVerifierTest.java | View secret |
| 34069022 | Triggered | Generic High Entropy Secret | f3d9c7b | adcp-server/src/test/java/org/adcontextprotocol/adcp/server/signing/webhook/StandardWebhooksVerifierTest.java | View secret |
| 34069023 | Triggered | Generic High Entropy Secret | f3d9c7b | adcp-server/src/test/java/org/adcontextprotocol/adcp/server/signing/webhook/StandardWebhooksVerifierTest.java | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
|
The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final This is an automated message from the Argus AI review workflow. |
|
The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final This is an automated message from the Argus AI review workflow. |
The compliance test vectors in adcp-server/src/test/resources/compliance/ contain intentionally public test keys with _private_d_for_test_only fields. These are for signer/verifier round-trip conformance testing and MUST NOT be used in production. The keys.json files carry explicit warnings in their _WARNING and fields.
|
The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final This is an automated message from the Argus AI review workflow. |
…h presence) The .gitguardian file only takes effect when present on the default branch. For now, the test key findings will be marked as false positives via the GitGuardian dashboard. The keys.json files contain intentionally public test keys from the AdCP compliance suite with explicit warnings.
|
The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final This is an automated message from the Argus AI review workflow. |
bokelley
left a comment
There was a problem hiding this comment.
Thanks for the large signing implementation. I reviewed the verifier, signing providers, JWKS/brand.json resolution, revocation handling, and webhook helpers. I think this needs another pass before merge; the targeted tests pass locally, but there are several correctness/security issues that the current coverage does not catch.
Findings:
-
[P1] Malformed signature inputs can throw instead of returning
VerificationResult.Invalid.
adcp-server/src/main/java/org/adcontextprotocol/adcp/server/signing/Rfc9421Verifier.java:100parsescreated/expireswithLong.parseLongoutside a catch, andextractSignatureBytescan throw fromBase64.getUrlDecoder().decode(...)at line 364. A bad remote header can therefore become a verifier exception/500 instead of a signature rejection. Please keep all wire-format failures on theVerificationResult.Invalidpath. -
[P1] Empty webhook bodies cannot be signed correctly.
Webhook signing requirescontent-digest, butDefaultWebhookSigner.java:44andInProcessSigningProvider.java:77only add it whenbody.length > 0. ForWEBHOOK_SIGNING, the covered component list still includescontent-digest, so empty-payload webhooks either fail canonicalization or skip digest validation. The digest of an empty byte array is still a valid requiredContent-Digestvalue. -
[P1]
brand.jsonJWKS URI rotation is broken.
InBrandJsonJwksResolver.java,selectedJwksUri = jwksUriis assigned at line 214 before comparing at line 218, so the resolver replacement condition never detects a changed URI onceinnerResolverexists. Ifbrand.jsonrotates to a newjwks_uri, the process can keep using the stale resolver until restart. Capture the previous selected URI before assignment, or compare against the existing resolver state. -
[P2] Standard Webhooks secret padding is incorrect.
StandardWebhooksVerifier.java:65uses"=".repeat((-payload.length()) % 4). Java preserves the negative remainder, so lengths where padding is needed can throwIllegalArgumentException: count is negative. Use(4 - payload.length() % 4) % 4instead, and add an unpadded secret test for non-multiple-of-4 lengths. -
[P2] Revocation stale handling does not match the result API.
CachingRevocationChecker.checkreturnsRevocationResult, and the class docs say stale lists returnRevocationResult.Stale, butensureFresh()throwsRevocationListStaleExceptionfrom line 132. Callers using the sealed result API will not see the advertisedStaleresult. Please either returnRevocationResult.Stalefromcheckor change the API/docs/tests to make stale an exception path consistently.
Local verification run:
./gradlew :adcp-server:test :adcp-signing-aws-kms:test :adcp-signing-gcp-kms:test
BUILD SUCCESSFUL in 2m 57s
Summary
Track 4 (L1 Signing) — complete RFC 9421 message signing and verification for the AdCP Java SDK.
Core SPI (
adcpmodule)SigningProvider/VerificationKeyResolver— SPI interfaces viaMETA-INF/services/SigningContext— tenant-aware key selection (D22):AdcpUse use(),@Nullable TenantId tenant(),@Nullable PrincipalRef principal()AdcpUseenum —REQUEST_SIGNING,WEBHOOK_SIGNINGwith wire name mappingVerificationKeyLookup— sealed interface:Found(key, tenant, principal)/Missing(kid)VerificationResult— sealed interface:Valid/Invalid(errorCode, reason, failedStep)VerificationException— typed errors matchingwebhook_signature_*taxonomyRFC 9421 Canonicalizer (
adcp-servermodule)org.tomitribe:http-signaturesdependency)Rfc9421Signer— producesSignature-Input,Signature,Content-DigestheadersRfc9421Verifier— full 13-step verification checklistAdcpSignatureProfile— required components, algorithms, tags, replay window enforcementHeaderNormalizer— RFC 9421 header name/value normalizationContentDigest— RFC 9530 SHA-256/512 content digestSigning Providers
InProcessSigningProvider— JCA Ed25519, ES256, ES384 (no Bouncy Castle in core)InProcessKeyGenerator— Ed25519/ES256/ES384 keypair generationAwsKmsSigningProvider— AWS KMS with lazy-init, tripwire verification, ECDSA DER-to-raw conversionGcpKmsSigningProvider— GCP KMS with lazy-init, tripwire, service account credentialsBouncyCastleSigningProvider— stub (FIPS environments only,UnsupportedOperationException)Verification & Key Resolution
JwksVerificationKeyResolver— SPI entry pointStaticJwksResolver— in-memory from pre-loaded JWKSCachingJwksResolver— HTTP-fetching with 30s cooldown, single-flight dedupBrandJsonJwksResolver— 3-hop resolution (brand.json → agent → jwks_uri)SsrfJwksUriValidator— HTTPS enforcement, private IP rejection, DNS rebinding defenseJwkParser— Ed25519, EC (P-256/P-384), RSA JWK parsing withadcp_useandkey_opsvalidationWebhook Signing
WebhookSignerseam interface for Track 6 (async-l3)DefaultWebhookSigner— implementation usingRfc9421SignerLegacyHmacWebhookVerifier— deprecated HMAC-SHA256 (removed in AdCP 4.0), timing-safe comparison, rotation supportStandardWebhooksVerifier— Svix/Resend v1 interopWebhookChallenge+WebhookChallengeVerifier— endpoint proof-of-controlKey Management
InMemoryReplayStore— nonce dedup with 300s TTL evictionInMemoryRevocationStore— revoked kid tracking with staleness detectionCachingRevocationChecker— JWS-verified revocation list fetcher with grace windowSigningKeyGenerator— Ed25519/ES256/ES384 keypair generation with JWK outputKeyOriginConsistencyCheck— ADCP #3690 eTLD+1 key origin verificationETldPlusOne— public suffix extraction utilityJWS Verification
JwsVerifier— compact and JSON general JWS verificationJwsDocument— parsed JWS recordCLI
signing-probesubcommand — pre-deploy KMS connectivity and key usability checkModules
adcp— core SPI interfacesadcp-server— canonicalizer, providers, verification, webhookadcp-signing-aws-kms— AWS KMS provider (real implementation)adcp-signing-gcp-kms— GCP KMS provider (real implementation)adcp-signing-bouncycastle— BC FIPS provider (stub)adcp-cli— signing-probe commandTests
226+ tests across all signing modules, including full conformance coverage against AdCP webhook signing and request signing test vectors.
Design Decisions
org.tomitribe:http-signatures) — Cavage draft ≠ RFC 9421SigningContext-based API (D22) — no single-argforUse(); tenant and principal nullable until v0.3Known Limitations
AccountStore→SigningContexttenant resolution wiring deferred to Track 5 (v0.3)ReplayStoredeferred to Track 6 (async-l3)adcp-keygenCLI wrapper not yet added (trivial, can land anytime)