1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import { getDefaultKeyFlags } from '@proton/shared/lib/keys';
5 import { getApiError } from '../../api/helpers/apiErrorHelper';
6 import { reactivateUserKeyRouteV2, reactiveLegacyAddressKeyRouteV2 } from '../../api/keys';
7 import { HTTP_STATUS_CODE } from '../../constants';
13 KeyTransparencyVerify,
16 } from '../../interfaces';
17 import type { SimpleMap } from '../../interfaces/utils';
18 import { generateAddressKeyTokens } from '../addressKeys';
22 getNormalizedActiveKeys,
24 getReactivatedKeyFlag,
25 } from '../getActiveKeys';
26 import { getDecryptedAddressKeysHelper } from '../getDecryptedAddressKeys';
27 import { getPrimaryKey } from '../getPrimaryKey';
28 import { getHasMigratedAddressKey } from '../keyMigration';
29 import { getSignedKeyListWithDeferredPublish } from '../signedKeyList';
30 import type { KeyReactivationData, KeyReactivationRecord, OnKeyReactivationCallback } from './interface';
31 import { getAddressReactivationPayload, getReactivatedAddressesKeys, resetUserId } from './reactivateKeyHelper';
33 interface ReactivateUserKeysArguments {
34 addressRecordsInV2Format: KeyReactivationRecord[];
38 activeKeys: ActiveKey[];
39 onReactivation: OnKeyReactivationCallback;
40 keysToReactivate: KeyReactivationData[];
42 keyTransparencyVerify: KeyTransparencyVerify;
45 export const reactivateUserKeys = async ({
46 addressRecordsInV2Format,
54 keyTransparencyVerify,
55 }: ReactivateUserKeysArguments) => {
56 const keyReactivationDataMap = addressRecordsInV2Format.reduce<SimpleMap<KeyReactivationData>>((acc, record) => {
57 record.keysToReactivate.forEach((keyData) => {
58 acc[keyData.Key.ID] = keyData;
62 const reactivatedAddressKeysMap: SimpleMap<boolean> = {};
63 const allReactivatedAddressKeysMap: SimpleMap<boolean> = {};
65 let mutableActiveKeys = activeKeys;
66 let mutableAddresses = addresses;
68 for (const keyToReactivate of keysToReactivate) {
69 const { id, Key, privateKey: reactivatedKey } = keyToReactivate;
72 if (!reactivatedKey) {
73 throw new Error('Missing key');
76 await resetUserId(Key, reactivatedKey);
78 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
79 privateKey: reactivatedKey,
80 passphrase: keyPassword,
82 const newActiveKey = await getActiveKeyObject(reactivatedKey, {
84 primary: getPrimaryFlag(mutableActiveKeys),
85 flags: getDefaultKeyFlags(undefined),
87 const updatedActiveKeys = getNormalizedActiveKeys(undefined, [...mutableActiveKeys, newActiveKey]);
89 const reactivatedAddressKeysResult = await getReactivatedAddressesKeys({
91 oldUserKeys: mutableActiveKeys,
92 newUserKeys: updatedActiveKeys,
95 keyTransparencyVerify,
97 const addressReactivationPayload = getAddressReactivationPayload(reactivatedAddressKeysResult);
98 mutableAddresses = mutableAddresses.map((address) => {
99 const updatedSignedKeyList = addressReactivationPayload.SignedKeyLists[address.ID];
100 if (updatedSignedKeyList) {
104 ...updatedSignedKeyList,
113 reactivateUserKeyRouteV2({
115 PrivateKey: privateKeyArmored,
116 ...addressReactivationPayload,
119 // Only once the SKLs have been successfully posted we add it to the KT commit state.
121 reactivatedAddressKeysResult.map(({ onSKLPublishSuccess }) =>
122 onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
126 mutableActiveKeys = updatedActiveKeys;
128 onReactivation(id, 'ok');
130 // Notify all the address keys that got reactivated from this user key
131 reactivatedAddressKeysResult.forEach(({ reactivatedKeys }) => {
132 reactivatedKeys?.forEach(({ ID }) => {
133 allReactivatedAddressKeysMap[ID] = true;
134 const reactivationData = keyReactivationDataMap[ID];
135 if (reactivationData) {
136 onReactivation(reactivationData.id, 'ok');
137 reactivatedAddressKeysMap[reactivationData.id] = true;
142 onReactivation(id, e);
143 const { status } = getApiError(e);
144 if (status === HTTP_STATUS_CODE.FORBIDDEN) {
145 // The password prompt has been cancelled. No need to attempt to reactivate the other keys.
151 addressRecordsInV2Format.forEach(({ keysToReactivate }) => {
152 keysToReactivate.forEach(({ id, privateKey }) => {
153 if (!reactivatedAddressKeysMap[id] && !privateKey) {
154 onReactivation(id, new Error('User key inactivate'));
160 userKeys: mutableActiveKeys,
161 addresses: mutableAddresses,
162 allReactivatedAddressKeysMap,
166 interface ReactivateAddressKeysV2Arguments {
169 activeKeys: ActiveKey[];
170 userKey: PrivateKeyReference;
171 onReactivation: OnKeyReactivationCallback;
172 keysToReactivate: KeyReactivationData[];
173 keyTransparencyVerify: KeyTransparencyVerify;
176 export const reactivateAddressKeysV2 = async ({
183 keyTransparencyVerify,
184 }: ReactivateAddressKeysV2Arguments) => {
185 let mutableActiveKeys = activeKeys;
187 for (const keyToReactivate of keysToReactivate) {
188 const { id, Key, privateKey: reactivatedKey } = keyToReactivate;
189 const { ID, Flags } = Key;
191 if (!reactivatedKey) {
192 throw new Error('Missing key');
195 await resetUserId(Key, reactivatedKey);
197 const { token, encryptedToken, signature } = await generateAddressKeyTokens(userKey);
198 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
199 privateKey: reactivatedKey,
202 const newActiveKey = await getActiveKeyObject(reactivatedKey, {
204 primary: getPrimaryFlag(mutableActiveKeys),
205 flags: getReactivatedKeyFlag(address, Flags),
207 const updatedActiveKeys = getNormalizedActiveKeys(address, [...mutableActiveKeys, newActiveKey]);
208 const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
211 keyTransparencyVerify
214 reactiveLegacyAddressKeyRouteV2({
216 PrivateKey: privateKeyArmored,
217 SignedKeyList: signedKeyList,
218 Token: encryptedToken,
219 Signature: signature,
222 await onSKLPublishSuccess();
224 mutableActiveKeys = updatedActiveKeys;
226 onReactivation(id, 'ok');
228 onReactivation(id, e);
232 return mutableActiveKeys;
235 export interface ReactivateKeysProcessV2Arguments {
239 addresses: tsAddress[];
240 userKeys: DecryptedKey[];
241 keyReactivationRecords: KeyReactivationRecord[];
242 onReactivation: OnKeyReactivationCallback;
243 keyTransparencyVerify: KeyTransparencyVerify;
246 const reactivateKeysProcessV2 = async ({
249 keyReactivationRecords,
252 addresses: oldAddresses,
253 userKeys: oldUserKeys,
254 keyTransparencyVerify,
255 }: ReactivateKeysProcessV2Arguments) => {
256 const { userRecord, addressRecordsInV2Format, addressRecordsInLegacyFormatOrWithBackup } =
257 keyReactivationRecords.reduce<{
258 addressRecordsInV2Format: KeyReactivationRecord[];
259 addressRecordsInLegacyFormatOrWithBackup: KeyReactivationRecord[];
260 userRecord?: KeyReactivationRecord;
263 const { user, address, keysToReactivate } = record;
265 acc.userRecord = record;
268 const keysInV2Format = keysToReactivate.filter(
269 ({ privateKey, Key }) => !privateKey && getHasMigratedAddressKey(Key)
271 const keysInLegacyFormatOrWithBackup = keysToReactivate.filter(
272 ({ privateKey, Key }) => privateKey || !getHasMigratedAddressKey(Key)
275 if (keysInV2Format.length) {
276 acc.addressRecordsInV2Format.push({
278 keysToReactivate: keysInV2Format,
282 if (keysInLegacyFormatOrWithBackup.length) {
283 acc.addressRecordsInLegacyFormatOrWithBackup.push({
285 keysToReactivate: keysInLegacyFormatOrWithBackup,
292 { addressRecordsInV2Format: [], addressRecordsInLegacyFormatOrWithBackup: [], userRecord: undefined }
295 let userKeys = oldUserKeys;
296 let addresses = oldAddresses;
297 let allReactivatedAddressKeysMap: SimpleMap<boolean> = {};
300 const activeUserKeys = await getActiveKeys(undefined, null, user.Keys, userKeys);
301 const userKeysReactivationResult = await reactivateUserKeys({
304 activeKeys: activeUserKeys,
305 addresses: oldAddresses,
306 keysToReactivate: userRecord.keysToReactivate,
309 addressRecordsInV2Format,
310 keyTransparencyVerify,
312 userKeys = userKeysReactivationResult.userKeys;
313 addresses = userKeysReactivationResult.addresses;
314 allReactivatedAddressKeysMap = userKeysReactivationResult.allReactivatedAddressKeysMap;
316 userRecord.keysToReactivate.forEach(({ id }) => onReactivation(id, e));
317 addressRecordsInV2Format.forEach(({ keysToReactivate }) => {
318 keysToReactivate.forEach(({ id }) => onReactivation(id, e));
323 const primaryPrivateUserKey = getPrimaryKey(userKeys)?.privateKey;
325 for (const { address: oldAddress, keysToReactivate } of addressRecordsInLegacyFormatOrWithBackup) {
326 const keysLeftToReactivate: KeyReactivationData[] = [];
327 keysToReactivate.forEach((x) => {
328 // Even if this key was uploaded, it may have gotten reactivated from a user key earlier, and so it
329 // should not be attempted to be reactivated again
330 const alreadyReactivated = allReactivatedAddressKeysMap[x.Key.ID] === true;
331 if (alreadyReactivated) {
332 onReactivation(x.id, 'ok');
334 keysLeftToReactivate.push(x);
338 const address = addresses.find(({ ID: otherID }) => oldAddress?.ID === otherID);
339 if (!address || !primaryPrivateUserKey) {
340 throw new Error('Missing dependency');
343 const addressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, userKeys, '');
344 const activeAddressKeys = await getActiveKeys(address, address.SignedKeyList, address.Keys, addressKeys);
346 await reactivateAddressKeysV2({
349 activeKeys: activeAddressKeys,
350 userKey: primaryPrivateUserKey,
352 keysToReactivate: keysLeftToReactivate,
353 keyTransparencyVerify,
356 keysLeftToReactivate.forEach(({ id }) => onReactivation(id, e));
361 export default reactivateKeysProcessV2;