Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / keys / getActiveKeys.ts
blobbe0ce63926c336cc77932bc5b25f9b5364084692
1 import type { PrivateKeyReferenceV4, PrivateKeyReferenceV6 } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
4 import { ADDRESS_TYPE, KEY_FLAG } from '../constants';
5 import { clearBit } from '../helpers/bitset';
6 import {
7     type ActiveKey,
8     type ActiveKeyWithVersion,
9     type Address,
10     type ActiveAddressKeysByVersion,
11     type DecryptedKey,
12     type Key,
13     type SignedKeyList,
14     isActiveKeyV6,
15 } from '../interfaces';
16 import { getDefaultKeyFlags, setExternalFlags } from './keyFlags';
17 import { getParsedSignedKeyList, getSignedKeyListMap } from './signedKeyList';
19 export const getPrimaryFlag = (keys: ActiveKey[]): 1 | 0 => {
20     return !keys.length ? 1 : 0;
23 // When a key is disabled, the NOT_OBSOLETE flag is removed. Thus when the key is reactivated, the client uses the old key flags, with the not obsolete flag removed. This is mainly to take into account the old NOT_COMPROMISED flag
24 export const getReactivatedKeyFlag = (address: Address, Flags: number | undefined) => {
25     return clearBit(Flags ?? getDefaultKeyFlags(address), KEY_FLAG.FLAG_NOT_OBSOLETE);
28 export const getActiveKeyObject = async <
29     PrivateKeyReferenceWithVersion extends PrivateKeyReferenceV4 | PrivateKeyReferenceV6,
31     privateKey: PrivateKeyReferenceWithVersion,
32     partial: Partial<ActiveKey> & { ID: string } & Pick<ActiveKey, 'flags'>
33 ): Promise<ActiveKey<PrivateKeyReferenceWithVersion>> => {
34     const publicKey = await CryptoProxy.importPublicKey({
35         binaryKey: await CryptoProxy.exportPublicKey({ key: privateKey, format: 'binary' }),
36     });
37     return {
38         privateKey,
39         publicKey,
40         primary: 0,
41         fingerprint: privateKey.getFingerprint(),
42         sha256Fingerprints: await CryptoProxy.getSHA256Fingerprints({ key: privateKey }),
43         ...partial,
44     } as ActiveKey<PrivateKeyReferenceWithVersion>;
47 export const getActiveAddressKeys = async (
48     address: Address | undefined,
49     signedKeyList: SignedKeyList | null | undefined,
50     keys: Key[],
51     decryptedKeys: DecryptedKey[]
52 ): Promise<ActiveAddressKeysByVersion> => {
53     if (!decryptedKeys.length) {
54         return { v4: [], v6: [] };
55     }
57     const signedKeyListMap = getSignedKeyListMap(getParsedSignedKeyList(signedKeyList?.Data));
58     const keysMap = keys.reduce<{ [key: string]: Key | undefined }>((acc, key) => {
59         acc[key.ID] = key;
60         return acc;
61     }, {});
63     const isV6Key = (
64         key: DecryptedKey<PrivateKeyReferenceV6> | DecryptedKey<PrivateKeyReferenceV4>
65     ): key is DecryptedKey<PrivateKeyReferenceV6> => key.privateKey.isPrivateKeyV6();
66     const decryptedKeysByVersion = (
67         decryptedKeys as (DecryptedKey<PrivateKeyReferenceV6> | DecryptedKey<PrivateKeyReferenceV4>)[]
68     ).reduce<{ v4: DecryptedKey<PrivateKeyReferenceV4>[]; v6: DecryptedKey<PrivateKeyReferenceV6>[] }>(
69         (prev, curr) => {
70             if (isV6Key(curr)) {
71                 prev.v6.push(curr);
72             } else {
73                 prev.v4.push(curr);
74             }
75             return prev;
76         },
77         { v4: [], v6: [] }
78     );
80     const decryptedKeyToActiveKey = async <KeyVersion extends PrivateKeyReferenceV4 | PrivateKeyReferenceV6>(
81         { ID, privateKey }: DecryptedKey<KeyVersion>,
82         index: number
83     ): Promise<ActiveKey<KeyVersion>> => {
84         const fingerprint = privateKey.getFingerprint();
85         const Key = keysMap[ID];
86         const signedKeyListItem = signedKeyListMap[fingerprint];
87         const defaultPrimaryValue = privateKey.isPrivateKeyV6() ? 0 : index === 0 ? 1 : 0; // there might not be any v6 primary key
88         return getActiveKeyObject(privateKey, {
89             ID,
90             primary: signedKeyListItem?.Primary ?? Key?.Primary ?? defaultPrimaryValue,
91             // SKL may not exist for non-migrated users, fall back to the flag value of the key.
92             // Should be improved by asserting SKLs for migrated users, but pushed to later since SKL
93             // signatures are not verified.
94             flags: signedKeyListItem?.Flags ?? Key?.Flags ?? getDefaultKeyFlags(address),
95         }) as Promise<ActiveKey<KeyVersion>>;
96     };
98     return {
99         v4: await Promise.all(decryptedKeysByVersion.v4.map(decryptedKeyToActiveKey)),
100         v6: await Promise.all(decryptedKeysByVersion.v6.map(decryptedKeyToActiveKey)),
101     };
104 export const getActiveUserKeys = async (keys: Key[], decryptedKeys: DecryptedKey[]): Promise<ActiveKey[]> => {
105     if (!decryptedKeys.length) {
106         return [];
107     }
109     const keysMap = keys.reduce<{ [key: string]: Key | undefined }>((acc, key) => {
110         acc[key.ID] = key;
111         return acc;
112     }, {});
114     const decryptedKeyToActiveKey = async ({ ID, privateKey }: DecryptedKey, index: number) => {
115         const Key = keysMap[ID];
116         const defaultPrimaryValue = index === 0 ? 1 : 0;
117         return getActiveKeyObject(privateKey as PrivateKeyReferenceV4 | PrivateKeyReferenceV6, {
118             ID,
119             primary: Key?.Primary ?? defaultPrimaryValue,
120             flags: Key?.Flags ?? getDefaultKeyFlags(undefined),
121         });
122     };
124     return Promise.all(decryptedKeys.map(decryptedKeyToActiveKey));
128  * Normalize the given `keys` by setting the primary flag appropriately,
129  * ensuring at most one primary key is set for each version (v6 keys might not have any primary key set).
130  * @return normalized active keys where the first entry for each version is the primary key,
131  * if it exists.
132  */
133 export const getNormalizedActiveAddressKeys = (address: Address | undefined, keys: ActiveAddressKeysByVersion) => {
134     const normalize = <V extends ActiveKeyWithVersion>(result: V, index: number): V => ({
135         ...result,
136         flags: address?.Type === ADDRESS_TYPE.TYPE_EXTERNAL ? setExternalFlags(result.flags) : result.flags,
137         // Reset and normalize the primary key. The primary values can be doubly set to 1 if an old SKL is used.
138         // v6 keys might not have any primary key set
139         primary: isActiveKeyV6(result) ? (index === 0 ? result.primary : 0) : index === 0 ? 1 : 0,
140     });
141     const normalized: ActiveAddressKeysByVersion = {
142         v4: keys.v4.sort((a, b) => b.primary - a.primary).map(normalize),
143         v6: keys.v6.sort((a, b) => b.primary - a.primary).map(normalize),
144     };
146     return normalized;
149 export const getNormalizedActiveUserKeys = (address: Address | undefined, keys: ActiveKey[]) => {
150     const normalize = (result: ActiveKey, index: number): ActiveKey => ({
151         ...result,
152         flags: address?.Type === ADDRESS_TYPE.TYPE_EXTERNAL ? setExternalFlags(result.flags) : result.flags,
153         // Reset and normalize the primary key. For user key, there is always a single primary key (either v4 or v6)
154         primary: index === 0 ? 1 : 0,
155     });
157     return keys.sort((a, b) => b.primary - a.primary).map(normalize);