Out of band my services have exchanged secret keys. These secret keys are then used to prevent two things of the webhooks (HTTP calls triggered by Service A to Service B when an event occurs in Service A).
- Webhook Spoofing
- Webhook Replaying
The basic problems with webhooks involve the fact that, short of Client SSL Certificates, there's no way to know that the sender of the HTTP call is who we think they are. Because of these, a mechanism is needed to ensure that the webhook is not being spoofed and that it isn't being replayed. For this, I'm using a Nonce and HMAC-SHA256. The definition of the Signing process (and a Python example) follows:
Creating the Signature
- Generate a Random UUID.
- Add this UUID as the HTTP Request Header "x-webhook-nonce".
- A new, empty string is created, henceforth known as the signing string.
- In alphabetical order of the Header names, append the HTTP Request headers (names and values, in that order, in lower case) onto the signing string.
- Base64 encode the request body. Append the resulting Base64 encoded string to the signing string.
- Using HMAC SHA256, create the signature. The previously exchanged key is to be used as the key and the signing string as the value to be signed.
- Take the resulting HMAC value and Base64 encode it. Create the HTTP Header “x-webhook-signature” with the resulting Base64 encoded HMAC string as the value.
Verifying the signature:
This process is almost the exact same as generating the signature with a few minor changes. 1. Verifiers should not generate their own nonce, but use the one provided in the “x-webhook-nonce” header. 2. Verifiers should first check and ensure that the nonce has not been used before. If it has, it should ignore the webhook. 3. When arranging the headers for the signing string, do not add the “x-webhook-signature” header. 4. Once you have generated a signing string, compare it to the included “x-webhook-signature” to ensure that the generated signature and the header value are the same. If they are, the request is valid. If not, ignore the webhook.
Generating Signature Example in Python:
signing_key = "SIGNING_KEY_FROM_SUBSCRIPTION"
nonce = uuid.uuid4()
body = "{\"url\": \"https://example.com/\"}"
headers = { 'Content-Type': 'application/json',
'Content-Length': 25,
'Host': 'example.com',
'x-webhook-nonce': nonce
}
to_sign = ""
for key in sorted(headers):
if key != "x-webhook-signature":
to_sign += key + headers[key]
to_sign += base64.b64encode(body)
signature = base64.b64encode(hmac.new(signing_key, to_sign, digestmod=hashlib.sha256).digest())
headers["x-webhook-signature"] = signature
So, after that large wall of text, does this solution correctly prevent against spoofing and replaying of webhooks?