@protontech/drive-sdk 0.11.0 → 0.12.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/crypto/driveCrypto.d.ts +16 -1
- package/dist/crypto/driveCrypto.js +55 -19
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/driveCrypto.test.js +1 -1
- package/dist/crypto/driveCrypto.test.js.map +1 -1
- package/dist/crypto/interface.d.ts +22 -8
- package/dist/crypto/openPGPCrypto.d.ts +30 -7
- package/dist/crypto/openPGPCrypto.js +46 -8
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/interface/featureFlags.d.ts +4 -1
- package/dist/interface/featureFlags.js +5 -0
- package/dist/interface/featureFlags.js.map +1 -1
- package/dist/interface/index.d.ts +1 -0
- package/dist/interface/index.js +3 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/internal/photos/index.d.ts +2 -2
- package/dist/internal/photos/index.js +2 -2
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -2
- package/dist/internal/photos/upload.js +2 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.js +11 -4
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +4 -2
- package/dist/internal/upload/cryptoService.js +14 -2
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/index.d.ts +2 -2
- package/dist/internal/upload/index.js +2 -2
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/protonDriveClient.js +1 -1
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +1 -1
- package/dist/protonDrivePhotosClient.js +6 -2
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +3 -2
- package/dist/protonDrivePublicLinkClient.js +6 -2
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.test.ts +1 -0
- package/src/crypto/driveCrypto.ts +51 -10
- package/src/crypto/interface.ts +21 -8
- package/src/crypto/openPGPCrypto.ts +68 -8
- package/src/interface/featureFlags.ts +5 -1
- package/src/interface/index.ts +1 -0
- package/src/internal/photos/index.ts +3 -1
- package/src/internal/photos/upload.ts +14 -6
- package/src/internal/sharingPublic/nodes.ts +11 -4
- package/src/internal/upload/cryptoService.ts +24 -2
- package/src/internal/upload/index.ts +3 -2
- package/src/protonDriveClient.ts +1 -0
- package/src/protonDrivePhotosClient.ts +14 -9
- package/src/protonDrivePublicLinkClient.ts +8 -0
package/package.json
CHANGED
|
@@ -27,6 +27,19 @@ enum SIGNING_CONTEXTS {
|
|
|
27
27
|
* but we do share same key generation across shares and nodes modules,
|
|
28
28
|
* for example, which we can generelise here and in each module just
|
|
29
29
|
* call with specific arguments.
|
|
30
|
+
*
|
|
31
|
+
* Note about AEAD encryption:
|
|
32
|
+
*
|
|
33
|
+
* The algorithm of generated session key or encrypted data is defined by
|
|
34
|
+
* the encryption key preferences. If encryption key was generated with
|
|
35
|
+
* `aeadProtect` set to true, session key or encrypted data should use
|
|
36
|
+
* AEAD algorithm.
|
|
37
|
+
*
|
|
38
|
+
* However, in Drive, we do not want to use the AEAD algorithm everywhere,
|
|
39
|
+
* only for file content. Thus, we must pass the `enableAeadWithEncryptionKeys`
|
|
40
|
+
* flag explicitely to control whether to use the encryption key preferences
|
|
41
|
+
* to avoid using AEAD on places where it would not be supported. It should
|
|
42
|
+
* be set to false by default everywhere except for content encryption.
|
|
30
43
|
*/
|
|
31
44
|
export class DriveCrypto {
|
|
32
45
|
constructor(
|
|
@@ -55,6 +68,7 @@ export class DriveCrypto {
|
|
|
55
68
|
async generateKey(
|
|
56
69
|
encryptionKeys: PrivateKey[],
|
|
57
70
|
signingKey: PrivateKey,
|
|
71
|
+
{ enableAead }: { enableAead: boolean } = { enableAead: false },
|
|
58
72
|
): Promise<{
|
|
59
73
|
encrypted: {
|
|
60
74
|
armoredKey: string;
|
|
@@ -69,8 +83,9 @@ export class DriveCrypto {
|
|
|
69
83
|
}> {
|
|
70
84
|
const passphrase = this.openPGPCrypto.generatePassphrase();
|
|
71
85
|
const [{ privateKey, armoredKey }, passphraseSessionKey] = await Promise.all([
|
|
72
|
-
this.openPGPCrypto.generateKey(passphrase),
|
|
73
|
-
|
|
86
|
+
this.openPGPCrypto.generateKey(passphrase, { enableAead }),
|
|
87
|
+
// See note in the interface documentation about AEAD encryption.
|
|
88
|
+
this.openPGPCrypto.generateSessionKey(encryptionKeys, { enableAeadWithEncryptionKeys: false }),
|
|
74
89
|
]);
|
|
75
90
|
|
|
76
91
|
const { armoredPassphrase, armoredPassphraseSignature } = await this.encryptPassphrase(
|
|
@@ -109,7 +124,10 @@ export class DriveCrypto {
|
|
|
109
124
|
contentKeyPacketSessionKey: SessionKey;
|
|
110
125
|
};
|
|
111
126
|
}> {
|
|
112
|
-
|
|
127
|
+
// See note in the interface documentation about AEAD encryption.
|
|
128
|
+
const contentKeyPacketSessionKey = await this.openPGPCrypto.generateSessionKey([encryptionKey], {
|
|
129
|
+
enableAeadWithEncryptionKeys: true,
|
|
130
|
+
});
|
|
113
131
|
const { signature: armoredContentKeyPacketSignature } = await this.openPGPCrypto.signArmored(
|
|
114
132
|
contentKeyPacketSessionKey.data,
|
|
115
133
|
[encryptionKey],
|
|
@@ -149,6 +167,8 @@ export class DriveCrypto {
|
|
|
149
167
|
sessionKey,
|
|
150
168
|
encryptionKeys,
|
|
151
169
|
signingKey,
|
|
170
|
+
// See note in the interface documentation about AEAD encryption.
|
|
171
|
+
{ enableAeadWithEncryptionKeys: false },
|
|
152
172
|
);
|
|
153
173
|
|
|
154
174
|
return {
|
|
@@ -242,7 +262,13 @@ export class DriveCrypto {
|
|
|
242
262
|
srp: SRPVerifier;
|
|
243
263
|
}> {
|
|
244
264
|
const [{ armoredData: armoredPassword }, { keyPacket }, srp] = await Promise.all([
|
|
245
|
-
this.openPGPCrypto.encryptArmored(
|
|
265
|
+
this.openPGPCrypto.encryptArmored(
|
|
266
|
+
new TextEncoder().encode(password),
|
|
267
|
+
[addressKey],
|
|
268
|
+
undefined,
|
|
269
|
+
// See note in the interface documentation about AEAD encryption.
|
|
270
|
+
{ enableAeadWithEncryptionKeys: false },
|
|
271
|
+
),
|
|
246
272
|
this.openPGPCrypto.encryptSessionKeyWithPassword(sharePassphraseSessionKey, bcryptPassphrase),
|
|
247
273
|
this.srpModule.getSrpVerifier(password),
|
|
248
274
|
]);
|
|
@@ -356,6 +382,8 @@ export class DriveCrypto {
|
|
|
356
382
|
signature,
|
|
357
383
|
[encryptionKey],
|
|
358
384
|
sessionKey,
|
|
385
|
+
// See note in the interface documentation about AEAD encryption.
|
|
386
|
+
{ enableAeadWithEncryptionKeys: false },
|
|
359
387
|
);
|
|
360
388
|
return {
|
|
361
389
|
armoredSignature,
|
|
@@ -381,6 +409,8 @@ export class DriveCrypto {
|
|
|
381
409
|
undefined,
|
|
382
410
|
[encryptionAndSigningKey],
|
|
383
411
|
encryptionAndSigningKey,
|
|
412
|
+
// See note in the interface documentation about AEAD encryption.
|
|
413
|
+
{ enableAeadWithEncryptionKeys: false },
|
|
384
414
|
);
|
|
385
415
|
return {
|
|
386
416
|
armoredHashKey,
|
|
@@ -420,6 +450,8 @@ export class DriveCrypto {
|
|
|
420
450
|
sessionKey,
|
|
421
451
|
encryptionKey ? [encryptionKey] : [],
|
|
422
452
|
signingKey,
|
|
453
|
+
// See note in the interface documentation about AEAD encryption.
|
|
454
|
+
{ enableAeadWithEncryptionKeys: false },
|
|
423
455
|
);
|
|
424
456
|
return {
|
|
425
457
|
armoredNodeName,
|
|
@@ -502,7 +534,8 @@ export class DriveCrypto {
|
|
|
502
534
|
undefined,
|
|
503
535
|
[encryptionKey],
|
|
504
536
|
signingKey,
|
|
505
|
-
|
|
537
|
+
// See note in the interface documentation about AEAD encryption.
|
|
538
|
+
{ compress: true, enableAeadWithEncryptionKeys: false },
|
|
506
539
|
);
|
|
507
540
|
return {
|
|
508
541
|
armoredExtendedAttributes,
|
|
@@ -626,8 +659,10 @@ export class DriveCrypto {
|
|
|
626
659
|
sessionKey,
|
|
627
660
|
[], // Thumbnails use the session key so we do not send encryption key.
|
|
628
661
|
signingKey,
|
|
662
|
+
// See note in the interface documentation about AEAD encryption.
|
|
663
|
+
{ enableAeadWithEncryptionKeys: true },
|
|
629
664
|
);
|
|
630
|
-
this.recordPerformance('content_encryption', thumbnailData.length, start);
|
|
665
|
+
this.recordPerformance('content_encryption', sessionKey, thumbnailData.length, start);
|
|
631
666
|
|
|
632
667
|
return {
|
|
633
668
|
encryptedData,
|
|
@@ -649,7 +684,7 @@ export class DriveCrypto {
|
|
|
649
684
|
verified,
|
|
650
685
|
verificationErrors,
|
|
651
686
|
} = await this.openPGPCrypto.decryptAndVerify(encryptedThumbnail, sessionKey, verificationKeys);
|
|
652
|
-
this.recordPerformance('content_decryption', decryptedThumbnail.length, start);
|
|
687
|
+
this.recordPerformance('content_decryption', sessionKey, decryptedThumbnail.length, start);
|
|
653
688
|
return {
|
|
654
689
|
decryptedThumbnail,
|
|
655
690
|
verified,
|
|
@@ -672,8 +707,10 @@ export class DriveCrypto {
|
|
|
672
707
|
sessionKey,
|
|
673
708
|
[], // Blocks use the session key so we do not send encryption key.
|
|
674
709
|
signingKey,
|
|
710
|
+
// See note in the interface documentation about AEAD encryption.
|
|
711
|
+
{ enableAeadWithEncryptionKeys: true },
|
|
675
712
|
);
|
|
676
|
-
this.recordPerformance('content_encryption', blockData.length, start);
|
|
713
|
+
this.recordPerformance('content_encryption', sessionKey, blockData.length, start);
|
|
677
714
|
|
|
678
715
|
const { armoredSignature } = await this.encryptSignature(signature, encryptionKey, sessionKey);
|
|
679
716
|
|
|
@@ -689,7 +726,7 @@ export class DriveCrypto {
|
|
|
689
726
|
): Promise<Uint8Array<ArrayBuffer>> {
|
|
690
727
|
const start = performance.now();
|
|
691
728
|
const { data: decryptedBlock } = await this.openPGPCrypto.decryptAndVerify(encryptedBlock, sessionKey, []);
|
|
692
|
-
this.recordPerformance('content_decryption', decryptedBlock.length, start);
|
|
729
|
+
this.recordPerformance('content_decryption', sessionKey, decryptedBlock.length, start);
|
|
693
730
|
|
|
694
731
|
return decryptedBlock;
|
|
695
732
|
}
|
|
@@ -740,21 +777,25 @@ export class DriveCrypto {
|
|
|
740
777
|
undefined,
|
|
741
778
|
[encryptionKey],
|
|
742
779
|
signingKey,
|
|
780
|
+
// See note in the interface documentation about AEAD encryption.
|
|
781
|
+
{ enableAeadWithEncryptionKeys: false },
|
|
743
782
|
);
|
|
744
783
|
return armoredData;
|
|
745
784
|
}
|
|
746
785
|
|
|
747
786
|
private recordPerformance(
|
|
748
787
|
type: 'content_encryption' | 'content_decryption',
|
|
788
|
+
sessionKey: SessionKey,
|
|
749
789
|
bytesProcessed: number,
|
|
750
790
|
start: number,
|
|
751
791
|
) {
|
|
752
792
|
const end = performance.now();
|
|
753
793
|
const duration = end - start;
|
|
794
|
+
const cryptoModel = sessionKey.aeadAlgorithm ? 'v1.5' : 'v1';
|
|
754
795
|
this.telemetry.recordMetric({
|
|
755
796
|
eventName: 'performance',
|
|
756
797
|
type,
|
|
757
|
-
cryptoModel
|
|
798
|
+
cryptoModel,
|
|
758
799
|
bytesProcessed,
|
|
759
800
|
milliseconds: Math.round(duration),
|
|
760
801
|
});
|
package/src/crypto/interface.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export interface PublicKey {
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
4
|
readonly _idx: any;
|
|
5
|
-
|
|
5
|
+
readonly _keyContentHash: [string, string];
|
|
6
6
|
|
|
7
7
|
getVersion(): number;
|
|
8
8
|
getFingerprint(): string;
|
|
@@ -39,8 +39,8 @@ export interface PrivateKey extends PublicKey {
|
|
|
39
39
|
|
|
40
40
|
export interface SessionKey {
|
|
41
41
|
data: Uint8Array<ArrayBuffer>;
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
algorithm: string | null;
|
|
43
|
+
aeadAlgorithm: string | null;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export enum VERIFICATION_STATUS {
|
|
@@ -91,7 +91,10 @@ export interface OpenPGPCrypto {
|
|
|
91
91
|
*/
|
|
92
92
|
generatePassphrase: () => string;
|
|
93
93
|
|
|
94
|
-
generateSessionKey: (
|
|
94
|
+
generateSessionKey: (
|
|
95
|
+
encryptionKeys: PublicKey[],
|
|
96
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
97
|
+
) => Promise<SessionKey>;
|
|
95
98
|
|
|
96
99
|
encryptSessionKey: (
|
|
97
100
|
sessionKey: SessionKey,
|
|
@@ -112,7 +115,10 @@ export interface OpenPGPCrypto {
|
|
|
112
115
|
*
|
|
113
116
|
* The key pair is generated using the Curve25519 algorithm.
|
|
114
117
|
*/
|
|
115
|
-
generateKey: (
|
|
118
|
+
generateKey: (
|
|
119
|
+
passphrase: string,
|
|
120
|
+
options: { enableAead: boolean },
|
|
121
|
+
) => Promise<{
|
|
116
122
|
privateKey: PrivateKey;
|
|
117
123
|
armoredKey: string;
|
|
118
124
|
}>;
|
|
@@ -120,7 +126,8 @@ export interface OpenPGPCrypto {
|
|
|
120
126
|
encryptArmored: (
|
|
121
127
|
data: Uint8Array<ArrayBuffer>,
|
|
122
128
|
encryptionKeys: PublicKey[],
|
|
123
|
-
sessionKey
|
|
129
|
+
sessionKey: SessionKey | undefined,
|
|
130
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
124
131
|
) => Promise<{
|
|
125
132
|
armoredData: string;
|
|
126
133
|
}>;
|
|
@@ -130,6 +137,7 @@ export interface OpenPGPCrypto {
|
|
|
130
137
|
sessionKey: SessionKey,
|
|
131
138
|
encryptionKeys: PublicKey[],
|
|
132
139
|
signingKey: PrivateKey,
|
|
140
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
133
141
|
) => Promise<{
|
|
134
142
|
encryptedData: Uint8Array<ArrayBuffer>;
|
|
135
143
|
}>;
|
|
@@ -139,7 +147,7 @@ export interface OpenPGPCrypto {
|
|
|
139
147
|
sessionKey: SessionKey | undefined,
|
|
140
148
|
encryptionKeys: PublicKey[],
|
|
141
149
|
signingKey: PrivateKey,
|
|
142
|
-
options
|
|
150
|
+
options: { compress?: boolean; enableAeadWithEncryptionKeys: boolean },
|
|
143
151
|
) => Promise<{
|
|
144
152
|
armoredData: string;
|
|
145
153
|
}>;
|
|
@@ -149,6 +157,7 @@ export interface OpenPGPCrypto {
|
|
|
149
157
|
sessionKey: SessionKey,
|
|
150
158
|
encryptionKeys: PublicKey[],
|
|
151
159
|
signingKey: PrivateKey,
|
|
160
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
152
161
|
) => Promise<{
|
|
153
162
|
encryptedData: Uint8Array<ArrayBuffer>;
|
|
154
163
|
signature: Uint8Array<ArrayBuffer>;
|
|
@@ -159,6 +168,7 @@ export interface OpenPGPCrypto {
|
|
|
159
168
|
sessionKey: SessionKey,
|
|
160
169
|
encryptionKeys: PublicKey[],
|
|
161
170
|
signingKey: PrivateKey,
|
|
171
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
162
172
|
) => Promise<{
|
|
163
173
|
armoredData: string;
|
|
164
174
|
armoredSignature: string;
|
|
@@ -198,7 +208,10 @@ export interface OpenPGPCrypto {
|
|
|
198
208
|
verificationErrors?: Error[];
|
|
199
209
|
}>;
|
|
200
210
|
|
|
201
|
-
decryptSessionKey: (
|
|
211
|
+
decryptSessionKey: (
|
|
212
|
+
data: Uint8Array<ArrayBuffer>,
|
|
213
|
+
decryptionKeys: PrivateKey | PrivateKey[],
|
|
214
|
+
) => Promise<SessionKey>;
|
|
202
215
|
|
|
203
216
|
decryptArmoredSessionKey: (armoredData: string, decryptionKeys: PrivateKey | PrivateKey[]) => Promise<SessionKey>;
|
|
204
217
|
|
|
@@ -8,10 +8,18 @@ import { uint8ArrayToBase64String } from './utils';
|
|
|
8
8
|
* clients/packages/crypto/lib/proxy/proxy.ts.
|
|
9
9
|
*/
|
|
10
10
|
export interface OpenPGPCryptoProxy {
|
|
11
|
-
generateKey: (options: {
|
|
11
|
+
generateKey: (options: {
|
|
12
|
+
userIDs: { name: string }[];
|
|
13
|
+
type: 'ecc';
|
|
14
|
+
curve: 'ed25519Legacy';
|
|
15
|
+
config?: { aeadProtect: boolean };
|
|
16
|
+
}) => Promise<PrivateKey>;
|
|
12
17
|
exportPrivateKey: (options: { privateKey: PrivateKey; passphrase: string | null }) => Promise<string>;
|
|
13
18
|
importPrivateKey: (options: { armoredKey: string; passphrase: string | null }) => Promise<PrivateKey>;
|
|
14
|
-
generateSessionKey: (options: {
|
|
19
|
+
generateSessionKey: (options: {
|
|
20
|
+
recipientKeys: PublicKey[];
|
|
21
|
+
config?: { ignoreSEIPDv2FeatureFlag: boolean };
|
|
22
|
+
}) => Promise<SessionKey>;
|
|
15
23
|
encryptSessionKey: (
|
|
16
24
|
options: SessionKey & {
|
|
17
25
|
format: 'binary';
|
|
@@ -32,6 +40,7 @@ export interface OpenPGPCryptoProxy {
|
|
|
32
40
|
signingKeys?: PrivateKey;
|
|
33
41
|
detached?: Detached;
|
|
34
42
|
compress?: boolean;
|
|
43
|
+
config?: { ignoreSEIPDv2FeatureFlag: boolean };
|
|
35
44
|
}) => Promise<
|
|
36
45
|
Detached extends true
|
|
37
46
|
? {
|
|
@@ -93,8 +102,15 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
93
102
|
return uint8ArrayToBase64String(value);
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
async generateSessionKey(encryptionKeys: PublicKey[]) {
|
|
97
|
-
return this.cryptoProxy.generateSessionKey({
|
|
105
|
+
async generateSessionKey(encryptionKeys: PublicKey[], options: { enableAeadWithEncryptionKeys: boolean }) {
|
|
106
|
+
return this.cryptoProxy.generateSessionKey({
|
|
107
|
+
recipientKeys: encryptionKeys,
|
|
108
|
+
// `ignoreSEIPDv2FeatureFlag` means that the key preferences are
|
|
109
|
+
// ignored. If set to `true`, the session key will be generated
|
|
110
|
+
// the standard non-AEAD algorithm. If set to `false`, the session
|
|
111
|
+
// key will always follow the encryption key preferences.
|
|
112
|
+
config: { ignoreSEIPDv2FeatureFlag: !options.enableAeadWithEncryptionKeys },
|
|
113
|
+
});
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
async encryptSessionKey(sessionKey: SessionKey, encryptionKeys: PublicKey | PublicKey[]) {
|
|
@@ -119,11 +135,12 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
119
135
|
};
|
|
120
136
|
}
|
|
121
137
|
|
|
122
|
-
async generateKey(passphrase: string) {
|
|
138
|
+
async generateKey(passphrase: string, options: { enableAead: boolean }) {
|
|
123
139
|
const privateKey = await this.cryptoProxy.generateKey({
|
|
124
140
|
userIDs: [{ name: 'Drive key' }],
|
|
125
141
|
type: 'ecc',
|
|
126
142
|
curve: 'ed25519Legacy',
|
|
143
|
+
config: { aeadProtect: options.enableAead },
|
|
127
144
|
});
|
|
128
145
|
|
|
129
146
|
const armoredKey = await this.cryptoProxy.exportPrivateKey({
|
|
@@ -137,11 +154,21 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
137
154
|
};
|
|
138
155
|
}
|
|
139
156
|
|
|
140
|
-
async encryptArmored(
|
|
157
|
+
async encryptArmored(
|
|
158
|
+
data: Uint8Array<ArrayBuffer>,
|
|
159
|
+
encryptionKeys: PublicKey[],
|
|
160
|
+
sessionKey: SessionKey | undefined,
|
|
161
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
162
|
+
) {
|
|
141
163
|
const { message: armoredData } = await this.cryptoProxy.encryptMessage({
|
|
142
164
|
binaryData: data,
|
|
143
165
|
sessionKey,
|
|
144
166
|
encryptionKeys,
|
|
167
|
+
// `ignoreSEIPDv2FeatureFlag` means that the key preferences are
|
|
168
|
+
// ignored. If set to `true`, the encrypted data will be generated
|
|
169
|
+
// the standard non-AEAD algorithm. If set to `false`, the session
|
|
170
|
+
// key will always follow the encryption key preferences.
|
|
171
|
+
config: { ignoreSEIPDv2FeatureFlag: !options.enableAeadWithEncryptionKeys },
|
|
145
172
|
});
|
|
146
173
|
return {
|
|
147
174
|
armoredData: armoredData,
|
|
@@ -153,6 +180,7 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
153
180
|
sessionKey: SessionKey,
|
|
154
181
|
encryptionKeys: PublicKey[],
|
|
155
182
|
signingKey: PrivateKey,
|
|
183
|
+
options: { compress?: boolean; enableAeadWithEncryptionKeys: boolean },
|
|
156
184
|
) {
|
|
157
185
|
const { message: encryptedData } = await this.cryptoProxy.encryptMessage({
|
|
158
186
|
binaryData: data,
|
|
@@ -161,6 +189,11 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
161
189
|
encryptionKeys,
|
|
162
190
|
format: 'binary',
|
|
163
191
|
detached: false,
|
|
192
|
+
// `ignoreSEIPDv2FeatureFlag` means that the key preferences are
|
|
193
|
+
// ignored. If set to `true`, the encrypted data will be generated
|
|
194
|
+
// the standard non-AEAD algorithm. If set to `false`, the session
|
|
195
|
+
// key will always follow the encryption key preferences.
|
|
196
|
+
config: { ignoreSEIPDv2FeatureFlag: !options.enableAeadWithEncryptionKeys },
|
|
164
197
|
});
|
|
165
198
|
return {
|
|
166
199
|
encryptedData: encryptedData,
|
|
@@ -172,7 +205,7 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
172
205
|
sessionKey: SessionKey | undefined,
|
|
173
206
|
encryptionKeys: PublicKey[],
|
|
174
207
|
signingKey: PrivateKey,
|
|
175
|
-
options: { compress?: boolean
|
|
208
|
+
options: { compress?: boolean; enableAeadWithEncryptionKeys: boolean },
|
|
176
209
|
) {
|
|
177
210
|
const { message: armoredData } = await this.cryptoProxy.encryptMessage({
|
|
178
211
|
binaryData: data,
|
|
@@ -181,6 +214,11 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
181
214
|
signingKeys: signingKey,
|
|
182
215
|
detached: false,
|
|
183
216
|
compress: options.compress || false,
|
|
217
|
+
// `ignoreSEIPDv2FeatureFlag` means that the key preferences are
|
|
218
|
+
// ignored. If set to `true`, the encrypted data will be generated
|
|
219
|
+
// the standard non-AEAD algorithm. If set to `false`, the session
|
|
220
|
+
// key will always follow the encryption key preferences.
|
|
221
|
+
config: { ignoreSEIPDv2FeatureFlag: !options.enableAeadWithEncryptionKeys },
|
|
184
222
|
});
|
|
185
223
|
return {
|
|
186
224
|
armoredData: armoredData,
|
|
@@ -192,6 +230,7 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
192
230
|
sessionKey: SessionKey,
|
|
193
231
|
encryptionKeys: PublicKey[],
|
|
194
232
|
signingKey: PrivateKey,
|
|
233
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
195
234
|
) {
|
|
196
235
|
const { message: encryptedData, signature } = await this.cryptoProxy.encryptMessage({
|
|
197
236
|
binaryData: data,
|
|
@@ -200,6 +239,11 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
200
239
|
encryptionKeys,
|
|
201
240
|
format: 'binary',
|
|
202
241
|
detached: true,
|
|
242
|
+
// `ignoreSEIPDv2FeatureFlag` means that the key preferences are
|
|
243
|
+
// ignored. If set to `true`, the encrypted data will be generated
|
|
244
|
+
// the standard non-AEAD algorithm. If set to `false`, the session
|
|
245
|
+
// key will always follow the encryption key preferences.
|
|
246
|
+
config: { ignoreSEIPDv2FeatureFlag: !options.enableAeadWithEncryptionKeys },
|
|
203
247
|
});
|
|
204
248
|
return {
|
|
205
249
|
encryptedData: encryptedData,
|
|
@@ -212,6 +256,7 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
212
256
|
sessionKey: SessionKey,
|
|
213
257
|
encryptionKeys: PublicKey[],
|
|
214
258
|
signingKey: PrivateKey,
|
|
259
|
+
options: { enableAeadWithEncryptionKeys: boolean },
|
|
215
260
|
) {
|
|
216
261
|
const { message: armoredData, signature: armoredSignature } = await this.cryptoProxy.encryptMessage({
|
|
217
262
|
binaryData: data,
|
|
@@ -219,6 +264,11 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
219
264
|
signingKeys: signingKey,
|
|
220
265
|
encryptionKeys,
|
|
221
266
|
detached: true,
|
|
267
|
+
// `ignoreSEIPDv2FeatureFlag` means that the key preferences are
|
|
268
|
+
// ignored. If set to `true`, the encrypted data will be generated
|
|
269
|
+
// the standard non-AEAD algorithm. If set to `false`, the session
|
|
270
|
+
// key will always follow the encryption key preferences.
|
|
271
|
+
config: { ignoreSEIPDv2FeatureFlag: !options.enableAeadWithEncryptionKeys },
|
|
222
272
|
});
|
|
223
273
|
return {
|
|
224
274
|
armoredData: armoredData,
|
|
@@ -251,7 +301,11 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
251
301
|
};
|
|
252
302
|
}
|
|
253
303
|
|
|
254
|
-
async verify(
|
|
304
|
+
async verify(
|
|
305
|
+
data: Uint8Array<ArrayBuffer>,
|
|
306
|
+
signature: Uint8Array<ArrayBuffer>,
|
|
307
|
+
verificationKeys: PublicKey | PublicKey[],
|
|
308
|
+
) {
|
|
255
309
|
const { verificationStatus, errors } = await this.cryptoProxy.verifyMessage({
|
|
256
310
|
binaryData: data,
|
|
257
311
|
binarySignature: signature,
|
|
@@ -292,6 +346,12 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
|
|
|
292
346
|
throw new Error('Could not decrypt session key');
|
|
293
347
|
}
|
|
294
348
|
|
|
349
|
+
// Encrypted OpenPGP v6 session keys used for AEAD do not store algorithm information, so we hardcode it
|
|
350
|
+
if (sessionKey.algorithm === null) {
|
|
351
|
+
sessionKey.algorithm = 'aes256';
|
|
352
|
+
sessionKey.aeadAlgorithm = 'gcm';
|
|
353
|
+
}
|
|
354
|
+
|
|
295
355
|
return sessionKey;
|
|
296
356
|
}
|
|
297
357
|
|
|
@@ -3,5 +3,9 @@
|
|
|
3
3
|
* Applications must supply their own implementation.
|
|
4
4
|
*/
|
|
5
5
|
export interface FeatureFlagProvider {
|
|
6
|
-
isEnabled(flagName:
|
|
6
|
+
isEnabled(flagName: FeatureFlags, signal?: AbortSignal): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum FeatureFlags {
|
|
10
|
+
DriveCryptoEncryptBlocksWithPgpAead = 'DriveCryptoEncryptBlocksWithPgpAead',
|
|
7
11
|
}
|
package/src/interface/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { Author, UnverifiedAuthorError, AnonymousUser } from './author';
|
|
|
14
14
|
export type { ProtonDriveConfig } from './config';
|
|
15
15
|
export type { Device, DeviceOrUid } from './devices';
|
|
16
16
|
export type { FeatureFlagProvider } from './featureFlags';
|
|
17
|
+
export { FeatureFlags } from './featureFlags';
|
|
17
18
|
export { DeviceType } from './devices';
|
|
18
19
|
export type { FileDownloader, DownloadController, SeekableReadableStream } from './download';
|
|
19
20
|
export type {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DriveCrypto } from '../../crypto';
|
|
2
2
|
import {
|
|
3
|
+
FeatureFlagProvider,
|
|
3
4
|
ProtonDriveAccount,
|
|
4
5
|
ProtonDriveCryptoCache,
|
|
5
6
|
ProtonDriveEntitiesCache,
|
|
@@ -148,10 +149,11 @@ export function initPhotoUploadModule(
|
|
|
148
149
|
driveCrypto: DriveCrypto,
|
|
149
150
|
sharesService: SharesService,
|
|
150
151
|
nodesService: UploadNodesService,
|
|
152
|
+
featureFlagProvider: FeatureFlagProvider,
|
|
151
153
|
clientUid?: string,
|
|
152
154
|
) {
|
|
153
155
|
const api = new PhotoUploadAPIService(apiService, clientUid);
|
|
154
|
-
const cryptoService = new PhotoUploadCryptoService(driveCrypto, nodesService);
|
|
156
|
+
const cryptoService = new PhotoUploadCryptoService(telemetry, driveCrypto, nodesService, featureFlagProvider);
|
|
155
157
|
|
|
156
158
|
const uploadTelemetry = new UploadTelemetry(telemetry, sharesService);
|
|
157
159
|
const manager = new PhotoUploadManager(telemetry, api, cryptoService, nodesService, clientUid);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DriveCrypto } from '../../crypto';
|
|
2
|
-
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail, AnonymousUser } from '../../interface';
|
|
2
|
+
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail, AnonymousUser, FeatureFlagProvider } from '../../interface';
|
|
3
3
|
import { DriveAPIService, drivePaths } from '../apiService';
|
|
4
4
|
import { generateFileExtendedAttributes } from '../nodes';
|
|
5
5
|
import { splitNodeRevisionUid } from '../uids';
|
|
@@ -162,7 +162,10 @@ export class PhotoUploadManager extends UploadManager {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// TODO: handle photo extended attributes in the SDK - now it must be passed from the client
|
|
165
|
-
const generatedExtendedAttributes = generateFileExtendedAttributes(
|
|
165
|
+
const generatedExtendedAttributes = generateFileExtendedAttributes(
|
|
166
|
+
extendedAttributes,
|
|
167
|
+
uploadMetadata.additionalMetadata,
|
|
168
|
+
);
|
|
166
169
|
const nodeCommitCrypto = await this.cryptoService.commitFile(
|
|
167
170
|
nodeRevisionDraft.nodeKeys,
|
|
168
171
|
manifest,
|
|
@@ -170,13 +173,16 @@ export class PhotoUploadManager extends UploadManager {
|
|
|
170
173
|
);
|
|
171
174
|
|
|
172
175
|
const sha1 = extendedAttributes.digests.sha1;
|
|
173
|
-
const contentHash = await this.photoCryptoService.generateContentHash(
|
|
176
|
+
const contentHash = await this.photoCryptoService.generateContentHash(
|
|
177
|
+
sha1,
|
|
178
|
+
nodeRevisionDraft.parentNodeKeys?.hashKey,
|
|
179
|
+
);
|
|
174
180
|
const photo = {
|
|
175
181
|
contentHash,
|
|
176
|
-
captureTime: uploadMetadata.captureTime ||
|
|
182
|
+
captureTime: uploadMetadata.captureTime || extendedAttributes.modificationTime,
|
|
177
183
|
mainPhotoLinkID: uploadMetadata.mainPhotoLinkID,
|
|
178
184
|
tags: uploadMetadata.tags,
|
|
179
|
-
}
|
|
185
|
+
};
|
|
180
186
|
await this.photoApiService.commitDraftPhoto(nodeRevisionDraft.nodeRevisionUid, nodeCommitCrypto, photo);
|
|
181
187
|
await this.notifyNodeUploaded(nodeRevisionDraft);
|
|
182
188
|
}
|
|
@@ -184,10 +190,12 @@ export class PhotoUploadManager extends UploadManager {
|
|
|
184
190
|
|
|
185
191
|
export class PhotoUploadCryptoService extends UploadCryptoService {
|
|
186
192
|
constructor(
|
|
193
|
+
telemetry: ProtonDriveTelemetry,
|
|
187
194
|
driveCrypto: DriveCrypto,
|
|
188
195
|
nodesService: NodesService,
|
|
196
|
+
featureFlagProvider: FeatureFlagProvider,
|
|
189
197
|
) {
|
|
190
|
-
super(driveCrypto, nodesService);
|
|
198
|
+
super(telemetry, driveCrypto, nodesService, featureFlagProvider);
|
|
191
199
|
}
|
|
192
200
|
|
|
193
201
|
async generateContentHash(sha1: string, parentHashKey: Uint8Array<ArrayBuffer>): Promise<string> {
|
|
@@ -86,12 +86,19 @@ export class SharingPublicNodesAPIService extends NodeAPIService {
|
|
|
86
86
|
const nodeUid = makeNodeUid(volumeId, link.Link.LinkID);
|
|
87
87
|
const encryptedNode = linkToEncryptedNode(this.logger, volumeId, link, isOwnVolumeId);
|
|
88
88
|
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
89
|
+
// TODO: This affects the cache. At this moment, the public link is not cached
|
|
90
|
+
// anywhere, thus OK. To avoid issues when public links reuses the same cache,
|
|
91
|
+
// we need to move this either to the interface of given instance, or leave
|
|
92
|
+
// this as a responsibility to the client.
|
|
93
93
|
if (this.publicRootNodeUid === nodeUid) {
|
|
94
|
+
// Inject public permissions for the root node only.
|
|
95
|
+
// This ensures the root node has the correct directRole instead of
|
|
96
|
+
// incorrectly falling back to 'admin' due to null DirectPermissions.
|
|
94
97
|
encryptedNode.directRole = this.publicRole;
|
|
98
|
+
// This prevent to have parentUid in case user visited parent folder public link of a public link
|
|
99
|
+
// Since the session got permissions to get the parentNode,
|
|
100
|
+
// when visiting children it will return the parentLinkID in links request.
|
|
101
|
+
encryptedNode.parentUid = undefined;
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
return encryptedNode;
|
|
@@ -2,7 +2,14 @@ import { c } from 'ttag';
|
|
|
2
2
|
|
|
3
3
|
import { DriveCrypto, PrivateKey, SessionKey } from '../../crypto';
|
|
4
4
|
import { IntegrityError } from '../../errors';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Thumbnail,
|
|
7
|
+
AnonymousUser,
|
|
8
|
+
FeatureFlagProvider,
|
|
9
|
+
FeatureFlags,
|
|
10
|
+
ProtonDriveTelemetry,
|
|
11
|
+
Logger,
|
|
12
|
+
} from '../../interface';
|
|
6
13
|
import {
|
|
7
14
|
EncryptedBlock,
|
|
8
15
|
EncryptedThumbnail,
|
|
@@ -13,12 +20,18 @@ import {
|
|
|
13
20
|
} from './interface';
|
|
14
21
|
|
|
15
22
|
export class UploadCryptoService {
|
|
23
|
+
protected logger: Logger;
|
|
24
|
+
|
|
16
25
|
constructor(
|
|
26
|
+
telemetry: ProtonDriveTelemetry,
|
|
17
27
|
protected driveCrypto: DriveCrypto,
|
|
18
28
|
protected nodesService: NodesService,
|
|
29
|
+
protected featureFlagProvider: FeatureFlagProvider,
|
|
19
30
|
) {
|
|
31
|
+
this.logger = telemetry.getLogger('upload');
|
|
20
32
|
this.driveCrypto = driveCrypto;
|
|
21
33
|
this.nodesService = nodesService;
|
|
34
|
+
this.featureFlagProvider = featureFlagProvider;
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
async generateFileCrypto(
|
|
@@ -26,6 +39,13 @@ export class UploadCryptoService {
|
|
|
26
39
|
parentKeys: { key: PrivateKey; hashKey: Uint8Array<ArrayBuffer> },
|
|
27
40
|
name: string,
|
|
28
41
|
): Promise<NodeCrypto> {
|
|
42
|
+
const useAeadFeatureFlag = await this.featureFlagProvider.isEnabled(
|
|
43
|
+
FeatureFlags.DriveCryptoEncryptBlocksWithPgpAead,
|
|
44
|
+
);
|
|
45
|
+
if (useAeadFeatureFlag) {
|
|
46
|
+
this.logger.info('Generating file crypto with AEAD enabled');
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
const signingKeys = await this.getSigningKeys({ parentNodeUid: parentUid });
|
|
30
50
|
|
|
31
51
|
if (!signingKeys.nameAndPassphraseSigningKey) {
|
|
@@ -33,7 +53,9 @@ export class UploadCryptoService {
|
|
|
33
53
|
}
|
|
34
54
|
|
|
35
55
|
const [nodeKeys, { armoredNodeName }, hash] = await Promise.all([
|
|
36
|
-
this.driveCrypto.generateKey([parentKeys.key], signingKeys.nameAndPassphraseSigningKey
|
|
56
|
+
this.driveCrypto.generateKey([parentKeys.key], signingKeys.nameAndPassphraseSigningKey, {
|
|
57
|
+
enableAead: useAeadFeatureFlag,
|
|
58
|
+
}),
|
|
37
59
|
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, signingKeys.nameAndPassphraseSigningKey),
|
|
38
60
|
this.driveCrypto.generateLookupHash(name, parentKeys.hashKey),
|
|
39
61
|
]);
|