diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts index 433ac5db80..88e51f3f22 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts @@ -10,6 +10,7 @@ import { import { Request } from 'express'; import { KycConfigService } from '@/config'; +import * as securityUtils from '@/utils/security'; @Injectable() export class KycWebhookAuthGuard implements CanActivate { @@ -18,7 +19,6 @@ export class KycWebhookAuthGuard implements CanActivate { const request: Request = context.switchToHttp().getRequest(); const { headers, body } = request; - const apiKey = headers['x-auth-client']; const hmacSignature = headers['x-hmac-signature']; if (!hmacSignature) { @@ -27,6 +27,12 @@ export class KycWebhookAuthGuard implements CanActivate { HttpStatus.BAD_REQUEST, ); } + if (typeof hmacSignature !== 'string') { + throw new HttpException( + 'Invalid HMAC Signature type', + HttpStatus.BAD_REQUEST, + ); + } const signedPayload = createHmac( 'sha256', @@ -35,10 +41,7 @@ export class KycWebhookAuthGuard implements CanActivate { .update(JSON.stringify(body)) .digest('hex'); - if ( - signedPayload !== hmacSignature || - this.kycConfigService.apiKey !== apiKey - ) { + if (!securityUtils.safeCompare(signedPayload, hmacSignature)) { throw new HttpException( 'HMAC Signature does not match', HttpStatus.UNAUTHORIZED, diff --git a/packages/apps/reputation-oracle/server/src/utils/security.ts b/packages/apps/reputation-oracle/server/src/utils/security.ts index 3503adb801..8bdefb2179 100644 --- a/packages/apps/reputation-oracle/server/src/utils/security.ts +++ b/packages/apps/reputation-oracle/server/src/utils/security.ts @@ -1,3 +1,5 @@ +import crypto from 'crypto'; + import * as bcrypt from 'bcrypt'; export function hashPassword(password: string): string { @@ -12,3 +14,15 @@ export function comparePasswordWithHash( ): boolean { return bcrypt.compareSync(password, passwordHash); } + +export function safeCompare(a: string, b: string): boolean { + const bufA = Buffer.from(a); + const bufB = Buffer.from(b); + + // Must be same length to avoid early return timing leaks + if (bufA.length !== bufB.length) { + return false; + } + + return crypto.timingSafeEqual(bufA, bufB); +}