@secrecy/lib 1.86.2-fix-crypto.1 → 1.86.2-fix-md5.1
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/client/SecrecyCloudClient.js +5 -6
- package/dist/lib/client/data-link.js +6 -11
- package/dist/lib/client/upload.js +9 -9
- package/dist/lib/crypto/data.js +4 -7
- package/dist/lib/crypto/domain.js +1 -3
- package/dist/lib/crypto/helpers.js +2 -10
- package/dist/lib/worker/generated/sodium.js +1 -1
- package/dist/lib/worker/sodium.js +3 -4
- package/dist/types/client/data-link.d.ts +4 -4
- package/dist/types/client/types/data.d.ts +2 -1
- package/dist/types/client.d.ts +60 -62
- package/dist/types/crypto/data.d.ts +3 -13
- package/dist/types/crypto/domain.d.ts +0 -1
- package/dist/types/crypto/helpers.d.ts +2 -6
- package/dist/types/worker/generated/sodium.d.ts +1 -1
- package/dist/types/worker/sodium.d.ts +2 -2
- package/package.json +2 -2
|
@@ -700,13 +700,12 @@ export class SecrecyCloudClient {
|
|
|
700
700
|
: this.#client.currentIdentity.identityPubKey, this.#client.currentIdentityPrivateKey)
|
|
701
701
|
: null;
|
|
702
702
|
const src = key
|
|
703
|
-
?
|
|
704
|
-
await decrypt(key, encryptedContent, progressDecrypt, signal, {
|
|
705
|
-
allowMissingFinalTag: true,
|
|
706
|
-
})
|
|
703
|
+
? await decrypt(key, encryptedContent, progressDecrypt, signal)
|
|
707
704
|
: encryptedContent;
|
|
708
|
-
|
|
709
|
-
if (
|
|
705
|
+
// Encrypted data is authenticated by the stream: only public data has an md5.
|
|
706
|
+
if (!key &&
|
|
707
|
+
dataContent.md5 !== null &&
|
|
708
|
+
(await md5(src)) !== dataContent.md5) {
|
|
710
709
|
throw new Error(`Content does not match`);
|
|
711
710
|
}
|
|
712
711
|
const decompressed = decompress(src);
|
|
@@ -3,11 +3,11 @@ import { buildBytesFromDataLink } from '../crypto/domain';
|
|
|
3
3
|
import { sodium } from '../sodium';
|
|
4
4
|
import { decryptKeyWithPassword } from '../crypto/helpers';
|
|
5
5
|
import { decrypt } from '../worker/sodium';
|
|
6
|
-
import { md5 } from '../worker/md5';
|
|
7
6
|
import { decompress } from '../minify';
|
|
8
7
|
const downloadDataLinkS3Schema = z.object({
|
|
9
8
|
name: z.string(),
|
|
10
|
-
|
|
9
|
+
// Only for unencrypted and legacy encrypted data.
|
|
10
|
+
md5: z.string().nullable(),
|
|
11
11
|
md5Encrypted: z.string().nullable(),
|
|
12
12
|
size: z.coerce.bigint().gte(0n),
|
|
13
13
|
parts: z.array(z.object({ order: z.number(), md5: z.string(), contentUrl: z.string() })),
|
|
@@ -16,7 +16,8 @@ const downloadDataLinkS3Schema = z.object({
|
|
|
16
16
|
});
|
|
17
17
|
const downloadDataLinkLiteSchema = z.object({
|
|
18
18
|
name: z.string(),
|
|
19
|
-
|
|
19
|
+
// Only for unencrypted and legacy encrypted data.
|
|
20
|
+
md5: z.string().nullable(),
|
|
20
21
|
md5Encrypted: z.string().nullable(),
|
|
21
22
|
size: z.coerce.bigint().gte(0n),
|
|
22
23
|
bytes: z.string(),
|
|
@@ -68,14 +69,8 @@ export async function downloadDataFromLink(opts) {
|
|
|
68
69
|
throw new Error('Unable to read encrypted data without password!');
|
|
69
70
|
}
|
|
70
71
|
try {
|
|
71
|
-
const decryptedSharingDataKey = decryptKeyWithPassword(sodium.from_hex(opts.crypto.key), opts.crypto.password
|
|
72
|
-
const decryptedBytes = await decrypt(decryptedSharingDataKey, bytes, opts.decryptProgress, opts.signal
|
|
73
|
-
// Legacy streams may lack the final tag: the md5 check below covers them.
|
|
74
|
-
{ allowMissingFinalTag: true });
|
|
75
|
-
const md5Content = await md5(decryptedBytes);
|
|
76
|
-
if (md5Content !== dataLink.md5) {
|
|
77
|
-
throw new Error(`Content does not match`);
|
|
78
|
-
}
|
|
72
|
+
const decryptedSharingDataKey = decryptKeyWithPassword(sodium.from_hex(opts.crypto.key), opts.crypto.password);
|
|
73
|
+
const decryptedBytes = await decrypt(decryptedSharingDataKey, bytes, opts.decryptProgress, opts.signal);
|
|
79
74
|
return decompress(decryptedBytes);
|
|
80
75
|
}
|
|
81
76
|
catch (err) {
|
|
@@ -40,7 +40,10 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
40
40
|
throw new Error('Cold storage is only for logged users!');
|
|
41
41
|
}
|
|
42
42
|
const compressed = encrypted ? compress(dataBuffer) : dataBuffer;
|
|
43
|
-
|
|
43
|
+
// Only unencrypted (public) data sends the md5 of its content, computed in
|
|
44
|
+
// the requests below: for encrypted data it would let the server
|
|
45
|
+
// fingerprint the plaintext.
|
|
46
|
+
let { encryptedData, encryptedDataKey, dataKey, md5Encrypted } = encrypted
|
|
44
47
|
? await encryptDataAndKey({
|
|
45
48
|
data: compressed,
|
|
46
49
|
progress: encryptProgress,
|
|
@@ -52,7 +55,6 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
52
55
|
encryptedData: compressed,
|
|
53
56
|
encryptedDataKey: null,
|
|
54
57
|
dataKey: null,
|
|
55
|
-
md5Data: await md5(compressed),
|
|
56
58
|
md5Encrypted: null,
|
|
57
59
|
};
|
|
58
60
|
await uploadProgress?.({
|
|
@@ -100,14 +102,13 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
100
102
|
size: BigInt(dataBuffer.byteLength),
|
|
101
103
|
key: encryptedDataKey ? sodium.to_hex(encryptedDataKey) : undefined,
|
|
102
104
|
md5Encrypted,
|
|
103
|
-
md5: md5Data,
|
|
104
105
|
fromIdentityPubKey: currentIdentity?.identityPubKey,
|
|
105
106
|
...filetype,
|
|
106
107
|
}
|
|
107
108
|
: {
|
|
108
109
|
type: 'unencrypted',
|
|
109
110
|
content: Buffer.from(encryptedData),
|
|
110
|
-
md5:
|
|
111
|
+
md5: await md5(compressed),
|
|
111
112
|
size: BigInt(dataBuffer.byteLength),
|
|
112
113
|
fromIdentityPubKey: currentIdentity?.identityPubKey,
|
|
113
114
|
...filetype,
|
|
@@ -122,7 +123,7 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
122
123
|
const localData = {
|
|
123
124
|
id: uploadData.id,
|
|
124
125
|
storageType: 'lite',
|
|
125
|
-
md5: uploadDataArgs.md5,
|
|
126
|
+
md5: uploadDataArgs.type === 'unencrypted' ? uploadDataArgs.md5 : null,
|
|
126
127
|
size: uploadDataArgs.size,
|
|
127
128
|
sizeEncrypted: uploadDataArgs.type === 'encrypted'
|
|
128
129
|
? uploadDataArgs.sizeEncrypted
|
|
@@ -144,13 +145,12 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
144
145
|
size: BigInt(dataBuffer.byteLength),
|
|
145
146
|
key: encryptedDataKey ? sodium.to_hex(encryptedDataKey) : undefined,
|
|
146
147
|
md5Encrypted,
|
|
147
|
-
md5: md5Data,
|
|
148
148
|
fromIdentityPubKey: currentIdentity?.identityPubKey,
|
|
149
149
|
...filetype,
|
|
150
150
|
}
|
|
151
151
|
: {
|
|
152
152
|
type: 'unencrypted',
|
|
153
|
-
md5:
|
|
153
|
+
md5: await md5(compressed),
|
|
154
154
|
size: BigInt(dataBuffer.byteLength),
|
|
155
155
|
fromIdentityPubKey: currentIdentity?.identityPubKey,
|
|
156
156
|
...filetype,
|
|
@@ -171,7 +171,7 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
171
171
|
const localData = {
|
|
172
172
|
id: uploadData.id,
|
|
173
173
|
storageType: storageType,
|
|
174
|
-
md5: uploadDataArgs.md5,
|
|
174
|
+
md5: uploadDataArgs.type === 'unencrypted' ? uploadDataArgs.md5 : null,
|
|
175
175
|
size: uploadDataArgs.size,
|
|
176
176
|
sizeEncrypted: uploadDataArgs.type === 'encrypted'
|
|
177
177
|
? uploadDataArgs.sizeEncrypted
|
|
@@ -255,7 +255,7 @@ export async function uploadData({ storageType, data, password, forcePassword =
|
|
|
255
255
|
const localData = {
|
|
256
256
|
id: uploadData.id,
|
|
257
257
|
storageType: storageType,
|
|
258
|
-
md5: uploadDataArgs.md5,
|
|
258
|
+
md5: uploadDataArgs.type === 'unencrypted' ? uploadDataArgs.md5 : null,
|
|
259
259
|
size: uploadDataArgs.size,
|
|
260
260
|
sizeEncrypted: uploadDataArgs.type === 'encrypted'
|
|
261
261
|
? uploadDataArgs.sizeEncrypted
|
package/dist/lib/crypto/data.js
CHANGED
|
@@ -79,7 +79,6 @@ export async function encryptSecretStream(key, data, progress, signal) {
|
|
|
79
79
|
});
|
|
80
80
|
const final = new Uint8Array(max);
|
|
81
81
|
const sparkEncrypted = new SparkMD5.ArrayBuffer();
|
|
82
|
-
const spark = new SparkMD5.ArrayBuffer();
|
|
83
82
|
final.set(header);
|
|
84
83
|
sparkEncrypted.append(header.buffer);
|
|
85
84
|
let total = header.byteLength;
|
|
@@ -93,8 +92,7 @@ export async function encryptSecretStream(key, data, progress, signal) {
|
|
|
93
92
|
if (signal?.aborted === true) {
|
|
94
93
|
throw new Error(`Encrypt aborted`);
|
|
95
94
|
}
|
|
96
|
-
const chunk = data.
|
|
97
|
-
spark.append(chunk.buffer);
|
|
95
|
+
const chunk = data.subarray(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
|
|
98
96
|
const tag = i === chunkCount - 1
|
|
99
97
|
? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
|
|
100
98
|
: sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
|
@@ -120,7 +118,6 @@ export async function encryptSecretStream(key, data, progress, signal) {
|
|
|
120
118
|
return {
|
|
121
119
|
data: final,
|
|
122
120
|
md5Encrypted: sparkEncrypted.end(),
|
|
123
|
-
md5: spark.end(),
|
|
124
121
|
};
|
|
125
122
|
}
|
|
126
123
|
finally {
|
|
@@ -130,9 +127,9 @@ export async function encryptSecretStream(key, data, progress, signal) {
|
|
|
130
127
|
/**
|
|
131
128
|
* Decrypt a secretstream produced by `encryptSecretStream`. Throws if any
|
|
132
129
|
* chunk fails authentication, if data follows the `TAG_FINAL` chunk, or if the
|
|
133
|
-
* stream ends without it (truncation)
|
|
130
|
+
* stream ends without it (truncation).
|
|
134
131
|
*/
|
|
135
|
-
export async function decryptSecretStream(key, data, progress, signal
|
|
132
|
+
export async function decryptSecretStream(key, data, progress, signal) {
|
|
136
133
|
await setup();
|
|
137
134
|
const headerBytes = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;
|
|
138
135
|
const chunkSize = CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
|
@@ -189,7 +186,7 @@ export async function decryptSecretStream(key, data, progress, signal, options)
|
|
|
189
186
|
lastPercent = percent;
|
|
190
187
|
}
|
|
191
188
|
}
|
|
192
|
-
if (!finalTagSeen
|
|
189
|
+
if (!finalTagSeen) {
|
|
193
190
|
throw new Error('Unable to decrypt the data: the data is truncated!');
|
|
194
191
|
}
|
|
195
192
|
void progress?.({
|
|
@@ -11,12 +11,11 @@ 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,
|
|
14
|
+
const { data: encryptedData, md5Encrypted } = await encrypt(dataKey, data, progress, signal);
|
|
15
15
|
if (!currentIdentity || !keyPairs) {
|
|
16
16
|
return {
|
|
17
17
|
encryptedData,
|
|
18
18
|
dataKey,
|
|
19
|
-
md5Data,
|
|
20
19
|
md5Encrypted,
|
|
21
20
|
};
|
|
22
21
|
}
|
|
@@ -29,7 +28,6 @@ export async function encryptDataAndKey({ data, currentIdentity, keyPairs, progr
|
|
|
29
28
|
encryptedDataKey: encDataKey,
|
|
30
29
|
encryptedData,
|
|
31
30
|
dataKey,
|
|
32
|
-
md5Data,
|
|
33
31
|
md5Encrypted,
|
|
34
32
|
};
|
|
35
33
|
}
|
|
@@ -17,19 +17,11 @@ export function encryptKeyWithPassword(key, password) {
|
|
|
17
17
|
const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
|
|
18
18
|
return concatenate(new Uint8Array([PASSWORD_KEY_VERSION]), salt, encryptSecretBox(key, derivePassword(password, salt)));
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
* legacy format (`nonce || secretbox(key)`) whose salt was derived from the
|
|
23
|
-
* password and the plaintext md5, hence `legacyMd5`.
|
|
24
|
-
*/
|
|
25
|
-
export function decryptKeyWithPassword(wrapped, password, legacyMd5) {
|
|
20
|
+
/** Unwrap a data key produced by `encryptKeyWithPassword`. */
|
|
21
|
+
export function decryptKeyWithPassword(wrapped, password) {
|
|
26
22
|
const boxBytes = sodium.crypto_secretbox_NONCEBYTES +
|
|
27
23
|
sodium.crypto_secretbox_MACBYTES +
|
|
28
24
|
sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES;
|
|
29
|
-
if (wrapped.byteLength === boxBytes) {
|
|
30
|
-
const salt = sodium.crypto_generichash(sodium.crypto_pwhash_SALTBYTES, password + legacyMd5);
|
|
31
|
-
return decryptSecretBox(wrapped, derivePassword(password, salt));
|
|
32
|
-
}
|
|
33
25
|
if (wrapped.byteLength === 1 + sodium.crypto_pwhash_SALTBYTES + boxBytes &&
|
|
34
26
|
wrapped[0] === PASSWORD_KEY_VERSION) {
|
|
35
27
|
const salt = wrapped.slice(1, 1 + sodium.crypto_pwhash_SALTBYTES);
|