Webhook verification fails

Hello, I am testing the new webhook feature. I have written a test script in python to receive the webhooks.
It receives the posts from zerotier but the signature verification fails.

If I feed my script with the test data (psk, signature and payload) provided in the test script /ztchooks/hooks_test.go, the payload gets verified correctly, so the python logic seems to be working.

With real data received by zerotier the verification always fails.
I have spent may hours and verified everything several times.
The psk is a copy/paste from the web page.
The header signature parsing works fine.
The json payload is taken directly from the request body.
The payload length from the request matches matches with the value in the Content-Length header
But even so, the sha256 hash does not match the one in the signature.
Either the payload used to generate the signature is different, or the secret is not the same.
The data used to calculate the hash is composed as follows:
<timestamp (taken from the t field in the header)>,
Each of the 3 parts are expressed as bytes.
Any help will be appreciated
Regards

Without seeing your code, I can’t help too much. The only thing that comes to mind is to make sure you’re converting both the PSK and the signature to bytes from the hex string.

bytes.fromhex(some_hex_string)

Hi, this is exactly what I have done. See the attached script.
Thanks for your answer
webhook.py.txt (928 Bytes)

One thing I noticed in your Python code that differs a bit from the Go or TypeScript reference implementations: your version appears sensitive to the order of the key/value pairs in the received signature (t = sig_parts[0]... and v1 = sig_pars[1]...) whereas the versions we provided explicitly look for t and v keys: https://github.com/zerotier/ztchooks-ts/blob/main/index.ts#L47

You are right but this is not the problem. The signature parts are parsed correctly, I have checked it. The print statements show the correct timestamp and hash from the received signature.
I agree with you that it can be written better because this logic would not work with more than one active secret. But it’s not the case. I have only one active secret. The issue here is that the hashes don’t match.

Given that the timestamp is correct, the only thing that can differ and lead to a different hash is the payload. Probably the data used to generate the hash differs from the data in the request.
Looking at the log details on the web site I have noticed that the fields in the json structure appear in a different order than in request.data. Can it mean anything?

Can you post the full value of the X-ZTC-Signature header?

I’m stumped on this one so far. Can’t get Python to generate the correct expected signature at all. I’ll look into it more tomorrow.

Here is the output of a run:

X-Ztc-Signature: t=1699414774,v1=3225cacab01852ddafe8bf49762abddd5986c52c4bf2db6efbbe0a0f7ec5dd74
Received timestamp: 1699414774
Received hash  : 3225cacab01852ddafe8bf49762abddd5986c52c4bf2db6efbbe0a0f7ec5dd74
Calculated hash: 050430423540fb8c2ebb4d4c111d74bea6d606dacd24b350c0fd1f01cf010b88
Compare hashes result: False
Content length from header: 2351
Content length from request body: 2351

Hi, I received an answer by mail but I cannot see it here.
By the way, I wouldn’t worry about the debugger’s representation of the bytes string. It could be a debugger’s issue.
I added to my script these lines and I get the same hex value back:

hexstr = '04d87956d1953f28ac04d441f139fc655109e9b5c64396fb55dbdf567c735f86'
psk = bytes.fromhex(hexstr)
print(f'{hexstr=}')
print(f'bytes.fromhex(hexstr).hex()= {psk.hex()}')

and this is the output:

hexstr='04d87956d1953f28ac04d441f139fc655109e9b5c64396fb55dbdf567c735f86'
bytes.fromhex(hexstr).hex()= 04d87956d1953f28ac04d441f139fc655109e9b5c64396fb55dbdf567c735f86

PS: I am using python 3.12

Alright. Just took my deepest dive into Python in over 10 years and verified everything was working properly over there. I did discover an issue in the backend service that your case was triggering and that caused the JSON not to get signed correctly, hence the results you were seeing. A fix for that just rolled out.

Added bonus: I just put up a package on GitHub for verifying signatures in Python3.

I still have to figure out the whole package release process in the Python ecosystem, but that will be coming eventually.

Thanks for bringing this to our attention

Great job Grant!
Now my test script gets the webhook verified. Glad of having helped to discover the issue.
Best.
Toni

Package is now up on PyPI

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.