diff --git a/lambda/lockdown/lockdown-handler.ts b/lambda/lockdown/lockdown-handler.ts index 706b942..23f375c 100644 --- a/lambda/lockdown/lockdown-handler.ts +++ b/lambda/lockdown/lockdown-handler.ts @@ -2,9 +2,17 @@ import { SSMClient, GetParameterCommand, } from "@aws-sdk/client-ssm"; +import { timingSafeEqual } from "node:crypto"; const ssm = new SSMClient({}); +function tokensMatch(provided: string, expected: string): boolean { + const a = Buffer.from(provided); + const b = Buffer.from(expected); + // timingSafeEqual throws on unequal-length buffers; check length first. + return a.length === b.length && timingSafeEqual(a, b); +} + let cachedAuthToken: string | undefined; let cachedApiKey: string | undefined; @@ -105,7 +113,7 @@ export async function handler(event: { return xmlResponse(500, textScreenXml("Error", "Internal error")); } - if (token !== secrets.authToken) { + if (!tokensMatch(token, secrets.authToken)) { console.log(JSON.stringify({ action: "lockdown", status: "rejected", reason: "invalid_token", sourceIp })); return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) }; } diff --git a/lambda/unlock/unlock-handler.ts b/lambda/unlock/unlock-handler.ts index 617554a..91c29ee 100644 --- a/lambda/unlock/unlock-handler.ts +++ b/lambda/unlock/unlock-handler.ts @@ -2,9 +2,17 @@ import { SSMClient, GetParameterCommand, } from "@aws-sdk/client-ssm"; +import { timingSafeEqual } from "node:crypto"; const ssm = new SSMClient({}); +function tokensMatch(provided: string, expected: string): boolean { + const a = Buffer.from(provided); + const b = Buffer.from(expected); + // timingSafeEqual throws on unequal-length buffers; check length first. + return a.length === b.length && timingSafeEqual(a, b); +} + let cachedAuthToken: string | undefined; let cachedApiKey: string | undefined; let cachedDoorId: string | undefined; @@ -51,7 +59,7 @@ export async function handler(event: { return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) }; } - if (token !== secrets.authToken) { + if (!tokensMatch(token, secrets.authToken)) { console.log(JSON.stringify({ action: "unlock_attempt", status: "rejected", reason: "invalid_token", sourceIp })); return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) }; }