@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.
- package/dist/facade/app.cjs +29 -0
- package/dist/facade/app.d.ts +83 -76
- package/dist/facade/app.js +330 -195
- package/dist/facade/crypto-session.cjs +29 -0
- package/dist/facade/crypto-session.d.ts +49 -54
- package/dist/facade/crypto-session.js +117 -140
- package/dist/facade/errors.cjs +29 -0
- package/dist/facade/errors.d.ts +29 -12
- package/dist/facade/errors.js +49 -8
- package/dist/facade/index.cjs +29 -0
- package/dist/facade/index.d.ts +27 -8
- package/dist/facade/index.js +23 -3
- package/dist/facade/local-storage-identity-store.cjs +29 -0
- package/dist/facade/local-storage-identity-store.d.ts +50 -0
- package/dist/facade/local-storage-identity-store.js +100 -0
- package/dist/facade/metrics-attestation.cjs +29 -0
- package/dist/facade/metrics-attestation.d.ts +209 -0
- package/dist/facade/metrics-attestation.js +333 -0
- package/dist/facade/metrics-engine.cjs +29 -0
- package/dist/facade/metrics-engine.d.ts +91 -0
- package/dist/facade/metrics-engine.js +170 -0
- package/dist/facade/redis-replay-cache.cjs +29 -0
- package/dist/facade/redis-replay-cache.d.ts +88 -0
- package/dist/facade/redis-replay-cache.js +60 -0
- package/dist/facade/relay-client.cjs +29 -0
- package/dist/facade/relay-client.d.ts +22 -23
- package/dist/facade/relay-client.js +107 -128
- package/dist/facade/replay-manager.cjs +29 -0
- package/dist/facade/replay-manager.d.ts +28 -35
- package/dist/facade/replay-manager.js +102 -69
- package/dist/facade/sodium-singleton.cjs +29 -0
- package/dist/facade/tofu-manager.cjs +29 -0
- package/dist/facade/tofu-manager.d.ts +38 -36
- package/dist/facade/tofu-manager.js +109 -77
- package/dist/facade/types.cjs +29 -0
- package/dist/facade/types.d.ts +2 -0
- package/dist/index.cjs +29 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/legacy.cjs +29 -0
- package/dist/legacy.d.ts +31 -1
- package/dist/legacy.js +90 -2
- package/dist/ratchet/core-production.cjs +29 -0
- package/dist/ratchet/core-production.d.ts +95 -0
- package/dist/ratchet/core-production.js +286 -0
- package/dist/ratchet/index.cjs +29 -0
- package/dist/ratchet/index.d.ts +49 -78
- package/dist/ratchet/index.js +313 -288
- package/dist/ratchet/key-recovery.cjs +29 -0
- package/dist/ratchet/replay-protection.cjs +29 -0
- package/dist/ratchet/tofu.cjs +29 -0
- package/dist/src/facade/app.cjs +29 -0
- package/dist/src/facade/app.d.ts +105 -0
- package/dist/src/facade/app.js +245 -0
- package/dist/src/facade/crypto.cjs +29 -0
- package/dist/src/facade/errors.cjs +29 -0
- package/dist/src/facade/errors.d.ts +19 -0
- package/dist/src/facade/errors.js +21 -0
- package/dist/src/facade/index.cjs +29 -0
- package/dist/src/facade/index.d.ts +8 -0
- package/dist/src/facade/index.js +5 -0
- package/dist/src/facade/relay-client.cjs +29 -0
- package/dist/src/facade/relay-client.d.ts +36 -0
- package/dist/src/facade/relay-client.js +154 -0
- package/dist/src/facade/types.cjs +29 -0
- package/dist/src/facade/types.d.ts +50 -0
- package/dist/src/facade/types.js +4 -0
- package/dist/src/index.cjs +29 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/legacy.cjs +29 -0
- package/dist/src/legacy.d.ts +0 -0
- package/dist/src/legacy.js +1 -0
- package/dist/src/mock-relay-server.cjs +29 -0
- package/dist/src/mock-relay-server.d.ts +30 -0
- package/dist/src/mock-relay-server.js +236 -0
- package/package.json +37 -11
- package/dist/ratchet/tests/ratchet.test.d.ts +0 -1
- package/dist/ratchet/tests/ratchet.test.js +0 -160
- /package/dist/{facade → src/facade}/crypto.d.ts +0 -0
- /package/dist/{facade → src/facade}/crypto.js +0 -0
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* STVOR Crypto Session Manager
|
|
3
|
-
*
|
|
3
|
+
* Uses ONLY Node.js built-in crypto module — zero external dependencies
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* TODO: Add persistent storage (IndexedDB/Keychain)
|
|
5
|
+
* Manages identity keys (IK + SPK), ECDSA signatures,
|
|
6
|
+
* X3DH session establishment, and Double Ratchet encrypt/decrypt.
|
|
9
7
|
*/
|
|
8
|
+
import { KeyPair } from '../ratchet/index.js';
|
|
10
9
|
export interface IdentityKeys {
|
|
11
|
-
identityKeyPair:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
signedPreKeyPair: {
|
|
16
|
-
publicKey: Uint8Array;
|
|
17
|
-
privateKey: Uint8Array;
|
|
18
|
-
};
|
|
19
|
-
oneTimePreKeys: Uint8Array[];
|
|
10
|
+
identityKeyPair: KeyPair;
|
|
11
|
+
signedPreKeyPair: KeyPair;
|
|
12
|
+
signedPreKeySignature: Buffer;
|
|
20
13
|
}
|
|
21
14
|
export interface SerializedPublicKeys {
|
|
22
15
|
identityKey: string;
|
|
@@ -24,53 +17,55 @@ export interface SerializedPublicKeys {
|
|
|
24
17
|
signedPreKeySignature: string;
|
|
25
18
|
oneTimePreKey: string;
|
|
26
19
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
export interface IIdentityStore {
|
|
21
|
+
saveIdentityKeys(userId: string, keys: {
|
|
22
|
+
identityKeyPair: {
|
|
23
|
+
publicKey: string;
|
|
24
|
+
privateKey: string;
|
|
25
|
+
};
|
|
26
|
+
signedPreKeyPair: {
|
|
27
|
+
publicKey: string;
|
|
28
|
+
privateKey: string;
|
|
29
|
+
};
|
|
30
|
+
signedPreKeySignature: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
loadIdentityKeys(userId: string): Promise<{
|
|
33
|
+
identityKeyPair: {
|
|
34
|
+
publicKey: string;
|
|
35
|
+
privateKey: string;
|
|
36
|
+
};
|
|
37
|
+
signedPreKeyPair: {
|
|
38
|
+
publicKey: string;
|
|
39
|
+
privateKey: string;
|
|
40
|
+
};
|
|
41
|
+
signedPreKeySignature: string;
|
|
42
|
+
} | null>;
|
|
43
|
+
}
|
|
44
|
+
export interface ISessionStore {
|
|
45
|
+
saveSession(userId: string, peerId: string, sessionData: Buffer): Promise<void>;
|
|
46
|
+
loadSession(userId: string, peerId: string): Promise<Buffer | null>;
|
|
47
|
+
deleteSession(userId: string, peerId: string): Promise<void>;
|
|
48
|
+
listSessions(userId: string): Promise<string[]>;
|
|
49
|
+
}
|
|
30
50
|
export declare class CryptoSessionManager {
|
|
31
51
|
private userId;
|
|
32
52
|
private identityKeys;
|
|
33
53
|
private sessions;
|
|
34
54
|
private initialized;
|
|
35
55
|
private initPromise;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* RACE CONDITION SAFE: Returns same promise if called concurrently
|
|
40
|
-
*/
|
|
56
|
+
private identityStore;
|
|
57
|
+
private sessionStore;
|
|
58
|
+
constructor(userId: string, identityStore?: IIdentityStore, sessionStore?: ISessionStore);
|
|
41
59
|
initialize(): Promise<void>;
|
|
42
|
-
private
|
|
43
|
-
/**
|
|
44
|
-
* Get serialized public keys for relay registration
|
|
45
|
-
*/
|
|
60
|
+
private _doInit;
|
|
46
61
|
getPublicKeys(): SerializedPublicKeys;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
establishSessionWithPeer(peerId: string, peerPublicKeys: SerializedPublicKeys): Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Encrypt message for peer using Double Ratchet
|
|
53
|
-
*/
|
|
54
|
-
encryptForPeer(peerId: string, plaintext: string): Promise<{
|
|
55
|
-
ciphertext: Uint8Array;
|
|
56
|
-
header: {
|
|
57
|
-
publicKey: Uint8Array;
|
|
58
|
-
nonce: Uint8Array;
|
|
59
|
-
};
|
|
60
|
-
}>;
|
|
61
|
-
/**
|
|
62
|
-
* Decrypt message from peer using Double Ratchet
|
|
63
|
-
*/
|
|
64
|
-
decryptFromPeer(peerId: string, ciphertext: Uint8Array, header: {
|
|
65
|
-
publicKey: Uint8Array;
|
|
66
|
-
nonce: Uint8Array;
|
|
67
|
-
}): Promise<string>;
|
|
68
|
-
/**
|
|
69
|
-
* Check if session exists with peer
|
|
70
|
-
*/
|
|
62
|
+
establishSession(peerId: string, peerPublicKeys: SerializedPublicKeys): Promise<void>;
|
|
63
|
+
establishSessionWithPeer(peerId: string, pk: SerializedPublicKeys): Promise<void>;
|
|
71
64
|
hasSession(peerId: string): boolean;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
encryptForPeer(peerId: string, plaintext: string): {
|
|
66
|
+
ciphertext: string;
|
|
67
|
+
header: string;
|
|
68
|
+
};
|
|
69
|
+
decryptFromPeer(peerId: string, ciphertext: string, header: string): string;
|
|
70
|
+
forceRatchet(peerId: string): Promise<void>;
|
|
76
71
|
}
|
|
@@ -1,175 +1,152 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* STVOR Crypto Session Manager
|
|
3
|
-
*
|
|
3
|
+
* Uses ONLY Node.js built-in crypto module — zero external dependencies
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* TODO: Add persistent storage (IndexedDB/Keychain)
|
|
9
|
-
*/
|
|
10
|
-
import sodium from 'libsodium-wrappers';
|
|
11
|
-
import { ensureSodiumReady } from './sodium-singleton.js';
|
|
12
|
-
import { encryptMessage as ratchetEncrypt, decryptMessage as ratchetDecrypt, incrementMessageCounter, } from '../ratchet/index.js';
|
|
13
|
-
/**
|
|
14
|
-
* Manages cryptographic sessions for all peers
|
|
5
|
+
* Manages identity keys (IK + SPK), ECDSA signatures,
|
|
6
|
+
* X3DH session establishment, and Double Ratchet encrypt/decrypt.
|
|
15
7
|
*/
|
|
8
|
+
import { generateKeyPair, encryptMessage as ratchetEncrypt, decryptMessage as ratchetDecrypt, establishSession as ratchetEstablishSession, serializeSession, initializeCrypto, ecSign, ecVerify, } from '../ratchet/index.js';
|
|
9
|
+
/* ================================================================
|
|
10
|
+
* Helpers
|
|
11
|
+
* ================================================================ */
|
|
12
|
+
function toB64(buf) { return buf.toString('base64url'); }
|
|
13
|
+
function fromB64(s) { return Buffer.from(s, 'base64url'); }
|
|
14
|
+
/* ================================================================
|
|
15
|
+
* CryptoSessionManager
|
|
16
|
+
* ================================================================ */
|
|
16
17
|
export class CryptoSessionManager {
|
|
17
|
-
constructor(userId) {
|
|
18
|
+
constructor(userId, identityStore, sessionStore) {
|
|
18
19
|
this.identityKeys = null;
|
|
19
20
|
this.sessions = new Map();
|
|
20
21
|
this.initialized = false;
|
|
21
22
|
this.initPromise = null;
|
|
23
|
+
this.identityStore = null;
|
|
24
|
+
this.sessionStore = null;
|
|
22
25
|
this.userId = userId;
|
|
26
|
+
this.identityStore = identityStore || null;
|
|
27
|
+
this.sessionStore = sessionStore || null;
|
|
23
28
|
}
|
|
24
|
-
|
|
25
|
-
* Initialize libsodium and generate identity keys
|
|
26
|
-
* RACE CONDITION SAFE: Returns same promise if called concurrently
|
|
27
|
-
*/
|
|
29
|
+
/* ---- Initialisation ---- */
|
|
28
30
|
async initialize() {
|
|
29
|
-
|
|
30
|
-
if (this.initialized && this.identityKeys) {
|
|
31
|
+
if (this.initialized && this.identityKeys)
|
|
31
32
|
return;
|
|
32
|
-
|
|
33
|
-
// Initialization in progress - return existing promise
|
|
34
|
-
if (this.initPromise) {
|
|
33
|
+
if (this.initPromise)
|
|
35
34
|
return this.initPromise;
|
|
36
|
-
|
|
37
|
-
// Start initialization
|
|
38
|
-
this.initPromise = this._doInitialize();
|
|
35
|
+
this.initPromise = this._doInit();
|
|
39
36
|
return this.initPromise;
|
|
40
37
|
}
|
|
41
|
-
async
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
async _doInit() {
|
|
39
|
+
await initializeCrypto();
|
|
40
|
+
if (this.identityStore) {
|
|
41
|
+
try {
|
|
42
|
+
const stored = await this.identityStore.loadIdentityKeys(this.userId);
|
|
43
|
+
if (stored) {
|
|
44
|
+
this.identityKeys = {
|
|
45
|
+
identityKeyPair: {
|
|
46
|
+
publicKey: fromB64(stored.identityKeyPair.publicKey),
|
|
47
|
+
privateKey: fromB64(stored.identityKeyPair.privateKey),
|
|
48
|
+
},
|
|
49
|
+
signedPreKeyPair: {
|
|
50
|
+
publicKey: fromB64(stored.signedPreKeyPair.publicKey),
|
|
51
|
+
privateKey: fromB64(stored.signedPreKeyPair.privateKey),
|
|
52
|
+
},
|
|
53
|
+
signedPreKeySignature: fromB64(stored.signedPreKeySignature),
|
|
54
|
+
};
|
|
55
|
+
this.initialized = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
console.warn('Failed to load identity keys:', e);
|
|
61
|
+
}
|
|
57
62
|
}
|
|
63
|
+
const ik = generateKeyPair();
|
|
64
|
+
const spk = generateKeyPair();
|
|
65
|
+
const sig = ecSign(spk.publicKey, ik);
|
|
58
66
|
this.identityKeys = {
|
|
59
|
-
identityKeyPair:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
signedPreKeyPair: {
|
|
64
|
-
publicKey: signedPreKeyPair.publicKey,
|
|
65
|
-
privateKey: signedPreKeyPair.privateKey,
|
|
66
|
-
},
|
|
67
|
-
oneTimePreKeys,
|
|
67
|
+
identityKeyPair: ik,
|
|
68
|
+
signedPreKeyPair: spk,
|
|
69
|
+
signedPreKeySignature: sig,
|
|
68
70
|
};
|
|
71
|
+
if (this.identityStore) {
|
|
72
|
+
try {
|
|
73
|
+
await this.identityStore.saveIdentityKeys(this.userId, {
|
|
74
|
+
identityKeyPair: { publicKey: toB64(ik.publicKey), privateKey: toB64(ik.privateKey) },
|
|
75
|
+
signedPreKeyPair: { publicKey: toB64(spk.publicKey), privateKey: toB64(spk.privateKey) },
|
|
76
|
+
signedPreKeySignature: toB64(sig),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.warn('Failed to save identity keys:', e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
69
83
|
this.initialized = true;
|
|
70
|
-
this.initPromise = null;
|
|
71
|
-
console.log(`[Crypto] Identity keys generated for ${this.userId}`);
|
|
72
84
|
}
|
|
73
|
-
|
|
74
|
-
* Get serialized public keys for relay registration
|
|
75
|
-
*/
|
|
85
|
+
/* ---- Public keys ---- */
|
|
76
86
|
getPublicKeys() {
|
|
77
|
-
if (!this.identityKeys)
|
|
78
|
-
throw new Error('
|
|
79
|
-
}
|
|
80
|
-
// Sign the pre-key
|
|
81
|
-
const signedPreKeySignature = sodium.crypto_sign_detached(this.identityKeys.signedPreKeyPair.publicKey, this.identityKeys.identityKeyPair.privateKey);
|
|
87
|
+
if (!this.identityKeys)
|
|
88
|
+
throw new Error('Not initialized');
|
|
82
89
|
return {
|
|
83
|
-
identityKey:
|
|
84
|
-
signedPreKey:
|
|
85
|
-
signedPreKeySignature:
|
|
86
|
-
oneTimePreKey:
|
|
90
|
+
identityKey: toB64(this.identityKeys.identityKeyPair.publicKey),
|
|
91
|
+
signedPreKey: toB64(this.identityKeys.signedPreKeyPair.publicKey),
|
|
92
|
+
signedPreKeySignature: toB64(this.identityKeys.signedPreKeySignature),
|
|
93
|
+
oneTimePreKey: '',
|
|
87
94
|
};
|
|
88
95
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Deserialize peer's public keys
|
|
101
|
-
const recipientIdentityKey = sodium.from_base64(peerPublicKeys.identityKey);
|
|
102
|
-
const recipientSignedPreKey = sodium.from_base64(peerPublicKeys.signedPreKey);
|
|
103
|
-
const recipientSPKSignature = sodium.from_base64(peerPublicKeys.signedPreKeySignature);
|
|
104
|
-
const recipientOneTimePreKey = sodium.from_base64(peerPublicKeys.oneTimePreKey);
|
|
105
|
-
// Verify SPK signature
|
|
106
|
-
const isValid = sodium.crypto_sign_verify_detached(recipientSPKSignature, recipientSignedPreKey, recipientIdentityKey);
|
|
107
|
-
if (!isValid) {
|
|
108
|
-
throw new Error(`Invalid SPK signature for peer ${peerId}`);
|
|
96
|
+
/* ---- Session establishment ---- */
|
|
97
|
+
async establishSession(peerId, peerPublicKeys) {
|
|
98
|
+
if (!this.identityKeys)
|
|
99
|
+
throw new Error('Not initialized');
|
|
100
|
+
const peerIK = fromB64(peerPublicKeys.identityKey);
|
|
101
|
+
const peerSPK = fromB64(peerPublicKeys.signedPreKey);
|
|
102
|
+
const peerSig = peerPublicKeys.signedPreKeySignature
|
|
103
|
+
? fromB64(peerPublicKeys.signedPreKeySignature)
|
|
104
|
+
: Buffer.alloc(0);
|
|
105
|
+
if (peerSig.length > 0 && !ecVerify(peerSPK, peerSig, peerIK)) {
|
|
106
|
+
throw new Error('Invalid signed pre-key signature — possible MITM attack');
|
|
109
107
|
}
|
|
110
|
-
|
|
111
|
-
const dh1 = sodium.crypto_scalarmult(this.identityKeys.signedPreKeyPair.privateKey, recipientSignedPreKey);
|
|
112
|
-
const dh2 = sodium.crypto_scalarmult(this.identityKeys.identityKeyPair.privateKey, recipientOneTimePreKey);
|
|
113
|
-
const dh3 = sodium.crypto_scalarmult(this.identityKeys.signedPreKeyPair.privateKey, recipientOneTimePreKey);
|
|
114
|
-
// Combine DH outputs
|
|
115
|
-
const sharedSecret = sodium.crypto_generichash(32, new Uint8Array([...dh1, ...dh2, ...dh3]));
|
|
116
|
-
// Derive root key
|
|
117
|
-
const rootKey = sodium.crypto_generichash(32, new Uint8Array([
|
|
118
|
-
...sharedSecret,
|
|
119
|
-
...sodium.from_string('x3dh-root-key-v1'),
|
|
120
|
-
]));
|
|
121
|
-
// Create initial session state
|
|
122
|
-
const session = {
|
|
123
|
-
identityKey: this.identityKeys.identityKeyPair.publicKey,
|
|
124
|
-
signedPreKey: this.identityKeys.signedPreKeyPair.publicKey,
|
|
125
|
-
oneTimePreKey: this.identityKeys.oneTimePreKeys[0] || new Uint8Array(32),
|
|
126
|
-
rootKey,
|
|
127
|
-
sendingChainKey: rootKey,
|
|
128
|
-
receivingChainKey: rootKey,
|
|
129
|
-
skippedMessageKeys: new Map(),
|
|
130
|
-
isPostCompromise: false,
|
|
131
|
-
};
|
|
108
|
+
const session = ratchetEstablishSession(this.identityKeys.identityKeyPair, this.identityKeys.signedPreKeyPair, peerIK, peerSPK);
|
|
132
109
|
this.sessions.set(peerId, session);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
throw new Error(`No session with peer ${peerId}`);
|
|
110
|
+
if (this.sessionStore) {
|
|
111
|
+
try {
|
|
112
|
+
await this.sessionStore.saveSession(this.userId, peerId, serializeSession(session));
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
console.warn('Failed to save session:', e);
|
|
116
|
+
}
|
|
141
117
|
}
|
|
142
|
-
// Encrypt using Double Ratchet
|
|
143
|
-
const result = ratchetEncrypt(plaintext, session);
|
|
144
|
-
// Enforce DH ratchet policy (Forward Secrecy + PCS)
|
|
145
|
-
const recipientKey = session.identityKey;
|
|
146
|
-
incrementMessageCounter(session, recipientKey);
|
|
147
|
-
return result;
|
|
148
118
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
*/
|
|
152
|
-
async decryptFromPeer(peerId, ciphertext, header) {
|
|
153
|
-
const session = this.sessions.get(peerId);
|
|
154
|
-
if (!session) {
|
|
155
|
-
throw new Error(`No session with peer ${peerId}`);
|
|
156
|
-
}
|
|
157
|
-
// Decrypt using Double Ratchet
|
|
158
|
-
const plaintext = ratchetDecrypt(ciphertext, header, session);
|
|
159
|
-
return plaintext;
|
|
119
|
+
async establishSessionWithPeer(peerId, pk) {
|
|
120
|
+
return this.establishSession(peerId, pk);
|
|
160
121
|
}
|
|
161
|
-
/**
|
|
162
|
-
* Check if session exists with peer
|
|
163
|
-
*/
|
|
164
122
|
hasSession(peerId) {
|
|
165
123
|
return this.sessions.has(peerId);
|
|
166
124
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
125
|
+
/* ---- Encrypt ---- */
|
|
126
|
+
encryptForPeer(peerId, plaintext) {
|
|
127
|
+
const session = this.sessions.get(peerId);
|
|
128
|
+
if (!session)
|
|
129
|
+
throw new Error('No session with peer');
|
|
130
|
+
const { ciphertext, header } = ratchetEncrypt(session, Buffer.from(plaintext, 'utf-8'));
|
|
131
|
+
return { ciphertext: toB64(ciphertext), header: toB64(header) };
|
|
132
|
+
}
|
|
133
|
+
/* ---- Decrypt ---- */
|
|
134
|
+
decryptFromPeer(peerId, ciphertext, header) {
|
|
135
|
+
const session = this.sessions.get(peerId);
|
|
136
|
+
if (!session)
|
|
137
|
+
throw new Error('No session with peer');
|
|
138
|
+
const pt = ratchetDecrypt(session, fromB64(ciphertext), fromB64(header));
|
|
139
|
+
return pt.toString('utf-8');
|
|
140
|
+
}
|
|
141
|
+
/* ---- Post-compromise ---- */
|
|
142
|
+
async forceRatchet(peerId) {
|
|
143
|
+
const session = this.sessions.get(peerId);
|
|
144
|
+
if (session) {
|
|
145
|
+
session.myRatchetKeyPair = generateKeyPair();
|
|
146
|
+
session.sendCount = 0;
|
|
147
|
+
session.recvCount = 0;
|
|
148
|
+
session.prevSendCount = 0;
|
|
149
|
+
session.isPostCompromise = true;
|
|
150
|
+
}
|
|
174
151
|
}
|
|
175
152
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Auto-generated CommonJS wrapper for facade/errors.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
|
+
});
|
package/dist/facade/errors.d.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade - Error Handling
|
|
3
|
+
*/
|
|
4
|
+
export declare const ErrorCode: {
|
|
5
|
+
readonly AUTH_FAILED: "AUTH_FAILED";
|
|
2
6
|
readonly INVALID_APP_TOKEN: "INVALID_APP_TOKEN";
|
|
3
|
-
readonly INVALID_API_KEY: "INVALID_API_KEY";
|
|
4
7
|
readonly RELAY_UNAVAILABLE: "RELAY_UNAVAILABLE";
|
|
5
|
-
readonly DELIVERY_FAILED: "DELIVERY_FAILED";
|
|
6
8
|
readonly RECIPIENT_NOT_FOUND: "RECIPIENT_NOT_FOUND";
|
|
7
9
|
readonly RECIPIENT_TIMEOUT: "RECIPIENT_TIMEOUT";
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
10
|
+
readonly CLIENT_NOT_READY: "CLIENT_NOT_READY";
|
|
11
|
+
readonly DELIVERY_FAILED: "DELIVERY_FAILED";
|
|
12
|
+
readonly QUOTA_EXCEEDED: "QUOTA_EXCEEDED";
|
|
13
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
12
14
|
};
|
|
13
|
-
export type ErrorCode =
|
|
15
|
+
export type ErrorCode = typeof ErrorCode[keyof typeof ErrorCode];
|
|
14
16
|
export declare class StvorError extends Error {
|
|
15
|
-
code:
|
|
16
|
-
action?: string
|
|
17
|
-
retryable?: boolean
|
|
18
|
-
constructor(code:
|
|
17
|
+
code: string;
|
|
18
|
+
action?: string;
|
|
19
|
+
retryable?: boolean;
|
|
20
|
+
constructor(code: string, message: string, action?: string, retryable?: boolean);
|
|
19
21
|
}
|
|
22
|
+
export declare const Errors: {
|
|
23
|
+
authFailed(): StvorError;
|
|
24
|
+
invalidAppToken(): StvorError;
|
|
25
|
+
relayUnavailable(): StvorError;
|
|
26
|
+
recipientNotFound(userId: string): StvorError;
|
|
27
|
+
messageIntegrityFailed(): StvorError;
|
|
28
|
+
keystoreCorrupted(): StvorError;
|
|
29
|
+
deviceCompromised(): StvorError;
|
|
30
|
+
protocolMismatch(): StvorError;
|
|
31
|
+
recipientTimeout(userId: string, timeoutMs: number): StvorError;
|
|
32
|
+
clientNotReady(): StvorError;
|
|
33
|
+
deliveryFailed(recipientId: string): StvorError;
|
|
34
|
+
quotaExceeded: () => StvorError;
|
|
35
|
+
rateLimited: () => StvorError;
|
|
36
|
+
};
|
package/dist/facade/errors.js
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade - Error Handling
|
|
3
|
+
*/
|
|
4
|
+
export const ErrorCode = {
|
|
5
|
+
AUTH_FAILED: 'AUTH_FAILED',
|
|
2
6
|
INVALID_APP_TOKEN: 'INVALID_APP_TOKEN',
|
|
3
|
-
INVALID_API_KEY: 'INVALID_API_KEY',
|
|
4
7
|
RELAY_UNAVAILABLE: 'RELAY_UNAVAILABLE',
|
|
5
|
-
DELIVERY_FAILED: 'DELIVERY_FAILED',
|
|
6
8
|
RECIPIENT_NOT_FOUND: 'RECIPIENT_NOT_FOUND',
|
|
7
9
|
RECIPIENT_TIMEOUT: 'RECIPIENT_TIMEOUT',
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
CLIENT_NOT_READY: 'CLIENT_NOT_READY',
|
|
11
|
+
DELIVERY_FAILED: 'DELIVERY_FAILED',
|
|
12
|
+
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
|
|
13
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
12
14
|
};
|
|
13
15
|
export class StvorError extends Error {
|
|
14
16
|
constructor(code, message, action, retryable) {
|
|
15
17
|
super(message);
|
|
18
|
+
this.name = 'StvorError';
|
|
16
19
|
this.code = code;
|
|
17
20
|
this.action = action;
|
|
18
21
|
this.retryable = retryable;
|
|
19
|
-
this.name = 'StvorError';
|
|
20
22
|
}
|
|
21
23
|
}
|
|
24
|
+
export const Errors = {
|
|
25
|
+
authFailed() {
|
|
26
|
+
return new StvorError(ErrorCode.AUTH_FAILED, 'The AppToken is invalid or has been revoked.', 'Check your dashboard and regenerate a new AppToken.', false);
|
|
27
|
+
},
|
|
28
|
+
invalidAppToken() {
|
|
29
|
+
return new StvorError(ErrorCode.INVALID_APP_TOKEN, 'Invalid AppToken format. AppToken must start with "stvor_".', 'Get your AppToken from the developer dashboard.', false);
|
|
30
|
+
},
|
|
31
|
+
relayUnavailable() {
|
|
32
|
+
return new StvorError(ErrorCode.RELAY_UNAVAILABLE, 'Cannot connect to STVOR relay server.', 'Check your internet connection.', true);
|
|
33
|
+
},
|
|
34
|
+
recipientNotFound(userId) {
|
|
35
|
+
return new StvorError(ErrorCode.RECIPIENT_NOT_FOUND, `User "${userId}" not found. They may not have registered with STVOR.`, 'Ask the recipient to initialize STVOR first, or verify the userId is correct.', false);
|
|
36
|
+
},
|
|
37
|
+
messageIntegrityFailed() {
|
|
38
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Message integrity check failed or decryption failed.', 'Request the message again from the sender.', false);
|
|
39
|
+
},
|
|
40
|
+
keystoreCorrupted() {
|
|
41
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Local keystore error (not supported in v0.1).', 'Investigate local storage configuration.', false);
|
|
42
|
+
},
|
|
43
|
+
deviceCompromised() {
|
|
44
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Device compromise detected (placeholder).', 'Investigate and revoke credentials.', false);
|
|
45
|
+
},
|
|
46
|
+
protocolMismatch() {
|
|
47
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Protocol version mismatch.', 'Update the SDK to the latest version.', false);
|
|
48
|
+
},
|
|
49
|
+
recipientTimeout(userId, timeoutMs) {
|
|
50
|
+
return new StvorError(ErrorCode.RECIPIENT_TIMEOUT, `Timed out waiting for user "${userId}" after ${timeoutMs}ms. ` +
|
|
51
|
+
`The user may not have registered with STVOR yet.`, 'Ensure the recipient has called connect() and is online, or increase the timeout.', true);
|
|
52
|
+
},
|
|
53
|
+
clientNotReady() {
|
|
54
|
+
return new StvorError(ErrorCode.CLIENT_NOT_READY, 'Client is not ready. Call connect() first and await it.', 'Make sure to await app.connect() before sending messages.', false);
|
|
55
|
+
},
|
|
56
|
+
deliveryFailed(recipientId) {
|
|
57
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, `Failed to deliver message to ${recipientId}.`, 'Check that the recipient exists and try again.', true);
|
|
58
|
+
},
|
|
59
|
+
quotaExceeded: () => new StvorError(ErrorCode.QUOTA_EXCEEDED, 'Message quota exceeded for this AppToken.', 'UPGRADE_PLAN', false),
|
|
60
|
+
rateLimited: () => new StvorError(ErrorCode.RATE_LIMITED, 'Rate limit exceeded. Please try again later.', 'WAIT', true),
|
|
61
|
+
// receive()/timeout APIs are not part of SDK v0.1 facade; use onMessage().
|
|
62
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Auto-generated CommonJS wrapper for facade/index.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
|
+
});
|
package/dist/facade/index.d.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
*/
|
|
11
|
+
export type { DecryptedMessage, SealedPayload } from './app';
|
|
12
|
+
export type { StvorAppConfig, AppToken, UserId, MessageContent } from './types';
|
|
13
|
+
export type { ErrorCode } from './errors';
|
|
14
|
+
export type { Metrics, SignedMetrics } from './metrics-engine';
|
|
15
|
+
export { StvorError } from './errors';
|
|
16
|
+
export { StvorApp, StvorFacadeClient, Stvor, init, createApp } from './app';
|
|
17
|
+
export { ErrorCode as STVOR_ERRORS } from './errors';
|
|
18
|
+
export { verifyMetricsSignature, MetricsEngine } from './metrics-engine';
|
|
19
|
+
export { CryptoSessionManager } from './crypto-session';
|
|
20
|
+
export type { IdentityKeys, SerializedPublicKeys, IIdentityStore, ISessionStore } from './crypto-session';
|
|
21
|
+
export { LocalStorageIdentityStore, LocalStorageSessionStore } from './local-storage-identity-store';
|
|
22
|
+
export { isReplay, validateMessage, validateMessageWithNonce, getCacheStats, cleanupExpiredNonces, initializeReplayProtection, startAutoCleanup, stopAutoCleanup, } from './replay-manager';
|
|
23
|
+
export type { IReplayCache } from './replay-manager';
|
|
24
|
+
export { RedisReplayCache } from './redis-replay-cache';
|
|
25
|
+
export type { RedisClient, RedisReplayCacheConfig } from './redis-replay-cache';
|
|
26
|
+
export { generateFingerprint, storeFingerprint, verifyFingerprint, isFingerprintTrusted, getFingerprint, revokeTrust, trustNewFingerprint, listTrustedFingerprints, getFingerprintInfo, initializeTofu, } from './tofu-manager';
|
|
27
|
+
export type { ITofuStore } from './tofu-manager';
|