diff --git a/lib/utils/razorpay-utils.js b/lib/utils/razorpay-utils.js index 1dbfc933..ea248da9 100644 --- a/lib/utils/razorpay-utils.js +++ b/lib/utils/razorpay-utils.js @@ -95,11 +95,32 @@ function validateWebhookSignature (body, signature, secret) { body = body.toString(); + if (typeof secret !== 'string') { + secret = String(secret); + } + var expectedSignature = crypto.createHmac('sha256', secret) .update(body) .digest('hex'); - return expectedSignature === signature; + if (typeof signature !== 'string') { + signature = String(signature); + } + + if (signature.length !== expectedSignature.length || !/^[0-9a-f]+$/.test(signature)) { + return false; + } + + try { + const a = Buffer.from(expectedSignature, 'utf8'); + const b = Buffer.from(signature, 'utf8'); + if (a.length !== b.length) { + return false; + } + return crypto.timingSafeEqual(a, b); + } catch (err) { + return false; + } } function validatePaymentVerification(params={}, signature, secret){ diff --git a/test/utils/razorpay-utils.spec.js b/test/utils/razorpay-utils.spec.js index a5242a3f..7e1bd0b5 100644 --- a/test/utils/razorpay-utils.spec.js +++ b/test/utils/razorpay-utils.spec.js @@ -107,6 +107,34 @@ describe('Razorpay Utils', () => { ); }); + it('validateWebhookSignature uses timing-safe comparison', () => { + const respBody = '{"a":1,"b":2,"c":{"d":3}}' + const secret = "123456" + const correctSignature = "2fe04e22977002e6c7cb553adab8b460cb" + + "9e2a4970d5953cb27a8472752e3bbc" + const wrongSignature = "000000000000000000000000000000000" + + "0000000000000000000000000000" + + assert.ok( + validateWebhookSignature(respBody, correctSignature, secret) && + !validateWebhookSignature(respBody, wrongSignature, secret), + 'Timing-safe: validates webhook signature with correct and wrong signatures' + ); + }); + + it('validateWebhookSignature rejects signatures of different length', () => { + const respBody = '{"test":"data"}' + const secret = "mysecret" + const shortSignature = "abcdef" + const longSignature = "a".repeat(128) + + assert.ok( + !validateWebhookSignature(respBody, shortSignature, secret) && + !validateWebhookSignature(respBody, longSignature, secret), + 'Rejects signatures with different lengths than expected HMAC-SHA256 (64 hex chars)' + ); + }); + it('Subscription Verfication', () => { const respBody = {