From 4f92c63702d6be695dbc9ceb88c1c1594b6fc563 Mon Sep 17 00:00:00 2001 From: Cotton Hou Date: Sat, 4 Apr 2026 03:00:51 +0800 Subject: [PATCH] fix(auth): secureCompare to reuse constantTimeEqual from @oslojs/crypto (#180) --- packages/auth/src/tokens.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/auth/src/tokens.ts b/packages/auth/src/tokens.ts index ff1a18f..ac3a71f 100644 --- a/packages/auth/src/tokens.ts +++ b/packages/auth/src/tokens.ts @@ -6,7 +6,9 @@ * Tokens are opaque random values. We store only the SHA-256 hash in the database. */ -import { sha256 } from "@oslojs/crypto/sha2"; +import { hmac } from "@oslojs/crypto/hmac"; +import { sha256, SHA256 } from "@oslojs/crypto/sha2"; +import { constantTimeEqual } from "@oslojs/crypto/subtle"; import { encodeBase64urlNoPadding, decodeBase64urlIgnorePadding } from "@oslojs/encoding"; const TOKEN_BYTES = 32; // 256 bits of entropy @@ -162,16 +164,11 @@ export function computeS256Challenge(codeVerifier: string): string { * Constant-time comparison to prevent timing attacks */ export function secureCompare(a: string, b: string): boolean { - if (a.length !== b.length) return false; + const text = new TextEncoder(); + const salt = crypto.getRandomValues(new Uint8Array(TOKEN_BYTES)); + const hash = (str: string) => hmac(SHA256, salt, text.encode(str)); - const aBytes = new TextEncoder().encode(a); - const bBytes = new TextEncoder().encode(b); - - let result = 0; - for (let i = 0; i < aBytes.length; i++) { - result |= aBytes[i]! ^ bBytes[i]!; - } - return result === 0; + return constantTimeEqual(hash(a), hash(b)); } // ============================================================================