1 import type { PrivateKeyReference, PrivateKeyReferenceV4, PrivateKeyReferenceV6 } 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';
10 ActiveAddressKeysByVersion
11 } from '../../interfaces';
17 type KeyTransparencyVerify,
19 type Address as tsAddress,
21 } from '../../interfaces';
22 import type { SimpleMap } from '../../interfaces/utils';
23 import { generateAddressKeyTokens } from '../addressKeys';
28 getNormalizedActiveAddressKeys,
29 getNormalizedActiveUserKeys,
31 getReactivatedKeyFlag,
32 } from '../getActiveKeys';
33 import { getDecryptedAddressKeysHelper } from '../getDecryptedAddressKeys';
34 import { getPrimaryKey } from '../getPrimaryKey';
35 import { getHasMigratedAddressKey } from '../keyMigration';
36 import { getSignedKeyListWithDeferredPublish } from '../signedKeyList';
37 import type { KeyReactivationData, KeyReactivationRecord, OnKeyReactivationCallback } from './interface';
38 import { getAddressReactivationPayload, getReactivatedAddressesKeys, resetUserId } from './reactivateKeyHelper';
40 interface ReactivateUserKeysArguments {
41 addressRecordsInV2Format: KeyReactivationRecord[];
45 activeKeys: ActiveKey[]; // user keys do not need to be differentiated by key version
46 onReactivation: OnKeyReactivationCallback;
47 keysToReactivate: KeyReactivationData[];
49 keyTransparencyVerify: KeyTransparencyVerify;
52 export const reactivateUserKeys = async ({
53 addressRecordsInV2Format,
61 keyTransparencyVerify,
62 }: ReactivateUserKeysArguments) => {
63 const keyReactivationDataMap = addressRecordsInV2Format.reduce<SimpleMap<KeyReactivationData>>((acc, record) => {
64 record.keysToReactivate.forEach((keyData) => {
65 acc[keyData.Key.ID] = keyData;
69 const reactivatedAddressKeysMap: SimpleMap<boolean> = {};
70 const allReactivatedAddressKeysMap: SimpleMap<boolean> = {};
72 let mutableActiveKeys = activeKeys;
73 let mutableAddresses = addresses;
75 for (const keyToReactivate of keysToReactivate) {
76 const { id, Key, privateKey: reactivatedKey } = keyToReactivate;
79 if (!reactivatedKey) {
80 throw new Error('Missing key');
83 await resetUserId(Key, reactivatedKey);
85 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
86 privateKey: reactivatedKey,
87 passphrase: keyPassword,
89 const newActiveKey = await getActiveKeyObject(
90 reactivatedKey as PrivateKeyReferenceV4 | PrivateKeyReferenceV6,
93 primary: getPrimaryFlag(mutableActiveKeys),
94 flags: getDefaultKeyFlags(undefined),
97 const updatedActiveKeys = getNormalizedActiveUserKeys(undefined, [...mutableActiveKeys, newActiveKey]);
99 const reactivatedAddressKeysResult = await getReactivatedAddressesKeys({
101 oldUserKeys: mutableActiveKeys,
102 newUserKeys: updatedActiveKeys,
105 keyTransparencyVerify,
107 const addressReactivationPayload = getAddressReactivationPayload(reactivatedAddressKeysResult);
108 mutableAddresses = mutableAddresses.map((address) => {
109 const updatedSignedKeyList = addressReactivationPayload.SignedKeyLists[address.ID];
110 if (updatedSignedKeyList) {
114 ...updatedSignedKeyList,
123 reactivateUserKeyRouteV2({
125 PrivateKey: privateKeyArmored,
126 ...addressReactivationPayload,
129 // Only once the SKLs have been successfully posted we add it to the KT commit state.
131 reactivatedAddressKeysResult.map(({ onSKLPublishSuccess }) =>
132 onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
136 mutableActiveKeys = updatedActiveKeys;
138 onReactivation(id, 'ok');
140 // Notify all the address keys that got reactivated from this user key
141 reactivatedAddressKeysResult.forEach(({ reactivatedKeys }) => {
142 reactivatedKeys?.forEach(({ ID }) => {
143 allReactivatedAddressKeysMap[ID] = true;
144 const reactivationData = keyReactivationDataMap[ID];
145 if (reactivationData) {
146 onReactivation(reactivationData.id, 'ok');
147 reactivatedAddressKeysMap[reactivationData.id] = true;
152 onReactivation(id, e);
153 const { status } = getApiError(e);
154 if (status === HTTP_STATUS_CODE.FORBIDDEN) {
155 // The password prompt has been cancelled. No need to attempt to reactivate the other keys.
161 addressRecordsInV2Format.forEach(({ keysToReactivate }) => {
162 keysToReactivate.forEach(({ id, privateKey }) => {
163 if (!reactivatedAddressKeysMap[id] && !privateKey) {
164 onReactivation(id, new Error('User key inactivate'));
170 userKeys: mutableActiveKeys,
171 addresses: mutableAddresses,
172 allReactivatedAddressKeysMap,
176 interface ReactivateAddressKeysV2Arguments {
179 activeKeys: ActiveAddressKeysByVersion;
180 userKey: PrivateKeyReference;
181 onReactivation: OnKeyReactivationCallback;
182 keysToReactivate: KeyReactivationData[];
183 keyTransparencyVerify: KeyTransparencyVerify;
186 export const reactivateAddressKeysV2 = async ({
193 keyTransparencyVerify,
194 }: ReactivateAddressKeysV2Arguments) => {
195 let mutableActiveKeys = activeKeys;
197 for (const keyToReactivate of keysToReactivate) {
198 const { id, Key, privateKey: reactivatedKey } = keyToReactivate;
199 const { ID, Flags } = Key;
201 if (!reactivatedKey) {
202 throw new Error('Missing key');
205 await resetUserId(Key, reactivatedKey);
207 const { token, encryptedToken, signature } = await generateAddressKeyTokens(userKey);
208 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
209 privateKey: reactivatedKey,
212 const newActiveKey = (await getActiveKeyObject(
213 reactivatedKey as PrivateKeyReferenceV4 | PrivateKeyReferenceV6,
216 // We do not automatically set a v6 key as primary, because it will fail if forwarding is enabled for the address
217 primary: reactivatedKey.isPrivateKeyV6() ? 0 : getPrimaryFlag(mutableActiveKeys.v4),
218 flags: getReactivatedKeyFlag(address, Flags),
220 )) as ActiveKeyWithVersion;
221 const toNormalize = isActiveKeyV6(newActiveKey)
222 ? { v4: mutableActiveKeys.v4, v6: [...mutableActiveKeys.v6, newActiveKey] }
223 : { v4: [...mutableActiveKeys.v4, newActiveKey], v6: mutableActiveKeys.v6 };
224 const updatedActiveKeys = getNormalizedActiveAddressKeys(address, toNormalize);
225 const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
228 keyTransparencyVerify
231 reactiveLegacyAddressKeyRouteV2({
233 PrivateKey: privateKeyArmored,
234 SignedKeyList: signedKeyList,
235 Token: encryptedToken,
236 Signature: signature,
239 await onSKLPublishSuccess();
241 mutableActiveKeys = updatedActiveKeys;
243 onReactivation(id, 'ok');
245 onReactivation(id, e);
249 return mutableActiveKeys;
252 export interface ReactivateKeysProcessV2Arguments {
256 addresses: tsAddress[];
257 userKeys: DecryptedKey[];
258 keyReactivationRecords: KeyReactivationRecord[];
259 onReactivation: OnKeyReactivationCallback;
260 keyTransparencyVerify: KeyTransparencyVerify;
263 const reactivateKeysProcessV2 = async ({
266 keyReactivationRecords,
269 addresses: oldAddresses,
270 userKeys: oldUserKeys,
271 keyTransparencyVerify,
272 }: ReactivateKeysProcessV2Arguments) => {
273 const { userRecord, addressRecordsInV2Format, addressRecordsInLegacyFormatOrWithBackup } =
274 keyReactivationRecords.reduce<{
275 addressRecordsInV2Format: KeyReactivationRecord[];
276 addressRecordsInLegacyFormatOrWithBackup: KeyReactivationRecord[];
277 userRecord?: KeyReactivationRecord;
280 const { user, address, keysToReactivate } = record;
282 acc.userRecord = record;
285 const keysInV2Format = keysToReactivate.filter(
286 ({ privateKey, Key }) => !privateKey && getHasMigratedAddressKey(Key)
288 const keysInLegacyFormatOrWithBackup = keysToReactivate.filter(
289 ({ privateKey, Key }) => privateKey || !getHasMigratedAddressKey(Key)
292 if (keysInV2Format.length) {
293 acc.addressRecordsInV2Format.push({
295 keysToReactivate: keysInV2Format,
299 if (keysInLegacyFormatOrWithBackup.length) {
300 acc.addressRecordsInLegacyFormatOrWithBackup.push({
302 keysToReactivate: keysInLegacyFormatOrWithBackup,
309 { addressRecordsInV2Format: [], addressRecordsInLegacyFormatOrWithBackup: [], userRecord: undefined }
312 let userKeys = oldUserKeys;
313 let addresses = oldAddresses;
314 let allReactivatedAddressKeysMap: SimpleMap<boolean> = {};
317 const activeUserKeys = await getActiveUserKeys(user.Keys, userKeys);
318 const userKeysReactivationResult = await reactivateUserKeys({
321 activeKeys: activeUserKeys,
322 addresses: oldAddresses,
323 keysToReactivate: userRecord.keysToReactivate,
326 addressRecordsInV2Format,
327 keyTransparencyVerify,
329 userKeys = userKeysReactivationResult.userKeys;
330 addresses = userKeysReactivationResult.addresses;
331 allReactivatedAddressKeysMap = userKeysReactivationResult.allReactivatedAddressKeysMap;
333 userRecord.keysToReactivate.forEach(({ id }) => onReactivation(id, e));
334 addressRecordsInV2Format.forEach(({ keysToReactivate }) => {
335 keysToReactivate.forEach(({ id }) => onReactivation(id, e));
340 const primaryPrivateUserKey = getPrimaryKey(userKeys)?.privateKey;
342 for (const { address: oldAddress, keysToReactivate } of addressRecordsInLegacyFormatOrWithBackup) {
343 const keysLeftToReactivate: KeyReactivationData[] = [];
344 keysToReactivate.forEach((x) => {
345 // Even if this key was uploaded, it may have gotten reactivated from a user key earlier, and so it
346 // should not be attempted to be reactivated again
347 const alreadyReactivated = allReactivatedAddressKeysMap[x.Key.ID] === true;
348 if (alreadyReactivated) {
349 onReactivation(x.id, 'ok');
351 keysLeftToReactivate.push(x);
355 const address = addresses.find(({ ID: otherID }) => oldAddress?.ID === otherID);
356 if (!address || !primaryPrivateUserKey) {
357 throw new Error('Missing dependency');
360 const addressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, userKeys, '');
361 const activeAddressKeys = await getActiveAddressKeys(
363 address.SignedKeyList,
368 await reactivateAddressKeysV2({
371 activeKeys: activeAddressKeys,
372 userKey: primaryPrivateUserKey,
374 keysToReactivate: keysLeftToReactivate,
375 keyTransparencyVerify,
378 keysLeftToReactivate.forEach(({ id }) => onReactivation(id, e));
383 export default reactivateKeysProcessV2;