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';
8 type ActiveKeyWithVersion,
10 type ActiveAddressKeysByVersion,
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' }),
41 fingerprint: privateKey.getFingerprint(),
42 sha256Fingerprints: await CryptoProxy.getSHA256Fingerprints({ key: privateKey }),
44 } as ActiveKey<PrivateKeyReferenceWithVersion>;
47 export const getActiveAddressKeys = async (
48 address: Address | undefined,
49 signedKeyList: SignedKeyList | null | undefined,
51 decryptedKeys: DecryptedKey[]
52 ): Promise<ActiveAddressKeysByVersion> => {
53 if (!decryptedKeys.length) {
54 return { v4: [], v6: [] };
57 const signedKeyListMap = getSignedKeyListMap(getParsedSignedKeyList(signedKeyList?.Data));
58 const keysMap = keys.reduce<{ [key: string]: Key | undefined }>((acc, key) => {
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>[] }>(
80 const decryptedKeyToActiveKey = async <KeyVersion extends PrivateKeyReferenceV4 | PrivateKeyReferenceV6>(
81 { ID, privateKey }: DecryptedKey<KeyVersion>,
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, {
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>>;
99 v4: await Promise.all(decryptedKeysByVersion.v4.map(decryptedKeyToActiveKey)),
100 v6: await Promise.all(decryptedKeysByVersion.v6.map(decryptedKeyToActiveKey)),
104 export const getActiveUserKeys = async (keys: Key[], decryptedKeys: DecryptedKey[]): Promise<ActiveKey[]> => {
105 if (!decryptedKeys.length) {
109 const keysMap = keys.reduce<{ [key: string]: Key | undefined }>((acc, key) => {
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, {
119 primary: Key?.Primary ?? defaultPrimaryValue,
120 flags: Key?.Flags ?? getDefaultKeyFlags(undefined),
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,
133 export const getNormalizedActiveAddressKeys = (address: Address | undefined, keys: ActiveAddressKeysByVersion) => {
134 const normalize = <V extends ActiveKeyWithVersion>(result: V, index: number): V => ({
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,
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),
149 export const getNormalizedActiveUserKeys = (address: Address | undefined, keys: ActiveKey[]) => {
150 const normalize = (result: ActiveKey, index: number): ActiveKey => ({
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,
157 return keys.sort((a, b) => b.primary - a.primary).map(normalize);