1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import unique from '@proton/utils/unique';
10 KeyTransparencyVerify,
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 {
22 oldUserKeys: KeyPair[];
23 newUserKeys: KeyPair[];
26 keyTransparencyVerify: KeyTransparencyVerify;
29 type GetReactivateAddressKeysReturnValue =
32 reactivatedKeys?: undefined;
33 signedKeyList?: undefined;
34 onSKLPublishSuccess?: undefined;
38 reactivatedKeys: DecryptedKey[];
39 signedKeyList: SignedKeyList;
40 onSKLPublishSuccess: OnSKLPublishSuccess;
43 export const getReactivatedAddressKeys = async ({
49 keyTransparencyVerify,
50 }: GetReactivatedAddressKeys): Promise<GetReactivateAddressKeysReturnValue> => {
53 reactivatedKeys: undefined,
54 signedKeyList: undefined,
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) {
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) {
69 if (newDecryptedAddressKeys.length < oldDecryptedAddressKeys.length) {
70 throw new Error('More old decryptable keys than new, should never happen');
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) {
82 const oldAddressKeysMap = new Map<string, Key>(address.Keys.map((Key) => [Key.ID, Key]));
83 const newActiveKeys = await getActiveAddressKeys(
85 address.SignedKeyList,
87 newDecryptedAddressKeys
89 const setReactivateKeyFlag = <V extends ActiveKeyWithVersion>(activeKey: V) => {
90 if (!reactivatedKeysSet.has(activeKey.ID)) {
95 flags: getReactivatedKeyFlag(address, oldAddressKeysMap.get(activeKey.ID)?.Flags),
98 const newActiveKeysFormatted = getNormalizedActiveAddressKeys(address, {
99 v4: newActiveKeys.v4.map(setReactivateKeyFlag),
100 v6: newActiveKeys.v6.map(setReactivateKeyFlag),
102 const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
103 newActiveKeysFormatted,
105 keyTransparencyVerify
110 signedKeyList: signedKeyList,
111 onSKLPublishSuccess: onSKLPublishSuccess,
115 export const getAddressReactivationPayload = (results: GetReactivateAddressKeysReturnValue[]) => {
116 const AddressKeyFingerprints = unique(
117 results.reduce<KeyPair[]>((acc, { reactivatedKeys }) => {
118 if (!reactivatedKeys) {
121 return acc.concat(reactivatedKeys);
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');
129 acc[result.address.ID] = result.signedKeyList;
134 AddressKeyFingerprints,
139 interface GetReactivatedAddressesKeys {
140 addresses: tsAddress[];
141 oldUserKeys: KeyPair[];
142 newUserKeys: KeyPair[];
145 keyTransparencyVerify: KeyTransparencyVerify;
148 export const getReactivatedAddressesKeys = async ({
154 keyTransparencyVerify,
155 }: GetReactivatedAddressesKeys) => {
156 const promises = addresses.map((address) =>
157 getReactivatedAddressKeys({
163 keyTransparencyVerify,
166 const results = await Promise.all(promises);
167 return results.filter(({ reactivatedKeys }) => {
168 if (!reactivatedKeys) {
171 return reactivatedKeys.length > 0;
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
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 });