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: InKong
you can setup keys for a route via a concept ofconsumer
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:
standard-webhooks/libraries/go/webhook.go
Lines 94 to 118 in d6b7156
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).