serverless-ircd 0.9.0 → 0.11.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.
- package/.github/workflows/ci.yml +28 -0
- package/.github/workflows/deploy-aws.yml +156 -32
- package/.github/workflows/deploy-cf-tcp.yml +35 -9
- package/.github/workflows/deploy-cf.yml +40 -14
- package/CHANGELOG.md +594 -0
- package/README.md +286 -60
- package/apps/aws-stack/README.md +3 -5
- package/apps/aws-stack/bin/aws.ts +118 -9
- package/apps/aws-stack/cdk.json +0 -3
- package/apps/aws-stack/package.json +3 -4
- package/apps/aws-stack/src/aws-stack.ts +398 -67
- package/apps/aws-stack/src/static-site.ts +323 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
- package/apps/aws-stack/tests/stack.test.ts +714 -105
- package/apps/aws-stack/tests/static-site.test.ts +491 -0
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
- package/apps/cf-tcp-container/Dockerfile +37 -5
- package/apps/cf-tcp-container/package.json +7 -3
- package/apps/cf-tcp-container/src/config-loader.ts +113 -2
- package/apps/cf-tcp-container/src/container-server.ts +267 -87
- package/apps/cf-tcp-container/src/main.ts +22 -7
- package/apps/cf-tcp-container/src/proxy-protocol.ts +112 -0
- package/apps/cf-tcp-container/terraform/spectrum.tf +40 -11
- package/apps/cf-tcp-container/tests/config-loader.test.ts +170 -0
- package/apps/cf-tcp-container/tests/container-server-tls.test.ts +382 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +358 -31
- package/apps/cf-tcp-container/tests/dockerfile.test.ts +110 -0
- package/apps/cf-tcp-container/tests/proxy-protocol.test.ts +187 -0
- package/apps/cf-tcp-container/tests/spectrum-terraform.test.ts +135 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +5 -1
- package/apps/cf-tcp-container/wrangler.toml +18 -14
- package/apps/cf-worker/package.json +3 -4
- package/apps/cf-worker/src/worker.ts +77 -5
- package/apps/cf-worker/tests/raw-modules.d.ts +11 -0
- package/apps/cf-worker/tests/smoke.test.ts +4 -0
- package/apps/cf-worker/tests/wrangler-config.test.ts +47 -0
- package/apps/cf-worker/tests/ws-admission.test.ts +112 -0
- package/apps/cf-worker/tests/ws-rate-limit.test.ts +133 -0
- package/apps/cf-worker/wrangler.test.toml +15 -1
- package/apps/cf-worker/wrangler.toml +95 -77
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +14 -2
- package/apps/local-cli/src/line-scanner.ts +26 -0
- package/apps/local-cli/src/server.ts +44 -19
- package/apps/local-cli/tests/line-scanner.test.ts +64 -0
- package/apps/local-cli/tests/tcp.test.ts +29 -0
- package/apps/web/landing/favicon.ico +0 -0
- package/apps/web/landing/index.html +1 -0
- package/apps/web/package.json +2 -2
- package/apps/web/scripts/build.mjs +66 -4
- package/apps/web/src/build-env.ts +125 -4
- package/apps/web/src/config-schema.ts +20 -6
- package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
- package/apps/web/tests/build-env.test.ts +210 -9
- package/apps/web/tests/build-smoke.test.ts +2 -2
- package/apps/web/tests/config-schema.test.ts +149 -25
- package/docs/AWS-Deployment.md +793 -118
- package/docs/AWS-TCP-Deployment.md +57 -47
- package/docs/Chat-History.md +55 -0
- package/docs/Cloudflare-Deployment-Guide.md +95 -114
- package/docs/Cloudflare-TCP-Deployment.md +160 -101
- package/docs/Release-Process.md +27 -23
- package/docs/SASL-EXTERNAL.md +175 -0
- package/docs/Services.md +69 -22
- package/docs/WebClientGuide.md +35 -26
- package/package.json +7 -10
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/admission.ts +28 -13
- package/packages/aws-adapter/src/aws-runtime.ts +30 -3
- package/packages/aws-adapter/src/cdk-table-defs.ts +39 -16
- package/packages/aws-adapter/src/config-loader.ts +153 -8
- package/packages/aws-adapter/src/dynamo-services-store.ts +19 -0
- package/packages/aws-adapter/src/handlers/connect.ts +73 -1
- package/packages/aws-adapter/src/handlers/default.ts +279 -123
- package/packages/aws-adapter/src/handlers/index.ts +98 -25
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +135 -14
- package/packages/aws-adapter/src/index.ts +5 -7
- package/packages/aws-adapter/src/ip-admission.ts +79 -0
- package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
- package/packages/aws-adapter/src/serialize.ts +23 -0
- package/packages/aws-adapter/src/tables.ts +11 -12
- package/packages/aws-adapter/tests/admission.test.ts +60 -2
- package/packages/aws-adapter/tests/aws-harness.ts +23 -2
- package/packages/aws-adapter/tests/aws-runtime.test.ts +64 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +217 -0
- package/packages/aws-adapter/tests/connect.test.ts +323 -3
- package/packages/aws-adapter/tests/default-frame-limit.test.ts +231 -0
- package/packages/aws-adapter/tests/default-occ.test.ts +226 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +134 -1
- package/packages/aws-adapter/tests/handlers.test.ts +174 -12
- package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
- package/packages/aws-adapter/tests/nlb-secure.test.ts +362 -0
- package/packages/aws-adapter/tests/nlb-stream.test.ts +628 -9
- package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
- package/packages/aws-adapter/tests/stats.test.ts +0 -3
- package/packages/aws-adapter/tests/sweeper.test.ts +0 -1
- package/packages/aws-adapter/tests/tables.test.ts +1 -8
- package/packages/aws-adapter/tests/transactions.test.ts +0 -1
- package/packages/cf-adapter/package.json +1 -5
- package/packages/cf-adapter/src/cf-runtime.ts +100 -10
- package/packages/cf-adapter/src/channel-do.ts +13 -3
- package/packages/cf-adapter/src/config-loader.ts +133 -8
- package/packages/cf-adapter/src/connection-do.ts +406 -116
- package/packages/cf-adapter/src/counter-do.ts +142 -0
- package/packages/cf-adapter/src/d1-services-store.ts +105 -26
- package/packages/cf-adapter/src/env.ts +99 -10
- package/packages/cf-adapter/src/index.ts +17 -7
- package/packages/cf-adapter/src/rate-limit-do.ts +87 -0
- package/packages/cf-adapter/tests/cf-runtime.test.ts +205 -16
- package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
- package/packages/cf-adapter/tests/config-loader.test.ts +159 -0
- package/packages/cf-adapter/tests/connection-do-counter.test.ts +165 -0
- package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
- package/packages/cf-adapter/tests/connection-do-frame-limit.test.ts +177 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +164 -54
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
- package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +7 -4
- package/packages/cf-adapter/tests/counter-do.test.ts +181 -0
- package/packages/cf-adapter/tests/d1-services-store.test.ts +245 -3
- package/packages/cf-adapter/tests/rate-limit-do.test.ts +160 -0
- package/packages/cf-adapter/tests/serialize.test.ts +25 -0
- package/packages/cf-adapter/tests/worker/main.ts +4 -0
- package/packages/cf-adapter/wrangler.test.toml +18 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +25 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +74 -0
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/account-migration.ts +140 -0
- package/packages/irc-core/src/caps/capabilities.ts +20 -10
- package/packages/irc-core/src/certfp.ts +178 -0
- package/packages/irc-core/src/commands/account-auth.ts +16 -19
- package/packages/irc-core/src/commands/cap.ts +10 -2
- package/packages/irc-core/src/commands/chanserv.ts +117 -14
- package/packages/irc-core/src/commands/chathistory.ts +13 -5
- package/packages/irc-core/src/commands/hostserv.ts +84 -8
- package/packages/irc-core/src/commands/index.ts +2 -1
- package/packages/irc-core/src/commands/invite.ts +1 -7
- package/packages/irc-core/src/commands/join.ts +1 -16
- package/packages/irc-core/src/commands/kick.ts +1 -8
- package/packages/irc-core/src/commands/list.ts +1 -8
- package/packages/irc-core/src/commands/memoserv.ts +1 -1
- package/packages/irc-core/src/commands/mode.ts +1 -8
- package/packages/irc-core/src/commands/multiline.ts +4 -10
- package/packages/irc-core/src/commands/names.ts +53 -13
- package/packages/irc-core/src/commands/nickserv.ts +161 -11
- package/packages/irc-core/src/commands/oper.ts +361 -8
- package/packages/irc-core/src/commands/part.ts +4 -10
- package/packages/irc-core/src/commands/privmsg.ts +8 -4
- package/packages/irc-core/src/commands/registration.ts +148 -4
- package/packages/irc-core/src/commands/sasl.ts +154 -46
- package/packages/irc-core/src/commands/topic.ts +10 -12
- package/packages/irc-core/src/commands/who.ts +1 -8
- package/packages/irc-core/src/config.ts +424 -25
- package/packages/irc-core/src/credential-hashing.ts +11 -54
- package/packages/irc-core/src/effects.ts +24 -0
- package/packages/irc-core/src/flood-control.ts +10 -10
- package/packages/irc-core/src/frame-rate-limit.ts +82 -0
- package/packages/irc-core/src/index.ts +9 -0
- package/packages/irc-core/src/oper-hashing.ts +43 -0
- package/packages/irc-core/src/oper-lockout.ts +87 -0
- package/packages/irc-core/src/ports.ts +529 -190
- package/packages/irc-core/src/protocol/bytes.ts +65 -0
- package/packages/irc-core/src/protocol/channel-name.ts +37 -0
- package/packages/irc-core/src/protocol/index.ts +12 -1
- package/packages/irc-core/src/protocol/outbound.ts +43 -10
- package/packages/irc-core/src/protocol/parser.ts +79 -10
- package/packages/irc-core/src/state/connection.ts +13 -0
- package/packages/irc-core/src/types.ts +266 -23
- package/packages/irc-core/src/ws-framing.ts +5 -4
- package/packages/irc-core/tests/account-migration.test.ts +133 -0
- package/packages/irc-core/tests/bytes.test.ts +89 -0
- package/packages/irc-core/tests/certfp.test.ts +117 -0
- package/packages/irc-core/tests/commands/cap.test.ts +76 -2
- package/packages/irc-core/tests/commands/chanserv.test.ts +166 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +140 -0
- package/packages/irc-core/tests/commands/hostserv.test.ts +316 -0
- package/packages/irc-core/tests/commands/join.test.ts +78 -1
- package/packages/irc-core/tests/commands/markread.test.ts +54 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
- package/packages/irc-core/tests/commands/names.test.ts +193 -0
- package/packages/irc-core/tests/commands/nickserv.test.ts +419 -3
- package/packages/irc-core/tests/commands/oper.test.ts +574 -1
- package/packages/irc-core/tests/commands/privmsg.test.ts +16 -0
- package/packages/irc-core/tests/commands/registration.test.ts +602 -133
- package/packages/irc-core/tests/commands/sasl.test.ts +742 -172
- package/packages/irc-core/tests/commands/topic.test.ts +137 -2
- package/packages/irc-core/tests/commands/unified-account.test.ts +104 -84
- package/packages/irc-core/tests/config.test.ts +534 -2
- package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
- package/packages/irc-core/tests/effects.test.ts +14 -0
- package/packages/irc-core/tests/flood-control.test.ts +29 -1
- package/packages/irc-core/tests/frame-rate-limit.test.ts +98 -0
- package/packages/irc-core/tests/message-store.test.ts +5 -0
- package/packages/irc-core/tests/oper-hashing.test.ts +60 -0
- package/packages/irc-core/tests/oper-lockout.test.ts +74 -0
- package/packages/irc-core/tests/outbound.test.ts +148 -0
- package/packages/irc-core/tests/parser.test.ts +287 -5
- package/packages/irc-core/tests/persistent-services-store.test.ts +212 -12
- package/packages/irc-core/tests/ports.test.ts +170 -7
- package/packages/irc-core/tests/services-store.test.ts +567 -1
- package/packages/irc-core/tests/ws-framing.test.ts +45 -0
- package/packages/irc-core/vitest.config.ts +6 -1
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +123 -22
- package/packages/irc-server/src/dispatch.ts +1 -0
- package/packages/irc-server/src/index.ts +7 -0
- package/packages/irc-server/src/redact.ts +159 -0
- package/packages/irc-server/src/runtime.ts +14 -0
- package/packages/irc-server/src/transport.ts +28 -1
- package/packages/irc-server/tests/actor.test.ts +563 -54
- package/packages/irc-server/tests/dispatch.test.ts +31 -0
- package/packages/irc-server/tests/redact.test.ts +198 -0
- package/packages/irc-server/tests/runtime.test.ts +2 -0
- package/packages/irc-server/tests/transport.test.ts +66 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +4 -0
- package/pnpm-workspace.yaml +1 -0
- package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
- package/scripts/deploy-web-aws.mjs +290 -0
- package/scripts/package.json +23 -0
- package/scripts/tsconfig.test.json +12 -0
- package/scripts/vitest.config.ts +19 -0
- package/tools/ci-hardening/package.json +2 -2
- package/tools/ci-hardening/src/cf-deploy-cli.ts +3 -0
- package/tools/ci-hardening/src/cf-deploy.ts +118 -0
- package/tools/ci-hardening/src/deploy-hostname.ts +118 -0
- package/tools/ci-hardening/src/env-var-drift.ts +192 -0
- package/tools/ci-hardening/src/hostname-guard.ts +11 -0
- package/tools/ci-hardening/src/index.ts +19 -0
- package/tools/ci-hardening/src/validate.ts +57 -0
- package/tools/ci-hardening/tests/__wrangler_missing__.toml +2 -0
- package/tools/ci-hardening/tests/__wrangler_placeholder__.toml +3 -0
- package/tools/ci-hardening/tests/__wrangler_real__.toml +3 -0
- package/tools/ci-hardening/tests/cf-deploy.test.ts +200 -0
- package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
- package/tools/ci-hardening/tests/deploy-hostname.test.ts +348 -0
- package/tools/ci-hardening/tests/env-var-drift.test.ts +284 -0
- package/tools/ci-hardening/tests/validate.test.ts +42 -0
- package/tools/ci-hardening/vitest.config.ts +5 -1
- package/tools/hash-oper-cred.ts +85 -0
- package/tools/load-test/package.json +1 -1
- package/tools/migrate-accounts-to-services.ts +270 -0
- package/tools/package.json +2 -1
- package/tools/seed-aws-accounts.ts +35 -10
- package/tools/seed-cf-accounts.ts +42 -9
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/packages/aws-adapter/src/account-store.ts +0 -121
- package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
- package/packages/aws-adapter/tests/account-store.test.ts +0 -276
- package/packages/cf-adapter/src/d1-account-store.ts +0 -198
- package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
- package/packages/irc-core/tests/account-store.test.ts +0 -131
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { certIdentityFromSubject } from '../src/certfp';
|
|
2
3
|
import {
|
|
3
4
|
CapturingLogger,
|
|
4
5
|
type Clock,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
SequentialIdFactory,
|
|
15
16
|
SystemClock,
|
|
16
17
|
UuidIdFactory,
|
|
18
|
+
constantTimeEquals,
|
|
17
19
|
} from '../src/ports';
|
|
18
20
|
|
|
19
21
|
describe('SystemClock', () => {
|
|
@@ -54,6 +56,11 @@ describe('FakeClock', () => {
|
|
|
54
56
|
});
|
|
55
57
|
});
|
|
56
58
|
|
|
59
|
+
// RFC 4122 v4 layout: 8-4-4-4-12 hex chars with the version nibble (M) fixed
|
|
60
|
+
// to 4 and the variant nibble (N) constrained to 8-b. Used by the shape and
|
|
61
|
+
// CSPRNG-sourcing tests below.
|
|
62
|
+
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
63
|
+
|
|
57
64
|
describe('UuidIdFactory', () => {
|
|
58
65
|
it('implements the IdFactory port', () => {
|
|
59
66
|
const f: IdFactory = new UuidIdFactory();
|
|
@@ -74,6 +81,72 @@ describe('UuidIdFactory', () => {
|
|
|
74
81
|
const f = new UuidIdFactory();
|
|
75
82
|
expect(f.sessionId()).not.toBe(f.sessionId());
|
|
76
83
|
});
|
|
84
|
+
|
|
85
|
+
it('emits RFC 4122 v4-shaped strings from every method', () => {
|
|
86
|
+
const f = new UuidIdFactory();
|
|
87
|
+
expect(f.batchId()).toMatch(UUID_V4_RE);
|
|
88
|
+
expect(f.nonce()).toMatch(UUID_V4_RE);
|
|
89
|
+
expect(f.sessionId()).toMatch(UUID_V4_RE);
|
|
90
|
+
expect(f.traceId()).toMatch(UUID_V4_RE);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('does not consult Math.random (randomness comes from the CSPRNG)', () => {
|
|
94
|
+
// Regression guard: if a future change reverts to Math.random, mocking it
|
|
95
|
+
// to a constant would make the output deterministic. A CSPRNG-backed
|
|
96
|
+
// factory must not touch Math.random at all.
|
|
97
|
+
const original = Math.random;
|
|
98
|
+
let calls = 0;
|
|
99
|
+
Math.random = () => {
|
|
100
|
+
calls++;
|
|
101
|
+
return 0.5;
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
const f = new UuidIdFactory();
|
|
105
|
+
const a = f.nonce();
|
|
106
|
+
const b = f.sessionId();
|
|
107
|
+
expect(calls).toBe(0);
|
|
108
|
+
expect(a).toMatch(UUID_V4_RE);
|
|
109
|
+
expect(b).toMatch(UUID_V4_RE);
|
|
110
|
+
// With Math.random pinned, a Math.random-backed impl would emit the
|
|
111
|
+
// same value every call; the CSPRNG emits two distinct values.
|
|
112
|
+
expect(a).not.toBe(b);
|
|
113
|
+
} finally {
|
|
114
|
+
Math.random = original;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('produces hex digits indistinguishable from uniform (chi-square, CSPRNG regression guard)', () => {
|
|
119
|
+
// Regression guard against a biased or constant RNG. Counts the trailing
|
|
120
|
+
// 12 hex chars of each id — those bytes are 100% random in a UUIDv4 (the
|
|
121
|
+
// version nibble at index 14 and variant nibble at index 19 are
|
|
122
|
+
// structural and excluded from the bucket count). Buckets the chars by
|
|
123
|
+
// nibble value (0-15) and chi-squares against the uniform expectation.
|
|
124
|
+
//
|
|
125
|
+
// Non-determinism guard: the chi-square statistic for genuinely uniform
|
|
126
|
+
// input follows chi2 with 15 degrees of freedom (mean 15, stddev ~5.5).
|
|
127
|
+
// The chosen threshold (70) sits ~10σ above the mean — P(chi2_15 > 70) ≈
|
|
128
|
+
// 2e-9, i.e. effectively zero false-positives across billions of CI
|
|
129
|
+
// runs. A catastrophic revert (Math.random pinned to a constant) drives
|
|
130
|
+
// chi into the hundreds of millions, so headroom is enormous.
|
|
131
|
+
const f = new UuidIdFactory();
|
|
132
|
+
const N = 10_000;
|
|
133
|
+
const buckets = new Array<number>(16).fill(0);
|
|
134
|
+
for (let i = 0; i < N; i++) {
|
|
135
|
+
// Slice(24) is the trailing 'xxxxxxxxxxxx' group of the UUID string —
|
|
136
|
+
// 12 hex chars, all sourced from getRandomValues.
|
|
137
|
+
const tail = f.nonce().slice(24);
|
|
138
|
+
for (const c of tail) {
|
|
139
|
+
const idx = Number.parseInt(c, 16);
|
|
140
|
+
buckets[idx] = (buckets[idx] ?? 0) + 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const expected = (N * 12) / 16;
|
|
144
|
+
let chi = 0;
|
|
145
|
+
for (const observed of buckets) {
|
|
146
|
+
chi += (observed - expected) ** 2 / expected;
|
|
147
|
+
}
|
|
148
|
+
expect(chi).toBeLessThan(70);
|
|
149
|
+
});
|
|
77
150
|
});
|
|
78
151
|
|
|
79
152
|
describe('SequentialIdFactory', () => {
|
|
@@ -365,17 +438,22 @@ const _assertLogRecord: LogRecord = {
|
|
|
365
438
|
void _assertLogRecord;
|
|
366
439
|
|
|
367
440
|
describe('InMemoryMtlsIdentityProvider', () => {
|
|
368
|
-
it('returns the certificate
|
|
441
|
+
it('returns the certificate identity for a known connection id', () => {
|
|
369
442
|
const provider = new InMemoryMtlsIdentityProvider([
|
|
370
|
-
{ connId: 'c1', identity: 'CN=alice' },
|
|
371
|
-
{ connId: 'c2', identity: 'CN=bob' },
|
|
443
|
+
{ connId: 'c1', identity: certIdentityFromSubject('CN=alice') },
|
|
444
|
+
{ connId: 'c2', identity: certIdentityFromSubject('CN=bob', 'ff11') },
|
|
372
445
|
]);
|
|
373
|
-
expect(provider.getIdentity('c1')).
|
|
374
|
-
|
|
446
|
+
expect(provider.getIdentity('c1')).toEqual({
|
|
447
|
+
subject: 'CN=alice',
|
|
448
|
+
canonicalDn: 'cn=alice',
|
|
449
|
+
});
|
|
450
|
+
expect(provider.getIdentity('c2')?.fingerprint).toBe('ff11');
|
|
375
451
|
});
|
|
376
452
|
|
|
377
453
|
it('returns undefined for an unknown connection id', () => {
|
|
378
|
-
const provider = new InMemoryMtlsIdentityProvider([
|
|
454
|
+
const provider = new InMemoryMtlsIdentityProvider([
|
|
455
|
+
{ connId: 'c1', identity: certIdentityFromSubject('CN=alice') },
|
|
456
|
+
]);
|
|
379
457
|
expect(provider.getIdentity('nope')).toBeUndefined();
|
|
380
458
|
});
|
|
381
459
|
|
|
@@ -384,3 +462,88 @@ describe('InMemoryMtlsIdentityProvider', () => {
|
|
|
384
462
|
expect(provider.getIdentity('c1')).toBeUndefined();
|
|
385
463
|
});
|
|
386
464
|
});
|
|
465
|
+
|
|
466
|
+
// ============================================================================
|
|
467
|
+
// constantTimeEquals — constant-time string equality
|
|
468
|
+
// ============================================================================
|
|
469
|
+
|
|
470
|
+
describe('constantTimeEquals', () => {
|
|
471
|
+
it('returns true for equal strings', () => {
|
|
472
|
+
expect(constantTimeEquals('s3cret', 's3cret')).toBe(true);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('returns true for two empty strings', () => {
|
|
476
|
+
expect(constantTimeEquals('', '')).toBe(true);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('returns false when the first character differs', () => {
|
|
480
|
+
expect(constantTimeEquals('X3cret', 's3cret')).toBe(false);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('returns false when the last character differs', () => {
|
|
484
|
+
expect(constantTimeEquals('s3creX', 's3cret')).toBe(false);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('returns false when the attempt is a proper prefix of the secret', () => {
|
|
488
|
+
expect(constantTimeEquals('s3cr', 's3cret')).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('returns false when the attempt is longer than the secret', () => {
|
|
492
|
+
expect(constantTimeEquals('s3cret-extra', 's3cret')).toBe(false);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('returns false for an empty attempt against a non-empty secret', () => {
|
|
496
|
+
expect(constantTimeEquals('', 's3cret')).toBe(false);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('returns false for a non-empty attempt against an empty secret', () => {
|
|
500
|
+
expect(constantTimeEquals('s3cret', '')).toBe(false);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('returns true for equal multi-byte strings', () => {
|
|
504
|
+
expect(constantTimeEquals('pässwörd', 'pässwörd')).toBe(true);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('returns false for unequal multi-byte strings', () => {
|
|
508
|
+
expect(constantTimeEquals('pässwörd', 'password')).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Deterministic timing-bucket proof: instead of measuring wall-clock time the
|
|
513
|
+
// per-position read primitive (`charCodeAt`) is stubbed with a counter. A
|
|
514
|
+
// constant-time compare reads every position of both inputs up to the longer
|
|
515
|
+
// length, so the read count is identical across equal inputs, first-char
|
|
516
|
+
// mismatches, last-char mismatches, and length mismatches of the same
|
|
517
|
+
// magnitude — no distinguishable work bucket exists.
|
|
518
|
+
describe('constantTimeEquals — timing buckets (deterministic read counts)', () => {
|
|
519
|
+
function countReads(a: string, b: string): number {
|
|
520
|
+
const spy = vi.spyOn(String.prototype, 'charCodeAt');
|
|
521
|
+
try {
|
|
522
|
+
constantTimeEquals(a, b);
|
|
523
|
+
return spy.mock.calls.length;
|
|
524
|
+
} finally {
|
|
525
|
+
spy.mockRestore();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
it('reads every position of both inputs for same-length secrets (2 × length)', () => {
|
|
530
|
+
expect(countReads('s3cret', 's3cret')).toBe(12);
|
|
531
|
+
expect(countReads('X3cret', 's3cret')).toBe(12);
|
|
532
|
+
expect(countReads('s3creX', 's3cret')).toBe(12);
|
|
533
|
+
expect(countReads('s3c!et', 's3cret')).toBe(12);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('reads the longer length even when the attempt is a length-mismatched prefix', () => {
|
|
537
|
+
expect(countReads('s3cr', 's3cret')).toBe(12);
|
|
538
|
+
expect(countReads('', 's3cret')).toBe(12);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('reads the attempt length when the attempt is longer than the secret', () => {
|
|
542
|
+
expect(countReads('s3cret-longer', 's3cret')).toBe(26);
|
|
543
|
+
expect(countReads('X3cret-longer', 's3cret')).toBe(26);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('spends zero reads on two empty strings', () => {
|
|
547
|
+
expect(countReads('', '')).toBe(0);
|
|
548
|
+
});
|
|
549
|
+
});
|