@opensyber/tokenforge 0.1.1 → 1.0.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/LICENSE.md +32 -0
- package/README.md +205 -47
- package/dist/adapters/astro.d.ts +55 -0
- package/dist/adapters/astro.d.ts.map +1 -0
- package/dist/adapters/astro.js +91 -0
- package/dist/adapters/astro.js.map +1 -0
- package/dist/adapters/express.d.ts +15 -0
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +27 -0
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts +17 -0
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +27 -0
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/hono.d.ts +8 -4
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +7 -4
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +5 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/nextjs.d.ts +16 -0
- package/dist/adapters/nextjs.d.ts.map +1 -1
- package/dist/adapters/nextjs.js +26 -0
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/sveltekit.d.ts +57 -0
- package/dist/adapters/sveltekit.d.ts.map +1 -0
- package/dist/adapters/sveltekit.js +88 -0
- package/dist/adapters/sveltekit.js.map +1 -0
- package/dist/client/action-signer.d.ts +53 -0
- package/dist/client/action-signer.d.ts.map +1 -0
- package/dist/client/action-signer.js +48 -0
- package/dist/client/action-signer.js.map +1 -0
- package/dist/client/binding.d.ts +24 -7
- package/dist/client/binding.d.ts.map +1 -1
- package/dist/client/binding.js +17 -6
- package/dist/client/binding.js.map +1 -1
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +27 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/signer.d.ts +17 -0
- package/dist/client/signer.d.ts.map +1 -1
- package/dist/client/signer.js +23 -0
- package/dist/client/signer.js.map +1 -1
- package/dist/client/webauthn.d.ts +45 -0
- package/dist/client/webauthn.d.ts.map +1 -0
- package/dist/client/webauthn.js +118 -0
- package/dist/client/webauthn.js.map +1 -0
- package/dist/server/action-verify.d.ts +56 -0
- package/dist/server/action-verify.d.ts.map +1 -0
- package/dist/server/action-verify.js +47 -0
- package/dist/server/action-verify.js.map +1 -0
- package/dist/server/aitm-heuristics.d.ts +57 -0
- package/dist/server/aitm-heuristics.d.ts.map +1 -0
- package/dist/server/aitm-heuristics.js +120 -0
- package/dist/server/aitm-heuristics.js.map +1 -0
- package/dist/server/bound-cookie.d.ts +39 -0
- package/dist/server/bound-cookie.d.ts.map +1 -0
- package/dist/server/bound-cookie.js +59 -0
- package/dist/server/bound-cookie.js.map +1 -0
- package/dist/server/cbor.d.ts +20 -0
- package/dist/server/cbor.d.ts.map +1 -0
- package/dist/server/cbor.js +110 -0
- package/dist/server/cbor.js.map +1 -0
- package/dist/server/dbsc-challenge.d.ts +51 -0
- package/dist/server/dbsc-challenge.d.ts.map +1 -0
- package/dist/server/dbsc-challenge.js +78 -0
- package/dist/server/dbsc-challenge.js.map +1 -0
- package/dist/server/index.d.ts +2 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/jws-verify.d.ts +46 -0
- package/dist/server/jws-verify.d.ts.map +1 -0
- package/dist/server/jws-verify.js +64 -0
- package/dist/server/jws-verify.js.map +1 -0
- package/dist/server/middleware.d.ts +15 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +27 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/oidc-verify.d.ts +56 -0
- package/dist/server/oidc-verify.d.ts.map +1 -0
- package/dist/server/oidc-verify.js +107 -0
- package/dist/server/oidc-verify.js.map +1 -0
- package/dist/server/policy.d.ts +47 -0
- package/dist/server/policy.d.ts.map +1 -0
- package/dist/server/policy.js +84 -0
- package/dist/server/policy.js.map +1 -0
- package/dist/server/step-up-policy.d.ts +36 -0
- package/dist/server/step-up-policy.d.ts.map +1 -0
- package/dist/server/step-up-policy.js +101 -0
- package/dist/server/step-up-policy.js.map +1 -0
- package/dist/server/trust-score-fixtures.d.ts +9 -0
- package/dist/server/trust-score-fixtures.d.ts.map +1 -0
- package/dist/server/trust-score-fixtures.js +23 -0
- package/dist/server/trust-score-fixtures.js.map +1 -0
- package/dist/server/webauthn-verify.d.ts +41 -0
- package/dist/server/webauthn-verify.d.ts.map +1 -0
- package/dist/server/webauthn-verify.js +175 -0
- package/dist/server/webauthn-verify.js.map +1 -0
- package/dist/server/webauthn-verify.test-helpers.d.ts +8 -0
- package/dist/server/webauthn-verify.test-helpers.d.ts.map +1 -0
- package/dist/server/webauthn-verify.test-helpers.js +39 -0
- package/dist/server/webauthn-verify.test-helpers.js.map +1 -0
- package/dist/server/webhooks.d.ts +42 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +77 -0
- package/dist/server/webhooks.js.map +1 -0
- package/dist/shared/action-hash.d.ts +23 -0
- package/dist/shared/action-hash.d.ts.map +1 -0
- package/dist/shared/action-hash.js +35 -0
- package/dist/shared/action-hash.js.map +1 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/package.json +44 -16
- package/dist/server/binding.d.ts +0 -19
- package/dist/server/binding.d.ts.map +0 -1
- package/dist/server/binding.js +0 -87
- package/dist/server/binding.js.map +0 -1
- package/dist/server/step-up.d.ts +0 -19
- package/dist/server/step-up.d.ts.map +0 -1
- package/dist/server/step-up.js +0 -139
- package/dist/server/step-up.js.map +0 -1
- package/dist/server/storage/index.d.ts +0 -3
- package/dist/server/storage/index.d.ts.map +0 -1
- package/dist/server/storage/index.js +0 -2
- package/dist/server/storage/index.js.map +0 -1
- package/dist/server/storage/interface.d.ts +0 -61
- package/dist/server/storage/interface.d.ts.map +0 -1
- package/dist/server/storage/interface.js +0 -2
- package/dist/server/storage/interface.js.map +0 -1
- package/dist/server/storage/memory.d.ts +0 -40
- package/dist/server/storage/memory.d.ts.map +0 -1
- package/dist/server/storage/memory.js +0 -107
- package/dist/server/storage/memory.js.map +0 -1
- package/dist/server/totp.d.ts +0 -53
- package/dist/server/totp.d.ts.map +0 -1
- package/dist/server/totp.js +0 -114
- package/dist/server/totp.js.map +0 -1
package/dist/client/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { bindDevice } from './binding.js';
|
|
2
2
|
import { clearDeviceKeys } from './storage.js';
|
|
3
3
|
import { installFetchInterceptor } from './interceptor.js';
|
|
4
|
+
import { signAction as signActionInternal, } from './action-signer.js';
|
|
4
5
|
/**
|
|
5
6
|
* TokenForge Client SDK — main entry point.
|
|
6
7
|
*
|
|
@@ -33,7 +34,9 @@ export class TokenForge {
|
|
|
33
34
|
try {
|
|
34
35
|
const result = await bindDevice(this.config.apiBase, sessionId);
|
|
35
36
|
this.deviceId = result.deviceId;
|
|
36
|
-
|
|
37
|
+
// ECDSA path stores the keyPair; WebAuthn path keeps the credential on
|
|
38
|
+
// the authenticator (signed via navigator.credentials.get on demand).
|
|
39
|
+
this.keyPair = result.type === 'ecdsa' ? result.keyPair : null;
|
|
37
40
|
this.bound = true;
|
|
38
41
|
this.config.onDeviceBound?.(this.deviceId);
|
|
39
42
|
if (this.config.autoIntercept !== false) {
|
|
@@ -98,6 +101,29 @@ export class TokenForge {
|
|
|
98
101
|
await this.clearKeys();
|
|
99
102
|
await this.init();
|
|
100
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Sign a high-trust action (checkout, password change, admin grant…) with
|
|
106
|
+
* the device-bound private key. Returns a compact JWS the server verifies
|
|
107
|
+
* via `verifyCompactJws` + `actionHash` claim match.
|
|
108
|
+
*
|
|
109
|
+
* @throws when the device is not yet bound (call `init()` first).
|
|
110
|
+
*/
|
|
111
|
+
async signAction(payload, options = {}) {
|
|
112
|
+
if (!this.keyPair || !this.bound) {
|
|
113
|
+
throw new Error('TokenForge.signAction: device not bound — call init() first');
|
|
114
|
+
}
|
|
115
|
+
const sessionId = this.config.getSessionId();
|
|
116
|
+
if (!sessionId) {
|
|
117
|
+
throw new Error('TokenForge.signAction: no sessionId from getSessionId()');
|
|
118
|
+
}
|
|
119
|
+
return signActionInternal(payload, {
|
|
120
|
+
privateKey: this.keyPair.privateKey,
|
|
121
|
+
sessionId,
|
|
122
|
+
kid: options.kid,
|
|
123
|
+
ttlSeconds: options.ttlSeconds,
|
|
124
|
+
tlsExporter: options.tlsExporter,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
101
127
|
}
|
|
102
128
|
/**
|
|
103
129
|
* Factory function to create a TokenForge instance.
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAgB,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAgB,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EACL,UAAU,IAAI,kBAAkB,GAGjC,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,OAAO,UAAU;IACb,MAAM,CAAmB;IACzB,QAAQ,GAAkB,IAAI,CAAC;IAC/B,OAAO,GAAyB,IAAI,CAAC;IACrC,KAAK,GAAG,KAAK,CAAC;IACd,kBAAkB,GAAwB,IAAI,CAAC;IAEvD,YAAY,MAAwB;QAClC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,mCAAmC;QACnC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAC;YACnF,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;YAChC,uEAAuE;YACvE,sEAAsE;YACtE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;YAC/D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAElB,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE3C,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;gBACxC,IAAI,CAAC,kBAAkB,GAAG,uBAAuB,CAC/C,IAAI,CAAC,MAAM,EACX,KAAK,IAAI,EAAE;oBACT,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ;wBAAE,OAAO,IAAI,CAAC;oBACjD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;oBACvC,IAAI,CAAC,GAAG;wBAAE,OAAO,IAAI,CAAC;oBACtB,OAAO;wBACL,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;wBACnC,SAAS,EAAE,GAAG;wBACd,QAAQ,EAAE,IAAI,CAAC,QAAQ;qBACxB,CAAC;gBACJ,CAAC,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;YACjD,8DAA8D;QAChE,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,OAAgB;QAChC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC;QAEpD,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACrE,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,OAAO,OAAO,CAAC;QAE/B,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,MAAM,aAAa,CACnC,IAAI,CAAC,OAAO,CAAC,UAAU,EACvB,SAAS,EACT,KAAK,EACL,SAAS,CACV,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,SAAS,CAAC,CAAC;QAC3E,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,KAAK,CAAC,CAAC;QAC/D,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAClD,SAAS,CAAC,QAAQ,EAAE,CACrB,CAAC;QACF,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EACjD,IAAI,CAAC,QAAQ,CACd,CAAC;QAEF,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,gEAAgE;IAChE,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,8EAA8E;IAC9E,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CACd,OAAsB,EACtB,UAAuE,EAAE;QAEzE,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,kBAAkB,CAAC,OAAO,EAAE;YACjC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACnC,SAAS;YACT,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,CAAC,CAAC;IACL,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACvD,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC"}
|
package/dist/client/signer.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Challenge-response signing protocol.
|
|
3
3
|
* Signs: `${sessionId}:${nonce}:${timestamp}` with the device-bound private key.
|
|
4
|
+
*
|
|
5
|
+
* Also exports `isNativeDbscAvailable()` — feature-detect for the W3C
|
|
6
|
+
* Device-Bound Session Credentials API (Chrome 146+). When present, the
|
|
7
|
+
* SDK can defer key generation + binding to the platform; otherwise it
|
|
8
|
+
* continues using the Web Crypto fallback path implemented here.
|
|
4
9
|
*/
|
|
5
10
|
/**
|
|
6
11
|
* Sign a challenge payload with the device-bound private key.
|
|
@@ -12,4 +17,16 @@ export declare function signChallenge(privateKey: CryptoKey, sessionId: string,
|
|
|
12
17
|
*/
|
|
13
18
|
export declare function generateNonce(): string;
|
|
14
19
|
export declare function arrayBufferToBase64Url(buffer: ArrayBuffer): string;
|
|
20
|
+
/**
|
|
21
|
+
* Feature-detect the W3C Device-Bound Session Credentials API.
|
|
22
|
+
*
|
|
23
|
+
* Returns `true` when `navigator.deviceBoundSession` is a real object the
|
|
24
|
+
* SDK could route through. Treats undefined / null / non-object values
|
|
25
|
+
* (functions, primitives) as unavailable so a downgrade attacker who
|
|
26
|
+
* shadows `navigator` with a truthy primitive does not flip the SDK into
|
|
27
|
+
* thinking native DBSC is present.
|
|
28
|
+
*
|
|
29
|
+
* Spec: https://w3c.github.io/webappsec-dbsc/ (Chrome 146+)
|
|
30
|
+
*/
|
|
31
|
+
export declare function isNativeDbscAvailable(): boolean;
|
|
15
32
|
//# sourceMappingURL=signer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/client/signer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/client/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,SAAS,EACrB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAOlE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAK/C"}
|
package/dist/client/signer.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Challenge-response signing protocol.
|
|
3
3
|
* Signs: `${sessionId}:${nonce}:${timestamp}` with the device-bound private key.
|
|
4
|
+
*
|
|
5
|
+
* Also exports `isNativeDbscAvailable()` — feature-detect for the W3C
|
|
6
|
+
* Device-Bound Session Credentials API (Chrome 146+). When present, the
|
|
7
|
+
* SDK can defer key generation + binding to the platform; otherwise it
|
|
8
|
+
* continues using the Web Crypto fallback path implemented here.
|
|
4
9
|
*/
|
|
5
10
|
/**
|
|
6
11
|
* Sign a challenge payload with the device-bound private key.
|
|
@@ -28,4 +33,22 @@ export function arrayBufferToBase64Url(buffer) {
|
|
|
28
33
|
}
|
|
29
34
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
30
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Feature-detect the W3C Device-Bound Session Credentials API.
|
|
38
|
+
*
|
|
39
|
+
* Returns `true` when `navigator.deviceBoundSession` is a real object the
|
|
40
|
+
* SDK could route through. Treats undefined / null / non-object values
|
|
41
|
+
* (functions, primitives) as unavailable so a downgrade attacker who
|
|
42
|
+
* shadows `navigator` with a truthy primitive does not flip the SDK into
|
|
43
|
+
* thinking native DBSC is present.
|
|
44
|
+
*
|
|
45
|
+
* Spec: https://w3c.github.io/webappsec-dbsc/ (Chrome 146+)
|
|
46
|
+
*/
|
|
47
|
+
export function isNativeDbscAvailable() {
|
|
48
|
+
if (typeof navigator === 'undefined')
|
|
49
|
+
return false;
|
|
50
|
+
const nav = navigator;
|
|
51
|
+
const candidate = nav.deviceBoundSession;
|
|
52
|
+
return typeof candidate === 'object' && candidate !== null;
|
|
53
|
+
}
|
|
31
54
|
//# sourceMappingURL=signer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/client/signer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/client/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAqB,EACrB,SAAiB,EACjB,KAAa,EACb,SAAiB;IAEjB,MAAM,OAAO,GAAG,GAAG,SAAS,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAElD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CACxC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAC5C,UAAU,EACV,OAAO,CACR,CAAC;IAEF,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,sBAAsB,CAAC,KAAK,CAAC,MAAqB,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAmB;IACxD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,OAAO,SAAS,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IACnD,MAAM,GAAG,GAAG,SAAyD,CAAC;IACtE,MAAM,SAAS,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACzC,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn / FIDO2 client flow — alternative to the ECDSA browser key path.
|
|
3
|
+
*
|
|
4
|
+
* Coexists with the ECDSA flow in binding.ts. Caller opts in via
|
|
5
|
+
* `bindDevice(..., { prefer: 'webauthn' })`. The credential private key is
|
|
6
|
+
* never exposed to JS — it lives on the authenticator (YubiKey, Touch ID,
|
|
7
|
+
* Windows Hello, platform passkey provider).
|
|
8
|
+
*
|
|
9
|
+
* NOTE: WebAuthn signatures are over (authenticatorData || sha256(clientDataJSON)),
|
|
10
|
+
* not the raw `${sessionId}:${nonce}:${timestamp}` payload. The server reconstructs
|
|
11
|
+
* the same expected challenge from session/nonce/timestamp and compares it to
|
|
12
|
+
* the challenge embedded inside clientDataJSON.
|
|
13
|
+
*/
|
|
14
|
+
export interface WebAuthnBindResult {
|
|
15
|
+
deviceId: string;
|
|
16
|
+
credentialId: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WebAuthnAssertion {
|
|
19
|
+
signature: string;
|
|
20
|
+
authenticatorData: string;
|
|
21
|
+
clientDataJSON: string;
|
|
22
|
+
credentialId: string;
|
|
23
|
+
}
|
|
24
|
+
/** base64url encode a buffer (no padding, URL-safe alphabet). */
|
|
25
|
+
export declare function bufferToBase64Url(buffer: ArrayBuffer | Uint8Array): string;
|
|
26
|
+
/** base64url decode to ArrayBuffer. */
|
|
27
|
+
export declare function base64UrlToBuffer(b64url: string): ArrayBuffer;
|
|
28
|
+
/**
|
|
29
|
+
* Bind a hardware credential to the active session.
|
|
30
|
+
*
|
|
31
|
+
* Calls navigator.credentials.create with ES256 (alg -7) + EdDSA (alg -8)
|
|
32
|
+
* pubKeyCredParams. Sends the attestation to the server, receives the
|
|
33
|
+
* persisted deviceId.
|
|
34
|
+
*/
|
|
35
|
+
export declare function bindDeviceWebAuthn(apiBase: string, sessionId: string, userId: string, displayName: string): Promise<WebAuthnBindResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Sign a per-request challenge with the bound hardware credential.
|
|
38
|
+
*
|
|
39
|
+
* The challenge is `${sessionId}:${nonce}:${timestamp}` — same payload shape
|
|
40
|
+
* as the ECDSA path, but the WebAuthn signature is over
|
|
41
|
+
* (authenticatorData || sha256(clientDataJSON)) where clientDataJSON contains
|
|
42
|
+
* the challenge. The server reconstructs and compares.
|
|
43
|
+
*/
|
|
44
|
+
export declare function signChallengeWebAuthn(credentialId: string, sessionId: string, nonce: string, timestamp: number): Promise<WebAuthnAssertion>;
|
|
45
|
+
//# sourceMappingURL=webauthn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webauthn.d.ts","sourceRoot":"","sources":["../../src/client/webauthn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAK1E;AAED,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAO7D;AAOD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAgD7B;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,CAAC,CA0B5B"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn / FIDO2 client flow — alternative to the ECDSA browser key path.
|
|
3
|
+
*
|
|
4
|
+
* Coexists with the ECDSA flow in binding.ts. Caller opts in via
|
|
5
|
+
* `bindDevice(..., { prefer: 'webauthn' })`. The credential private key is
|
|
6
|
+
* never exposed to JS — it lives on the authenticator (YubiKey, Touch ID,
|
|
7
|
+
* Windows Hello, platform passkey provider).
|
|
8
|
+
*
|
|
9
|
+
* NOTE: WebAuthn signatures are over (authenticatorData || sha256(clientDataJSON)),
|
|
10
|
+
* not the raw `${sessionId}:${nonce}:${timestamp}` payload. The server reconstructs
|
|
11
|
+
* the same expected challenge from session/nonce/timestamp and compares it to
|
|
12
|
+
* the challenge embedded inside clientDataJSON.
|
|
13
|
+
*/
|
|
14
|
+
/** base64url encode a buffer (no padding, URL-safe alphabet). */
|
|
15
|
+
export function bufferToBase64Url(buffer) {
|
|
16
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
17
|
+
let binary = '';
|
|
18
|
+
for (const byte of bytes)
|
|
19
|
+
binary += String.fromCharCode(byte);
|
|
20
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
21
|
+
}
|
|
22
|
+
/** base64url decode to ArrayBuffer. */
|
|
23
|
+
export function base64UrlToBuffer(b64url) {
|
|
24
|
+
const base64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
|
|
25
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
26
|
+
const binary = atob(padded);
|
|
27
|
+
const bytes = new Uint8Array(binary.length);
|
|
28
|
+
for (let i = 0; i < binary.length; i++)
|
|
29
|
+
bytes[i] = binary.charCodeAt(i);
|
|
30
|
+
return bytes.buffer;
|
|
31
|
+
}
|
|
32
|
+
/** SHA-256 → bytes for the WebAuthn challenge derivation. */
|
|
33
|
+
async function sha256(input) {
|
|
34
|
+
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Bind a hardware credential to the active session.
|
|
38
|
+
*
|
|
39
|
+
* Calls navigator.credentials.create with ES256 (alg -7) + EdDSA (alg -8)
|
|
40
|
+
* pubKeyCredParams. Sends the attestation to the server, receives the
|
|
41
|
+
* persisted deviceId.
|
|
42
|
+
*/
|
|
43
|
+
export async function bindDeviceWebAuthn(apiBase, sessionId, userId, displayName) {
|
|
44
|
+
if (typeof navigator === 'undefined' || !navigator.credentials) {
|
|
45
|
+
throw new Error('WebAuthn not available in this environment');
|
|
46
|
+
}
|
|
47
|
+
// Server-bound challenge: derived from sessionId so a stolen challenge
|
|
48
|
+
// can't be replayed against another session.
|
|
49
|
+
const challenge = await sha256(`tf-bind:${sessionId}`);
|
|
50
|
+
const userIdBytes = new TextEncoder().encode(userId);
|
|
51
|
+
const publicKey = {
|
|
52
|
+
challenge,
|
|
53
|
+
rp: { name: 'TokenForge' },
|
|
54
|
+
user: { id: userIdBytes, name: userId, displayName },
|
|
55
|
+
pubKeyCredParams: [
|
|
56
|
+
{ type: 'public-key', alg: -7 }, // ES256
|
|
57
|
+
{ type: 'public-key', alg: -8 }, // EdDSA
|
|
58
|
+
],
|
|
59
|
+
authenticatorSelection: { userVerification: 'preferred' },
|
|
60
|
+
timeout: 60_000,
|
|
61
|
+
attestation: 'none',
|
|
62
|
+
};
|
|
63
|
+
const credential = (await navigator.credentials.create({ publicKey }));
|
|
64
|
+
if (!credential)
|
|
65
|
+
throw new Error('WebAuthn credential creation cancelled');
|
|
66
|
+
const response = credential.response;
|
|
67
|
+
const body = {
|
|
68
|
+
sessionId,
|
|
69
|
+
userId,
|
|
70
|
+
credentialId: bufferToBase64Url(credential.rawId),
|
|
71
|
+
attestationObject: bufferToBase64Url(response.attestationObject),
|
|
72
|
+
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
|
|
73
|
+
};
|
|
74
|
+
const httpResponse = await fetch(`${apiBase}/api/tf/bind/webauthn`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json' },
|
|
77
|
+
credentials: 'include',
|
|
78
|
+
body: JSON.stringify(body),
|
|
79
|
+
});
|
|
80
|
+
if (!httpResponse.ok) {
|
|
81
|
+
throw new Error(`TokenForge WebAuthn binding failed: ${httpResponse.status}`);
|
|
82
|
+
}
|
|
83
|
+
const { deviceId } = (await httpResponse.json());
|
|
84
|
+
return { deviceId, credentialId: body.credentialId };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Sign a per-request challenge with the bound hardware credential.
|
|
88
|
+
*
|
|
89
|
+
* The challenge is `${sessionId}:${nonce}:${timestamp}` — same payload shape
|
|
90
|
+
* as the ECDSA path, but the WebAuthn signature is over
|
|
91
|
+
* (authenticatorData || sha256(clientDataJSON)) where clientDataJSON contains
|
|
92
|
+
* the challenge. The server reconstructs and compares.
|
|
93
|
+
*/
|
|
94
|
+
export async function signChallengeWebAuthn(credentialId, sessionId, nonce, timestamp) {
|
|
95
|
+
if (typeof navigator === 'undefined' || !navigator.credentials) {
|
|
96
|
+
throw new Error('WebAuthn not available in this environment');
|
|
97
|
+
}
|
|
98
|
+
const challenge = await sha256(`${sessionId}:${nonce}:${timestamp}`);
|
|
99
|
+
const publicKey = {
|
|
100
|
+
challenge,
|
|
101
|
+
allowCredentials: [
|
|
102
|
+
{ id: base64UrlToBuffer(credentialId), type: 'public-key' },
|
|
103
|
+
],
|
|
104
|
+
userVerification: 'preferred',
|
|
105
|
+
timeout: 60_000,
|
|
106
|
+
};
|
|
107
|
+
const assertion = (await navigator.credentials.get({ publicKey }));
|
|
108
|
+
if (!assertion)
|
|
109
|
+
throw new Error('WebAuthn assertion cancelled');
|
|
110
|
+
const response = assertion.response;
|
|
111
|
+
return {
|
|
112
|
+
signature: bufferToBase64Url(response.signature),
|
|
113
|
+
authenticatorData: bufferToBase64Url(response.authenticatorData),
|
|
114
|
+
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
|
|
115
|
+
credentialId: bufferToBase64Url(assertion.rawId),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=webauthn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webauthn.js","sourceRoot":"","sources":["../../src/client/webauthn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side action-signing verifier (Sprint 39).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `verifyCompactJws` with two extra invariants the client SDK
|
|
5
|
+
* promises:
|
|
6
|
+
* 1. The JWS `action` claim equals the `expectedAction` the route
|
|
7
|
+
* handler is enforcing (e.g. POST /checkout asserts "checkout").
|
|
8
|
+
* 2. The JWS `actionHash` claim equals the SHA-256 of the
|
|
9
|
+
* canonicalized request body. This binds the signature to the
|
|
10
|
+
* payload — replaying the same JWS against a different body
|
|
11
|
+
* (different recipient, amount, etc.) fails.
|
|
12
|
+
*
|
|
13
|
+
* Replay prevention against the SAME body is the caller's
|
|
14
|
+
* responsibility: they should pass `claims.nonce` through
|
|
15
|
+
* `consumeChallenge` (server/dbsc-challenge.ts) so a second submission
|
|
16
|
+
* of the same JWS is rejected even within the maxAgeSeconds window.
|
|
17
|
+
*/
|
|
18
|
+
import type { JwsClaims } from './jws-verify.js';
|
|
19
|
+
export interface VerifyActionOptions {
|
|
20
|
+
/** Bound device public key (JWK JSON or PEM SPKI). */
|
|
21
|
+
publicKey: string;
|
|
22
|
+
/** Action verb the route asserts (must equal claims.action). */
|
|
23
|
+
expectedAction: string;
|
|
24
|
+
/** Optional request body — when present, must hash to claims.actionHash. */
|
|
25
|
+
body?: Record<string, unknown> | null;
|
|
26
|
+
/** Maximum age for the JWS in seconds. Default 60. */
|
|
27
|
+
maxAgeSeconds?: number;
|
|
28
|
+
/** Override clock for tests. */
|
|
29
|
+
now?: Date;
|
|
30
|
+
/**
|
|
31
|
+
* RFC 9266 TLS exporter material (hex) extracted from the active TLS
|
|
32
|
+
* connection by the runtime. When provided, claims.tlsExporter MUST
|
|
33
|
+
* match it — protects against replay across a different TLS session.
|
|
34
|
+
* Omit on workerd or any runtime that doesn't expose exporter
|
|
35
|
+
* material; the server emits Sec-TF-Channel-Bound: 0 in that case
|
|
36
|
+
* via the route handler.
|
|
37
|
+
*/
|
|
38
|
+
expectedTlsExporter?: string;
|
|
39
|
+
/**
|
|
40
|
+
* If true, reject when claims.tlsExporter is absent. Use this on
|
|
41
|
+
* sensitive routes when running on a runtime that *does* expose the
|
|
42
|
+
* exporter — a missing claim then indicates the client signed
|
|
43
|
+
* without binding the TLS channel.
|
|
44
|
+
*/
|
|
45
|
+
requireTlsExporter?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type VerifyActionResult = {
|
|
48
|
+
ok: true;
|
|
49
|
+
claims: JwsClaims;
|
|
50
|
+
protectedHeader: Record<string, unknown>;
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: string;
|
|
54
|
+
};
|
|
55
|
+
export declare function verifyAction(jws: string, opts: VerifyActionOptions): Promise<VerifyActionResult>;
|
|
56
|
+
//# sourceMappingURL=action-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-verify.d.ts","sourceRoot":"","sources":["../../src/server/action-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,GAAG,CAAC,EAAE,IAAI,CAAC;IACX;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACzE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CA4B7B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side action-signing verifier (Sprint 39).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `verifyCompactJws` with two extra invariants the client SDK
|
|
5
|
+
* promises:
|
|
6
|
+
* 1. The JWS `action` claim equals the `expectedAction` the route
|
|
7
|
+
* handler is enforcing (e.g. POST /checkout asserts "checkout").
|
|
8
|
+
* 2. The JWS `actionHash` claim equals the SHA-256 of the
|
|
9
|
+
* canonicalized request body. This binds the signature to the
|
|
10
|
+
* payload — replaying the same JWS against a different body
|
|
11
|
+
* (different recipient, amount, etc.) fails.
|
|
12
|
+
*
|
|
13
|
+
* Replay prevention against the SAME body is the caller's
|
|
14
|
+
* responsibility: they should pass `claims.nonce` through
|
|
15
|
+
* `consumeChallenge` (server/dbsc-challenge.ts) so a second submission
|
|
16
|
+
* of the same JWS is rejected even within the maxAgeSeconds window.
|
|
17
|
+
*/
|
|
18
|
+
import { verifyCompactJws } from './jws-verify.js';
|
|
19
|
+
import { hashActionPayload } from '../shared/action-hash.js';
|
|
20
|
+
export async function verifyAction(jws, opts) {
|
|
21
|
+
const inner = await verifyCompactJws(jws, {
|
|
22
|
+
publicKey: opts.publicKey,
|
|
23
|
+
maxAgeSeconds: opts.maxAgeSeconds,
|
|
24
|
+
now: opts.now,
|
|
25
|
+
});
|
|
26
|
+
if (!inner.ok)
|
|
27
|
+
return inner;
|
|
28
|
+
if (inner.claims.action !== opts.expectedAction) {
|
|
29
|
+
return { ok: false, reason: 'action_mismatch' };
|
|
30
|
+
}
|
|
31
|
+
if (opts.body !== undefined && opts.body !== null) {
|
|
32
|
+
const expectedHash = await hashActionPayload(opts.body);
|
|
33
|
+
if (inner.claims.actionHash !== expectedHash) {
|
|
34
|
+
return { ok: false, reason: 'action_hash_mismatch' };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (opts.expectedTlsExporter !== undefined) {
|
|
38
|
+
if (inner.claims.tlsExporter !== opts.expectedTlsExporter) {
|
|
39
|
+
return { ok: false, reason: 'tls_exporter_mismatch' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (opts.requireTlsExporter && !inner.claims.tlsExporter) {
|
|
43
|
+
return { ok: false, reason: 'tls_exporter_missing' };
|
|
44
|
+
}
|
|
45
|
+
return inner;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=action-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-verify.js","sourceRoot":"","sources":["../../src/server/action-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAmC7D,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,IAAyB;IAEzB,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE;QACxC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IAE5B,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,cAAc,EAAE,CAAC;QAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,KAAK,YAAY,EAAE,CAAC;YAC7C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;QACvD,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,KAAK,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC1D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QACxD,CAAC;IACH,CAAC;SAAM,IAAI,IAAI,CAAC,kBAAkB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAChE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACvD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adversary-in-the-middle (AitM) heuristic engine. Pure module.
|
|
3
|
+
* Compares a bind-time fingerprint vs. current request, emits anomalies.
|
|
4
|
+
* trust-score subtracts the delta; device-telemetry surfaces the list.
|
|
5
|
+
*/
|
|
6
|
+
export interface ClientFingerprint {
|
|
7
|
+
userAgent: string;
|
|
8
|
+
language: string;
|
|
9
|
+
timezone: string;
|
|
10
|
+
colorDepth: number;
|
|
11
|
+
screenWidth: number;
|
|
12
|
+
screenHeight: number;
|
|
13
|
+
/** Optional — only present on protected routes that capture them. */
|
|
14
|
+
origin?: string;
|
|
15
|
+
sni?: string;
|
|
16
|
+
host?: string;
|
|
17
|
+
rttMs?: number;
|
|
18
|
+
/** True when TLS exporter binding succeeded for this request. */
|
|
19
|
+
channelBound?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type AitmAnomalyKind = 'origin_mismatch' | 'latency_floor' | 'ua_drift' | 'tz_drift' | 'color_drift' | 'resolution_swap' | 'locale_drift' | 'channel_unbound';
|
|
22
|
+
export type AitmConfidence = 'low' | 'medium' | 'high';
|
|
23
|
+
export interface AitmAnomaly {
|
|
24
|
+
kind: AitmAnomalyKind;
|
|
25
|
+
confidence: AitmConfidence;
|
|
26
|
+
details?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface AitmEvalOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Persistent latency floor delta (ms) above baseline that's considered
|
|
31
|
+
* suspicious. Default 40ms — typical AitM proxy adds at least this much
|
|
32
|
+
* even after EWMA smoothing.
|
|
33
|
+
*/
|
|
34
|
+
rttFloorDeltaMs?: number;
|
|
35
|
+
/**
|
|
36
|
+
* When true, missing TLS-exporter binding emits `channel_unbound`.
|
|
37
|
+
* Default true. Disable on routes that don't require channel binding.
|
|
38
|
+
*/
|
|
39
|
+
requireChannelBinding?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Evaluate the eight AitM heuristics. Pure function.
|
|
43
|
+
*
|
|
44
|
+
* @param baseline - fingerprint captured at device-bind time
|
|
45
|
+
* @param current - fingerprint of the current request
|
|
46
|
+
* @param opts - thresholds (override per route / tenant)
|
|
47
|
+
* @returns list of anomalies; empty when nothing trips
|
|
48
|
+
*/
|
|
49
|
+
export declare function evaluateAitm(baseline: ClientFingerprint, current: ClientFingerprint, opts?: AitmEvalOptions): AitmAnomaly[];
|
|
50
|
+
/**
|
|
51
|
+
* Convert anomalies to a trust-score delta. Negative number; used by
|
|
52
|
+
* TrustScoreEngine to drop score on suspicious sessions. Cap at -60 so
|
|
53
|
+
* a single anomalous request can never block a clean baseline outright
|
|
54
|
+
* — that's the trust score's job once it falls below `step_up_threshold`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function aitmScoreDelta(anomalies: AitmAnomaly[]): number;
|
|
57
|
+
//# sourceMappingURL=aitm-heuristics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aitm-heuristics.d.ts","sourceRoot":"","sources":["../../src/server/aitm-heuristics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GACvB,iBAAiB,GACjB,eAAe,GACf,UAAU,GACV,UAAU,GACV,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,iBAAiB,CAAC;AAEtB,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,IAAI,GAAE,eAAoB,GACzB,WAAW,EAAE,CA0Ff;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,CAQ/D"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adversary-in-the-middle (AitM) heuristic engine. Pure module.
|
|
3
|
+
* Compares a bind-time fingerprint vs. current request, emits anomalies.
|
|
4
|
+
* trust-score subtracts the delta; device-telemetry surfaces the list.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_RTT_FLOOR_DELTA_MS = 40;
|
|
7
|
+
/**
|
|
8
|
+
* Evaluate the eight AitM heuristics. Pure function.
|
|
9
|
+
*
|
|
10
|
+
* @param baseline - fingerprint captured at device-bind time
|
|
11
|
+
* @param current - fingerprint of the current request
|
|
12
|
+
* @param opts - thresholds (override per route / tenant)
|
|
13
|
+
* @returns list of anomalies; empty when nothing trips
|
|
14
|
+
*/
|
|
15
|
+
export function evaluateAitm(baseline, current, opts = {}) {
|
|
16
|
+
const out = [];
|
|
17
|
+
const rttFloorDelta = opts.rttFloorDeltaMs ?? DEFAULT_RTT_FLOOR_DELTA_MS;
|
|
18
|
+
const requireChannel = opts.requireChannelBinding ?? true;
|
|
19
|
+
if (current.origin && current.sni && current.host) {
|
|
20
|
+
const originHost = safeHost(current.origin);
|
|
21
|
+
const sni = current.sni.toLowerCase();
|
|
22
|
+
const host = current.host.toLowerCase();
|
|
23
|
+
const all = [originHost, sni, host].filter(Boolean);
|
|
24
|
+
const allMatch = all.every((h) => h === all[0]);
|
|
25
|
+
if (!allMatch) {
|
|
26
|
+
out.push({
|
|
27
|
+
kind: 'origin_mismatch',
|
|
28
|
+
confidence: 'high',
|
|
29
|
+
details: { origin: originHost, sni, host },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (baseline.userAgent !== current.userAgent) {
|
|
34
|
+
out.push({
|
|
35
|
+
kind: 'ua_drift',
|
|
36
|
+
confidence: 'high',
|
|
37
|
+
details: { from: baseline.userAgent, to: current.userAgent },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (baseline.timezone !== current.timezone) {
|
|
41
|
+
out.push({
|
|
42
|
+
kind: 'tz_drift',
|
|
43
|
+
confidence: 'medium',
|
|
44
|
+
details: { from: baseline.timezone, to: current.timezone },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (baseline.language !== current.language) {
|
|
48
|
+
out.push({
|
|
49
|
+
kind: 'locale_drift',
|
|
50
|
+
confidence: 'medium',
|
|
51
|
+
details: { from: baseline.language, to: current.language },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (baseline.colorDepth !== current.colorDepth) {
|
|
55
|
+
out.push({
|
|
56
|
+
kind: 'color_drift',
|
|
57
|
+
confidence: 'low',
|
|
58
|
+
details: { from: baseline.colorDepth, to: current.colorDepth },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const baseDim = `${baseline.screenWidth}x${baseline.screenHeight}`;
|
|
62
|
+
const curDim = `${current.screenWidth}x${current.screenHeight}`;
|
|
63
|
+
const swapped = baseline.screenWidth === current.screenHeight &&
|
|
64
|
+
baseline.screenHeight === current.screenWidth &&
|
|
65
|
+
baseline.screenWidth !== baseline.screenHeight;
|
|
66
|
+
if (swapped) {
|
|
67
|
+
out.push({
|
|
68
|
+
kind: 'resolution_swap',
|
|
69
|
+
confidence: 'medium',
|
|
70
|
+
details: { from: baseDim, to: curDim },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (typeof baseline.rttMs === 'number' &&
|
|
74
|
+
typeof current.rttMs === 'number' &&
|
|
75
|
+
current.rttMs - baseline.rttMs >= rttFloorDelta) {
|
|
76
|
+
out.push({
|
|
77
|
+
kind: 'latency_floor',
|
|
78
|
+
confidence: 'medium',
|
|
79
|
+
details: {
|
|
80
|
+
baselineMs: baseline.rttMs,
|
|
81
|
+
currentMs: current.rttMs,
|
|
82
|
+
deltaMs: current.rttMs - baseline.rttMs,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (requireChannel && current.channelBound === false) {
|
|
87
|
+
out.push({
|
|
88
|
+
kind: 'channel_unbound',
|
|
89
|
+
confidence: 'high',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Convert anomalies to a trust-score delta. Negative number; used by
|
|
96
|
+
* TrustScoreEngine to drop score on suspicious sessions. Cap at -60 so
|
|
97
|
+
* a single anomalous request can never block a clean baseline outright
|
|
98
|
+
* — that's the trust score's job once it falls below `step_up_threshold`.
|
|
99
|
+
*/
|
|
100
|
+
export function aitmScoreDelta(anomalies) {
|
|
101
|
+
let delta = 0;
|
|
102
|
+
for (const a of anomalies) {
|
|
103
|
+
if (a.confidence === 'high')
|
|
104
|
+
delta -= 25;
|
|
105
|
+
else if (a.confidence === 'medium')
|
|
106
|
+
delta -= 12;
|
|
107
|
+
else
|
|
108
|
+
delta -= 5;
|
|
109
|
+
}
|
|
110
|
+
return Math.max(delta, -60);
|
|
111
|
+
}
|
|
112
|
+
function safeHost(originUrl) {
|
|
113
|
+
try {
|
|
114
|
+
return new URL(originUrl).host.toLowerCase();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=aitm-heuristics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aitm-heuristics.js","sourceRoot":"","sources":["../../src/server/aitm-heuristics.ts"],"names":[],"mappings":"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"}
|