Expand description
Outbound webhook delivery signatures: the sshsig over each delivered body. Outbound webhook delivery signatures (the event stream, the outbound leg).
A webhook is a subscriber to the event bus that POSTs each event to a configured URL. This module is the part that makes gunnar’s webhook worth more than a plain POST: every delivery is signed, so the receiver can prove the body came from this gunnar and was not forged or replayed by anything that merely knows the URL.
§Why sshsig, and why its own namespace
The signature is an sshsig — the same SSHSIG format ssh-keygen -Y sign
produces and ssh-keygen -Y verify checks — exactly as the build record
(crate::provenance), the forge event (crate::forge) and the signed
tree head (crate::transparency) are. gunnar signs everything this way,
and a webhook is not a place to invent a second scheme (one implementation).
The receiver verifies with the server’s published public key and no shared secret: a leaked delivery URL is not a leaked signing key, and a receiver behind a CDN or a queue can still tell a real delivery from a replayed or forged one. That is the property an HMAC shared secret — the GitHub model — cannot give, because with HMAC the verifier holds the same secret the signer does and anything that can verify can also forge.
The namespace is WEBHOOK_NAMESPACE, distinct from git,
gunnar-provenance, gunnar-grpc and gunnar-transparency. sshsig binds
the namespace into what is signed, so a webhook delivery signature can never
be lifted and presented as a commit signature, a build attestation or a
control-plane credential, and none of those can be presented as a webhook
delivery. A signature is scoped to the one job it was made for.
§What is signed
The exact bytes of the request body — the JSON the receiver reads off the socket, byte for byte. Not a re-serialisation the receiver has to reproduce (JSON has no canonical form, so signing the meaning rather than the bytes would make the signature depend on the receiver’s serialiser), and not a digest the receiver has to trust the sender computed. The verifier hashes the body it actually received and checks the signature over that, so a single flipped byte anywhere in transit fails the check.
Structs§
- Signed
Delivery - A signed delivery: the fingerprint of the signing key and the
sshsig.
Enums§
- Delivery
Problem - What went wrong verifying a delivery. The variants a receiver distinguishes.
Constants§
- DELIVERY_
HEADER - The header carrying a per-delivery id (
<epoch>:<seq>), so a receiver can deduplicate a redelivered event. - EVENT_
HEADER - The header naming the event kind, e.g.
ref.updated. Lets a receiver route or drop before parsing the body. - HMAC_
HEADER - The GitHub-compatible HMAC header, set only when an endpoint configures a
shared secret.
sha256=<hex>over the body. Thesshsigabove is the default and the one that does not require the receiver to hold a secret; this exists so a receiver written for GitHub’s webhooks can be pointed at gunnar unchanged. - KEY_
FINGERPRINT_ HEADER - The header carrying
SHA256:…, the fingerprint of the key that signed — which of the server’s published keys to verify against. - NAMESPACE_
HEADER - The header naming the
sshsignamespace, so a receiver can build thessh-keygen -Y verify -n <namespace>invocation without hard-coding it. - SIGNATURE_
HEADER - The HTTP header carrying the base64 of the
sshsigPEM. - WEBHOOK_
NAMESPACE - The
sshsignamespace an outbound webhook delivery is signed under.
Functions§
- hmac_
sha256_ header - The GitHub-compatible
X-Hub-Signature-256value forbodyundersecret:sha256=<hex>ofHMAC-SHA256(secret, body). - sign_
delivery - Sign a delivery body under
WEBHOOK_NAMESPACE. - verify_
delivery - Verify a signed delivery against a public key.