cursedbelt-server 4.13.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,7 +29,7 @@
29
29
  * every path including a thrown KDF. Callers hand over ownership and must not
30
30
  * touch the buffer afterwards.
31
31
  */
32
- import { timingSafeEqual } from 'node:crypto';
32
+ import { constantTimeEqualBytes } from 'cwip/constant-time';
33
33
  import {
34
34
  type Argon2idDeriver,
35
35
  createSecretEnvelopeManager,
@@ -79,7 +79,7 @@ export function sealPasswordVerifier(userId: string, password: Uint8Array, deriv
79
79
  * stored envelope is corruption, not a wrong password, and re-throws.
80
80
  *
81
81
  * The comparison is constant-time in the sensitive part: argon2id derivation
82
- * dominates the timing, and GCM tag verification plus the final `timingSafeEqual`
82
+ * dominates the timing, and GCM tag verification plus the final `constantTimeEqualBytes`
83
83
  * are constant-time, so a right and a wrong password of equal length are
84
84
  * indistinguishable by timing.
85
85
  */
@@ -106,7 +106,7 @@ export function verifyPasswordEnvelope(
106
106
  // openWithPassphrase re-derives a one-shot key from the envelope's own salt +
107
107
  // the presented password, opens, and zeroizes `password` on every path.
108
108
  opened = manager.openWithPassphrase(envelope, refFor(userId), password);
109
- return opened.byteLength === VERIFIER_PLAINTEXT.byteLength && timingSafeEqual(opened, VERIFIER_PLAINTEXT);
109
+ return constantTimeEqualBytes(opened, VERIFIER_PLAINTEXT);
110
110
  } catch (error) {
111
111
  // GCM's rejection is the wrong-password verdict — the ONLY error we swallow.
112
112
  if (isAppError(error) && error.code === 'SECRET_ENVELOPE_AUTH_FAILED') return false;
@@ -4,10 +4,11 @@
4
4
  * per capability, sha256 at rest so a database read never yields a usable credential).
5
5
  *
6
6
  * The plaintext token is returned exactly once, at mint. Verification compares sha256
7
- * digests via `timingSafeEqual` — length-independent, no oracle.
7
+ * digests via cwip's `constantTimeEqualBytes` — no length guard, no early exit.
8
8
  */
9
9
  import type { Database } from "bun:sqlite";
10
- import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
10
+ import { createHash, randomBytes } from "node:crypto";
11
+ import { constantTimeEqualBytes } from "cwip/constant-time";
11
12
 
12
13
  export interface DeviceToken {
13
14
  name: string;
@@ -79,7 +80,7 @@ export function createTokenStore(db: Database): TokenStore {
79
80
  // the lookup timing depend on the presented value.
80
81
  for (const raw of allLive.all() as Row[]) {
81
82
  const want = Buffer.from(raw.token_sha256);
82
- if (want.length === got.length && timingSafeEqual(want, got)) {
83
+ if (constantTimeEqualBytes(want, got)) {
83
84
  if (raw.scope !== scope) return null;
84
85
  return toToken(raw);
85
86
  }