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>;
22 interface ProcessedContactData {
23 type: CRYPTO_PROCESSING_TYPES;
25 signatureTimestamp?: Date;
29 export const decrypt = async ({ Data }: ContactCard, { privateKeys }: Pick<KeysPair, 'privateKeys'>) => {
31 const { data } = await CryptoProxy.decryptMessage({ armoredMessage: Data, decryptionKeys: privateKeys });
33 if (data && typeof data !== 'string') {
34 throw new Error('Unknown data');
36 return { type: SUCCESS, data };
37 } catch (error: any) {
38 return { type: FAIL_TO_DECRYPT, error };
42 export const readSigned = async (
43 { Data, Signature = '' }: ContactCard,
44 { publicKeys }: Pick<KeysPair, 'publicKeys'>
48 throw new Error(c('Error').t`Missing signature`);
50 const { verified, signatureTimestamp } = await CryptoProxy.verifyMessage({
52 stripTrailingSpaces: true,
53 verificationKeys: publicKeys,
54 armoredSignature: Signature,
57 if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
60 type: SIGNATURE_NOT_VERIFIED,
61 signatureTimestamp: undefined,
62 error: new Error(c('Error').t`Contact signature not verified`),
65 return { type: SUCCESS, data: Data, signatureTimestamp: signatureTimestamp! };
66 } catch (error: any) {
68 type: SIGNATURE_NOT_VERIFIED,
70 signatureTimestamp: undefined,
76 export const decryptSigned = async ({ Data, Signature }: ContactCard, { publicKeys, privateKeys }: KeysPair) => {
78 const { data, verified } = await CryptoProxy.decryptMessage({
80 decryptionKeys: privateKeys,
81 verificationKeys: publicKeys,
82 armoredSignature: Signature || undefined,
85 if (data && typeof data !== 'string') {
86 throw new Error('Unknown data');
89 if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
90 return { data, type: SIGNATURE_NOT_VERIFIED, error: new Error(c('Error').t`Signature not verified`) };
93 return { type: SUCCESS, data };
94 } catch (error: any) {
95 return { type: FAIL_TO_DECRYPT, error };
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 (
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') };
120 return ACTIONS[card.Type](card, { publicKeys, privateKeys });
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) {
129 return { ...card, data: card.data.replace(/\nUID:.*\n/i, '\n') };
132 const { vcards, errors } = sanitizedCards.reduce<{ vcards: string[]; errors: CryptoProcessingError[] }>(
133 (acc, { type, data, error }) => {
135 acc.vcards.push(data);
138 if (type === SUCCESS) {
139 throw new Error('Inconsistency detected during contact card processing');
141 if (type === SIGNATURE_NOT_VERIFIED) {
144 acc.errors.push({ type, error });
148 { vcards: [], errors: [] }
151 return { isVerified, vcards, errors };
154 export const prepareVCardContact = async (
156 { publicKeys, privateKeys }: KeysPair
157 ): Promise<{ vCardContact: VCardContact; errors: (CryptoProcessingError | Error)[]; isVerified: boolean }> => {
158 const { isVerified, vcards, errors } = await decryptContact(contact, { publicKeys, privateKeys });
161 const vCardContacts = vcards.map((vcard) => parseToVCard(vcard));
162 const vCardContact = mergeVCard(vCardContacts);
164 return { vCardContact, errors, isVerified };
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 };