@secrecy/lib 1.86.2-fix-md5.1 → 1.86.3
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/lib/base-client.js +6 -5
- package/dist/lib/cache.js +14 -5
- package/dist/lib/client/SecrecyCloudClient.js +2 -4
- package/dist/lib/client/SecrecyMailClient.js +12 -5
- package/dist/lib/client/data-link.js +12 -6
- package/dist/lib/client/helpers.js +34 -4
- package/dist/lib/client/index.js +6 -8
- package/dist/lib/client/upload.js +14 -12
- package/dist/lib/client.js +6 -10
- package/dist/lib/crypto/data.js +90 -145
- package/dist/lib/crypto/domain.js +3 -1
- package/dist/lib/crypto/helpers.js +1 -23
- package/dist/lib/crypto/index.js +9 -2
- package/dist/lib/error/client.js +7 -1
- package/dist/lib/react/index.js +69 -0
- package/dist/lib/transformer.js +3 -2
- package/dist/lib/utils/popup-tools.js +20 -24
- package/dist/lib/versioning.js +1 -1
- package/dist/lib/worker/md5.js +2 -2
- package/dist/lib/worker/sodium.js +15 -17
- package/dist/lib/worker/workerCodes.js +254 -0
- package/dist/types/base-client.d.ts +2 -0
- package/dist/types/cache.d.ts +7 -6
- package/dist/types/client/SecrecyMailClient.d.ts +23 -1
- package/dist/types/client/data-link.d.ts +4 -4
- package/dist/types/client/helpers.d.ts +13 -0
- package/dist/types/client/index.d.ts +4 -2
- package/dist/types/client/types/data.d.ts +1 -2
- package/dist/types/client.d.ts +75 -66
- package/dist/types/crypto/data.d.ts +1 -11
- package/dist/types/crypto/domain.d.ts +1 -0
- package/dist/types/crypto/helpers.d.ts +0 -7
- package/dist/types/error/client.d.ts +8 -0
- package/dist/types/error/index.d.ts +4 -0
- package/dist/types/react/index.d.ts +20 -0
- package/dist/types/utils/popup-tools.d.ts +2 -5
- package/dist/types/versioning.d.ts +1 -1
- package/dist/types/worker/workerCodes.d.ts +2 -0
- package/package.json +38 -16
- package/dist/lib/client/SecrecyDbClient.js +0 -3
- package/dist/lib/worker/generated/md5.js +0 -2
- package/dist/lib/worker/generated/sodium.js +0 -2
- package/dist/types/client/SecrecyDbClient.d.ts +0 -4
- package/dist/types/worker/generated/md5.d.ts +0 -1
- package/dist/types/worker/generated/sodium.d.ts +0 -1
package/dist/lib/crypto/data.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { setup, sodium } from '../sodium.js';
|
|
2
2
|
import SparkMD5 from 'spark-md5';
|
|
3
|
+
import { chunks } from '../utils/array.js';
|
|
3
4
|
function assert(c, message) {
|
|
4
5
|
if (!c) {
|
|
5
6
|
throw new Error(message);
|
|
@@ -8,19 +9,7 @@ function assert(c, message) {
|
|
|
8
9
|
export function secretStreamKeygen() {
|
|
9
10
|
return sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
|
10
11
|
}
|
|
11
|
-
function assertKey(key) {
|
|
12
|
-
assert(key.byteLength === sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES, `key must be KEYBYTES (${sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES}) long`);
|
|
13
|
-
}
|
|
14
|
-
// The wrappers return the secretstream state as a raw address in the wasm
|
|
15
|
-
// heap and never release it: wipe it (it holds the stream key) and free it.
|
|
16
|
-
function wipeState(state) {
|
|
17
|
-
const lib = sodium.libsodium;
|
|
18
|
-
const size = lib._crypto_secretstream_xchacha20poly1305_statebytes();
|
|
19
|
-
lib.HEAPU8.fill(0, state, state + size);
|
|
20
|
-
lib._free(state);
|
|
21
|
-
}
|
|
22
12
|
function encrypt(key) {
|
|
23
|
-
assertKey(key);
|
|
24
13
|
let destroyed = false;
|
|
25
14
|
const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
|
|
26
15
|
const encrypt = (tag, plaintext) => {
|
|
@@ -30,7 +19,6 @@ function encrypt(key) {
|
|
|
30
19
|
function destroy() {
|
|
31
20
|
assert(!destroyed, 'state already destroyed');
|
|
32
21
|
destroyed = true;
|
|
33
|
-
wipeState(state);
|
|
34
22
|
}
|
|
35
23
|
return {
|
|
36
24
|
encrypt,
|
|
@@ -39,9 +27,9 @@ function encrypt(key) {
|
|
|
39
27
|
};
|
|
40
28
|
}
|
|
41
29
|
function decrypt(header, key) {
|
|
42
|
-
assert(header.byteLength
|
|
43
|
-
sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES, `header must be HEADERBYTES (${sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES}) long`);
|
|
44
|
-
|
|
30
|
+
assert(header.byteLength >=
|
|
31
|
+
sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES, `header must be at least HEADERBYTES (${sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES}) long`);
|
|
32
|
+
assert(key.byteLength >= sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES, `key must be at least KEYBYTES (${sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES}) long`);
|
|
45
33
|
let destroyed = false;
|
|
46
34
|
const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key);
|
|
47
35
|
const decrypt = (ciphertext) => {
|
|
@@ -51,7 +39,6 @@ function decrypt(header, key) {
|
|
|
51
39
|
function destroy() {
|
|
52
40
|
assert(!destroyed, 'state already destroyed');
|
|
53
41
|
destroyed = true;
|
|
54
|
-
wipeState(state);
|
|
55
42
|
}
|
|
56
43
|
return {
|
|
57
44
|
decrypt,
|
|
@@ -59,146 +46,104 @@ function decrypt(header, key) {
|
|
|
59
46
|
};
|
|
60
47
|
}
|
|
61
48
|
export const CHUNK_SIZE = 8192;
|
|
62
|
-
/**
|
|
63
|
-
* Encrypt `data` as a secretstream: the header followed by one authenticated
|
|
64
|
-
* chunk per CHUNK_SIZE bytes of plaintext. The last chunk (an empty one for
|
|
65
|
-
* empty data) is always tagged `TAG_FINAL` so that truncation is detected.
|
|
66
|
-
*/
|
|
67
49
|
export async function encryptSecretStream(key, data, progress, signal) {
|
|
68
50
|
await setup();
|
|
69
51
|
const { encrypt: crypt, destroy, header } = encrypt(key);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
lastPercent = percent;
|
|
111
|
-
}
|
|
52
|
+
const encryptedChunk = CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
|
53
|
+
const max = Math.ceil(data.byteLength / CHUNK_SIZE) * encryptedChunk + header.byteLength;
|
|
54
|
+
void progress?.({
|
|
55
|
+
percent: 0,
|
|
56
|
+
total: max,
|
|
57
|
+
current: 0,
|
|
58
|
+
});
|
|
59
|
+
const final = new Uint8Array(max);
|
|
60
|
+
const sparkEncrypted = new SparkMD5.ArrayBuffer();
|
|
61
|
+
const spark = new SparkMD5.ArrayBuffer();
|
|
62
|
+
final.set(header);
|
|
63
|
+
sparkEncrypted.append(header.buffer);
|
|
64
|
+
let total = header.byteLength;
|
|
65
|
+
void progress?.({
|
|
66
|
+
percent: total / max,
|
|
67
|
+
total: max,
|
|
68
|
+
current: total,
|
|
69
|
+
});
|
|
70
|
+
let lastPercent = total / max;
|
|
71
|
+
for (const chunk of chunks(data, CHUNK_SIZE)) {
|
|
72
|
+
if (signal?.aborted === true) {
|
|
73
|
+
throw new Error(`Encrypt aborted`);
|
|
74
|
+
}
|
|
75
|
+
spark.append(chunk.buffer);
|
|
76
|
+
const tag = chunk.length < CHUNK_SIZE
|
|
77
|
+
? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
|
|
78
|
+
: sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
|
79
|
+
const encrypted = crypt(tag, chunk);
|
|
80
|
+
sparkEncrypted.append(encrypted.buffer);
|
|
81
|
+
final.set(encrypted, total);
|
|
82
|
+
total += encrypted.byteLength;
|
|
83
|
+
const percent = total / max;
|
|
84
|
+
if (percent > lastPercent + 0.01) {
|
|
85
|
+
void progress?.({
|
|
86
|
+
percent,
|
|
87
|
+
total: max,
|
|
88
|
+
current: total,
|
|
89
|
+
});
|
|
90
|
+
lastPercent = percent;
|
|
112
91
|
}
|
|
113
|
-
void progress?.({
|
|
114
|
-
percent: 1,
|
|
115
|
-
total,
|
|
116
|
-
current: total,
|
|
117
|
-
});
|
|
118
|
-
return {
|
|
119
|
-
data: final,
|
|
120
|
-
md5Encrypted: sparkEncrypted.end(),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
finally {
|
|
124
|
-
destroy();
|
|
125
92
|
}
|
|
93
|
+
destroy();
|
|
94
|
+
void progress?.({
|
|
95
|
+
percent: 1,
|
|
96
|
+
total,
|
|
97
|
+
current: total,
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
data: final.slice(0, total),
|
|
101
|
+
md5Encrypted: sparkEncrypted.end(),
|
|
102
|
+
md5: spark.end(),
|
|
103
|
+
};
|
|
126
104
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Decrypt a secretstream produced by `encryptSecretStream`. Throws if any
|
|
129
|
-
* chunk fails authentication, if data follows the `TAG_FINAL` chunk, or if the
|
|
130
|
-
* stream ends without it (truncation).
|
|
131
|
-
*/
|
|
132
105
|
export async function decryptSecretStream(key, data, progress, signal) {
|
|
133
106
|
await setup();
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
// A valid stream always holds at least one (possibly empty) chunk.
|
|
137
|
-
if (data.byteLength <
|
|
138
|
-
headerBytes + sodium.crypto_secretstream_xchacha20poly1305_ABYTES) {
|
|
139
|
-
throw new Error('Unable to decrypt the data: the data is too short!');
|
|
140
|
-
}
|
|
141
|
-
const header = data.slice(0, headerBytes);
|
|
142
|
-
const body = data.subarray(headerBytes);
|
|
143
|
-
const chunkCount = Math.ceil(body.byteLength / chunkSize);
|
|
144
|
-
const max = body.byteLength -
|
|
145
|
-
chunkCount * sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
|
107
|
+
const header = data.slice(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
|
|
108
|
+
data = data.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
|
|
146
109
|
const { decrypt: decryptFn, destroy } = decrypt(header, key);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
const chunk = body.subarray(i * chunkSize, (i + 1) * chunkSize);
|
|
162
|
-
if (finalTagSeen) {
|
|
163
|
-
throw new Error('Unable to decrypt the data: unexpected data after the final chunk!');
|
|
164
|
-
}
|
|
165
|
-
const messageTag = decryptFn(chunk);
|
|
166
|
-
if (typeof messageTag === 'boolean') {
|
|
167
|
-
throw new Error('Unable to decrypt the data!');
|
|
168
|
-
}
|
|
169
|
-
if (messageTag.tag ===
|
|
170
|
-
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
|
|
171
|
-
finalTagSeen = true;
|
|
172
|
-
}
|
|
173
|
-
else if (messageTag.tag !==
|
|
174
|
-
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE) {
|
|
175
|
-
throw new Error('Unable to decrypt the data: unexpected chunk tag!');
|
|
176
|
-
}
|
|
177
|
-
final.set(messageTag.message, total);
|
|
178
|
-
total += messageTag.message.byteLength;
|
|
179
|
-
const percent = total / max;
|
|
180
|
-
if (percent > lastPercent + 0.01) {
|
|
181
|
-
void progress?.({
|
|
182
|
-
percent,
|
|
183
|
-
total: max,
|
|
184
|
-
current: total,
|
|
185
|
-
});
|
|
186
|
-
lastPercent = percent;
|
|
187
|
-
}
|
|
110
|
+
const chunkSize = CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
|
111
|
+
const max = Math.ceil(data.byteLength / chunkSize) * CHUNK_SIZE;
|
|
112
|
+
void progress?.({
|
|
113
|
+
percent: 0,
|
|
114
|
+
total: max,
|
|
115
|
+
current: 0,
|
|
116
|
+
});
|
|
117
|
+
const final = new Uint8Array(max);
|
|
118
|
+
let total = 0;
|
|
119
|
+
let lastPercent = total / max;
|
|
120
|
+
for (const chunk of chunks(data, chunkSize)) {
|
|
121
|
+
if (signal?.aborted === true) {
|
|
122
|
+
throw new Error(`Decrypt aborted`);
|
|
188
123
|
}
|
|
189
|
-
|
|
190
|
-
|
|
124
|
+
const messageTag = decryptFn(chunk);
|
|
125
|
+
if (typeof messageTag === 'boolean') {
|
|
126
|
+
throw new Error('Unable to decrypt the data!');
|
|
127
|
+
}
|
|
128
|
+
final.set(messageTag.message, total);
|
|
129
|
+
total += messageTag.message.byteLength;
|
|
130
|
+
const percent = total / max;
|
|
131
|
+
if (percent > lastPercent + 0.01) {
|
|
132
|
+
void progress?.({
|
|
133
|
+
percent,
|
|
134
|
+
total: max,
|
|
135
|
+
current: total,
|
|
136
|
+
});
|
|
137
|
+
lastPercent = percent;
|
|
191
138
|
}
|
|
192
|
-
void progress?.({
|
|
193
|
-
percent: 1,
|
|
194
|
-
total,
|
|
195
|
-
current: total,
|
|
196
|
-
});
|
|
197
|
-
return final;
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
destroy();
|
|
201
139
|
}
|
|
140
|
+
destroy();
|
|
141
|
+
void progress?.({
|
|
142
|
+
percent: 1,
|
|
143
|
+
total,
|
|
144
|
+
current: total,
|
|
145
|
+
});
|
|
146
|
+
return final.slice(0, total);
|
|
202
147
|
}
|
|
203
148
|
// async function mainSecretStream(random: Uint8Array): Promise<vo id> {
|
|
204
149
|
// const key = secretstreamKeygen();
|
|
@@ -11,11 +11,12 @@ import { concatenate } from '../utils/array.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export async function encryptDataAndKey({ data, currentIdentity, keyPairs, progress, signal, }) {
|
|
13
13
|
const dataKey = secretStreamKeygen();
|
|
14
|
-
const { data: encryptedData, md5Encrypted } = await encrypt(dataKey, data, progress, signal);
|
|
14
|
+
const { data: encryptedData, md5: md5Data, md5Encrypted, } = await encrypt(dataKey, data, progress, signal);
|
|
15
15
|
if (!currentIdentity || !keyPairs) {
|
|
16
16
|
return {
|
|
17
17
|
encryptedData,
|
|
18
18
|
dataKey,
|
|
19
|
+
md5Data,
|
|
19
20
|
md5Encrypted,
|
|
20
21
|
};
|
|
21
22
|
}
|
|
@@ -28,6 +29,7 @@ export async function encryptDataAndKey({ data, currentIdentity, keyPairs, progr
|
|
|
28
29
|
encryptedDataKey: encDataKey,
|
|
29
30
|
encryptedData,
|
|
30
31
|
dataKey,
|
|
32
|
+
md5Data,
|
|
31
33
|
md5Encrypted,
|
|
32
34
|
};
|
|
33
35
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encryptCryptoBox } from '.';
|
|
2
2
|
import { sodium } from '../sodium';
|
|
3
|
-
import { concatenate } from '../utils/array.js';
|
|
4
3
|
import { encryptSecretStream, secretStreamKeygen } from './data';
|
|
5
4
|
export function generatePassword() {
|
|
6
5
|
return sodium.randombytes_buf(16, 'hex');
|
|
@@ -8,27 +7,6 @@ export function generatePassword() {
|
|
|
8
7
|
export function derivePassword(password, salt) {
|
|
9
8
|
return sodium.crypto_pwhash(sodium.crypto_secretbox_KEYBYTES, password, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_ARGON2ID13, 'hex');
|
|
10
9
|
}
|
|
11
|
-
const PASSWORD_KEY_VERSION = 1;
|
|
12
|
-
/**
|
|
13
|
-
* Wrap a data key under a password, with a random salt:
|
|
14
|
-
* `version (1 byte) || salt || nonce || secretbox(key)`.
|
|
15
|
-
*/
|
|
16
|
-
export function encryptKeyWithPassword(key, password) {
|
|
17
|
-
const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
|
|
18
|
-
return concatenate(new Uint8Array([PASSWORD_KEY_VERSION]), salt, encryptSecretBox(key, derivePassword(password, salt)));
|
|
19
|
-
}
|
|
20
|
-
/** Unwrap a data key produced by `encryptKeyWithPassword`. */
|
|
21
|
-
export function decryptKeyWithPassword(wrapped, password) {
|
|
22
|
-
const boxBytes = sodium.crypto_secretbox_NONCEBYTES +
|
|
23
|
-
sodium.crypto_secretbox_MACBYTES +
|
|
24
|
-
sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES;
|
|
25
|
-
if (wrapped.byteLength === 1 + sodium.crypto_pwhash_SALTBYTES + boxBytes &&
|
|
26
|
-
wrapped[0] === PASSWORD_KEY_VERSION) {
|
|
27
|
-
const salt = wrapped.slice(1, 1 + sodium.crypto_pwhash_SALTBYTES);
|
|
28
|
-
return decryptSecretBox(wrapped.slice(1 + sodium.crypto_pwhash_SALTBYTES), derivePassword(password, salt));
|
|
29
|
-
}
|
|
30
|
-
throw new Error('Unsupported password protected key format');
|
|
31
|
-
}
|
|
32
10
|
export async function encryptName(name, nameKey) {
|
|
33
11
|
const { data } = await encryptSecretStream(sodium.from_hex(nameKey), sodium.from_string(name));
|
|
34
12
|
return sodium.to_hex(data);
|
package/dist/lib/crypto/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { concatenate } from '../utils/array.js';
|
|
2
2
|
import { sodium } from '../sodium.js';
|
|
3
|
+
function dataTooShortError() {
|
|
4
|
+
return {
|
|
5
|
+
name: 'ClientError',
|
|
6
|
+
code: 'DATA_TOO_SHORT',
|
|
7
|
+
message: 'Encrypted payload is shorter than nonce + MAC',
|
|
8
|
+
};
|
|
9
|
+
}
|
|
3
10
|
export function encryptCryptoBox(data, publicKeyBob, privateKeyAlice) {
|
|
4
11
|
const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
|
|
5
12
|
const crypt = sodium.crypto_box_easy(data, nonce, sodium.from_hex(publicKeyBob), sodium.from_hex(privateKeyAlice));
|
|
@@ -10,7 +17,7 @@ export function generateCryptoBoxKeyPair() {
|
|
|
10
17
|
}
|
|
11
18
|
export function decryptCryptoBox(data, publicKeyAlice, privateKeyBob) {
|
|
12
19
|
if (data.length < sodium.crypto_box_NONCEBYTES + sodium.crypto_box_MACBYTES) {
|
|
13
|
-
throw
|
|
20
|
+
throw dataTooShortError();
|
|
14
21
|
}
|
|
15
22
|
const nonce = data.slice(0, sodium.crypto_box_NONCEBYTES);
|
|
16
23
|
const cipher = data.slice(sodium.crypto_box_NONCEBYTES);
|
|
@@ -27,7 +34,7 @@ export function encryptSecretBox(data, key) {
|
|
|
27
34
|
export function decryptSecretBox(data, key) {
|
|
28
35
|
if (data.length <
|
|
29
36
|
sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES) {
|
|
30
|
-
throw
|
|
37
|
+
throw dataTooShortError();
|
|
31
38
|
}
|
|
32
39
|
const nonce = data.slice(0, sodium.crypto_secretbox_NONCEBYTES);
|
|
33
40
|
const cipher = data.slice(sodium.crypto_secretbox_NONCEBYTES);
|
package/dist/lib/error/client.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export const clientErrorCodeKey = z.enum([
|
|
2
|
+
export const clientErrorCodeKey = z.enum([
|
|
3
|
+
'NOT_FOUND',
|
|
4
|
+
'DATA_TOO_SHORT',
|
|
5
|
+
'POPUP_BLOCKED',
|
|
6
|
+
'POPUP_CLOSED',
|
|
7
|
+
'SERVER_ENVIRONMENT',
|
|
8
|
+
]);
|
|
3
9
|
export const clientError = z.object({
|
|
4
10
|
name: z.literal('ClientError'),
|
|
5
11
|
code: clientErrorCodeKey,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
2
|
+
import { initSecrecy, login as secrecyLogin } from '../client/helpers.js';
|
|
3
|
+
const SecrecyContext = createContext(null);
|
|
4
|
+
export function SecrecyProvider(props) {
|
|
5
|
+
const { appId, session, redirect, secrecyUrls, children } = props;
|
|
6
|
+
const [client, setClient] = useState(null);
|
|
7
|
+
const [status, setStatus] = useState('idle');
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const secrecyUrlsKey = JSON.stringify(secrecyUrls ?? null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
setStatus('loading');
|
|
13
|
+
initSecrecy({ appId, session, redirect, secrecyUrls })
|
|
14
|
+
.then((result) => {
|
|
15
|
+
if (cancelled)
|
|
16
|
+
return;
|
|
17
|
+
setClient(result);
|
|
18
|
+
setStatus(result !== null ? 'authenticated' : 'idle');
|
|
19
|
+
})
|
|
20
|
+
.catch((err) => {
|
|
21
|
+
if (cancelled)
|
|
22
|
+
return;
|
|
23
|
+
setError(err);
|
|
24
|
+
setStatus('error');
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
cancelled = true;
|
|
28
|
+
};
|
|
29
|
+
// secrecyUrls is tracked through secrecyUrlsKey, a stable primitive.
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [appId, session, redirect, secrecyUrlsKey]);
|
|
32
|
+
const login = useCallback(async () => {
|
|
33
|
+
setStatus('loading');
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const result = await secrecyLogin({
|
|
37
|
+
appId,
|
|
38
|
+
session,
|
|
39
|
+
secrecyUrls,
|
|
40
|
+
forceLogin: true,
|
|
41
|
+
});
|
|
42
|
+
setClient(result);
|
|
43
|
+
setStatus('authenticated');
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
setError(err);
|
|
47
|
+
setStatus('error');
|
|
48
|
+
}
|
|
49
|
+
// secrecyUrls is tracked through secrecyUrlsKey, a stable primitive.
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [appId, session, secrecyUrlsKey]);
|
|
52
|
+
const logout = useCallback(async () => {
|
|
53
|
+
if (client === null) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await client.logout();
|
|
57
|
+
setClient(null);
|
|
58
|
+
setStatus('idle');
|
|
59
|
+
}, [client]);
|
|
60
|
+
const value = useMemo(() => ({ client, status, error, login, logout }), [client, status, error, login, logout]);
|
|
61
|
+
return createElement(SecrecyContext.Provider, { value }, children);
|
|
62
|
+
}
|
|
63
|
+
export function useSecrecy() {
|
|
64
|
+
const ctx = useContext(SecrecyContext);
|
|
65
|
+
if (ctx === null) {
|
|
66
|
+
throw new Error('useSecrecy() must be used within a <SecrecyProvider>');
|
|
67
|
+
}
|
|
68
|
+
return ctx;
|
|
69
|
+
}
|
package/dist/lib/transformer.js
CHANGED
|
@@ -10,9 +10,10 @@ superjson.registerCustom({
|
|
|
10
10
|
serialize: (v) => v.toJSON(),
|
|
11
11
|
deserialize: (v) => Temporal.PlainDateTime.from(v),
|
|
12
12
|
}, 'Temporal.PlainDateTime');
|
|
13
|
+
const hasBuffer = typeof globalThis.Buffer !== 'undefined';
|
|
13
14
|
superjson.registerCustom({
|
|
14
|
-
isApplicable: (v) => v instanceof Buffer,
|
|
15
|
+
isApplicable: (v) => hasBuffer && v instanceof globalThis.Buffer,
|
|
15
16
|
serialize: (v) => [...v],
|
|
16
|
-
deserialize: (v) => Buffer.from(v),
|
|
17
|
+
deserialize: (v) => globalThis.Buffer.from(v),
|
|
17
18
|
}, 'buffer');
|
|
18
19
|
export const transformer = superjson;
|
|
@@ -10,7 +10,6 @@ const defaultOptions = {
|
|
|
10
10
|
scrollbars: 'no',
|
|
11
11
|
centered: true,
|
|
12
12
|
};
|
|
13
|
-
let popupCount = 1;
|
|
14
13
|
/**
|
|
15
14
|
* Return options converted to a string
|
|
16
15
|
*
|
|
@@ -23,12 +22,11 @@ function optionsToString(options) {
|
|
|
23
22
|
.join(',');
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
26
|
-
* Get
|
|
25
|
+
* Get an unpredictable, unique name on each call
|
|
27
26
|
* @return {String}
|
|
28
27
|
*/
|
|
29
28
|
function defaultPopupName() {
|
|
30
|
-
|
|
31
|
-
return `Popup ${popupCount}`;
|
|
29
|
+
return `secrecy-${crypto.randomUUID()}`;
|
|
32
30
|
}
|
|
33
31
|
/**
|
|
34
32
|
* Convert "centered: true" key into concrete left and top arguments
|
|
@@ -100,30 +98,26 @@ function popupExecute(execute, url, name, options, callback) {
|
|
|
100
98
|
//
|
|
101
99
|
};
|
|
102
100
|
const optionsString = optionsToString(popupOptions);
|
|
103
|
-
// Only the page we opened may answer: the popup name is predictable, so
|
|
104
|
-
// without these checks any window could inject a response (e.g. an
|
|
105
|
-
// attacker's session and keys on login).
|
|
106
|
-
const expectedOrigin = new URL(url, window.location.href).origin;
|
|
107
101
|
const win = execute(url, popupName, optionsString);
|
|
102
|
+
const expectedOrigin = new URL(url, window.location.href).origin;
|
|
108
103
|
let isMessageSent = false;
|
|
109
104
|
let interval;
|
|
110
105
|
function popupCallbackOnce(err, data) {
|
|
111
106
|
if (!isMessageSent) {
|
|
112
107
|
isMessageSent = true;
|
|
113
|
-
window.removeEventListener('message', onMessage);
|
|
114
|
-
clearInterval(interval);
|
|
115
108
|
popupCallback(err, data);
|
|
116
109
|
}
|
|
117
110
|
}
|
|
118
111
|
function onMessage(message) {
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
message.origin !== expectedOrigin) {
|
|
112
|
+
if (message === undefined || message.origin !== expectedOrigin) {
|
|
113
|
+
console.error('Invalid message origin', message?.origin, expectedOrigin);
|
|
122
114
|
return;
|
|
123
115
|
}
|
|
124
116
|
const data = message.data;
|
|
125
|
-
if (data !== undefined && data
|
|
117
|
+
if (data !== undefined && data.id === popupName) {
|
|
126
118
|
popupCallbackOnce(undefined, data.data);
|
|
119
|
+
window.removeEventListener('message', onMessage);
|
|
120
|
+
clearInterval(interval);
|
|
127
121
|
}
|
|
128
122
|
}
|
|
129
123
|
window.addEventListener('message', onMessage, false);
|
|
@@ -132,13 +126,21 @@ function popupExecute(execute, url, name, options, callback) {
|
|
|
132
126
|
if (win === null || win.closed) {
|
|
133
127
|
setTimeout(function delayWindowClosing() {
|
|
134
128
|
clearInterval(interval);
|
|
135
|
-
popupCallbackOnce(
|
|
129
|
+
popupCallbackOnce({
|
|
130
|
+
name: 'ClientError',
|
|
131
|
+
code: 'POPUP_CLOSED',
|
|
132
|
+
message: 'The popup was closed before completing',
|
|
133
|
+
});
|
|
136
134
|
}, 500);
|
|
137
135
|
}
|
|
138
136
|
}, 100);
|
|
139
137
|
}
|
|
140
138
|
else {
|
|
141
|
-
popupCallbackOnce(
|
|
139
|
+
popupCallbackOnce({
|
|
140
|
+
name: 'ClientError',
|
|
141
|
+
code: 'POPUP_BLOCKED',
|
|
142
|
+
message: 'The popup was blocked by the browser',
|
|
143
|
+
});
|
|
142
144
|
}
|
|
143
145
|
return win;
|
|
144
146
|
}
|
|
@@ -179,14 +181,8 @@ export function popupWithPost(url, postData, name, options, callback) {
|
|
|
179
181
|
}
|
|
180
182
|
/**
|
|
181
183
|
* Return html that when executed, will trigger the popup to callback with a response
|
|
182
|
-
*
|
|
183
|
-
* @param {Object}
|
|
184
|
-
* @param {String} targetOrigin origin of the opener allowed to receive `data`
|
|
185
|
-
* @return {String}
|
|
186
184
|
*/
|
|
187
185
|
export function popupResponse(id, data, targetOrigin) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const origin = JSON.stringify(targetOrigin).replaceAll('<', '\\u003c');
|
|
191
|
-
return `<script>window.opener.postMessage(${jsonData}, ${origin});setTimeout(function() { window.close(); }, 50);</script>`;
|
|
186
|
+
const jsonData = JSON.stringify({ id, data });
|
|
187
|
+
return `<script>window.opener.postMessage(${jsonData}, ${JSON.stringify(targetOrigin)});setTimeout(function() { window.close(); }, 50);</script>`;
|
|
192
188
|
}
|
package/dist/lib/versioning.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SECRECY_LIB_VERSION = '1.
|
|
1
|
+
export const SECRECY_LIB_VERSION = '1.86.3';
|
package/dist/lib/worker/md5.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import SparkMD5 from 'spark-md5';
|
|
2
|
+
import { workerMd5Script } from './workerCodes.js';
|
|
2
3
|
function* chunks(arr, n) {
|
|
3
4
|
for (let i = 0; i < arr.length; i += n) {
|
|
4
5
|
yield arr.slice(i, i + n);
|
|
@@ -7,9 +8,8 @@ function* chunks(arr, n) {
|
|
|
7
8
|
const CHUNK_SIZE = 8192;
|
|
8
9
|
export const md5 = process.env.NODE_ENV !== 'test'
|
|
9
10
|
? async (data) => {
|
|
10
|
-
const { workerMd5Script } = await import('./generated/md5.js');
|
|
11
|
-
const worker = new Worker(URL.createObjectURL(new Blob([workerMd5Script], { type: 'text/javascript' })));
|
|
12
11
|
return await new Promise((resolve, reject) => {
|
|
12
|
+
const worker = new Worker(URL.createObjectURL(new Blob([workerMd5Script], { type: 'text/javascript' })));
|
|
13
13
|
worker.addEventListener('error', reject);
|
|
14
14
|
worker.addEventListener('messageerror', reject);
|
|
15
15
|
worker.addEventListener('message', ({ data }) => {
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { decryptSecretStream, encryptSecretStream, } from '../crypto/data.js';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { workerSodiumScript } from './workerCodes.js';
|
|
3
|
+
// const ensureNonDetachedUniqueBuffers = (
|
|
4
|
+
// values: Transferable[]
|
|
5
|
+
// ): Set<Transferable> =>
|
|
6
|
+
// new Set(
|
|
7
|
+
// values.filter(v => {
|
|
8
|
+
// if (v instanceof ArrayBuffer) {
|
|
9
|
+
// return v.byteLength === 0;
|
|
10
|
+
// } else {
|
|
11
|
+
// return true;
|
|
12
|
+
// }
|
|
13
|
+
// })
|
|
14
|
+
// );
|
|
7
15
|
export const encrypt = process.env.NODE_ENV !== 'test'
|
|
8
16
|
? async (key, dataToEncrypt, progress, signal) => {
|
|
9
|
-
const worker = await createSodiumWorker();
|
|
10
17
|
return await new Promise((resolve, reject) => {
|
|
11
18
|
void progress?.({
|
|
12
19
|
current: 0,
|
|
13
20
|
percent: 0,
|
|
14
21
|
total: dataToEncrypt.byteLength,
|
|
15
22
|
});
|
|
23
|
+
const worker = new Worker(URL.createObjectURL(new Blob([workerSodiumScript], { type: 'text/javascript' })));
|
|
16
24
|
worker.addEventListener('error', reject);
|
|
17
25
|
worker.addEventListener('messageerror', reject);
|
|
18
26
|
worker.addEventListener('message', ({ data }) => {
|
|
@@ -38,11 +46,6 @@ export const encrypt = process.env.NODE_ENV !== 'test'
|
|
|
38
46
|
void progress?.(data.data);
|
|
39
47
|
break;
|
|
40
48
|
}
|
|
41
|
-
case 'error': {
|
|
42
|
-
worker.terminate();
|
|
43
|
-
reject(new Error(data.data));
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
49
|
case 'encrypt-result': {
|
|
47
50
|
worker.terminate();
|
|
48
51
|
resolve(data.data);
|
|
@@ -56,13 +59,13 @@ export const encrypt = process.env.NODE_ENV !== 'test'
|
|
|
56
59
|
};
|
|
57
60
|
export const decrypt = process.env.NODE_ENV !== 'test'
|
|
58
61
|
? async (key, dataToDecrypt, progress, signal) => {
|
|
59
|
-
const worker = await createSodiumWorker();
|
|
60
62
|
return await new Promise((resolve, reject) => {
|
|
61
63
|
void progress?.({
|
|
62
64
|
current: 0,
|
|
63
65
|
percent: 0,
|
|
64
66
|
total: dataToDecrypt.byteLength,
|
|
65
67
|
});
|
|
68
|
+
const worker = new Worker(URL.createObjectURL(new Blob([workerSodiumScript], { type: 'text/javascript' })));
|
|
66
69
|
worker.addEventListener('error', reject);
|
|
67
70
|
worker.addEventListener('messageerror', reject);
|
|
68
71
|
worker.addEventListener('message', ({ data }) => {
|
|
@@ -88,11 +91,6 @@ export const decrypt = process.env.NODE_ENV !== 'test'
|
|
|
88
91
|
void progress?.(data.data);
|
|
89
92
|
break;
|
|
90
93
|
}
|
|
91
|
-
case 'error': {
|
|
92
|
-
worker.terminate();
|
|
93
|
-
reject(new Error(data.data));
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
94
|
case 'decrypt-result': {
|
|
97
95
|
worker.terminate();
|
|
98
96
|
resolve(data.data);
|