A signature a customer can actually check, and the timestamp that stops a replay

Contribution Date
Contribution Project
Contribution Details
A signature a customer can actually check, and the timestamp that stops a replay dam-connect has been a one-line doc comment since the repository started. This is the sending half of the outbox: sign, post, and classify the answer. The signed string is timestamp.body, not body. A signature over the body alone is valid forever, so anybody who ever saw one delivery can send it again — and for a CMS consuming asset.expired that means replaying a withdrawal, or replaying the asset.published that came before it and un-withdrawing an asset. Stripe's scheme, for that reason. That delimiter is only safe because a decimal integer cannot contain a dot, which is a property of the field rather than of the format — so it is asserted, along with the collision it would allow: 12 . "34.body" must not sign the same as 1234 . "body". dam_core::signed_url uses length prefixes precisely because its fields carry no such guarantee. verify() ships as part of the crate rather than living in the test, so the scheme has one implementation and the tests check the thing a customer will write instead of a second copy that happens to agree. Constant-time, because a comparison that returns early leaks the prefix of a valid signature, and with an oracle that is a forgery. What counts as delivered is any 2xx, not 200 alone: a receiver that queues the event and answers 202 has accepted it, and one that answers 204 has done the work and has nothing to say. A 3xx is not success — a redirect from a webhook endpoint is a misconfiguration, and following it would post a customer's data to a host they never nominated. Which failures are retried: anything that might be temporary, including a 429, which is the receiver asking for less rather than saying no. A 4xx is rejected but still spends the attempt, and that trade is written down rather than assumed: retrying a permanent failure wastes a bounded number of tries, while abandoning a temporary one on the first stray 404 from a load balancer loses an event. The delivery id is stable across attempts. That is what lets a receiver drop a duplicate after timing out behind the work rather than in front of it; a fresh id per attempt would make every retry look like a new event. 11 cases, and the HTTP ones go over a real socket to a real axum receiver — a mocked client would test this code's opinion of itself while leaving untested whether the headers arrive, whether the body is the bytes that were signed, and whether a receiver can verify a delivery from the documented scheme alone.
Contribution Author
Bassam Ismail
Files count
0
Patches count
1