3
$\begingroup$

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).

  1. Webhook Spoofing
  2. 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

  1. Generate a Random UUID.
  2. Add this UUID as the HTTP Request Header "x-webhook-nonce".
  3. A new, empty string is created, henceforth known as the signing string.
  4. 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.
  5. Base64 encode the request body. Append the resulting Base64 encoded string to the signing string.
  6. 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.
  7. 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?

$\endgroup$
7
  • $\begingroup$ A server certificate plus a password is a way "short of Client SSL Certificates" "to know $\hspace{1.12 in}$ that the sender of the HTTP call is who we think they are". $\;$ $\endgroup$
    – user991
    Aug 11, 2014 at 21:47
  • 1
    $\begingroup$ Why sign webhooks at all? I prefer a pattern where the recipient doesn't assume that the webhook is authentic and simply uses it as a trigger to fetch the relevant information. That fetch is authenticated and protected against replay attacks since it uses HTTPS with your server as target. $\endgroup$ Aug 12, 2014 at 8:02
  • $\begingroup$ @CodesInChaos mostly because there's no good reason not to. If one has a reliable way to ensure authenticity, this reduces latency in the system and helps to reduce superfluous HTTP calls. It'd also be somewhat trivial to perform a DoS attach against someone that accepts all webhooks as a trigger to update from the HTTPS target. I send very small payloads to you and you fetch much larger payloads from an external system. I can send 1000's of requests quite easily which then causes your system to use significantly more bandwidth than I am using. $\endgroup$ Aug 12, 2014 at 13:15
  • $\begingroup$ @RickyDemer Certainly, but the receivers of the webhooks would be the "server" in this case. The receivers would likely receive the webhooks for multiple different systems and many different users on each system. They are also likely going to be third party systems. I don't want to require them to set up a password. It puts significantly higher burden on the receiver of the webhooks. $\endgroup$ Aug 12, 2014 at 13:18
  • $\begingroup$ Is there a reason you aren't just using client certificates? They were designed for solving this problem. The TLS folks thought a lot about the security challenges, so you wouldn't have to. $\endgroup$
    – D.W.
    Aug 15, 2014 at 3:06

1 Answer 1

2
$\begingroup$

I do not recommend inventing your own protocol for this.

Instead, I recommend using TLS client certificates. They solve exactly your problem, and solve it well. The TLS folks thought a lot about the security challenges involved in designing such a protocol, so you wouldn't have to.

You mention that you perceive the complexity of client certificates to be significant. My sense is that the complexity of rolling your own is actually higher than just using an existing TLS library's support for client certs; and the chances of making a subtle security mistake are significantly higher when rolling your own. Therefore, I do not recommend the direction you are taking of trying to design your own protocol: I recommend using a well-vetted existing mechanism (like TLS client certs) whenever you possibly can.

$\endgroup$
4
  • $\begingroup$ I'm not creating my own crypto algorithm. I'm computing a signature using a well tested algorithm in a known and reasonable way. Client certificates do introduce significant extra work for both parties, especially since I want each key to be different for every webhook subscription, so the keys are generated dynamically. $\endgroup$ Aug 15, 2014 at 15:50
  • $\begingroup$ @JessieA.Morris, you're still creating your own mini-protocol. My advice stands. P.S. I'm not certain you mean by "work". Your statement that client-side certs introduce more work doesn't sound right to me. If you mean development cost, I don't agree. If you mean computation time, then I suggest you benchmark it, but I bet you'll find that TLS client certs are no worse than anything else that involves a client-side signature. Either way, if TLS+client certs doesn't meet your needs, you should clarify the question by being more precise about requirements. $\endgroup$
    – D.W.
    Aug 15, 2014 at 17:02
  • $\begingroup$ If I were to do this, I'd have to distribute the root level CA .crt and each client would have to verify the connecting client is using a cert that was signed by the root level (or one of its delegates') key. Since TLS generally happens prior to the application server gets the request, this would require setting up Apache, Nginx, or some other front end with the certificate. And if my root level CA gets compromised, all clients have to update with a new CA. This is a distributed system with 3rd parties out of my hands. Logistics with the TLS client cert model is much more complex. $\endgroup$ Aug 15, 2014 at 17:37
  • $\begingroup$ @JessieA.Morris, for what it's worth, there are multiple different ways one can do TLS with client certs; they don't all require having a single trusted CA. But I've said my piece. $\endgroup$
    – D.W.
    Aug 15, 2014 at 17:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.