From 370d9f51f71063195a4a385d46c978e0af6565f9 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:30:48 +0200 Subject: [PATCH 1/2] feat: create util function for verifying webhook signature --- lib/utils.ts | 23 +++++++++++++++++++++++ test/util.ts | 28 +++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/utils.ts b/lib/utils.ts index d93723d..b8f672d 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,4 +1,5 @@ import { IncomingMessage } from 'http'; +import { createHmac, timingSafeEqual } from 'crypto'; import { IdentifierType } from '../lib/types'; export const isEmpty = (value: unknown) => { @@ -51,3 +52,25 @@ export class MissingParamError extends Error { this.message = `${param} is required`; } } + +export const verifyWebhookSignature = ( + webhookSigningSecret: string, + timestamp: string, + signature: string, + payload: Buffer, +): boolean => { + if (isEmpty(webhookSigningSecret) || isEmpty(timestamp) || isEmpty(signature) || isEmpty(payload)) { + throw new MissingParamError('webhookSigningSecret, timestamp, signature, payload'); + } + + const hmac = createHmac('sha256', webhookSigningSecret); + hmac.update(`v0:${timestamp}:`); + hmac.update(payload); + + const hash = hmac.digest(); + if (!timingSafeEqual(hash, Buffer.from(signature, 'hex'))) { + return false; + } + + return true; +}; diff --git a/test/util.ts b/test/util.ts index c2f07ee..a05000b 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,5 +1,5 @@ import avaTest, { TestFn } from 'ava'; -import { cleanEmail } from '../lib/utils'; +import { cleanEmail, verifyWebhookSignature } from '../lib/utils'; type TestContext = {}; @@ -18,3 +18,29 @@ test('#cleanEmail correctly formats email (default)', (t) => { result = cleanEmail(email); t.is(result, ''); }); + +test('#verifyWebhookSignature: should return false if an argument is empty', (t) => { + const timestamp = ''; + const payload = '{"hello": "world"}'; + const signature = '2380722c30fe151144a151cbbf6b5a207291314d78582594cb18bcdb66098d5c'; + const webhookSigningSecret = 'abcd'; + t.throws(() => verifyWebhookSignature(webhookSigningSecret, timestamp, signature, Buffer.from(payload)), { + message: 'webhookSigningSecret, timestamp, signature, payload is required', + }); +}); + +test('#verifyWebhookSignature: should return false if signature is wrong', (t) => { + const timestamp = '1536353830'; + const payload = '{"hello": "world"}'; + const signature = '2e4c14c338df161be248bca5cf0e7836a96077c6f773a97e6b8f3e0cc9a14a1a'; + const webhookSigningSecret = 'abcd'; + t.false(verifyWebhookSignature(webhookSigningSecret, timestamp, signature, Buffer.from(payload))); +}); + +test('#verifyWebhookSignature: should return true if signature is matching', (t) => { + const timestamp = '1536353830'; + const payload = '{"hello": "world"}'; + const signature = '2380722c30fe151144a151cbbf6b5a207291314d78582594cb18bcdb66098d5c'; + const webhookSigningSecret = 'abcd'; + t.true(verifyWebhookSignature(webhookSigningSecret, timestamp, signature, Buffer.from(payload))); +}); From 010c6436279f224b0247503d7e699654592f13cf Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:33:45 +0200 Subject: [PATCH 2/2] fix: return `timingSafeEqual` directly Co-authored-by: Lee Hansel Solevilla <13744167+sshanzel@users.noreply.github.com> --- lib/utils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index b8f672d..ed2e7da 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -68,9 +68,5 @@ export const verifyWebhookSignature = ( hmac.update(payload); const hash = hmac.digest(); - if (!timingSafeEqual(hash, Buffer.from(signature, 'hex'))) { - return false; - } - - return true; + return timingSafeEqual(hash, Buffer.from(signature, 'hex')); };