Sign and verify HTTP messages under RFC 9421 and the older
Cavage draft 12, with body digests
(RFC 9530 Content-Digest and legacy Digest). Both schemes matter because the Fediverse still runs mostly on
Cavage while newer servers accept both. The same module serves webhooks and API-to-API authentication with no
ActivityPub involved.
| Artifact | Purpose |
|---|---|
http-signatures-core |
Plain library: signature base, signing, verification, structured fields, digests, key parsing. No HTTP framework. |
quarkus-http-signatures |
Extension: Vert.x, Quarkus REST, REST Client and Quarkus Security integration, config keys, Dev UI. |
Minimum supported Quarkus: 3.40 (LTS). Status: experimental, version 0.1.
quarkus.http-signatures.keys.hook.location=keys/hook.jwk
quarkus.http-signatures.keys.hook.algorithm=hmac-sha256@POST @Path("/webhook")
@SignatureRequired(components = {"@method", "@target-uri", "content-digest"})
public void receive(String body) { ... }Outbound: @RegisterProvider(HttpSignatureClientFilter.class) @SignRequests on a REST Client interface, or
SigningHttpClient.send(...) for java.net.http (with double knocking: RFC 9421 first, Cavage on 401/403,
remembered per host).
| Property | Default | Purpose |
|---|---|---|
quarkus.http-signatures.verify.clock-skew |
5m |
Allowed drift on created or Date |
quarkus.http-signatures.verify.max-age |
1h |
Oldest signature accepted |
quarkus.http-signatures.verify.require-digest |
true |
Reject bodies without a digest |
quarkus.http-signatures.verify.schemes |
rfc9421,cavage |
Schemes accepted |
quarkus.http-signatures.verify.paths |
none | Path patterns for the Vert.x handler |
quarkus.http-signatures.verify.max-body-size |
10M |
Largest signed body buffered for digest checks |
quarkus.http-signatures.sign.preferred-scheme |
rfc9421 |
First scheme tried outbound |
quarkus.http-signatures.sign.fallback |
true |
Retry with the other scheme on 401/403 |
quarkus.http-signatures.keys."<id>".location |
none | PEM or JWK file for a static key |
quarkus.http-signatures.key-cache.ttl |
1h |
Resolved-key cache lifetime |
quarkus.http-signatures.nonce.enabled |
false |
Enable replay protection |
See docs/ for the full guide and examples/webhook for an HMAC webhook receiver with no ActivityPub dependency.
JAVA_HOME=/path/to/jdk21 mvn -T 8 install, then cd examples/webhook && mvn test.
RFC 9421 signatures cover @target-uri/@authority, which the verifier derives from the request as Quarkus sees it. Behind TLS termination or a reverse proxy set quarkus.http.proxy.proxy-address-forwarding=true (and quarkus.http.proxy.enable-forwarded-host=true / enable-forwarded-prefix=true as needed) so the public scheme, host and path are reconstructed; otherwise every RFC 9421 signature fails with INVALID_SIGNATURE.
Outbound key choice without a client name: the key named default, else the only configured key; with several keys name one default or configure quarkus.http-signatures.clients."<name>".key-id. The same fallback applies to a client name configured under quarkus.http-signatures.clients."<name>" without a key-id. A client name that is not configured there (for example one answered by an application SigningKeyProvider) never falls back to a config key: if no provider answers it, signing fails with IllegalStateException.
Config keys stay active alongside your own SigningKeyProvider / KeyResolver beans (they are @Priority(0)); order yours with @Priority (higher is asked first).