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;
29 * Given an email address and the user mail settings, return the verification preferences for verifying messages
30 * from that email address.
32 const useGetVerificationPreferences = () => {
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
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);
57 const selfAddressKeys = await getAddressKeys(selfAddress.ID);
58 const activeAddressKeysByVersion = await getActiveAddressKeys(
60 selfAddress.SignedKeyList,
64 const activeAddressKeys = [...activeAddressKeysByVersion.v6, ...activeAddressKeysByVersion.v4];
65 const activePublicKeys = activeAddressKeys.map(({ publicKey }) => publicKey);
66 const compromisedFingerprints = new Set(
68 .filter(({ flags }) => !hasBit(flags, KEY_FLAG.FLAG_NOT_COMPROMISED))
69 .map(({ fingerprint }) => fingerprint)
71 const verifyingKeys = getVerifyingKeys(activePublicKeys, compromisedFingerprints);
75 apiKeys: activePublicKeys,
77 compromisedFingerprints,
78 ktVerificationResult: { status: KT_VERIFICATION_STATUS.VERIFIED_KEYS },
86 }: ApiKeysConfig = await getPublicKeysForInbox({
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,
94 const isInternal = RecipientType === RECIPIENT_TYPES.TYPE_INTERNAL;
95 const { publicKeys } = splitKeys(await getUserKeys());
96 const { pinnedKeys, isContactSignatureVerified: pinnedKeysVerified } = await getPublicKeysVcardHelper(
103 const compromisedKeysFingerprints = new Set(
105 .filter(({ flags }) => !hasBit(flags, KEY_FLAG.FLAG_NOT_COMPROMISED))
106 .map(({ publicKey }) => publicKey!.getFingerprint())
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);
120 apiKeys: apiPublicKeys,
121 ktVerificationResult,
122 pinnedKeysFingerprints,
123 compromisedKeysFingerprints,
125 apiKeysErrors: Errors,
128 [api, getAddressKeys, getAddresses, getPublicKeysForInbox, getMailSettings]
131 return useCallback<GetVerificationPreferences>(
132 ({ email, lifetime = DEFAULT_LIFETIME, contactEmailsMap }) => {
133 if (!cache.has(CACHE_KEY)) {
134 cache.set(CACHE_KEY, new Map());
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);
144 [cache, getVerificationPreferences]
148 export default useGetVerificationPreferences;