@stvor/sdk 2.4.0 → 3.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.
Files changed (82) hide show
  1. package/dist/facade/app.cjs +29 -0
  2. package/dist/facade/app.d.ts +83 -76
  3. package/dist/facade/app.js +330 -195
  4. package/dist/facade/crypto-session.cjs +29 -0
  5. package/dist/facade/crypto-session.d.ts +49 -54
  6. package/dist/facade/crypto-session.js +117 -140
  7. package/dist/facade/errors.cjs +29 -0
  8. package/dist/facade/errors.d.ts +29 -12
  9. package/dist/facade/errors.js +49 -8
  10. package/dist/facade/index.cjs +29 -0
  11. package/dist/facade/index.d.ts +27 -8
  12. package/dist/facade/index.js +23 -3
  13. package/dist/facade/local-storage-identity-store.cjs +29 -0
  14. package/dist/facade/local-storage-identity-store.d.ts +50 -0
  15. package/dist/facade/local-storage-identity-store.js +100 -0
  16. package/dist/facade/metrics-attestation.cjs +29 -0
  17. package/dist/facade/metrics-attestation.d.ts +209 -0
  18. package/dist/facade/metrics-attestation.js +333 -0
  19. package/dist/facade/metrics-engine.cjs +29 -0
  20. package/dist/facade/metrics-engine.d.ts +91 -0
  21. package/dist/facade/metrics-engine.js +170 -0
  22. package/dist/facade/redis-replay-cache.cjs +29 -0
  23. package/dist/facade/redis-replay-cache.d.ts +88 -0
  24. package/dist/facade/redis-replay-cache.js +60 -0
  25. package/dist/facade/relay-client.cjs +29 -0
  26. package/dist/facade/relay-client.d.ts +22 -23
  27. package/dist/facade/relay-client.js +107 -128
  28. package/dist/facade/replay-manager.cjs +29 -0
  29. package/dist/facade/replay-manager.d.ts +28 -35
  30. package/dist/facade/replay-manager.js +102 -69
  31. package/dist/facade/sodium-singleton.cjs +29 -0
  32. package/dist/facade/tofu-manager.cjs +29 -0
  33. package/dist/facade/tofu-manager.d.ts +38 -36
  34. package/dist/facade/tofu-manager.js +109 -77
  35. package/dist/facade/types.cjs +29 -0
  36. package/dist/facade/types.d.ts +2 -0
  37. package/dist/index.cjs +29 -0
  38. package/dist/index.d.cts +6 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +7 -0
  41. package/dist/legacy.cjs +29 -0
  42. package/dist/legacy.d.ts +31 -1
  43. package/dist/legacy.js +90 -2
  44. package/dist/ratchet/core-production.cjs +29 -0
  45. package/dist/ratchet/core-production.d.ts +95 -0
  46. package/dist/ratchet/core-production.js +286 -0
  47. package/dist/ratchet/index.cjs +29 -0
  48. package/dist/ratchet/index.d.ts +49 -78
  49. package/dist/ratchet/index.js +313 -288
  50. package/dist/ratchet/key-recovery.cjs +29 -0
  51. package/dist/ratchet/replay-protection.cjs +29 -0
  52. package/dist/ratchet/tofu.cjs +29 -0
  53. package/dist/src/facade/app.cjs +29 -0
  54. package/dist/src/facade/app.d.ts +105 -0
  55. package/dist/src/facade/app.js +245 -0
  56. package/dist/src/facade/crypto.cjs +29 -0
  57. package/dist/src/facade/errors.cjs +29 -0
  58. package/dist/src/facade/errors.d.ts +19 -0
  59. package/dist/src/facade/errors.js +21 -0
  60. package/dist/src/facade/index.cjs +29 -0
  61. package/dist/src/facade/index.d.ts +8 -0
  62. package/dist/src/facade/index.js +5 -0
  63. package/dist/src/facade/relay-client.cjs +29 -0
  64. package/dist/src/facade/relay-client.d.ts +36 -0
  65. package/dist/src/facade/relay-client.js +154 -0
  66. package/dist/src/facade/types.cjs +29 -0
  67. package/dist/src/facade/types.d.ts +50 -0
  68. package/dist/src/facade/types.js +4 -0
  69. package/dist/src/index.cjs +29 -0
  70. package/dist/src/index.d.ts +2 -0
  71. package/dist/src/index.js +2 -0
  72. package/dist/src/legacy.cjs +29 -0
  73. package/dist/src/legacy.d.ts +0 -0
  74. package/dist/src/legacy.js +1 -0
  75. package/dist/src/mock-relay-server.cjs +29 -0
  76. package/dist/src/mock-relay-server.d.ts +30 -0
  77. package/dist/src/mock-relay-server.js +236 -0
  78. package/package.json +37 -11
  79. package/dist/ratchet/tests/ratchet.test.d.ts +0 -1
  80. package/dist/ratchet/tests/ratchet.test.js +0 -160
  81. /package/dist/{facade → src/facade}/crypto.d.ts +0 -0
  82. /package/dist/{facade → src/facade}/crypto.js +0 -0
@@ -1,5 +1,25 @@
1
- export * from './app.js';
2
- export * from './errors.js';
3
- export * from './types.js';
1
+ /**
2
+ * STVOR DX Facade SDK
3
+ * High-level developer experience layer for STVOR E2E encryption
4
+ *
5
+ * Design goals:
6
+ * - Minimal API surface
7
+ * - Zero crypto knowledge required
8
+ * - Secure by default
9
+ * - Opinionated (no configuration)
10
+ */
4
11
  export { StvorError } from './errors.js';
12
+ // Re-export classes and functions
5
13
  export { StvorApp, StvorFacadeClient, Stvor, init, createApp } from './app.js';
14
+ export { ErrorCode as STVOR_ERRORS } from './errors.js';
15
+ // Re-export metrics verification for Dashboard
16
+ export { verifyMetricsSignature, MetricsEngine } from './metrics-engine.js';
17
+ // Re-export crypto session management
18
+ export { CryptoSessionManager } from './crypto-session.js';
19
+ export { LocalStorageIdentityStore, LocalStorageSessionStore } from './local-storage-identity-store.js';
20
+ // Re-export replay protection
21
+ export { isReplay, validateMessage, validateMessageWithNonce, getCacheStats, cleanupExpiredNonces, initializeReplayProtection, startAutoCleanup, stopAutoCleanup, } from './replay-manager.js';
22
+ // Re-export Redis replay cache for production
23
+ export { RedisReplayCache } from './redis-replay-cache.js';
24
+ // Re-export TOFU management
25
+ export { generateFingerprint, storeFingerprint, verifyFingerprint, isFingerprintTrusted, getFingerprint, revokeTrust, trustNewFingerprint, listTrustedFingerprints, getFingerprintInfo, initializeTofu, } from './tofu-manager.js';
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Auto-generated CommonJS wrapper for facade/local-storage-identity-store.js
4
+ // This allows `require('@stvor/sdk')` to work alongside ESM `import`.
5
+
6
+ const mod = require('module');
7
+ const url = require('url');
8
+
9
+ // Use dynamic import to load the ESM module
10
+ let _cached;
11
+ async function _load() {
12
+ if (!_cached) {
13
+ _cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
14
+ }
15
+ return _cached;
16
+ }
17
+
18
+ // For simple CJS usage, expose a promise-based loader
19
+ module.exports = new Proxy({ load: _load }, {
20
+ get(target, prop) {
21
+ if (prop === '__esModule') return true;
22
+ if (prop === 'then') return undefined; // prevent treating as thenable
23
+ if (prop === 'load') return _load;
24
+ if (prop === 'default') {
25
+ return _load().then(m => m.default);
26
+ }
27
+ return _load().then(m => m[prop]);
28
+ }
29
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * LocalStorage-based Identity Store for browser environments
3
+ * Implements IIdentityStore for persistent identity key storage
4
+ */
5
+ import { IIdentityStore } from './crypto-session.js';
6
+ /**
7
+ * Browser-based identity storage using localStorage
8
+ * CRITICAL: Keys are stored in base64url — in production, use encrypted storage
9
+ */
10
+ export declare class LocalStorageIdentityStore implements IIdentityStore {
11
+ private storageKey;
12
+ constructor(userId: string, storageKeyPrefix?: string);
13
+ saveIdentityKeys(userId: string, keys: {
14
+ identityKeyPair: {
15
+ publicKey: string;
16
+ privateKey: string;
17
+ };
18
+ signedPreKeyPair: {
19
+ publicKey: string;
20
+ privateKey: string;
21
+ };
22
+ signedPreKeySignature: string;
23
+ }): Promise<void>;
24
+ loadIdentityKeys(userId: string): Promise<{
25
+ identityKeyPair: {
26
+ publicKey: string;
27
+ privateKey: string;
28
+ };
29
+ signedPreKeyPair: {
30
+ publicKey: string;
31
+ privateKey: string;
32
+ };
33
+ signedPreKeySignature: string;
34
+ } | null>;
35
+ /** Delete stored keys (for logout / account reset) */
36
+ deleteIdentityKeys(userId: string): Promise<void>;
37
+ }
38
+ /**
39
+ * Session storage implementation for browser environments
40
+ */
41
+ export declare class LocalStorageSessionStore {
42
+ private storageKey;
43
+ constructor(userId: string, storageKeyPrefix?: string);
44
+ saveSession(userId: string, peerId: string, _session: unknown): Promise<void>;
45
+ loadSession(userId: string, peerId: string): Promise<unknown | null>;
46
+ deleteSession(userId: string, peerId: string): Promise<void>;
47
+ listSessions(userId: string): Promise<string[]>;
48
+ private getAllSessions;
49
+ }
50
+ export default LocalStorageIdentityStore;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * LocalStorage-based Identity Store for browser environments
3
+ * Implements IIdentityStore for persistent identity key storage
4
+ */
5
+ /**
6
+ * Browser-based identity storage using localStorage
7
+ * CRITICAL: Keys are stored in base64url — in production, use encrypted storage
8
+ */
9
+ export class LocalStorageIdentityStore {
10
+ constructor(userId, storageKeyPrefix = 'stvor_identity_') {
11
+ this.storageKey = `${storageKeyPrefix}${userId}`;
12
+ }
13
+ async saveIdentityKeys(userId, keys) {
14
+ try {
15
+ const data = {
16
+ identityKeyPair: keys.identityKeyPair,
17
+ signedPreKeyPair: keys.signedPreKeyPair,
18
+ signedPreKeySignature: keys.signedPreKeySignature,
19
+ };
20
+ localStorage.setItem(this.storageKey, JSON.stringify(data));
21
+ console.log(`[LocalStorageIdentityStore] Keys saved for user: ${userId}`);
22
+ }
23
+ catch (error) {
24
+ console.error('[LocalStorageIdentityStore] Failed to save keys:', error);
25
+ throw new Error('Failed to save identity keys to localStorage');
26
+ }
27
+ }
28
+ async loadIdentityKeys(userId) {
29
+ try {
30
+ const data = localStorage.getItem(this.storageKey);
31
+ if (!data) {
32
+ console.log(`[LocalStorageIdentityStore] No keys found for user: ${userId}`);
33
+ return null;
34
+ }
35
+ const parsed = JSON.parse(data);
36
+ console.log(`[LocalStorageIdentityStore] Keys loaded for user: ${userId}`);
37
+ return {
38
+ identityKeyPair: parsed.identityKeyPair,
39
+ signedPreKeyPair: parsed.signedPreKeyPair,
40
+ signedPreKeySignature: parsed.signedPreKeySignature,
41
+ };
42
+ }
43
+ catch (error) {
44
+ console.error('[LocalStorageIdentityStore] Failed to load keys:', error);
45
+ return null;
46
+ }
47
+ }
48
+ /** Delete stored keys (for logout / account reset) */
49
+ async deleteIdentityKeys(userId) {
50
+ localStorage.removeItem(this.storageKey);
51
+ console.log(`[LocalStorageIdentityStore] Keys deleted for user: ${userId}`);
52
+ }
53
+ }
54
+ /**
55
+ * Session storage implementation for browser environments
56
+ */
57
+ export class LocalStorageSessionStore {
58
+ constructor(userId, storageKeyPrefix = 'stvor_session_') {
59
+ this.storageKey = `${storageKeyPrefix}${userId}`;
60
+ }
61
+ async saveSession(userId, peerId, _session) {
62
+ try {
63
+ const allSessions = this.getAllSessions();
64
+ allSessions[peerId] = { savedAt: Date.now() };
65
+ localStorage.setItem(this.storageKey, JSON.stringify(allSessions));
66
+ }
67
+ catch (error) {
68
+ console.error('[LocalStorageSessionStore] Failed to save session:', error);
69
+ }
70
+ }
71
+ async loadSession(userId, peerId) {
72
+ try {
73
+ const allSessions = this.getAllSessions();
74
+ return allSessions[peerId] || null;
75
+ }
76
+ catch (error) {
77
+ console.error('[LocalStorageSessionStore] Failed to load session:', error);
78
+ return null;
79
+ }
80
+ }
81
+ async deleteSession(userId, peerId) {
82
+ try {
83
+ const allSessions = this.getAllSessions();
84
+ delete allSessions[peerId];
85
+ localStorage.setItem(this.storageKey, JSON.stringify(allSessions));
86
+ }
87
+ catch (error) {
88
+ console.error('[LocalStorageSessionStore] Failed to delete session:', error);
89
+ }
90
+ }
91
+ async listSessions(userId) {
92
+ const allSessions = this.getAllSessions();
93
+ return Object.keys(allSessions);
94
+ }
95
+ getAllSessions() {
96
+ const data = localStorage.getItem(this.storageKey);
97
+ return data ? JSON.parse(data) : {};
98
+ }
99
+ }
100
+ export default LocalStorageIdentityStore;
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Auto-generated CommonJS wrapper for facade/metrics-attestation.js
4
+ // This allows `require('@stvor/sdk')` to work alongside ESM `import`.
5
+
6
+ const mod = require('module');
7
+ const url = require('url');
8
+
9
+ // Use dynamic import to load the ESM module
10
+ let _cached;
11
+ async function _load() {
12
+ if (!_cached) {
13
+ _cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
14
+ }
15
+ return _cached;
16
+ }
17
+
18
+ // For simple CJS usage, expose a promise-based loader
19
+ module.exports = new Proxy({ load: _load }, {
20
+ get(target, prop) {
21
+ if (prop === '__esModule') return true;
22
+ if (prop === 'then') return undefined; // prevent treating as thenable
23
+ if (prop === 'load') return _load;
24
+ if (prop === 'default') {
25
+ return _load().then(m => m.default);
26
+ }
27
+ return _load().then(m => m[prop]);
28
+ }
29
+ });
@@ -0,0 +1,209 @@
1
+ /**
2
+ * STVOR v2.4.0 - Metrics Attestation Engine
3
+ *
4
+ * ⚠️ CRITICAL SECURITY MODEL:
5
+ *
6
+ * This module ONLY records real E2EE events and creates attestations.
7
+ * It does NOT verify attestations (that's backend's job).
8
+ *
9
+ * Trust boundary:
10
+ * ┌─────────────────────────────────────────┐
11
+ * │ SDK (Trusted) - Record + Sign │
12
+ * │ ┌─────────────────────────────────────┐ │
13
+ * │ │ MetricsAttestationEngine │ │
14
+ * │ └─────────────────────────────────────┘ │
15
+ * └──────────────┬──────────────────────────┘
16
+ * │ POST /api/metrics/attest
17
+ * ▼
18
+ * ┌─────────────────────────────────────────┐
19
+ * │ BACKEND (Trusted) - Verify + Store │
20
+ * │ ┌─────────────────────────────────────┐ │
21
+ * │ │ MetricsVerificationService │ │
22
+ * │ │ - Check signature │ │
23
+ * │ │ - Check monotonicity │ │
24
+ * │ │ - Check anti-replay │ │
25
+ * │ └─────────────────────────────────────┘ │
26
+ * └──────────────┬──────────────────────────┘
27
+ * │ Verified metrics in DB
28
+ * ▼
29
+ * ┌─────────────────────────────────────────┐
30
+ * │ Dashboard (Untrusted) - Display Only │
31
+ * │ - No crypto verification in browser │
32
+ * │ - No calculations │
33
+ * │ - No fallback numbers │
34
+ * │ - Only: fetch from /api/metrics │
35
+ * └─────────────────────────────────────────┘
36
+ */
37
+ /**
38
+ * Raw metrics from SDK (before attestation)
39
+ * INVARIANT: Only incremented after crypto success
40
+ */
41
+ export interface RawMetrics {
42
+ messagesEncrypted: number;
43
+ messagesDecrypted: number;
44
+ messagesRejected: number;
45
+ replayAttempts: number;
46
+ authFailures: number;
47
+ }
48
+ /**
49
+ * Attestation = metrics + proof that can be sent to backend
50
+ * Backend will verify this, not Dashboard
51
+ */
52
+ export interface MetricsAttestation {
53
+ metrics: RawMetrics;
54
+ attestationId: string;
55
+ timestamp: number;
56
+ sessionId: string;
57
+ sequenceNumber: number;
58
+ proof: string;
59
+ }
60
+ /**
61
+ * MetricsAttestationEngine
62
+ *
63
+ * RESPONSIBILITY: Record real events + create attestations
64
+ * NOT RESPONSIBLE: Verify attestations (backend does that)
65
+ */
66
+ export declare class MetricsAttestationEngine {
67
+ private metrics;
68
+ private sessionId;
69
+ private sequenceNumber;
70
+ private attestationKey;
71
+ constructor(appToken: string);
72
+ /**
73
+ * Record real event: Successful encryption with AEAD
74
+ * INVARIANT: Only called after cryptoSession.encryptForPeer() succeeds
75
+ */
76
+ recordMessageEncrypted(): void;
77
+ /**
78
+ * Record real event: Successful decryption with AAD verification
79
+ * INVARIANT: Only called after cryptoSession.decryptFromPeer() succeeds
80
+ */
81
+ recordMessageDecrypted(): void;
82
+ /**
83
+ * Record real event: AAD verification failed (auth failure)
84
+ * INVARIANT: Only called when AEAD auth tag is invalid
85
+ */
86
+ recordMessageRejected(): void;
87
+ /**
88
+ * Record real event: Replay attack detected
89
+ * INVARIANT: Only called when nonce is duplicate
90
+ */
91
+ recordReplayAttempt(): void;
92
+ /**
93
+ * Record real event: Signature verification failed
94
+ * INVARIANT: Only called on crypto auth failure
95
+ */
96
+ recordAuthFailure(): void;
97
+ /**
98
+ * Create attestation that can be sent to backend
99
+ *
100
+ * Backend MUST verify:
101
+ * - Signature is valid
102
+ * - Metrics are monotonic
103
+ * - Timestamp is within acceptable window
104
+ * - sessionId matches
105
+ * - sequenceNumber hasn't been seen before
106
+ */
107
+ createAttestation(): MetricsAttestation;
108
+ /**
109
+ * Get current metrics snapshot (immutable)
110
+ * Used for monitoring/debugging, NOT for Dashboard display
111
+ */
112
+ getMetrics(): Readonly<RawMetrics>;
113
+ /**
114
+ * Internal: Create proof for attestation
115
+ *
116
+ * Format:
117
+ * proof = HMAC-SHA256(
118
+ * JSON.stringify({metrics, sessionId, sequenceNumber, timestamp}),
119
+ * attestationKey
120
+ * )
121
+ *
122
+ * Backend will recompute this with the appToken sent by SDK.
123
+ * If proof matches, backend knows:
124
+ * - Metrics came from this SDK instance
125
+ * - Metrics haven't been tampered
126
+ * - This is the correct sequence number
127
+ */
128
+ private createProof;
129
+ /**
130
+ * Derive attestation key from appToken
131
+ *
132
+ * HKDF-SHA256 with:
133
+ * - IKM: appToken
134
+ * - salt: 32 zero bytes
135
+ * - info: "stvor-metrics-attestation-v1"
136
+ *
137
+ * Result is deterministic: same appToken → same key
138
+ *
139
+ * This key is sent to backend for verification.
140
+ * Backend computes same key from appToken and verifies proof.
141
+ */
142
+ private deriveAttestationKey;
143
+ /**
144
+ * Generate unique session ID
145
+ * Used to distinguish different SDK instances
146
+ */
147
+ private generateSessionId;
148
+ /**
149
+ * Generate unique attestation ID
150
+ * Used for anti-replay detection
151
+ */
152
+ private generateAttestationId;
153
+ }
154
+ /**
155
+ * Backend Verification Service (Pseudo-code)
156
+ *
157
+ * This runs on BACKEND, not in browser or SDK
158
+ */
159
+ export declare class MetricsVerificationService {
160
+ /**
161
+ * Verify attestation received from SDK
162
+ *
163
+ * RETURN: VerificationResult
164
+ */
165
+ verifyAttestation(attestation: MetricsAttestation, appToken: string, lastSequenceNumber: number): VerificationResult;
166
+ /**
167
+ * Verify proof signature (backend-side)
168
+ */
169
+ private verifyProof;
170
+ private deriveAttestationKey;
171
+ private constantTimeCompare;
172
+ private hasSeenAttestationId;
173
+ private getLastVerifiedMetrics;
174
+ }
175
+ export interface VerificationResult {
176
+ valid: boolean;
177
+ reason: string;
178
+ }
179
+ /**
180
+ * SECURITY INVARIANTS (MUST BE ENFORCED)
181
+ *
182
+ * I1: Dashboard NEVER generates metric numbers
183
+ * ✓ MetricsAttestationEngine records only on crypto success
184
+ * ✓ Dashboard only fetches from /api/metrics
185
+ *
186
+ * I2: Metrics without backend verification are discarded
187
+ * ✓ Backend verifies proof before storing
188
+ * ✓ Only verified metrics go to DB
189
+ * ✓ Dashboard reads DB, not SDK
190
+ *
191
+ * I3: Metric counters are monotonic
192
+ * ✓ SDK: counters only increment (never set)
193
+ * ✓ Backend: checks sequenceNumber is sequential
194
+ * ✓ Backend: checks metrics don't roll back
195
+ *
196
+ * I4: Metrics replay is impossible
197
+ * ✓ SDK: each attestation has unique attestationId
198
+ * ✓ Backend: stores all seen attestationIds
199
+ * ✓ Backend: rejects duplicate attestationIds
200
+ *
201
+ * I5: Different SDK instances cannot forge each other
202
+ * ✓ Each SDK has unique sessionId
203
+ * ✓ Backend checks sessionId matches appToken
204
+ *
205
+ * I6: appToken compromise ≠ metrics forgery
206
+ * ✓ appToken only derives the signing key
207
+ * ✓ Backend verifies timestamp is recent
208
+ * ✓ Backend checks monotonicity constraints
209
+ */