Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / components / hooks / useGetVerificationPreferences.ts
blob9acae4acdb36b77b670471215cb08876d39fe184
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 { getActiveAddressKeys } 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 activeAddressKeysByVersion = await getActiveAddressKeys(
59                     selfAddress,
60                     selfAddress.SignedKeyList,
61                     selfAddress.Keys,
62                     selfAddressKeys
63                 );
64                 const activeAddressKeys = [...activeAddressKeysByVersion.v6, ...activeAddressKeysByVersion.v4];
65                 const activePublicKeys = activeAddressKeys.map(({ publicKey }) => publicKey);
66                 const compromisedFingerprints = new Set(
67                     activeAddressKeys
68                         .filter(({ flags }) => !hasBit(flags, KEY_FLAG.FLAG_NOT_COMPROMISED))
69                         .map(({ fingerprint }) => fingerprint)
70                 );
71                 const verifyingKeys = getVerifyingKeys(activePublicKeys, compromisedFingerprints);
72                 return {
73                     isOwnAddress: true,
74                     verifyingKeys,
75                     apiKeys: activePublicKeys,
76                     pinnedKeys: [],
77                     compromisedFingerprints,
78                     ktVerificationResult: { status: KT_VERIFICATION_STATUS.VERIFIED_KEYS },
79                 };
80             }
81             const {
82                 RecipientType,
83                 publicKeys: apiKeys,
84                 ktVerificationResult,
85                 Errors,
86             }: ApiKeysConfig = await getPublicKeysForInbox({
87                 email,
88                 lifetime,
89                 // messages from internal senders with e2ee disabled are still signed, thus we need to fetch the corresponding verification keys
90                 includeInternalKeysWithE2EEDisabledForMail: true,
91                 // 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.
92                 internalKeysOnly: true,
93             });
94             const isInternal = RecipientType === RECIPIENT_TYPES.TYPE_INTERNAL;
95             const { publicKeys } = splitKeys(await getUserKeys());
96             const { pinnedKeys, isContactSignatureVerified: pinnedKeysVerified } = await getPublicKeysVcardHelper(
97                 api,
98                 email,
99                 publicKeys,
100                 isInternal,
101                 contactEmailsMap
102             );
103             const compromisedKeysFingerprints = new Set(
104                 apiKeys
105                     .filter(({ flags }) => !hasBit(flags, KEY_FLAG.FLAG_NOT_COMPROMISED))
106                     .map(({ publicKey }) => publicKey!.getFingerprint())
107             );
108             const pinnedKeysFingerprints = new Set(pinnedKeys.map((key) => key.getFingerprint()));
109             const apiPublicKeys = apiKeys.filter(({ publicKey }) => !!publicKey).map(({ publicKey }) => publicKey!);
110             let verifyingKeys: PublicKeyReference[] = [];
111             if (pinnedKeys.length) {
112                 verifyingKeys = getVerifyingKeys(pinnedKeys, compromisedKeysFingerprints);
113             } else if (isInternal && ktVerificationResult?.status === KT_VERIFICATION_STATUS.VERIFIED_KEYS) {
114                 verifyingKeys = getVerifyingKeys(apiPublicKeys, compromisedKeysFingerprints);
115             }
116             return {
117                 isOwnAddress: false,
118                 verifyingKeys,
119                 pinnedKeys,
120                 apiKeys: apiPublicKeys,
121                 ktVerificationResult,
122                 pinnedKeysFingerprints,
123                 compromisedKeysFingerprints,
124                 pinnedKeysVerified,
125                 apiKeysErrors: Errors,
126             };
127         },
128         [api, getAddressKeys, getAddresses, getPublicKeysForInbox, getMailSettings]
129     );
131     return useCallback<GetVerificationPreferences>(
132         ({ email, lifetime = DEFAULT_LIFETIME, contactEmailsMap }) => {
133             if (!cache.has(CACHE_KEY)) {
134                 cache.set(CACHE_KEY, new Map());
135             }
136             const subCache = cache.get(CACHE_KEY);
137             // By normalizing email here, we consider that it could not exists different encryption preferences
138             // For 2 addresses identical but for the cases.
139             // If a provider does different one day, this would have to evolve.
140             const canonicalEmail = canonicalizeEmail(email);
141             const miss = () => getVerificationPreferences({ email: canonicalEmail, lifetime, contactEmailsMap });
142             return getPromiseValue(subCache, canonicalEmail, miss, lifetime);
143         },
144         [cache, getVerificationPreferences]
145     );
148 export default useGetVerificationPreferences;