@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.
Files changed (52) hide show
  1. package/dist/crypto/driveCrypto.d.ts +16 -1
  2. package/dist/crypto/driveCrypto.js +55 -19
  3. package/dist/crypto/driveCrypto.js.map +1 -1
  4. package/dist/crypto/driveCrypto.test.js +1 -1
  5. package/dist/crypto/driveCrypto.test.js.map +1 -1
  6. package/dist/crypto/interface.d.ts +22 -8
  7. package/dist/crypto/openPGPCrypto.d.ts +30 -7
  8. package/dist/crypto/openPGPCrypto.js +46 -8
  9. package/dist/crypto/openPGPCrypto.js.map +1 -1
  10. package/dist/interface/featureFlags.d.ts +4 -1
  11. package/dist/interface/featureFlags.js +5 -0
  12. package/dist/interface/featureFlags.js.map +1 -1
  13. package/dist/interface/index.d.ts +1 -0
  14. package/dist/interface/index.js +3 -1
  15. package/dist/interface/index.js.map +1 -1
  16. package/dist/internal/photos/index.d.ts +2 -2
  17. package/dist/internal/photos/index.js +2 -2
  18. package/dist/internal/photos/index.js.map +1 -1
  19. package/dist/internal/photos/upload.d.ts +2 -2
  20. package/dist/internal/photos/upload.js +2 -2
  21. package/dist/internal/photos/upload.js.map +1 -1
  22. package/dist/internal/sharingPublic/nodes.js +11 -4
  23. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  24. package/dist/internal/upload/cryptoService.d.ts +4 -2
  25. package/dist/internal/upload/cryptoService.js +14 -2
  26. package/dist/internal/upload/cryptoService.js.map +1 -1
  27. package/dist/internal/upload/index.d.ts +2 -2
  28. package/dist/internal/upload/index.js +2 -2
  29. package/dist/internal/upload/index.js.map +1 -1
  30. package/dist/protonDriveClient.js +1 -1
  31. package/dist/protonDriveClient.js.map +1 -1
  32. package/dist/protonDrivePhotosClient.d.ts +1 -1
  33. package/dist/protonDrivePhotosClient.js +6 -2
  34. package/dist/protonDrivePhotosClient.js.map +1 -1
  35. package/dist/protonDrivePublicLinkClient.d.ts +3 -2
  36. package/dist/protonDrivePublicLinkClient.js +6 -2
  37. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/crypto/driveCrypto.test.ts +1 -0
  40. package/src/crypto/driveCrypto.ts +51 -10
  41. package/src/crypto/interface.ts +21 -8
  42. package/src/crypto/openPGPCrypto.ts +68 -8
  43. package/src/interface/featureFlags.ts +5 -1
  44. package/src/interface/index.ts +1 -0
  45. package/src/internal/photos/index.ts +3 -1
  46. package/src/internal/photos/upload.ts +14 -6
  47. package/src/internal/sharingPublic/nodes.ts +11 -4
  48. package/src/internal/upload/cryptoService.ts +24 -2
  49. package/src/internal/upload/index.ts +3 -2
  50. package/src/protonDriveClient.ts +1 -0
  51. package/src/protonDrivePhotosClient.ts +14 -9
  52. package/src/protonDrivePublicLinkClient.ts +8 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protontech/drive-sdk",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Proton Drive SDK",
5
5
  "license": "GPL-3.0",
6
6
  "main": "dist/index.js",
@@ -68,6 +68,7 @@ describe('DriveCrypto.encryptShareUrlPassword', () => {
68
68
  undefined,
69
69
  [encryptionKey],
70
70
  signingKey,
71
+ { enableAeadWithEncryptionKeys: false },
71
72
  );
72
73
  });
73
74
  });
@@ -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
- this.openPGPCrypto.generateSessionKey(encryptionKeys),
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
- const contentKeyPacketSessionKey = await this.openPGPCrypto.generateSessionKey([encryptionKey]);
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(new TextEncoder().encode(password), [addressKey]),
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
- { compress: true },
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: 'v1',
798
+ cryptoModel,
758
799
  bytesProcessed,
759
800
  milliseconds: Math.round(duration),
760
801
  });
@@ -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
- readonly _keyContentHash: [string, string];
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- algorithm: any;
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: (encryptionKeys: PublicKey[]) => Promise<SessionKey>;
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: (passphrase: string) => Promise<{
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?: 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?: { compress?: boolean },
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: (data: Uint8Array<ArrayBuffer>, decryptionKeys: PrivateKey | PrivateKey[]) => Promise<SessionKey>;
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: { userIDs: { name: string }[]; type: 'ecc'; curve: 'ed25519Legacy' }) => Promise<PrivateKey>;
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: { recipientKeys: PublicKey[] }) => Promise<SessionKey>;
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({ recipientKeys: encryptionKeys });
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(data: Uint8Array<ArrayBuffer>, encryptionKeys: PublicKey[], sessionKey?: SessionKey) {
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(data: Uint8Array<ArrayBuffer>, signature: Uint8Array<ArrayBuffer>, verificationKeys: PublicKey | PublicKey[]) {
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: string, signal?: AbortSignal): Promise<boolean>;
6
+ isEnabled(flagName: FeatureFlags, signal?: AbortSignal): Promise<boolean>;
7
+ }
8
+
9
+ export enum FeatureFlags {
10
+ DriveCryptoEncryptBlocksWithPgpAead = 'DriveCryptoEncryptBlocksWithPgpAead',
7
11
  }
@@ -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(extendedAttributes, uploadMetadata.additionalMetadata);
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(sha1, nodeRevisionDraft.parentNodeKeys?.hashKey);
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 || extendedAttributes.modificationTime,
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
- // TEMPORARY: Inject public permissions for the root node only.
90
- // This ensures the root node has the correct directRole instead of
91
- // incorrectly falling back to 'admin' due to null DirectPermissions.
92
- // May be fixed by backend later.
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 { Thumbnail, AnonymousUser } from '../../interface';
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
  ]);