Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / keys / keyMigration.ts
blob5481fba59d006f76e0f404d1a0ffec26ff663fca
1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy, toPublicKeyReference } from '@proton/crypto';
3 import { getMemberKeys } from '@proton/shared/lib/keys/memberKeys';
4 import noop from '@proton/utils/noop';
6 import { queryScopes } from '../api/auth';
7 import { getApiError, getIsConnectionIssue } from '../api/helpers/apiErrorHelper';
8 import { migrateAddressKeysRoute } from '../api/keys';
9 import { migrateMembersAddressKeysRoute } from '../api/memberKeys';
10 import { getAllMemberAddresses, getAllMembers, getMember } from '../api/members';
11 import { getOrganizationKeys } from '../api/organization';
12 import { MEMBER_PRIVATE, USER_ROLES } from '../constants';
13 import { ApiError } from '../fetch/ApiError';
14 import type {
15     Address,
16     Api,
17     DecryptedKey,
18     KeyMigrationKTVerifier,
19     KeyTransparencyVerify,
20     Member,
21     Organization,
22     OrganizationKey,
23     PreAuthKTVerify,
24     SignedKeyList,
25     User,
26 } from '../interfaces';
27 import { generateAddressKeyTokens } from './addressKeys';
28 import { getDecryptedAddressKeysHelper } from './getDecryptedAddressKeys';
29 import { getDecryptedOrganizationKeyHelper } from './getDecryptedOrganizationKey';
30 import { getDecryptedUserKeysHelper } from './getDecryptedUserKeys';
31 import { getPrimaryKey } from './getPrimaryKey';
32 import type { OnSKLPublishSuccess } from './signedKeyList';
33 import { createSignedKeyListForMigration } from './signedKeyList';
35 export const getSentryError = (error: any): any => {
36     // Only interested in api errors where the API gave a valid error response, or run time errors.
37     if (error instanceof ApiError) {
38         const { message, code } = getApiError(error);
39         return message && code >= 400 && code < 500 ? message : null;
40     }
41     if (
42         !error ||
43         error.ignore ||
44         getIsConnectionIssue(error) ||
45         error.message === 'Failed to fetch' ||
46         error.message === 'Load failed' ||
47         error.message === 'Operation aborted' ||
48         error.name === 'AbortError'
49     ) {
50         return;
51     }
52     return error;
55 export const getHasMigratedAddressKey = ({ Token, Signature }: { Token?: string; Signature?: string }): boolean => {
56     return !!Token && !!Signature;
59 export const getHasMigratedAddressKeys = (addresses?: Address[]) => {
60     return !!addresses?.some((address) => address.Keys?.some(getHasMigratedAddressKey));
63 export const getHasMemberMigratedAddressKeys = (memberAddresses: Address[], ownerAddresses: Address[]) => {
64     const primaryMemberAddress = memberAddresses[0];
65     return primaryMemberAddress?.Keys?.length > 0
66         ? getHasMigratedAddressKeys(memberAddresses)
67         : getHasMigratedAddressKeys(ownerAddresses);
70 export interface MigrateAddressKeyPayload {
71     ID: string;
72     Token: string;
73     Signature: string;
74     PrivateKey: string;
77 export interface MigrateMemberAddressKeyPayload extends MigrateAddressKeyPayload {
78     OrgSignature: string;
81 interface MigrationResult<T extends MigrateAddressKeyPayload> {
82     SignedKeyLists: { [id: string]: SignedKeyList };
83     AddressKeys: T[];
86 interface AddressKeyMigrationValue<T extends MigrateAddressKeyPayload> {
87     Address: Address;
88     AddressKeys: T[];
89     SignedKeyList: SignedKeyList | undefined;
90     onSKLPublishSuccess: OnSKLPublishSuccess | undefined;
93 interface AddressesKeys {
94     address: Address;
95     keys: DecryptedKey[];
98 export function getAddressKeysMigration(data: {
99     api: Api;
100     addressesKeys: AddressesKeys[];
101     userKey: PrivateKeyReference;
102     keyTransparencyVerify: KeyTransparencyVerify;
103     keyMigrationKTVerifier: KeyMigrationKTVerifier;
104     organizationKey: PrivateKeyReference;
105 }): Promise<AddressKeyMigrationValue<MigrateMemberAddressKeyPayload>[]>;
106 export function getAddressKeysMigration(data: {
107     api: Api;
108     addressesKeys: AddressesKeys[];
109     userKey: PrivateKeyReference;
110     keyTransparencyVerify: KeyTransparencyVerify;
111     keyMigrationKTVerifier: KeyMigrationKTVerifier;
112     organizationKey?: PrivateKeyReference;
113 }): Promise<AddressKeyMigrationValue<MigrateAddressKeyPayload>[]>;
115 export function getAddressKeysMigration({
116     api,
117     addressesKeys,
118     userKey,
119     organizationKey,
120     keyMigrationKTVerifier,
121     keyTransparencyVerify,
122 }: {
123     api: Api;
124     addressesKeys: AddressesKeys[];
125     userKey: PrivateKeyReference;
126     keyTransparencyVerify: KeyTransparencyVerify;
127     keyMigrationKTVerifier: KeyMigrationKTVerifier;
128     organizationKey?: PrivateKeyReference;
129 }) {
130     return Promise.all(
131         addressesKeys.map(async ({ address, keys }) => {
132             const migratedKeys = await Promise.all(
133                 keys.map(async ({ ID, privateKey }) => {
134                     const { token, encryptedToken, signature, organizationSignature } = await generateAddressKeyTokens(
135                         userKey,
136                         organizationKey
137                     );
138                     const privateKeyArmored = await CryptoProxy.exportPrivateKey({
139                         privateKey,
140                         passphrase: token,
141                     });
142                     return {
143                         encryptedToken,
144                         signature,
145                         organizationSignature,
146                         privateKey,
147                         privateKeyArmored,
148                         ID,
149                     };
150                 })
151             );
152             const migratedDecryptedKeys = await Promise.all(
153                 migratedKeys.map(async ({ ID, privateKey }) => ({
154                     ID,
155                     privateKey,
156                     publicKey: await toPublicKeyReference(privateKey),
157                 }))
158             );
159             const [signedKeyList, onSKLPublishSuccess] = await createSignedKeyListForMigration({
160                 api,
161                 address,
162                 decryptedKeys: migratedDecryptedKeys,
163                 keyTransparencyVerify,
164                 keyMigrationKTVerifier,
165             });
166             return {
167                 Address: address,
168                 SignedKeyList: signedKeyList,
169                 onSKLPublishSuccess: onSKLPublishSuccess,
170                 AddressKeys: migratedKeys.map((migratedKey) => {
171                     return {
172                         ID: migratedKey.ID,
173                         PrivateKey: migratedKey.privateKeyArmored,
174                         Token: migratedKey.encryptedToken,
175                         Signature: migratedKey.signature,
176                         ...(migratedKey.organizationSignature
177                             ? { OrgSignature: migratedKey.organizationSignature }
178                             : undefined),
179                     };
180                 }),
181             };
182         })
183     );
186 export function getAddressKeysMigrationPayload<T extends MigrateAddressKeyPayload>(
187     addressKeysMigration: AddressKeyMigrationValue<T>[]
188 ) {
189     return addressKeysMigration.reduce<MigrationResult<T>>(
190         (acc, { AddressKeys, Address, SignedKeyList }) => {
191             // Some addresses may not have keys and thus won't have generated a signed key list
192             if (AddressKeys.length > 0) {
193                 acc.AddressKeys = acc.AddressKeys.concat(AddressKeys);
194                 if (SignedKeyList) {
195                     acc.SignedKeyLists[Address.ID] = SignedKeyList;
196                 }
197             }
198             return acc;
199         },
200         { AddressKeys: [], SignedKeyLists: {} }
201     );
204 interface MigrateAddressKeysArguments {
205     api: Api;
206     keyPassword: string;
207     user: User;
208     addresses: Address[];
209     organizationKey?: OrganizationKey;
210     preAuthKTVerify: PreAuthKTVerify;
211     keyMigrationKTVerifier: KeyMigrationKTVerifier;
214 export async function migrateAddressKeys(
215     args: MigrateAddressKeysArguments & {
216         organizationKey: OrganizationKey;
217     }
218 ): Promise<AddressKeyMigrationValue<MigrateMemberAddressKeyPayload>[]>;
219 export async function migrateAddressKeys(
220     args: MigrateAddressKeysArguments
221 ): Promise<AddressKeyMigrationValue<MigrateAddressKeyPayload>[]>;
223 export async function migrateAddressKeys({
224     api,
225     user,
226     addresses,
227     keyPassword,
228     organizationKey,
229     preAuthKTVerify,
230     keyMigrationKTVerifier,
231 }: MigrateAddressKeysArguments) {
232     const userKeys = await getDecryptedUserKeysHelper(user, keyPassword);
234     const primaryUserKey = getPrimaryKey(userKeys)?.privateKey;
235     if (!primaryUserKey) {
236         throw new Error('Missing primary private user key');
237     }
239     const addressesKeys = await Promise.all(
240         addresses.map(async (address) => {
241             return {
242                 address,
243                 keys: await getDecryptedAddressKeysHelper(address.Keys, user, userKeys, keyPassword),
244             };
245         })
246     );
248     const keyTransparencyVerify = preAuthKTVerify(userKeys);
250     if (!organizationKey) {
251         return getAddressKeysMigration({
252             api,
253             addressesKeys,
254             userKey: primaryUserKey,
255             keyTransparencyVerify,
256             keyMigrationKTVerifier,
257         });
258     }
260     const decryptedOrganizationKeyResult = await getDecryptedOrganizationKeyHelper({
261         userKeys,
262         Key: organizationKey,
263         keyPassword,
264     }).catch(noop);
265     if (!decryptedOrganizationKeyResult) {
266         const error = new Error('Failed to decrypt organization key');
267         (error as any).ignore = true;
268         throw error;
269     }
270     return getAddressKeysMigration({
271         api,
272         addressesKeys,
273         userKey: primaryUserKey,
274         keyTransparencyVerify,
275         keyMigrationKTVerifier,
276         organizationKey: decryptedOrganizationKeyResult.privateKey,
277     });
280 interface MigrateMemberAddressKeysArguments {
281     api: Api;
282     keyPassword: string;
283     timeout?: number;
284     user: User;
285     organization: Organization;
286     keyTransparencyVerify: KeyTransparencyVerify;
287     keyMigrationKTVerifier: KeyMigrationKTVerifier;
290 export async function migrateMemberAddressKeys({
291     api,
292     keyPassword,
293     timeout = 120000,
294     user,
295     organization,
296     keyTransparencyVerify,
297     keyMigrationKTVerifier,
298 }: MigrateMemberAddressKeysArguments) {
299     if (organization.ToMigrate !== 1) {
300         return false;
301     }
303     if (user.Role !== USER_ROLES.ADMIN_ROLE) {
304         return;
305     }
307     // NOTE: The API following calls are done in a waterfall to lower the amount of unnecessary requests.
308     // Ensure scope...
309     const { Scopes } = await api<{ Scopes: string[] }>(queryScopes());
310     if (!Scopes.includes('organization')) {
311         return;
312     }
314     const organizationKey = await api<OrganizationKey>(getOrganizationKeys());
315     const userKeys = await getDecryptedUserKeysHelper(user, keyPassword);
316     // Ensure that the organization key can be decrypted...
317     const decryptedOrganizationKeyResult = await getDecryptedOrganizationKeyHelper({
318         userKeys,
319         Key: organizationKey,
320         keyPassword,
321     }).catch(noop);
322     if (!decryptedOrganizationKeyResult?.privateKey) {
323         return;
324     }
326     // Ensure that there are members to migrate...
327     const members = await getAllMembers(api);
328     const membersToMigrate = members.filter(({ ToMigrate, Private, Self }) => {
329         return !Self && ToMigrate === 1 && Private === MEMBER_PRIVATE.READABLE;
330     });
331     if (!membersToMigrate.length) {
332         return;
333     }
335     for (const member of membersToMigrate) {
336         // Some members might not be setup.
337         if (!member.Keys?.length) {
338             continue;
339         }
340         const memberAddresses = await getAllMemberAddresses(api, member.ID);
341         const { memberUserKeyPrimary, memberAddressesKeys } = await getMemberKeys({
342             member,
343             memberAddresses,
344             organizationKey: decryptedOrganizationKeyResult,
345         });
347         // Some members might not have keys setup for the address.
348         if (!memberAddressesKeys.length) {
349             continue;
350         }
352         const migratedKeys = await getAddressKeysMigration({
353             api,
354             addressesKeys: memberAddressesKeys,
355             userKey: memberUserKeyPrimary,
356             keyTransparencyVerify,
357             keyMigrationKTVerifier,
358             organizationKey: decryptedOrganizationKeyResult.privateKey,
359         });
360         const payload = await getAddressKeysMigrationPayload(migratedKeys);
361         if (payload) {
362             await api({ ...migrateMembersAddressKeysRoute({ MemberID: member.ID, ...payload }), timeout });
363             await Promise.all(
364                 migratedKeys.map(({ onSKLPublishSuccess }) =>
365                     onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
366                 )
367             );
368         }
369     }
372 export const migrateUser = async ({
373     api,
374     user,
375     addresses,
376     keyPassword,
377     timeout = 120000,
378     preAuthKTVerify,
379     keyMigrationKTVerifier,
380 }: {
381     api: Api;
382     user: User;
383     addresses: Address[];
384     keyPassword: string;
385     timeout?: number;
386     preAuthKTVerify: PreAuthKTVerify;
387     keyMigrationKTVerifier: KeyMigrationKTVerifier;
388 }) => {
389     if (user.ToMigrate !== 1 || getHasMigratedAddressKeys(addresses)) {
390         return false;
391     }
393     if (user.Private === MEMBER_PRIVATE.READABLE && user.Role === USER_ROLES.MEMBER_ROLE) {
394         return false;
395     }
397     if (user.Private === MEMBER_PRIVATE.READABLE && user.Role === USER_ROLES.ADMIN_ROLE) {
398         const [selfMember, organizationKey] = await Promise.all([
399             api<{ Member: Member }>(getMember('me')).then(({ Member }) => Member),
400             api<OrganizationKey>(getOrganizationKeys()),
401         ]);
402         const migratedKeys = await migrateAddressKeys({
403             api,
404             user,
405             organizationKey,
406             addresses,
407             keyPassword,
408             keyMigrationKTVerifier,
409             preAuthKTVerify,
410         });
411         const payload = await getAddressKeysMigrationPayload(migratedKeys);
412         await api({
413             ...migrateMembersAddressKeysRoute({ MemberID: selfMember.ID, ...payload }),
414             timeout,
415         });
416         await Promise.all(
417             migratedKeys.map(({ onSKLPublishSuccess }) =>
418                 onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
419             )
420         );
421         return true;
422     }
424     const migratedKeys = await migrateAddressKeys({
425         api,
426         user,
427         addresses,
428         keyPassword,
429         preAuthKTVerify,
430         keyMigrationKTVerifier,
431     });
432     const payload = await getAddressKeysMigrationPayload(migratedKeys);
433     await api({ ...migrateAddressKeysRoute(payload), timeout });
434     await Promise.all(
435         migratedKeys.map(({ onSKLPublishSuccess }) => (onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()))
436     );
437     return true;