Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / mail / encryptionPreferences.ts
blob340637d137090d1ea5cfa85e153ab432b60869f0
1 import { c } from 'ttag';
3 import type { PublicKeyReference } from '@proton/crypto';
5 import { extractDraftMIMEType, extractScheme, extractSign } from '../api/helpers/mailSettings';
6 import type { CONTACT_MIME_TYPES, PGP_SCHEMES } from '../constants';
7 import { BRAND_NAME } from '../constants';
8 import type {
9     ContactPublicKeyModel,
10     KeyTransparencyVerificationResult,
11     MailSettings,
12     PublicKeyModel,
13     SelfSend,
14 } from '../interfaces';
15 import { getEmailMismatchWarning, getIsValidForSending } from '../keys/publicKeys';
17 export enum ENCRYPTION_PREFERENCES_ERROR_TYPES {
18     EMAIL_ADDRESS_ERROR,
19     INTERNAL_USER_DISABLED,
20     INTERNAL_USER_NO_API_KEY,
21     PRIMARY_CANNOT_SEND,
22     PRIMARY_NOT_PINNED,
23     EXTERNAL_USER_NO_VALID_EXTERNALLY_FETCHED_KEY,
24     EXTERNAL_USER_NO_VALID_PINNED_KEY,
25     CONTACT_SIGNATURE_NOT_VERIFIED,
28 export class EncryptionPreferencesError extends Error {
29     type: ENCRYPTION_PREFERENCES_ERROR_TYPES;
31     constructor(type: ENCRYPTION_PREFERENCES_ERROR_TYPES, message: string) {
32         super(message);
33         this.type = type;
34         Object.setPrototypeOf(this, EncryptionPreferencesError.prototype);
35     }
38 export interface EncryptionPreferences {
39     encrypt: boolean;
40     sign: boolean;
41     scheme: PGP_SCHEMES;
42     mimeType: CONTACT_MIME_TYPES;
43     isInternalWithDisabledE2EEForMail: boolean; // both `encrypt` and `isInternalWithDisabledE2EEForMail` might be true at this stage
44     sendKey?: PublicKeyReference;
45     isSendKeyPinned?: boolean;
46     apiKeys: PublicKeyReference[];
47     pinnedKeys: PublicKeyReference[];
48     verifyingPinnedKeys: PublicKeyReference[];
49     isInternal: boolean;
50     hasApiKeys: boolean;
51     hasPinnedKeys: boolean;
52     isContact: boolean;
53     isContactSignatureVerified?: boolean;
54     contactSignatureTimestamp?: Date;
55     warnings?: string[];
56     error?: EncryptionPreferencesError;
57     emailAddressWarnings?: string[];
58     ktVerificationResult?: KeyTransparencyVerificationResult;
61 const extractEncryptionPreferencesOwnAddress = (
62     publicKeyModel: PublicKeyModel,
63     selfSend: SelfSend
64 ): EncryptionPreferences => {
65     const {
66         publicKeys: { apiKeys },
67         emailAddress,
68         scheme,
69         mimeType,
70         isInternalWithDisabledE2EEForMail,
71         isContact,
72         isContactSignatureVerified,
73         contactSignatureTimestamp,
74         emailAddressWarnings,
75         emailAddressErrors,
76         ktVerificationResult,
77     } = publicKeyModel;
78     const { address, publicKey, canSend } = selfSend;
79     const hasApiKeys = !!address.HasKeys;
80     const canAddressReceive = !!address.Receive;
81     const result = {
82         encrypt: true,
83         sign: true,
84         scheme,
85         mimeType,
86         isInternal: true,
87         apiKeys,
88         pinnedKeys: [],
89         verifyingPinnedKeys: [],
90         hasApiKeys,
91         hasPinnedKeys: false,
92         isInternalWithDisabledE2EEForMail: isInternalWithDisabledE2EEForMail, // this may be true if own addresses are disabled
93         isContact,
94         isContactSignatureVerified,
95         contactSignatureTimestamp,
96         emailAddressWarnings,
97         ktVerificationResult,
98     };
99     if (emailAddressErrors?.length) {
100         const errorString = emailAddressErrors[0];
101         return {
102             ...result,
103             error: new EncryptionPreferencesError(ENCRYPTION_PREFERENCES_ERROR_TYPES.EMAIL_ADDRESS_ERROR, errorString),
104         };
105     }
106     if (!canAddressReceive) {
107         return {
108             ...result,
109             error: new EncryptionPreferencesError(
110                 ENCRYPTION_PREFERENCES_ERROR_TYPES.INTERNAL_USER_DISABLED,
111                 c('Error').t`Email address disabled`
112             ),
113         };
114     }
115     if (!hasApiKeys) {
116         return {
117             ...result,
118             error: new EncryptionPreferencesError(
119                 ENCRYPTION_PREFERENCES_ERROR_TYPES.INTERNAL_USER_NO_API_KEY,
120                 // Proton users should always have keys, so this case should never happen in practice. Therefore we don't translate the error message
121                 `No key was found for the ${BRAND_NAME} user`
122             ),
123         };
124     }
125     if (!publicKey || !canSend) {
126         return {
127             ...result,
128             error: new EncryptionPreferencesError(
129                 ENCRYPTION_PREFERENCES_ERROR_TYPES.PRIMARY_CANNOT_SEND,
130                 c('Error').t`Primary key is not valid for sending`
131             ),
132         };
133     }
134     const warnings = getEmailMismatchWarning(publicKey, emailAddress, true);
136     return { ...result, sendKey: publicKey, isSendKeyPinned: false, warnings };
139 const extractEncryptionPreferencesInternal = (publicKeyModel: PublicKeyModel): EncryptionPreferences => {
140     const {
141         emailAddress,
142         publicKeys: { apiKeys, pinnedKeys, verifyingPinnedKeys },
143         scheme,
144         mimeType,
145         trustedFingerprints,
146         isContact,
147         isContactSignatureVerified,
148         contactSignatureTimestamp,
149         emailAddressWarnings,
150         emailAddressErrors,
151         ktVerificationResult,
152     } = publicKeyModel;
153     const hasApiKeys = !!apiKeys.length;
154     const hasPinnedKeys = !!pinnedKeys.length;
155     const result = {
156         encrypt: true,
157         sign: true,
158         scheme,
159         mimeType,
160         apiKeys,
161         pinnedKeys,
162         verifyingPinnedKeys,
163         isInternal: true,
164         hasApiKeys,
165         hasPinnedKeys,
166         isInternalWithDisabledE2EEForMail: false,
167         isContact,
168         isContactSignatureVerified,
169         contactSignatureTimestamp,
170         emailAddressWarnings,
171         ktVerificationResult,
172     };
173     if (emailAddressErrors?.length) {
174         const errorString = emailAddressErrors[0];
175         return {
176             ...result,
177             error: new EncryptionPreferencesError(ENCRYPTION_PREFERENCES_ERROR_TYPES.EMAIL_ADDRESS_ERROR, errorString),
178         };
179     }
180     if (isContact && isContactSignatureVerified === false) {
181         return {
182             ...result,
183             error: new EncryptionPreferencesError(
184                 ENCRYPTION_PREFERENCES_ERROR_TYPES.CONTACT_SIGNATURE_NOT_VERIFIED,
185                 c('Error').t`Contact signature could not be verified`
186             ),
187         };
188     }
189     if (!hasApiKeys) {
190         return {
191             ...result,
192             error: new EncryptionPreferencesError(
193                 ENCRYPTION_PREFERENCES_ERROR_TYPES.INTERNAL_USER_NO_API_KEY,
194                 // Proton users should always have keys, so this case should never happen in practice. Therefore we don't translate the error message
195                 `No key was found for the ${BRAND_NAME} user`
196             ),
197         };
198     }
199     // API keys are ordered in terms of user preference. The primary key (first in the list) will be used for sending
200     const [primaryKey] = apiKeys;
201     const primaryKeyFingerprint = primaryKey.getFingerprint();
202     if (!getIsValidForSending(primaryKeyFingerprint, publicKeyModel)) {
203         return {
204             ...result,
205             error: new EncryptionPreferencesError(
206                 ENCRYPTION_PREFERENCES_ERROR_TYPES.PRIMARY_CANNOT_SEND,
207                 c('Error').t`Primary key retrieved for ${BRAND_NAME} user is not valid for sending`
208             ),
209         };
210     }
211     if (!hasPinnedKeys) {
212         const warnings = getEmailMismatchWarning(primaryKey, emailAddress, true);
213         return { ...result, sendKey: primaryKey, isSendKeyPinned: false, warnings };
214     }
215     // if there are pinned keys, make sure the primary API key is trusted and valid for sending
216     const isPrimaryTrustedAndValid =
217         trustedFingerprints.has(primaryKeyFingerprint) && getIsValidForSending(primaryKeyFingerprint, publicKeyModel);
218     const sendKey = pinnedKeys.find((key) => key.getFingerprint() === primaryKeyFingerprint);
219     if (!isPrimaryTrustedAndValid || !sendKey) {
220         return {
221             ...result,
222             sendKey: primaryKey,
223             error: new EncryptionPreferencesError(
224                 ENCRYPTION_PREFERENCES_ERROR_TYPES.PRIMARY_NOT_PINNED,
225                 c('Error').t`Trusted keys are not valid for sending`
226             ),
227         };
228     }
229     const warnings = getEmailMismatchWarning(sendKey, emailAddress, true);
231     // return the pinned key, not the API one
232     return { ...result, sendKey, isSendKeyPinned: true, warnings };
235 const extractEncryptionPreferencesExternalWithExternallyFetchedKeys = (
236     publicKeyModel: PublicKeyModel
237 ): EncryptionPreferences => {
238     const {
239         encrypt,
240         sign,
241         emailAddress,
242         publicKeys: { apiKeys, pinnedKeys, verifyingPinnedKeys },
243         scheme,
244         mimeType,
245         trustedFingerprints,
246         isContact,
247         isContactSignatureVerified,
248         contactSignatureTimestamp,
249         emailAddressWarnings,
250         emailAddressErrors,
251         ktVerificationResult,
252     } = publicKeyModel;
253     const hasApiKeys = true;
254     const hasPinnedKeys = !!pinnedKeys.length;
255     const result = {
256         encrypt,
257         sign,
258         scheme,
259         mimeType,
260         apiKeys,
261         pinnedKeys,
262         verifyingPinnedKeys,
263         isInternal: false,
264         hasApiKeys,
265         hasPinnedKeys,
266         isInternalWithDisabledE2EEForMail: false,
267         isContact,
268         isContactSignatureVerified,
269         contactSignatureTimestamp,
270         emailAddressWarnings,
271         ktVerificationResult,
272     };
273     if (emailAddressErrors?.length) {
274         const errorString = emailAddressErrors[0];
275         return {
276             ...result,
277             error: new EncryptionPreferencesError(ENCRYPTION_PREFERENCES_ERROR_TYPES.EMAIL_ADDRESS_ERROR, errorString),
278         };
279     }
280     if (isContact && isContactSignatureVerified === false) {
281         return {
282             ...result,
283             error: new EncryptionPreferencesError(
284                 ENCRYPTION_PREFERENCES_ERROR_TYPES.CONTACT_SIGNATURE_NOT_VERIFIED,
285                 c('Error').t`Contact signature could not be verified`
286             ),
287         };
288     }
289     // WKD and KOO keys are ordered in terms of user preference. The primary key (first in the list) will be used for sending
290     const [primaryKey] = apiKeys;
291     const primaryKeyFingerprint = primaryKey.getFingerprint();
292     const validApiSendKey = apiKeys.find((key) => getIsValidForSending(key.getFingerprint(), publicKeyModel));
293     if (!validApiSendKey) {
294         return {
295             ...result,
296             error: new EncryptionPreferencesError(
297                 ENCRYPTION_PREFERENCES_ERROR_TYPES.EXTERNAL_USER_NO_VALID_EXTERNALLY_FETCHED_KEY,
298                 c('Error').t`No external key retrieved for user is valid for sending`
299             ),
300         };
301     }
302     if (!hasPinnedKeys) {
303         const warnings = getEmailMismatchWarning(primaryKey, emailAddress, false);
304         return { ...result, sendKey: primaryKey, isSendKeyPinned: false, warnings };
305     }
306     // if there are pinned keys, make sure the primary API key is trusted and valid for sending
307     const isPrimaryTrustedAndValid =
308         trustedFingerprints.has(primaryKeyFingerprint) && getIsValidForSending(primaryKeyFingerprint, publicKeyModel);
309     const sendKey = pinnedKeys.find((key) => key.getFingerprint() === primaryKeyFingerprint);
310     if (!isPrimaryTrustedAndValid || !sendKey) {
311         return {
312             ...result,
313             sendKey: validApiSendKey,
314             error: new EncryptionPreferencesError(
315                 ENCRYPTION_PREFERENCES_ERROR_TYPES.PRIMARY_NOT_PINNED,
316                 c('Error').t`Trusted keys are not valid for sending`
317             ),
318         };
319     }
320     const warnings = getEmailMismatchWarning(sendKey, emailAddress, false);
322     // return the pinned key, not the API one
323     return { ...result, sendKey, isSendKeyPinned: true, warnings };
326 const extractEncryptionPreferencesExternalWithoutExternallyFetchedKeys = (
327     publicKeyModel: PublicKeyModel
328 ): EncryptionPreferences => {
329     const {
330         emailAddress,
331         publicKeys: { apiKeys, pinnedKeys, verifyingPinnedKeys },
332         encrypt,
333         sign,
334         scheme,
335         mimeType,
336         isInternalWithDisabledE2EEForMail,
337         isContact,
338         isContactSignatureVerified,
339         contactSignatureTimestamp,
340         emailAddressWarnings,
341         emailAddressErrors,
342         ktVerificationResult,
343     } = publicKeyModel;
344     const hasPinnedKeys = !!pinnedKeys.length;
345     const result = {
346         encrypt,
347         sign,
348         mimeType,
349         scheme,
350         apiKeys,
351         pinnedKeys,
352         verifyingPinnedKeys,
353         isInternal: false,
354         hasApiKeys: false,
355         hasPinnedKeys,
356         isInternalWithDisabledE2EEForMail: isInternalWithDisabledE2EEForMail,
357         isContact,
358         isContactSignatureVerified,
359         contactSignatureTimestamp,
360         emailAddressWarnings,
361         ktVerificationResult,
362     };
363     if (emailAddressErrors?.length) {
364         const errorString = emailAddressErrors[0];
365         return {
366             ...result,
367             error: new EncryptionPreferencesError(ENCRYPTION_PREFERENCES_ERROR_TYPES.EMAIL_ADDRESS_ERROR, errorString),
368         };
369     }
370     if (isContact && isContactSignatureVerified === false) {
371         return {
372             ...result,
373             error: new EncryptionPreferencesError(
374                 ENCRYPTION_PREFERENCES_ERROR_TYPES.CONTACT_SIGNATURE_NOT_VERIFIED,
375                 c('Error').t`Contact signature could not be verified`
376             ),
377         };
378     }
379     if (!hasPinnedKeys || !encrypt) {
380         return result;
381     }
382     // Pinned keys are ordered in terms of preference. Make sure the first is valid
383     const [sendKey] = pinnedKeys;
384     if (!getIsValidForSending(sendKey.getFingerprint(), publicKeyModel)) {
385         return {
386             ...result,
387             error: new EncryptionPreferencesError(
388                 ENCRYPTION_PREFERENCES_ERROR_TYPES.EXTERNAL_USER_NO_VALID_PINNED_KEY,
389                 c('Error').t`The sending key is not valid`
390             ),
391         };
392     }
393     const warnings = getEmailMismatchWarning(sendKey, emailAddress, false);
395     return { ...result, sendKey, isSendKeyPinned: true, warnings };
399  * Extract the encryption preferences from a public-key model corresponding to a certain email address
400  */
401 const extractEncryptionPreferences = (
402     model: ContactPublicKeyModel,
403     mailSettings: MailSettings,
404     selfSend?: SelfSend
405 ): EncryptionPreferences => {
406     // Determine encrypt and sign flags, plus PGP scheme and MIME type.
407     // Take mail settings into account if they are present
408     const encrypt = !!model.encrypt;
409     const sign = extractSign(model, mailSettings);
410     const scheme = extractScheme(model, mailSettings);
411     const mimeType = extractDraftMIMEType(model, mailSettings);
413     const publicKeyModel = {
414         ...model,
415         encrypt,
416         sign: encrypt || sign,
417         scheme,
418         mimeType,
419     };
420     // case of own address
421     if (selfSend) {
422         return extractEncryptionPreferencesOwnAddress(publicKeyModel, selfSend);
423     }
424     // case of internal user
425     if (model.isPGPInternal) {
426         return extractEncryptionPreferencesInternal(publicKeyModel);
427     }
428     // case of external user with WKD keys
429     if (model.isPGPExternalWithExternallyFetchedKeys) {
430         return extractEncryptionPreferencesExternalWithExternallyFetchedKeys(publicKeyModel);
431     }
432     // case of external user without WKD keys
433     return extractEncryptionPreferencesExternalWithoutExternallyFetchedKeys(publicKeyModel);
436 export default extractEncryptionPreferences;