1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import unique from '@proton/utils/unique';
13 } from '../../interfaces';
14 import { getActiveKeys, getNormalizedActiveKeys, getReactivatedKeyFlag } from '../getActiveKeys';
15 import { getDecryptedAddressKeysHelper } from '../getDecryptedAddressKeys';
16 import type { OnSKLPublishSuccess } from '../signedKeyList';
17 import { getSignedKeyListWithDeferredPublish } from '../signedKeyList';
19 interface GetReactivatedAddressKeys {
21 oldUserKeys: KeyPair[];
22 newUserKeys: KeyPair[];
25 keyTransparencyVerify: KeyTransparencyVerify;
28 type GetReactivateAddressKeysReturnValue =
31 reactivatedKeys?: undefined;
32 signedKeyList?: undefined;
33 onSKLPublishSuccess?: undefined;
37 reactivatedKeys: DecryptedKey[];
38 signedKeyList: SignedKeyList;
39 onSKLPublishSuccess: OnSKLPublishSuccess;
42 export const getReactivatedAddressKeys = async ({
48 keyTransparencyVerify,
49 }: GetReactivatedAddressKeys): Promise<GetReactivateAddressKeysReturnValue> => {
52 reactivatedKeys: undefined,
53 signedKeyList: undefined,
56 const oldDecryptedAddressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, oldUserKeys, keyPassword);
58 // All keys were able to decrypt previously, can just return.
59 if (oldDecryptedAddressKeys.length === address.Keys.length) {
62 const newDecryptedAddressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, newUserKeys, keyPassword);
64 // No difference in how many keys were able to get decrypted
65 if (oldDecryptedAddressKeys.length === newDecryptedAddressKeys.length) {
68 if (newDecryptedAddressKeys.length < oldDecryptedAddressKeys.length) {
69 throw new Error('More old decryptable keys than new, should never happen');
72 // New keys were able to get decrypted
73 const oldDecryptedAddressKeysSet = new Set<string>(oldDecryptedAddressKeys.map(({ ID }) => ID));
74 const reactivatedKeys = newDecryptedAddressKeys.filter(({ ID }) => !oldDecryptedAddressKeysSet.has(ID));
75 const reactivatedKeysSet = new Set<string>(reactivatedKeys.map(({ ID }) => ID));
77 if (!reactivatedKeysSet.size) {
81 const oldAddressKeysMap = new Map<string, Key>(address.Keys.map((Key) => [Key.ID, Key]));
82 const newActiveKeys = await getActiveKeys(address, address.SignedKeyList, address.Keys, newDecryptedAddressKeys);
83 const newActiveKeysFormatted = getNormalizedActiveKeys(
85 newActiveKeys.map((activeKey) => {
86 if (!reactivatedKeysSet.has(activeKey.ID)) {
91 flags: getReactivatedKeyFlag(address, oldAddressKeysMap.get(activeKey.ID)?.Flags),
95 const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
96 newActiveKeysFormatted,
103 signedKeyList: signedKeyList,
104 onSKLPublishSuccess: onSKLPublishSuccess,
108 export const getAddressReactivationPayload = (results: GetReactivateAddressKeysReturnValue[]) => {
109 const AddressKeyFingerprints = unique(
110 results.reduce<KeyPair[]>((acc, { reactivatedKeys }) => {
111 if (!reactivatedKeys) {
114 return acc.concat(reactivatedKeys);
116 ).map(({ privateKey }) => privateKey.getFingerprint());
118 const SignedKeyLists = results.reduce<{ [key: string]: SignedKeyList }>((acc, result) => {
119 if (!result.reactivatedKeys?.length || !result.signedKeyList) {
120 throw new Error('Missing reactivated keys');
122 acc[result.address.ID] = result.signedKeyList;
127 AddressKeyFingerprints,
132 interface GetReactivatedAddressesKeys {
133 addresses: tsAddress[];
134 oldUserKeys: KeyPair[];
135 newUserKeys: KeyPair[];
138 keyTransparencyVerify: KeyTransparencyVerify;
141 export const getReactivatedAddressesKeys = async ({
147 keyTransparencyVerify,
148 }: GetReactivatedAddressesKeys) => {
149 const promises = addresses.map((address) =>
150 getReactivatedAddressKeys({
156 keyTransparencyVerify,
159 const results = await Promise.all(promises);
160 return results.filter(({ reactivatedKeys }) => {
161 if (!reactivatedKeys) {
164 return reactivatedKeys.length > 0;
168 export const resetUserId = async (Key: Key, reactivatedKey: PrivateKeyReference) => {
169 // Before the new key format imposed after key migration, the address and user key were the same key.
170 // Users may have exported one of the two. Upon reactivation the fingerprint could match a user key
171 // to the corresponding address key or vice versa. For that reason, the userids are reset to the userids
173 const inactiveKey = await CryptoProxy.importPublicKey({ armoredKey: Key.PrivateKey });
174 // Warning: This function mutates the target key.
175 await CryptoProxy.replaceUserIDs({ sourceKey: inactiveKey, targetKey: reactivatedKey });