Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / hooks / useGetVerificationPreferences.ts
blob441a95498ebd8c5e780d28d7ea9728739992ece3
1 import { useCallback } from 'react';
3 import { useGetAddressKeys } from '@proton/account/addressKeys/hooks';
4 import { useGetAddresses } from '@proton/account/addresses/hooks';
5 import { useGetUserKeys } from '@proton/account/userKeys/hooks';
6 import type { PublicKeyReference } from '@proton/crypto';
7 import { useGetMailSettings } from '@proton/mail/mailSettings/hooks';
8 import getPublicKeysVcardHelper from '@proton/shared/lib/api/helpers/getPublicKeysVcardHelper';
9 import { ADDRESS_STATUS, KEY_FLAG, MINUTE, RECIPIENT_TYPES } from '@proton/shared/lib/constants';
10 import { hasBit } from '@proton/shared/lib/helpers/bitset';
11 import { canonicalizeEmail, canonicalizeInternalEmail } from '@proton/shared/lib/helpers/email';
12 import type { ApiKeysConfig } from '@proton/shared/lib/interfaces';
13 import { KT_VERIFICATION_STATUS } from '@proton/shared/lib/interfaces';
14 import type { GetVerificationPreferences } from '@proton/shared/lib/interfaces/hooks/GetVerificationPreferences';
15 import { splitKeys } from '@proton/shared/lib/keys';
16 import { getActiveKeys } from '@proton/shared/lib/keys/getActiveKeys';
17 import { getVerifyingKeys } from '@proton/shared/lib/keys/publicKeys';
19 import useApi from './useApi';
20 import useCache from './useCache';
21 import { getPromiseValue } from './useCachedModelResult';
22 import useGetPublicKeysForInbox from './useGetPublicKeysForInbox';
24 export const CACHE_KEY = 'VERIFICATION_PREFERENCES';
26 const DEFAULT_LIFETIME = 5 * MINUTE;
28 /**
29  * Given an email address and the user mail settings, return the verification preferences for verifying messages
30  * from that email address.
31  */
32 const useGetVerificationPreferences = () => {
33     const api = useApi();
34     const cache = useCache();
35     const getAddresses = useGetAddresses();
36     const getUserKeys = useGetUserKeys();
37     const getAddressKeys = useGetAddressKeys();
38     const getPublicKeysForInbox = useGetPublicKeysForInbox();
39     const getMailSettings = useGetMailSettings();
41     const getVerificationPreferences = useCallback<GetVerificationPreferences>(
42         async ({ email, lifetime, contactEmailsMap }) => {
43             const addresses = await getAddresses();
44             const canonicalEmail = canonicalizeInternalEmail(email);
45             // Disabled addresses might now belong to other users (internal or external), who reused them.
46             // Since own keys take precedence on verification, but the message might be signed by a new/different address owner,
47             // we want to avoid considering disabled addresses as own addresses, in order to successfully verify messages sent by
48             // different owners.
49             // As a result, however, verification will fail for messages who were indeed sent from the disabled address.
50             // This downside has been deemed acceptable, since it aligns with the more general issue (to be tackled separately)
51             // of needing to preserve  the message verification status at the time when a message is first read, regardless of
52             // any subsequent public key changes (for non-pinned keys).
53             const selfAddress = addresses
54                 .filter(({ Status }) => Status === ADDRESS_STATUS.STATUS_ENABLED)
55                 .find(({ Email }) => canonicalizeInternalEmail(Email) === canonicalEmail);
56             if (selfAddress) {
57                 const selfAddressKeys = await getAddressKeys(selfAddress.ID);
58                 const activeAddressKeys = await getActiveKeys(
59                     selfAddress,
60                     selfAddress.SignedKeyList,
61                     selfAddress.Keys,
62                     selfAddressKeys
63                 );
64                 const activePublicKeys = activeAddressKeys.map(({ publicKey }) => publicKey);
65                 const compromisedFingerprints = new Set(
66                     activeAddressKeys
67                         .filter(({ flags }) => !hasBit(flags, KEY_FLAG.FLAG_NOT_COMPROMISED))
68                         .map(({ fingerprint }) => fingerprint)
69                 );
70                 const verifyingKeys = getVerifyingKeys(activePublicKeys, compromisedFingerprints);
71                 return {
72                     isOwnAddress: true,
73                     verifyingKeys,
74                     apiKeys: activePublicKeys,
75                     pinnedKeys: [],
76                     compromisedFingerprints,
77                     ktVerificationResult: { status: KT_VERIFICATION_STATUS.VERIFIED_KEYS },
78                 };
79             }
80             const {
81                 RecipientType,
82                 publicKeys: apiKeys,
83                 ktVerificationResult,
84                 Errors,
85             }: ApiKeysConfig = await getPublicKeysForInbox({
86                 email,
87                 lifetime,
88                 // messages from internal senders with e2ee disabled are still signed, thus we need to fetch the corresponding verification keys
89                 includeInternalKeysWithE2EEDisabledForMail: true,
90                 // untrusted WKD keys are not used for verification, and requesting WKD keys leaks to the sender's domain owner that the message has been read.
91                 internalKeysOnly: true,
92             });
93             const isInternal = RecipientType === RECIPIENT_TYPES.TYPE_INTERNAL;
94             const { publicKeys } = splitKeys(await getUserKeys());
95             const { pinnedKeys, isContactSignatureVerified: pinnedKeysVerified } = await getPublicKeysVcardHelper(
96                 api,
97                 email,
98                 publicKeys,
99                 isInternal,
100                 contactEmailsMap
101             );
102             const compromisedKeysFingerprints = new Set(
103                 apiKeys
104                     .filter(({ flags }) => !hasBit(flags, KEY_FLAG.FLAG_NOT_COMPROMISED))
105                     .map(({ publicKey }) => publicKey!.getFingerprint())
106             );
107             const pinnedKeysFingerprints = new Set(pinnedKeys.map((key) => key.getFingerprint()));
108             const apiPublicKeys = apiKeys.filter(({ publicKey }) => !!publicKey).map(({ publicKey }) => publicKey!);
109             let verifyingKeys: PublicKeyReference[] = [];
110             if (pinnedKeys.length) {
111                 verifyingKeys = getVerifyingKeys(pinnedKeys, compromisedKeysFingerprints);
112             } else if (isInternal && ktVerificationResult?.status === KT_VERIFICATION_STATUS.VERIFIED_KEYS) {
113                 verifyingKeys = getVerifyingKeys(apiPublicKeys, compromisedKeysFingerprints);
114             }
115             return {
116                 isOwnAddress: false,
117                 verifyingKeys,
118                 pinnedKeys,
119                 apiKeys: apiPublicKeys,
120                 ktVerificationResult,
121                 pinnedKeysFingerprints,
122                 compromisedKeysFingerprints,
123                 pinnedKeysVerified,
124                 apiKeysErrors: Errors,
125             };
126         },
127         [api, getAddressKeys, getAddresses, getPublicKeysForInbox, getMailSettings]
128     );
130     return useCallback<GetVerificationPreferences>(
131         ({ email, lifetime = DEFAULT_LIFETIME, contactEmailsMap }) => {
132             if (!cache.has(CACHE_KEY)) {
133                 cache.set(CACHE_KEY, new Map());
134             }
135             const subCache = cache.get(CACHE_KEY);
136             // By normalizing email here, we consider that it could not exists different encryption preferences
137             // For 2 addresses identical but for the cases.
138             // If a provider does different one day, this would have to evolve.
139             const canonicalEmail = canonicalizeEmail(email);
140             const miss = () => getVerificationPreferences({ email: canonicalEmail, lifetime, contactEmailsMap });
141             return getPromiseValue(subCache, canonicalEmail, miss, lifetime);
142         },
143         [cache, getVerificationPreferences]
144     );
147 export default useGetVerificationPreferences;