Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / keys / upgradeKeysV2.ts
blobf10013dd8d3a5885e731dfeec039336855a5adcf
1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import { computeKeyPassword, generateKeySalt } from '@proton/srp';
5 import type { UpgradeAddressKeyPayload } from '../api/keys';
6 import { upgradeKeysRoute } from '../api/keys';
7 import { getOrganizationKeys } from '../api/organization';
8 import { USER_ROLES } from '../constants';
9 import { toMap } from '../helpers/object';
10 import type {
11     Api,
12     CachedOrganizationKey,
13     DecryptedKey,
14     Key,
15     KeyMigrationKTVerifier,
16     KeyTransparencyVerify,
17     PreAuthKTVerify,
18     SignedKeyList,
19     User,
20     Address as tsAddress,
21     Key as tsKey,
22     OrganizationKey as tsOrganizationKey,
23     User as tsUser,
24 } from '../interfaces';
25 import { srpVerify } from '../srp';
26 import { generateAddressKeyTokens, reformatAddressKey } from './addressKeys';
27 import { getDecryptedAddressKeysHelper } from './getDecryptedAddressKeys';
28 import { getCachedOrganizationKey } from './getDecryptedOrganizationKey';
29 import { getDecryptedUserKeysHelper } from './getDecryptedUserKeys';
30 import { getHasMigratedAddressKeys } from './keyMigration';
31 import { reformatOrganizationKey } from './organizationKeys';
32 import { createSignedKeyListForMigration } from './signedKeyList';
33 import { USER_KEY_USERID } from './userKeys';
35 export const getV2KeyToUpgrade = (Key: tsKey) => {
36     return Key.Version < 3;
39 export const getV2KeysToUpgrade = (Keys?: tsKey[]) => {
40     if (!Keys) {
41         return [];
42     }
43     return Keys.filter(getV2KeyToUpgrade);
46 export const getHasV2KeysToUpgrade = (User: tsUser, Addresses: tsAddress[]) => {
47     return (
48         getV2KeysToUpgrade(User.Keys).length > 0 ||
49         Addresses.some((Address) => getV2KeysToUpgrade(Address.Keys).length > 0)
50     );
53 const reEncryptOrReformatKey = async (privateKey: PrivateKeyReference, Key: Key, email: string, passphrase: string) => {
54     if (Key && getV2KeyToUpgrade(Key)) {
55         return reformatAddressKey({
56             email,
57             passphrase,
58             privateKey,
59         });
60     }
61     const privateKeyArmored = await CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase });
62     return { privateKey, privateKeyArmored };
65 const getReEncryptedKeys = (keys: DecryptedKey[], Keys: Key[], email: string, passphrase: string) => {
66     const keysMap = Keys.reduce<{ [key: string]: Key }>((acc, Key) => {
67         acc[Key.ID] = Key;
68         return acc;
69     }, {});
70     return Promise.all(
71         keys.map(async ({ privateKey, ID }) => {
72             const { privateKeyArmored } = await reEncryptOrReformatKey(privateKey, keysMap[ID], email, passphrase);
73             return {
74                 ID,
75                 PrivateKey: privateKeyArmored,
76             };
77         })
78     );
81 const getReformattedAddressKeysV2 = (
82     keys: DecryptedKey[],
83     Keys: Key[],
84     email: string,
85     userKey: PrivateKeyReference
86 ) => {
87     const keysMap = Keys.reduce<{ [key: string]: Key }>((acc, Key) => {
88         acc[Key.ID] = Key;
89         return acc;
90     }, {});
91     return Promise.all(
92         keys.map(async ({ ID, privateKey: originalPrivateKey }) => {
93             const { token, encryptedToken, signature } = await generateAddressKeyTokens(userKey);
94             const { privateKey, privateKeyArmored } = await reEncryptOrReformatKey(
95                 originalPrivateKey,
96                 keysMap[ID],
97                 email,
98                 token
99             );
100             const publicKey = await CryptoProxy.importPublicKey({
101                 binaryKey: await CryptoProxy.exportPublicKey({ key: privateKey, format: 'binary' }),
102             });
103             return {
104                 decryptedKey: {
105                     ID,
106                     privateKey,
107                     publicKey,
108                 },
109                 Key: {
110                     ID,
111                     PrivateKey: privateKeyArmored,
112                     Token: encryptedToken,
113                     Signature: signature,
114                 },
115             };
116         })
117     );
120 interface UpgradeV2KeysLegacyArgs {
121     loginPassword: string;
122     clearKeyPassword: string;
123     api: Api;
124     isOnePasswordMode?: boolean;
125     user: User;
126     userKeys: DecryptedKey[];
127     organizationKey?: CachedOrganizationKey;
128     addressesKeys: {
129         address: tsAddress;
130         keys: DecryptedKey[];
131     }[];
134 export const upgradeV2KeysLegacy = async ({
135     user,
136     userKeys,
137     addressesKeys,
138     organizationKey,
139     loginPassword,
140     clearKeyPassword,
141     isOnePasswordMode,
142     api,
143 }: UpgradeV2KeysLegacyArgs) => {
144     const keySalt = generateKeySalt();
145     const newKeyPassword = await computeKeyPassword(clearKeyPassword, keySalt);
147     const [reformattedUserKeys, reformattedAddressesKeys, reformattedOrganizationKey] = await Promise.all([
148         getReEncryptedKeys(userKeys, user.Keys, USER_KEY_USERID, newKeyPassword),
149         Promise.all(
150             addressesKeys.map(({ address, keys }) => {
151                 return getReEncryptedKeys(keys, address.Keys, address.Email, newKeyPassword);
152             })
153         ),
154         organizationKey?.privateKey ? reformatOrganizationKey(organizationKey.privateKey, newKeyPassword) : undefined,
155     ]);
157     const reformattedKeys = [...reformattedUserKeys, ...reformattedAddressesKeys.flat()];
159     const config = upgradeKeysRoute({
160         KeySalt: keySalt,
161         Keys: reformattedKeys,
162         OrganizationKey: reformattedOrganizationKey?.privateKeyArmored,
163     });
165     if (isOnePasswordMode) {
166         await srpVerify({
167             api,
168             credentials: { password: loginPassword },
169             config,
170         });
171         return newKeyPassword;
172     }
174     await api(config);
175     return newKeyPassword;
178 interface UpgradeV2KeysArgs extends UpgradeV2KeysLegacyArgs {
179     keyTransparencyVerify: KeyTransparencyVerify;
180     keyMigrationKTVerifier: KeyMigrationKTVerifier;
183 export const upgradeV2KeysV2 = async ({
184     user,
185     userKeys,
186     addressesKeys,
187     organizationKey,
188     loginPassword,
189     clearKeyPassword,
190     isOnePasswordMode,
191     api,
192     keyTransparencyVerify,
193     keyMigrationKTVerifier,
194 }: UpgradeV2KeysArgs) => {
195     if (!userKeys.length) {
196         return;
197     }
198     const keySalt = generateKeySalt();
199     const newKeyPassword: string = await computeKeyPassword(clearKeyPassword, keySalt);
200     const [reformattedUserKeys, reformattedOrganizationKey] = await Promise.all([
201         getReEncryptedKeys(userKeys, user.Keys, USER_KEY_USERID, newKeyPassword),
202         organizationKey?.privateKey ? reformatOrganizationKey(organizationKey.privateKey, newKeyPassword) : undefined,
203     ]);
205     const primaryUserKey = await CryptoProxy.importPrivateKey({
206         armoredKey: reformattedUserKeys[0].PrivateKey,
207         passphrase: newKeyPassword,
208     });
210     const reformattedAddressesKeys = await Promise.all(
211         addressesKeys.map(async ({ address, keys }) => {
212             const reformattedAddressKeys = await getReformattedAddressKeysV2(
213                 keys,
214                 address.Keys,
215                 address.Email,
216                 primaryUserKey
217             );
218             const [decryptedKeys, addressKeys] = reformattedAddressKeys.reduce<
219                 [DecryptedKey[], UpgradeAddressKeyPayload[]]
220             >(
221                 (acc, cur) => {
222                     acc[0].push(cur.decryptedKey);
223                     acc[1].push(cur.Key);
224                     return acc;
225                 },
226                 [[], []]
227             );
228             const [signedKeyList, onSKLPublishSuccess] = await createSignedKeyListForMigration({
229                 api,
230                 address,
231                 decryptedKeys,
232                 keyTransparencyVerify,
233                 keyMigrationKTVerifier,
234             });
235             return {
236                 address,
237                 addressKeys,
238                 signedKeyList: signedKeyList,
239                 onSKLPublishSuccess: onSKLPublishSuccess,
240             };
241         })
242     );
244     const AddressKeys = reformattedAddressesKeys.map(({ addressKeys }) => addressKeys).flat();
245     const SignedKeyLists = reformattedAddressesKeys.reduce<{ [id: string]: SignedKeyList }>(
246         (acc, { address, signedKeyList }) => {
247             if (signedKeyList) {
248                 acc[address.ID] = signedKeyList;
249             }
250             return acc;
251         },
252         {}
253     );
255     const config = upgradeKeysRoute({
256         KeySalt: keySalt,
257         UserKeys: reformattedUserKeys,
258         AddressKeys,
259         OrganizationKey: reformattedOrganizationKey?.privateKeyArmored,
260         SignedKeyLists,
261     });
263     await Promise.all(
264         reformattedAddressesKeys.map(({ onSKLPublishSuccess }) =>
265             onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
266         )
267     );
269     if (isOnePasswordMode) {
270         await srpVerify({
271             api,
272             credentials: { password: loginPassword },
273             config,
274         });
275         return newKeyPassword;
276     }
278     await api(config);
279     return newKeyPassword;
282 interface UpgradeV2KeysHelperArgs {
283     addresses: tsAddress[];
284     user: tsUser;
285     loginPassword: string;
286     clearKeyPassword: string;
287     keyPassword: string;
288     api: Api;
289     isOnePasswordMode?: boolean;
290     preAuthKTVerify: PreAuthKTVerify;
291     keyMigrationKTVerifier: KeyMigrationKTVerifier;
294 export const upgradeV2KeysHelper = async ({
295     user,
296     addresses,
297     loginPassword,
298     clearKeyPassword,
299     keyPassword,
300     isOnePasswordMode,
301     api,
302     preAuthKTVerify,
303     keyMigrationKTVerifier,
304 }: UpgradeV2KeysHelperArgs) => {
305     const userKeys = await getDecryptedUserKeysHelper(user, keyPassword);
307     const addressesKeys = await Promise.all(
308         addresses.map(async (address) => {
309             return {
310                 address,
311                 keys: await getDecryptedAddressKeysHelper(address.Keys, user, userKeys, keyPassword),
312             };
313         })
314     );
316     const organizationKey =
317         user.Role === USER_ROLES.ADMIN_ROLE
318             ? await api<tsOrganizationKey>(getOrganizationKeys()).then((Key) => {
319                   return getCachedOrganizationKey({ keyPassword, Key, userKeys });
320               })
321             : undefined;
323     if (!clearKeyPassword || !loginPassword) {
324         throw new Error('Password required');
325     }
326     // Not allowed signed into member
327     if (user.OrganizationPrivateKey) {
328         return;
329     }
331     const userKeyMap = toMap(user.Keys, 'ID');
332     const hasDecryptedUserKeysToUpgrade = userKeys.some(({ privateKey, ID }) => {
333         const Key = userKeyMap[ID];
334         return Key && privateKey && getV2KeyToUpgrade(Key);
335     });
336     const hasDecryptedAddressKeyToUpgrade = addressesKeys.some(({ address, keys }) => {
337         const addressKeyMap = toMap(address.Keys, 'ID');
338         return keys.some(({ privateKey, ID }) => {
339             const Key = addressKeyMap[ID];
340             return Key && privateKey && getV2KeyToUpgrade(Key);
341         });
342     });
344     if (!hasDecryptedUserKeysToUpgrade && !hasDecryptedAddressKeyToUpgrade) {
345         return;
346     }
348     if (getHasMigratedAddressKeys(addresses)) {
349         const keyTransparencyVerify = preAuthKTVerify(userKeys);
351         return upgradeV2KeysV2({
352             api,
353             user,
354             userKeys,
355             addressesKeys,
356             organizationKey,
357             loginPassword,
358             clearKeyPassword,
359             isOnePasswordMode,
360             keyTransparencyVerify,
361             keyMigrationKTVerifier,
362         });
363     }
365     return upgradeV2KeysLegacy({
366         api,
367         user,
368         userKeys,
369         addressesKeys,
370         organizationKey,
371         loginPassword,
372         clearKeyPassword,
373         isOnePasswordMode,
374     });