Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / keys / reactivation / reactivateKeyHelper.ts
bloba73caff2cef21d1378278c62371d6376d110f846
1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import unique from '@proton/utils/unique';
5 import type {
6     ActiveKeyWithVersion,
7     DecryptedKey,
8     Key,
9     KeyPair,
10     KeyTransparencyVerify,
11     SignedKeyList,
12     Address as tsAddress,
13     User as tsUser,
14 } from '../../interfaces';
15 import { getActiveAddressKeys, getNormalizedActiveAddressKeys, getReactivatedKeyFlag } from '../getActiveKeys';
16 import { getDecryptedAddressKeysHelper } from '../getDecryptedAddressKeys';
17 import type { OnSKLPublishSuccess } from '../signedKeyList';
18 import { getSignedKeyListWithDeferredPublish } from '../signedKeyList';
20 interface GetReactivatedAddressKeys {
21     address: tsAddress;
22     oldUserKeys: KeyPair[];
23     newUserKeys: KeyPair[];
24     user: tsUser;
25     keyPassword: string;
26     keyTransparencyVerify: KeyTransparencyVerify;
29 type GetReactivateAddressKeysReturnValue =
30     | {
31           address: tsAddress;
32           reactivatedKeys?: undefined;
33           signedKeyList?: undefined;
34           onSKLPublishSuccess?: undefined;
35       }
36     | {
37           address: tsAddress;
38           reactivatedKeys: DecryptedKey[];
39           signedKeyList: SignedKeyList;
40           onSKLPublishSuccess: OnSKLPublishSuccess;
41       };
43 export const getReactivatedAddressKeys = async ({
44     address,
45     user,
46     oldUserKeys,
47     newUserKeys,
48     keyPassword,
49     keyTransparencyVerify,
50 }: GetReactivatedAddressKeys): Promise<GetReactivateAddressKeysReturnValue> => {
51     const empty = {
52         address,
53         reactivatedKeys: undefined,
54         signedKeyList: undefined,
55     } as const;
57     const oldDecryptedAddressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, oldUserKeys, keyPassword);
59     // All keys were able to decrypt previously, can just return.
60     if (oldDecryptedAddressKeys.length === address.Keys.length) {
61         return empty;
62     }
63     const newDecryptedAddressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, newUserKeys, keyPassword);
65     // No difference in how many keys were able to get decrypted
66     if (oldDecryptedAddressKeys.length === newDecryptedAddressKeys.length) {
67         return empty;
68     }
69     if (newDecryptedAddressKeys.length < oldDecryptedAddressKeys.length) {
70         throw new Error('More old decryptable keys than new, should never happen');
71     }
73     // New keys were able to get decrypted
74     const oldDecryptedAddressKeysSet = new Set<string>(oldDecryptedAddressKeys.map(({ ID }) => ID));
75     const reactivatedKeys = newDecryptedAddressKeys.filter(({ ID }) => !oldDecryptedAddressKeysSet.has(ID));
76     const reactivatedKeysSet = new Set<string>(reactivatedKeys.map(({ ID }) => ID));
78     if (!reactivatedKeysSet.size) {
79         return empty;
80     }
82     const oldAddressKeysMap = new Map<string, Key>(address.Keys.map((Key) => [Key.ID, Key]));
83     const newActiveKeys = await getActiveAddressKeys(
84         address,
85         address.SignedKeyList,
86         address.Keys,
87         newDecryptedAddressKeys
88     );
89     const setReactivateKeyFlag = <V extends ActiveKeyWithVersion>(activeKey: V) => {
90         if (!reactivatedKeysSet.has(activeKey.ID)) {
91             return activeKey;
92         }
93         return {
94             ...activeKey,
95             flags: getReactivatedKeyFlag(address, oldAddressKeysMap.get(activeKey.ID)?.Flags),
96         };
97     };
98     const newActiveKeysFormatted = getNormalizedActiveAddressKeys(address, {
99         v4: newActiveKeys.v4.map(setReactivateKeyFlag),
100         v6: newActiveKeys.v6.map(setReactivateKeyFlag),
101     });
102     const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
103         newActiveKeysFormatted,
104         address,
105         keyTransparencyVerify
106     );
107     return {
108         address,
109         reactivatedKeys,
110         signedKeyList: signedKeyList,
111         onSKLPublishSuccess: onSKLPublishSuccess,
112     };
115 export const getAddressReactivationPayload = (results: GetReactivateAddressKeysReturnValue[]) => {
116     const AddressKeyFingerprints = unique(
117         results.reduce<KeyPair[]>((acc, { reactivatedKeys }) => {
118             if (!reactivatedKeys) {
119                 return acc;
120             }
121             return acc.concat(reactivatedKeys);
122         }, [])
123     ).map(({ privateKey }) => privateKey.getFingerprint());
125     const SignedKeyLists = results.reduce<{ [key: string]: SignedKeyList }>((acc, result) => {
126         if (!result.reactivatedKeys?.length || !result.signedKeyList) {
127             throw new Error('Missing reactivated keys');
128         }
129         acc[result.address.ID] = result.signedKeyList;
130         return acc;
131     }, {});
133     return {
134         AddressKeyFingerprints,
135         SignedKeyLists,
136     };
139 interface GetReactivatedAddressesKeys {
140     addresses: tsAddress[];
141     oldUserKeys: KeyPair[];
142     newUserKeys: KeyPair[];
143     user: tsUser;
144     keyPassword: string;
145     keyTransparencyVerify: KeyTransparencyVerify;
148 export const getReactivatedAddressesKeys = async ({
149     addresses,
150     oldUserKeys,
151     newUserKeys,
152     user,
153     keyPassword,
154     keyTransparencyVerify,
155 }: GetReactivatedAddressesKeys) => {
156     const promises = addresses.map((address) =>
157         getReactivatedAddressKeys({
158             address,
159             oldUserKeys,
160             newUserKeys,
161             user,
162             keyPassword,
163             keyTransparencyVerify,
164         })
165     );
166     const results = await Promise.all(promises);
167     return results.filter(({ reactivatedKeys }) => {
168         if (!reactivatedKeys) {
169             return false;
170         }
171         return reactivatedKeys.length > 0;
172     });
175 export const resetUserId = async (Key: Key, reactivatedKey: PrivateKeyReference) => {
176     // Before the new key format imposed after key migration, the address and user key were the same key.
177     // Users may have exported one of the two. Upon reactivation the fingerprint could match a user key
178     // to the corresponding address key or vice versa. For that reason, the userids are reset to the userids
179     // of the old key.
180     const inactiveKey = await CryptoProxy.importPublicKey({ armoredKey: Key.PrivateKey });
181     // Warning: This function mutates the target key.
182     await CryptoProxy.replaceUserIDs({ sourceKey: inactiveKey, targetKey: reactivatedKey });