Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / contacts / decrypt.ts
blob257c8bdb6c1bd63bac1dffb611a67f02c9a8944b
1 import { c } from 'ttag';
3 import { CryptoProxy, VERIFICATION_STATUS } from '@proton/crypto';
5 import { CONTACT_CARD_TYPE } from '../constants';
6 import type { KeysPair } from '../interfaces';
7 import type { Contact, ContactCard } from '../interfaces/contacts';
8 import type { VCardContact } from '../interfaces/contacts/VCard';
9 import { CRYPTO_PROCESSING_TYPES } from './constants';
10 import { mergeVCard } from './properties';
11 import { parseToVCard } from './vcard';
13 const { SUCCESS, SIGNATURE_NOT_VERIFIED, FAIL_TO_READ, FAIL_TO_DECRYPT } = CRYPTO_PROCESSING_TYPES;
15 const { CLEAR_TEXT, ENCRYPTED_AND_SIGNED, ENCRYPTED, SIGNED } = CONTACT_CARD_TYPE;
17 export interface CryptoProcessingError {
18     type: Exclude<CRYPTO_PROCESSING_TYPES, CRYPTO_PROCESSING_TYPES.SUCCESS>;
19     error: Error;
22 interface ProcessedContactData {
23     type: CRYPTO_PROCESSING_TYPES;
24     data?: string;
25     signatureTimestamp?: Date;
26     error?: Error;
29 export const decrypt = async ({ Data }: ContactCard, { privateKeys }: Pick<KeysPair, 'privateKeys'>) => {
30     try {
31         const { data } = await CryptoProxy.decryptMessage({ armoredMessage: Data, decryptionKeys: privateKeys });
33         if (data && typeof data !== 'string') {
34             throw new Error('Unknown data');
35         }
36         return { type: SUCCESS, data };
37     } catch (error: any) {
38         return { type: FAIL_TO_DECRYPT, error };
39     }
42 export const readSigned = async (
43     { Data, Signature = '' }: ContactCard,
44     { publicKeys }: Pick<KeysPair, 'publicKeys'>
45 ) => {
46     try {
47         if (!Signature) {
48             throw new Error(c('Error').t`Missing signature`);
49         }
50         const { verified, signatureTimestamp } = await CryptoProxy.verifyMessage({
51             textData: Data,
52             stripTrailingSpaces: true,
53             verificationKeys: publicKeys,
54             armoredSignature: Signature,
55         });
57         if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
58             return {
59                 data: Data,
60                 type: SIGNATURE_NOT_VERIFIED,
61                 signatureTimestamp: undefined,
62                 error: new Error(c('Error').t`Contact signature not verified`),
63             };
64         }
65         return { type: SUCCESS, data: Data, signatureTimestamp: signatureTimestamp! };
66     } catch (error: any) {
67         return {
68             type: SIGNATURE_NOT_VERIFIED,
69             data: Data,
70             signatureTimestamp: undefined,
71             error,
72         };
73     }
76 export const decryptSigned = async ({ Data, Signature }: ContactCard, { publicKeys, privateKeys }: KeysPair) => {
77     try {
78         const { data, verified } = await CryptoProxy.decryptMessage({
79             armoredMessage: Data,
80             decryptionKeys: privateKeys,
81             verificationKeys: publicKeys,
82             armoredSignature: Signature || undefined,
83         });
85         if (data && typeof data !== 'string') {
86             throw new Error('Unknown data');
87         }
89         if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
90             return { data, type: SIGNATURE_NOT_VERIFIED, error: new Error(c('Error').t`Signature not verified`) };
91         }
93         return { type: SUCCESS, data };
94     } catch (error: any) {
95         return { type: FAIL_TO_DECRYPT, error };
96     }
99 const clearText = ({ Data }: ContactCard) => Promise.resolve({ type: SUCCESS, data: Data });
101 const ACTIONS: { [index: number]: (...params: any) => Promise<ProcessedContactData> } = {
102     [ENCRYPTED_AND_SIGNED]: decryptSigned,
103     [SIGNED]: readSigned,
104     [ENCRYPTED]: decrypt,
105     [CLEAR_TEXT]: clearText,
108 export const decryptContact = async (
109     contact: Contact,
110     { publicKeys, privateKeys }: KeysPair
111 ): Promise<{ vcards: string[]; errors: (CryptoProcessingError | Error)[]; isVerified: boolean }> => {
112     const { Cards } = contact;
113     let isVerified = Cards.some(({ Type }) => [SIGNED, ENCRYPTED_AND_SIGNED].includes(Type));
115     const decryptedCards = await Promise.all(
116         Cards.map(async (card) => {
117             if (!ACTIONS[card.Type]) {
118                 return { type: FAIL_TO_READ, error: new Error('Unknown card type') };
119             }
120             return ACTIONS[card.Type](card, { publicKeys, privateKeys });
121         })
122     );
124     // remove UIDs put by mistake in encrypted cards
125     const sanitizedCards = decryptedCards.map((card, i) => {
126         if (![ENCRYPTED_AND_SIGNED, ENCRYPTED].includes(Cards[i].Type) || !card.data) {
127             return card;
128         }
129         return { ...card, data: card.data.replace(/\nUID:.*\n/i, '\n') };
130     });
132     const { vcards, errors } = sanitizedCards.reduce<{ vcards: string[]; errors: CryptoProcessingError[] }>(
133         (acc, { type, data, error }) => {
134             if (data) {
135                 acc.vcards.push(data);
136             }
137             if (error) {
138                 if (type === SUCCESS) {
139                     throw new Error('Inconsistency detected during contact card processing');
140                 }
141                 if (type === SIGNATURE_NOT_VERIFIED) {
142                     isVerified = false;
143                 }
144                 acc.errors.push({ type, error });
145             }
146             return acc;
147         },
148         { vcards: [], errors: [] }
149     );
151     return { isVerified, vcards, errors };
154 export const prepareVCardContact = async (
155     contact: Contact,
156     { publicKeys, privateKeys }: KeysPair
157 ): Promise<{ vCardContact: VCardContact; errors: (CryptoProcessingError | Error)[]; isVerified: boolean }> => {
158     const { isVerified, vcards, errors } = await decryptContact(contact, { publicKeys, privateKeys });
160     try {
161         const vCardContacts = vcards.map((vcard) => parseToVCard(vcard));
162         const vCardContact = mergeVCard(vCardContacts);
164         return { vCardContact, errors, isVerified };
165     } catch (e: any) {
166         // eslint-disable-next-line no-console
167         console.error('Error in prepare vCard', e);
168         const error = e instanceof Error ? e : new Error('Corrupted vcard data');
170         return { vCardContact: { fn: [] }, errors: [error], isVerified };
171     }