standard-webhooks / standard-webhooks

The Standard Webhooks specification

Home Page:https://www.standardwebhooks.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

spec: signing the headers

hf opened this issue · comments

It's not super clear whether the headers of the HTTP message are also signed under webhook-signature. This should generally be the case so that they too are authenticated. This probably requires adding a section about canonicalizing the headers and including them in the signature computation.

So the signature would sign all the headers except the webhook-signature one right?
That sounds a legitimate but i have a question:

On the consumer side what would happen then when the request goes to N proxies before landing to the webhook handler? If any proxy mutates the http headers it would fail no? There's many doing so. eg:
In Kong you can setup keys for a route via a concept of consumer and this route will add headers related to the identication of this key. ref: https://docs.konghq.com/hub/kong-inc/key-auth/#upstream-headers

Under the current spec (we probably should clarify that), they would indeed not be signed. There are two reasons for that: (1) what @zekth said about all the intermediary that could potentially modify the headers, and (2) they shouldn't hold any information of significance. Per the per spec the data should only be sent as part of the payload, so as long as that's verified, everything that matters is verified. Verifying the headers should therefore not add any security.

One additional challenge that you just mentioned is the rabbit hole of canonicalizing headers. I think it would be immensely difficult to maintain a consistent behavior across different implementations. So even if we wanted it, I think it's going to add a tonne of complexity.

So the signature would sign all the headers except the webhook-signature one right? That sounds a legitimate but i have a question:

On the consumer side what would happen then when the request goes to N proxies before landing to the webhook handler? If any proxy mutates the http headers it would fail no? There's many doing so. eg: In Kong you can setup keys for a route via a concept of consumer and this route will add headers related to the identication of this key. ref: https://docs.konghq.com/hub/kong-inc/key-auth/#upstream-headers

That's fine. Not all headers should participate in the signature. And in any case, if the intermediary is trusted, it can have access to the private key and recompute the signature if necessary.

Under the current spec (we probably should clarify that), they would indeed not be signed. There are two reasons for that: (1) what @zekth said about all the intermediary that could potentially modify the headers, and (2) they shouldn't hold any information of significance. Per the per spec the data should only be sent as part of the payload, so as long as that's verified, everything that matters is verified. Verifying the headers should therefore not add any security.

Ah but this is the problem. For example, the spec says check webhook-id and webhook-timestamp but me as an attacker, I can choose any value for those. So I can replay the same message with its valid signature, and vary those two headers to my choosing.

Had they been included in the signature, the whole payload would be immediately rejected if I tried to do anything like that.

One additional challenge that you just mentioned is the rabbit hole of canonicalizing headers. I think it would be immensely difficult to maintain a consistent behavior across different implementations. So even if we wanted it, I think it's going to add a tonne of complexity.

We can say something like:

  • All webhook- prefixed headers are sorted alphabetically.
  • Concatenated in their original form, i.e. webhook-xyz: abc\n\r.
  • Prepended to the body in its original form.
  • Finally signature is computed.

@hf i guess we had the same idea in mind today; i was about to answer this issue with the same approach in mind ✌️
I like the approach, we just need to write this down in a lib proposal.

All webhook- prefixed headers are sorted alphabetically

I'm not sure all languages support this. related: golang/go#24375

The webhook- headers are already signed exactly for the reasons you mentioned!
See https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md?plain=1#L143
The ID and timestamp are signed so that they can't be manipulated. Because it's just the values and just these headers, it's also resistant to intermediary mangling.

My bad i forgot this part.
For ref:

computedSignature, err := wh.Sign(msgId, timestamp, payload)
if err != nil {
return err
}
expectedSignature := []byte(strings.Split(computedSignature, ",")[1])
passedSignatures := strings.Split(msgSignature, " ")
for _, versionedSignature := range passedSignatures {
sigParts := strings.Split(versionedSignature, ",")
if len(sigParts) < 2 {
continue
}
version := sigParts[0]
signature := []byte(sigParts[1])
if version != "v1" {
continue
}
if hmac.Equal(signature, expectedSignature) {
return nil
}
}
return errNoMatchingSignature
}

I'm not sure if this might be of interest to the project, but there is a "HTTP message signatures" RFC standard draft which might work for this use case.

Hey @teliosdev, thanks for the link! We are familiar with the RFC (it's even mentioned in the README!). There were a few reasons, but one of the main ones is: one of the design goals of this spec is to meet people where they are. So following but cementing industry best practices, rather than trying to do something very different. It's much better to have a great spec that's widely adopted than a perfect one that's used by no one. With that being said, as HTTP message signatures become more widespread/widely supported, this recommendation will probably change (future version of the spec).

I'm closing this ticket, as the original issue is resolved (headers are signed).