@agora-sdk/secure-chat-react-js 0.6.5 → 0.8.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.
@@ -0,0 +1,140 @@
1
+ import type { SecureChatStore } from "@agora-sdk/secure-chat-core";
2
+ /**
3
+ * Thrown by every {@link EncryptedStore} data operation (`get`/`set`/`delete`/`list`) while the store
4
+ * is locked. Surfacing it (rather than silently returning empty) keeps the store fail-closed: a caller
5
+ * must `unlock(password)` before any persistence happens. Carries no secret material.
6
+ */
7
+ export declare class StoreLockedError extends Error {
8
+ constructor(message?: string);
9
+ }
10
+ /** Options for {@link createEncryptedStore}. */
11
+ export interface EncryptedStoreOptions {
12
+ }
13
+ /**
14
+ * A {@link SecureChatStore} decorator that adds at-rest encryption: it seals each VALUE under a
15
+ * password-derived key before delegating to a base store, and opens it on read. Store KEYS pass
16
+ * through in the clear (see the file header for the honest scope). Beyond the four store methods it
17
+ * exposes `unlock`/`lock`/`isLocked`/`changePassword` so the app can drive lock state while the
18
+ * provider still receives a plain `SecureChatStore`.
19
+ *
20
+ * Construct via {@link createEncryptedStore}; do not `new` it directly.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const enc = createEncryptedStore(createIndexedDBStore());
25
+ * await enc.unlock(password); // derive KEK, unwrap/generate DEK
26
+ * <SecureChatProvider store={enc} crypto={crypto} projectId={id} />
27
+ * // later, on logout / idle:
28
+ * enc.lock(); // drops the in-memory DEK/KEK
29
+ * ```
30
+ */
31
+ export declare class EncryptedStore implements SecureChatStore {
32
+ #private;
33
+ private readonly base;
34
+ /**
35
+ * @param base - The underlying store to seal/open values against (normally `createIndexedDBStore()`).
36
+ * @param _opts - {@link EncryptedStoreOptions} (reserved; no effect today).
37
+ */
38
+ constructor(base: SecureChatStore, _opts?: EncryptedStoreOptions);
39
+ /**
40
+ * Whether the store is currently locked (no DEK in memory). All data operations throw
41
+ * {@link StoreLockedError} while locked.
42
+ *
43
+ * @returns `true` if locked, `false` once {@link unlock} has succeeded.
44
+ */
45
+ isLocked(): boolean;
46
+ /**
47
+ * Unlock the store with the user's password. On first use (no meta record) this generates a fresh
48
+ * salt + DEK, wraps the DEK under the password-derived KEK, and persists the meta record. On a
49
+ * re-open it derives the KEK from the stored salt and unwraps the existing DEK. After success the
50
+ * (non-extractable) DEK is held in memory and data operations work.
51
+ *
52
+ * @param password - The user's password. Never logged, thrown, or persisted in the clear.
53
+ * @returns A promise that resolves once the DEK is in memory.
54
+ * @throws {Error} A generic "wrong password or corrupt store" error if the meta record exists but
55
+ * the DEK cannot be unwrapped (wrong password OR tampered store — deliberately indistinguishable).
56
+ * The store stays locked on failure.
57
+ */
58
+ unlock(password: string): Promise<void>;
59
+ /**
60
+ * Lock the store by dropping the in-memory DEK. Subsequent data operations throw
61
+ * {@link StoreLockedError} until {@link unlock} is called again. This is a disk-lock: it does not
62
+ * purge plaintext already cached elsewhere (provider/crypto in-memory state) — that is a later
63
+ * feature.
64
+ */
65
+ lock(): void;
66
+ /**
67
+ * Change the password without re-encrypting any data: verify/derive against the SAME DEK and re-wrap
68
+ * it under a KEK derived from the new password + a fresh salt, then rewrite the meta record.
69
+ *
70
+ * Requires the store to be unlocked (the in-memory DEK is the source of truth); `oldPassword` is
71
+ * additionally verified by re-deriving its KEK and unwrapping the stored DEK, so a wrong old password
72
+ * fails closed without touching the meta record.
73
+ *
74
+ * @param oldPassword - The current password (verified before any change).
75
+ * @param newPassword - The replacement password.
76
+ * @returns A promise that resolves once the meta record has been rewritten.
77
+ * @throws {StoreLockedError} If the store is locked (unlock first).
78
+ * @throws {Error} A generic "wrong password or corrupt store" error if `oldPassword` does not unwrap
79
+ * the stored DEK. The meta record is left unchanged.
80
+ */
81
+ changePassword(oldPassword: string, newPassword: string): Promise<void>;
82
+ /**
83
+ * Read and open the value at `key`. A miss returns `null`. On any decrypt/authentication failure it
84
+ * THROWS (fail closed) and never returns raw or partially-decrypted bytes.
85
+ *
86
+ * @param key - The store key (passed through to the base store unencrypted).
87
+ * @returns The decrypted bytes, or `null` if the key is absent.
88
+ * @throws {StoreLockedError} If the store is locked.
89
+ * @throws {Error} If the stored blob is malformed or fails AES-GCM authentication (tamper/corruption).
90
+ */
91
+ get(key: string): Promise<Uint8Array | null>;
92
+ /**
93
+ * Seal `value` and write it at `key`. Format: `[version:1][nonce:12][AES-GCM ciphertext+tag]`, with
94
+ * a fresh CSPRNG nonce per write.
95
+ *
96
+ * @param key - The store key (passed through unencrypted).
97
+ * @param value - The plaintext bytes to seal.
98
+ * @returns A promise that resolves once the sealed blob is persisted.
99
+ * @throws {StoreLockedError} If the store is locked.
100
+ */
101
+ set(key: string, value: Uint8Array): Promise<void>;
102
+ /**
103
+ * Remove `key` from the base store (no-op when absent). Requires the store to be unlocked.
104
+ *
105
+ * @param key - The store key to delete.
106
+ * @returns A promise that resolves once the key is removed.
107
+ * @throws {StoreLockedError} If the store is locked.
108
+ */
109
+ delete(key: string): Promise<void>;
110
+ /**
111
+ * List base-store keys starting with `prefix`, with the reserved meta key filtered out so it never
112
+ * surfaces to the repository.
113
+ *
114
+ * @param prefix - The key prefix (`""` for every key).
115
+ * @returns The matching keys, excluding the internal `__enc__` meta record.
116
+ * @throws {StoreLockedError} If the store is locked.
117
+ */
118
+ list(prefix: string): Promise<string[]>;
119
+ }
120
+ /**
121
+ * Wrap a base {@link SecureChatStore} (normally `createIndexedDBStore()`) with at-rest encryption.
122
+ * Returns a locked store: the app must call `unlock(password)` before mounting `<SecureChatProvider>`,
123
+ * and may `lock()` it on logout/idle. Values are sealed with AES-256-GCM under a DEK that is wrapped by
124
+ * an argon2id-derived KEK; store keys pass through in the clear (see this module's header for scope).
125
+ *
126
+ * @param base - The underlying durable store to seal/open values against.
127
+ * @param opts - {@link EncryptedStoreOptions} (reserved; no effect today).
128
+ * @returns A locked {@link EncryptedStore} — call `unlock(password)` to enable persistence.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * import { createIndexedDBStore, createEncryptedStore } from "@agora-sdk/secure-chat-react-js";
133
+ *
134
+ * const store = createEncryptedStore(createIndexedDBStore());
135
+ * await store.unlock(userPassword); // first use mints the DEK; later opens unwrap it
136
+ * // pass `store` to <SecureChatProvider store={store} … />
137
+ * store.lock(); // drop the in-memory DEK when locking the app
138
+ * ```
139
+ */
140
+ export declare function createEncryptedStore(base: SecureChatStore, opts?: EncryptedStoreOptions): EncryptedStore;
@@ -0,0 +1,353 @@
1
+ // Encryption-at-rest decorator for the web SecureChatStore.
2
+ //
3
+ // Where it sits in the blind-server / client-crypto model: everything the secure-chat client persists
4
+ // flows through one `SecureChatStore` key→blob seam — MLS group/ratchet secrets (`group:`), the device
5
+ // signing key (`device`), DECRYPTED message plaintext (`msg:`, the durable history), and delivery
6
+ // cursors. On web the base store is `createIndexedDBStore()`, which writes plaintext readable by any
7
+ // same-origin script or anyone with disk access. The blind server still never sees any of it; this
8
+ // decorator closes the *local* at-rest gap by sealing every VALUE under a password-derived key before
9
+ // it reaches the base store, and opening it on the way out.
10
+ //
11
+ // Key hierarchy (Approach A): a password is stretched with argon2id into a Key-Encryption-Key (KEK,
12
+ // once per unlock), which unwraps a random AES-256-GCM Data-Encryption-Key (DEK) held only as a
13
+ // NON-EXTRACTABLE WebCrypto CryptoKey. Per-value AES-GCM uses the DEK with a fresh random nonce. The
14
+ // AEAD unlock IS the password check — there is no separate stored hash to leak. A password change
15
+ // re-wraps the SAME DEK (no bulk re-encryption), so existing values stay readable.
16
+ //
17
+ // SECURITY POSTURE (honest, per CLAUDE.md §1):
18
+ // • Values are sealed; store KEYS pass through in the clear. Keys leak conversation ids and
19
+ // per-conversation message counts — which the blind server already observes — but never plaintext
20
+ // or key material. Encrypting keys/metadata is a deliberately-deferred later feature.
21
+ // • Fail closed everywhere: locked → every op throws `StoreLockedError`; a wrong password or a
22
+ // tampered value → the AEAD throws and we rethrow a GENERIC error (we never distinguish wrong
23
+ // password from tamper, and never return raw/undecrypted bytes).
24
+ // • Nothing here ever logs, throws, or serializes the password, KEK, DEK, or any plaintext.
25
+ // • All crypto is WebCrypto + @noble/hashes argon2id — no hand-rolled primitives, CSPRNG nonces only.
26
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
27
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
28
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
29
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
30
+ };
31
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
32
+ if (kind === "m") throw new TypeError("Private method is not writable");
33
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
34
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
35
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
36
+ };
37
+ var _EncryptedStore_instances, _EncryptedStore_dek, _EncryptedStore_requireUnlocked, _EncryptedStore_deriveKek, _EncryptedStore_unwrapDek, _EncryptedStore_parseMeta;
38
+ import { toBase64, fromBase64 } from "@agora-sdk/secure-chat-core";
39
+ import { argon2idAsync } from "@noble/hashes/argon2.js";
40
+ /**
41
+ * argon2id parameters for deriving the KEK from the password — RFC 9106 "FIRST RECOMMENDED"
42
+ * high-memory profile (m=64 MiB, t=3, p=1) with a 32-byte output, matching
43
+ * `@agora-sdk/secure-chat-crypto`'s backup codec. Unlock is interactive-but-rare, so the ~0.5–1s cost
44
+ * is acceptable and buys strong resistance to offline brute-force of the password if the on-disk store
45
+ * is exfiltrated.
46
+ */
47
+ const ARGON2_PARAMS = { m: 65536, t: 3, p: 1, dkLen: 32 };
48
+ /** The on-disk format version stamped into the meta record and every sealed value. */
49
+ const VERSION = 1;
50
+ /** Reserved base-store key for the (never-encrypted, never-listed) meta record. */
51
+ const META_KEY = "__enc__";
52
+ /** Random salt length for argon2id (bytes). */
53
+ const SALT_BYTES = 16;
54
+ /** AES-GCM nonce length (bytes) — 96-bit, the WebCrypto/NIST-recommended IV size. */
55
+ const NONCE_BYTES = 12;
56
+ const utf8 = (s) => new TextEncoder().encode(s);
57
+ /**
58
+ * Coerce bytes to a WebCrypto `BufferSource` backed by a plain `ArrayBuffer`. `@noble/hashes` and
59
+ * `subarray` views are typed `Uint8Array<ArrayBufferLike>`, which TS will not accept where WebCrypto
60
+ * wants an `ArrayBuffer`-backed `BufferSource`; copying into a fresh `Uint8Array` guarantees that
61
+ * backing. The copies are small (nonces, the wrapped DEK, per-value ciphertext) — no secret is exposed.
62
+ */
63
+ const buf = (bytes) => new Uint8Array(bytes);
64
+ /**
65
+ * Thrown by every {@link EncryptedStore} data operation (`get`/`set`/`delete`/`list`) while the store
66
+ * is locked. Surfacing it (rather than silently returning empty) keeps the store fail-closed: a caller
67
+ * must `unlock(password)` before any persistence happens. Carries no secret material.
68
+ */
69
+ export class StoreLockedError extends Error {
70
+ constructor(message = "secure-chat: store is locked — call unlock(password) first") {
71
+ super(message);
72
+ this.name = "StoreLockedError";
73
+ }
74
+ }
75
+ /**
76
+ * A {@link SecureChatStore} decorator that adds at-rest encryption: it seals each VALUE under a
77
+ * password-derived key before delegating to a base store, and opens it on read. Store KEYS pass
78
+ * through in the clear (see the file header for the honest scope). Beyond the four store methods it
79
+ * exposes `unlock`/`lock`/`isLocked`/`changePassword` so the app can drive lock state while the
80
+ * provider still receives a plain `SecureChatStore`.
81
+ *
82
+ * Construct via {@link createEncryptedStore}; do not `new` it directly.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const enc = createEncryptedStore(createIndexedDBStore());
87
+ * await enc.unlock(password); // derive KEK, unwrap/generate DEK
88
+ * <SecureChatProvider store={enc} crypto={crypto} projectId={id} />
89
+ * // later, on logout / idle:
90
+ * enc.lock(); // drops the in-memory DEK/KEK
91
+ * ```
92
+ */
93
+ export class EncryptedStore {
94
+ /**
95
+ * @param base - The underlying store to seal/open values against (normally `createIndexedDBStore()`).
96
+ * @param _opts - {@link EncryptedStoreOptions} (reserved; no effect today).
97
+ */
98
+ constructor(base, _opts = {}) {
99
+ _EncryptedStore_instances.add(this);
100
+ this.base = base;
101
+ /** The unwrapped, non-extractable per-value AES-GCM key. `null` ⇒ locked. */
102
+ _EncryptedStore_dek.set(this, null);
103
+ }
104
+ /**
105
+ * Whether the store is currently locked (no DEK in memory). All data operations throw
106
+ * {@link StoreLockedError} while locked.
107
+ *
108
+ * @returns `true` if locked, `false` once {@link unlock} has succeeded.
109
+ */
110
+ isLocked() {
111
+ return __classPrivateFieldGet(this, _EncryptedStore_dek, "f") === null;
112
+ }
113
+ /**
114
+ * Unlock the store with the user's password. On first use (no meta record) this generates a fresh
115
+ * salt + DEK, wraps the DEK under the password-derived KEK, and persists the meta record. On a
116
+ * re-open it derives the KEK from the stored salt and unwraps the existing DEK. After success the
117
+ * (non-extractable) DEK is held in memory and data operations work.
118
+ *
119
+ * @param password - The user's password. Never logged, thrown, or persisted in the clear.
120
+ * @returns A promise that resolves once the DEK is in memory.
121
+ * @throws {Error} A generic "wrong password or corrupt store" error if the meta record exists but
122
+ * the DEK cannot be unwrapped (wrong password OR tampered store — deliberately indistinguishable).
123
+ * The store stays locked on failure.
124
+ */
125
+ async unlock(password) {
126
+ const metaBytes = await this.base.get(META_KEY);
127
+ if (metaBytes === null) {
128
+ // First unlock: mint a salt + DEK, wrap it under the KEK, persist the meta record.
129
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
130
+ const kek = await __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_deriveKek).call(this, password, salt);
131
+ // Extractable so we can wrap it now; the in-memory handle below is re-imported non-extractable.
132
+ const freshDek = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [
133
+ "encrypt",
134
+ "decrypt",
135
+ ]);
136
+ const wrapNonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
137
+ const wrapped = new Uint8Array(await crypto.subtle.wrapKey("raw", freshDek, kek, { name: "AES-GCM", iv: wrapNonce }));
138
+ const meta = {
139
+ version: VERSION,
140
+ kdf: "argon2id",
141
+ kdfParams: { salt: toBase64(salt), m: ARGON2_PARAMS.m, t: ARGON2_PARAMS.t, p: ARGON2_PARAMS.p },
142
+ wrappedDEK: toBase64(wrapped),
143
+ wrapNonce: toBase64(wrapNonce),
144
+ };
145
+ await this.base.set(META_KEY, utf8(JSON.stringify(meta)));
146
+ // Re-derive the in-memory DEK as NON-EXTRACTABLE (the extractable handle is dropped here).
147
+ __classPrivateFieldSet(this, _EncryptedStore_dek, await __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_unwrapDek).call(this, kek, wrapped, wrapNonce), "f");
148
+ return;
149
+ }
150
+ // Re-open: derive the KEK from the stored salt and unwrap the existing DEK.
151
+ const meta = __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_parseMeta).call(this, metaBytes);
152
+ const kek = await __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_deriveKek).call(this, password, fromBase64(meta.kdfParams.salt));
153
+ try {
154
+ __classPrivateFieldSet(this, _EncryptedStore_dek, await __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_unwrapDek).call(this, kek, fromBase64(meta.wrappedDEK), fromBase64(meta.wrapNonce)), "f");
155
+ }
156
+ catch {
157
+ // Wrong password and a tampered wrappedDEK are indistinguishable here, and we keep it that way.
158
+ // Stay locked; surface nothing about the password or the failure cause.
159
+ __classPrivateFieldSet(this, _EncryptedStore_dek, null, "f");
160
+ throw new Error("secure-chat: unlock failed (wrong password or corrupt store)");
161
+ }
162
+ }
163
+ /**
164
+ * Lock the store by dropping the in-memory DEK. Subsequent data operations throw
165
+ * {@link StoreLockedError} until {@link unlock} is called again. This is a disk-lock: it does not
166
+ * purge plaintext already cached elsewhere (provider/crypto in-memory state) — that is a later
167
+ * feature.
168
+ */
169
+ lock() {
170
+ __classPrivateFieldSet(this, _EncryptedStore_dek, null, "f");
171
+ }
172
+ /**
173
+ * Change the password without re-encrypting any data: verify/derive against the SAME DEK and re-wrap
174
+ * it under a KEK derived from the new password + a fresh salt, then rewrite the meta record.
175
+ *
176
+ * Requires the store to be unlocked (the in-memory DEK is the source of truth); `oldPassword` is
177
+ * additionally verified by re-deriving its KEK and unwrapping the stored DEK, so a wrong old password
178
+ * fails closed without touching the meta record.
179
+ *
180
+ * @param oldPassword - The current password (verified before any change).
181
+ * @param newPassword - The replacement password.
182
+ * @returns A promise that resolves once the meta record has been rewritten.
183
+ * @throws {StoreLockedError} If the store is locked (unlock first).
184
+ * @throws {Error} A generic "wrong password or corrupt store" error if `oldPassword` does not unwrap
185
+ * the stored DEK. The meta record is left unchanged.
186
+ */
187
+ async changePassword(oldPassword, newPassword) {
188
+ if (__classPrivateFieldGet(this, _EncryptedStore_dek, "f") === null)
189
+ throw new StoreLockedError();
190
+ const metaBytes = await this.base.get(META_KEY);
191
+ if (metaBytes === null)
192
+ throw new Error("secure-chat: cannot change password before first unlock");
193
+ const meta = __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_parseMeta).call(this, metaBytes);
194
+ // Verify the old password by unwrapping the stored DEK with its KEK (we re-wrap THIS exact DEK).
195
+ const oldKek = await __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_deriveKek).call(this, oldPassword, fromBase64(meta.kdfParams.salt));
196
+ let dekToRewrap;
197
+ try {
198
+ // Re-wrap needs an EXTRACTABLE handle, so unwrap extractable here (held only for the wrap below).
199
+ dekToRewrap = await crypto.subtle.unwrapKey("raw", buf(fromBase64(meta.wrappedDEK)), oldKek, { name: "AES-GCM", iv: buf(fromBase64(meta.wrapNonce)) }, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
200
+ }
201
+ catch {
202
+ throw new Error("secure-chat: unlock failed (wrong password or corrupt store)");
203
+ }
204
+ // Derive a new KEK from the new password + a fresh salt, and re-wrap the same DEK under it.
205
+ const newSalt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
206
+ const newKek = await __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_deriveKek).call(this, newPassword, newSalt);
207
+ const newWrapNonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
208
+ const newWrapped = new Uint8Array(await crypto.subtle.wrapKey("raw", dekToRewrap, newKek, { name: "AES-GCM", iv: newWrapNonce }));
209
+ const newMeta = {
210
+ version: VERSION,
211
+ kdf: "argon2id",
212
+ kdfParams: { salt: toBase64(newSalt), m: ARGON2_PARAMS.m, t: ARGON2_PARAMS.t, p: ARGON2_PARAMS.p },
213
+ wrappedDEK: toBase64(newWrapped),
214
+ wrapNonce: toBase64(newWrapNonce),
215
+ };
216
+ await this.base.set(META_KEY, utf8(JSON.stringify(newMeta)));
217
+ }
218
+ /**
219
+ * Read and open the value at `key`. A miss returns `null`. On any decrypt/authentication failure it
220
+ * THROWS (fail closed) and never returns raw or partially-decrypted bytes.
221
+ *
222
+ * @param key - The store key (passed through to the base store unencrypted).
223
+ * @returns The decrypted bytes, or `null` if the key is absent.
224
+ * @throws {StoreLockedError} If the store is locked.
225
+ * @throws {Error} If the stored blob is malformed or fails AES-GCM authentication (tamper/corruption).
226
+ */
227
+ async get(key) {
228
+ const dek = __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_requireUnlocked).call(this);
229
+ const sealed = await this.base.get(key);
230
+ if (sealed === null)
231
+ return null;
232
+ if (sealed.length < 1 + NONCE_BYTES || sealed[0] !== VERSION) {
233
+ throw new Error("secure-chat: stored value is malformed (bad header)");
234
+ }
235
+ const nonce = sealed.subarray(1, 1 + NONCE_BYTES);
236
+ const ciphertext = sealed.subarray(1 + NONCE_BYTES);
237
+ let plain;
238
+ try {
239
+ plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv: buf(nonce) }, dek, buf(ciphertext));
240
+ }
241
+ catch {
242
+ // AEAD failure ⇒ wrong key or tampered ciphertext. Fail closed; never return raw bytes.
243
+ throw new Error("secure-chat: value decrypt failed (corrupt or tampered store)");
244
+ }
245
+ return new Uint8Array(plain);
246
+ }
247
+ /**
248
+ * Seal `value` and write it at `key`. Format: `[version:1][nonce:12][AES-GCM ciphertext+tag]`, with
249
+ * a fresh CSPRNG nonce per write.
250
+ *
251
+ * @param key - The store key (passed through unencrypted).
252
+ * @param value - The plaintext bytes to seal.
253
+ * @returns A promise that resolves once the sealed blob is persisted.
254
+ * @throws {StoreLockedError} If the store is locked.
255
+ */
256
+ async set(key, value) {
257
+ const dek = __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_requireUnlocked).call(this);
258
+ const nonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
259
+ const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: buf(nonce) }, dek, buf(value)));
260
+ const sealed = new Uint8Array(1 + NONCE_BYTES + ct.length);
261
+ sealed[0] = VERSION;
262
+ sealed.set(nonce, 1);
263
+ sealed.set(ct, 1 + NONCE_BYTES);
264
+ await this.base.set(key, sealed);
265
+ }
266
+ /**
267
+ * Remove `key` from the base store (no-op when absent). Requires the store to be unlocked.
268
+ *
269
+ * @param key - The store key to delete.
270
+ * @returns A promise that resolves once the key is removed.
271
+ * @throws {StoreLockedError} If the store is locked.
272
+ */
273
+ async delete(key) {
274
+ __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_requireUnlocked).call(this);
275
+ await this.base.delete(key);
276
+ }
277
+ /**
278
+ * List base-store keys starting with `prefix`, with the reserved meta key filtered out so it never
279
+ * surfaces to the repository.
280
+ *
281
+ * @param prefix - The key prefix (`""` for every key).
282
+ * @returns The matching keys, excluding the internal `__enc__` meta record.
283
+ * @throws {StoreLockedError} If the store is locked.
284
+ */
285
+ async list(prefix) {
286
+ __classPrivateFieldGet(this, _EncryptedStore_instances, "m", _EncryptedStore_requireUnlocked).call(this);
287
+ const keys = await this.base.list(prefix);
288
+ return keys.filter((k) => k !== META_KEY);
289
+ }
290
+ }
291
+ _EncryptedStore_dek = new WeakMap(), _EncryptedStore_instances = new WeakSet(), _EncryptedStore_requireUnlocked = function _EncryptedStore_requireUnlocked() {
292
+ if (__classPrivateFieldGet(this, _EncryptedStore_dek, "f") === null)
293
+ throw new StoreLockedError();
294
+ return __classPrivateFieldGet(this, _EncryptedStore_dek, "f");
295
+ }, _EncryptedStore_deriveKek =
296
+ /** Stretch the password + salt into a non-extractable AES-GCM KEK with wrap/unwrap usages. */
297
+ async function _EncryptedStore_deriveKek(password, salt) {
298
+ const raw = await argon2idAsync(utf8(password), salt, ARGON2_PARAMS);
299
+ try {
300
+ return await crypto.subtle.importKey("raw", buf(raw), { name: "AES-GCM" }, false, [
301
+ "wrapKey",
302
+ "unwrapKey",
303
+ ]);
304
+ }
305
+ finally {
306
+ raw.fill(0); // zeroize the raw KEK bytes; the CryptoKey handle is non-extractable.
307
+ }
308
+ }, _EncryptedStore_unwrapDek =
309
+ /** Unwrap the stored DEK under `kek` into a NON-EXTRACTABLE per-value AES-GCM key. */
310
+ async function _EncryptedStore_unwrapDek(kek, wrapped, wrapNonce) {
311
+ return crypto.subtle.unwrapKey("raw", buf(wrapped), kek, { name: "AES-GCM", iv: buf(wrapNonce) }, { name: "AES-GCM", length: 256 }, false, // non-extractable: the DEK bytes can never be read back out of WebCrypto.
312
+ ["encrypt", "decrypt"]);
313
+ }, _EncryptedStore_parseMeta = function _EncryptedStore_parseMeta(bytes) {
314
+ let meta;
315
+ try {
316
+ meta = JSON.parse(new TextDecoder().decode(bytes));
317
+ }
318
+ catch {
319
+ throw new Error("secure-chat: store meta record is malformed");
320
+ }
321
+ if (meta.version !== VERSION ||
322
+ meta.kdf !== "argon2id" ||
323
+ typeof meta.kdfParams?.salt !== "string" ||
324
+ typeof meta.wrappedDEK !== "string" ||
325
+ typeof meta.wrapNonce !== "string") {
326
+ throw new Error("secure-chat: unsupported or malformed store meta record");
327
+ }
328
+ return meta;
329
+ };
330
+ /**
331
+ * Wrap a base {@link SecureChatStore} (normally `createIndexedDBStore()`) with at-rest encryption.
332
+ * Returns a locked store: the app must call `unlock(password)` before mounting `<SecureChatProvider>`,
333
+ * and may `lock()` it on logout/idle. Values are sealed with AES-256-GCM under a DEK that is wrapped by
334
+ * an argon2id-derived KEK; store keys pass through in the clear (see this module's header for scope).
335
+ *
336
+ * @param base - The underlying durable store to seal/open values against.
337
+ * @param opts - {@link EncryptedStoreOptions} (reserved; no effect today).
338
+ * @returns A locked {@link EncryptedStore} — call `unlock(password)` to enable persistence.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { createIndexedDBStore, createEncryptedStore } from "@agora-sdk/secure-chat-react-js";
343
+ *
344
+ * const store = createEncryptedStore(createIndexedDBStore());
345
+ * await store.unlock(userPassword); // first use mints the DEK; later opens unwrap it
346
+ * // pass `store` to <SecureChatProvider store={store} … />
347
+ * store.lock(); // drop the in-memory DEK when locking the app
348
+ * ```
349
+ */
350
+ export function createEncryptedStore(base, opts = {}) {
351
+ return new EncryptedStore(base, opts);
352
+ }
353
+ //# sourceMappingURL=encrypted-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypted-store.js","sourceRoot":"","sources":["../../src/encrypted-store.ts"],"names":[],"mappings":"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"}
@@ -2,3 +2,5 @@ export * from "@agora-sdk/secure-chat-core";
2
2
  export { createWebSecureChatCrypto } from "./crypto-web.js";
3
3
  export { createIndexedDBStore } from "./indexeddb-store.js";
4
4
  export type { IndexedDBStoreOptions } from "./indexeddb-store.js";
5
+ export { createEncryptedStore, EncryptedStore, StoreLockedError } from "./encrypted-store.js";
6
+ export type { EncryptedStoreOptions } from "./encrypted-store.js";
package/dist/esm/index.js CHANGED
@@ -6,4 +6,5 @@
6
6
  export * from "@agora-sdk/secure-chat-core";
7
7
  export { createWebSecureChatCrypto } from "./crypto-web.js";
8
8
  export { createIndexedDBStore } from "./indexeddb-store.js";
9
+ export { createEncryptedStore, EncryptedStore, StoreLockedError } from "./encrypted-store.js";
9
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,iGAAiG;AACjG,kGAAkG;AAClG,8CAA8C;AAE9C,cAAc,6BAA6B,CAAC;AAE5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,iGAAiG;AACjG,kGAAkG;AAClG,8CAA8C;AAE9C,cAAc,6BAA6B,CAAC;AAE5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agora-sdk/secure-chat-react-js",
3
- "version": "0.6.5",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "author": "Agora SDK Plus, maintained by Jenova Marie",
@@ -27,7 +27,7 @@
27
27
  "module": "dist/esm/index.js",
28
28
  "types": "dist/esm/index.d.ts",
29
29
  "type": "module",
30
- "comment:esm-only": "ESM-only: this web binding depends on @agora-sdk/core (bundler-only, see UPSTREAM_FIX.md) and the ESM-only @agora-sdk/secure-chat-crypto/ts-mls core, so a CJS build would never load at runtime. Web/React consumers always bundle (Vite/webpack/Metro).",
30
+ "comment:esm-only": "ESM-only: this web binding depends on the ESM-only @agora-sdk/secure-chat-crypto/ts-mls core, so a CJS build would never load at runtime. Web/React consumers always bundle (Vite/webpack/Metro) anyway.",
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
@@ -35,11 +35,12 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@agora-sdk/secure-chat-core": "0.6.5",
39
- "@agora-sdk/secure-chat-crypto": "0.6.5"
38
+ "@noble/hashes": "2.0.1",
39
+ "@agora-sdk/secure-chat-core": "0.8.0",
40
+ "@agora-sdk/secure-chat-crypto": "0.8.0"
40
41
  },
41
42
  "peerDependencies": {
42
- "@agora-sdk/core": "^1.2.2",
43
+ "@types/react": "^18.0.0 || ^19.0.0",
43
44
  "react": "^18.0.0 || ^19.0.0",
44
45
  "react-dom": "^18.0.0 || ^19.0.0"
45
46
  },