1 import type { PublicKeyReference } from '@proton/crypto';
3 import { CONTACT_CARD_TYPE } from '../../constants';
4 import { CRYPTO_PROCESSING_TYPES } from '../../contacts/constants';
5 import { readSigned } from '../../contacts/decrypt';
6 import { getKeyInfoFromProperties } from '../../contacts/keyProperties';
7 import { parseToVCard } from '../../contacts/vcard';
8 import { CANONICALIZE_SCHEME, canonicalizeEmail } from '../../helpers/email';
9 import type { Api, PinnedKeysConfig } from '../../interfaces';
10 import type { ContactEmail, Contact as tsContact } from '../../interfaces/contacts';
11 import { getContact, queryContactEmails } from '../contacts';
13 const getContactEmail = async (
15 contactEmailsMap: { [email: string]: ContactEmail | undefined } = {},
18 // Simple normalize here, internal version is to aggressive relative to contacts emails
19 const canonicalEmail = canonicalizeEmail(emailAddress);
20 if (contactEmailsMap[canonicalEmail]) {
21 return contactEmailsMap[canonicalEmail];
23 const { ContactEmails = [] } = await api<{ ContactEmails: ContactEmail[] }>(
24 queryContactEmails({ Email: canonicalEmail })
26 return ContactEmails[0];
30 * Get the public keys stored in the vcard of a contact associated to a certain email address.
31 * Verify the signature on the contact in the process with the public keys provided
33 const getPublicKeysVcardHelper = async (
36 publicKeys: PublicKeyReference[],
38 contactEmailsMap: { [email: string]: ContactEmail | undefined } = {}
39 ): Promise<PinnedKeysConfig> => {
40 let isContact = false;
41 let isContactSignatureVerified;
42 let contactSignatureTimestamp;
44 const ContactEmail = await getContactEmail(emailAddress, contactEmailsMap, api);
45 if (ContactEmail === undefined) {
46 return { pinnedKeys: [], isContact };
49 // ContactEmail.Defaults flag informs if there is specific configuration in the contact for this email
50 if (ContactEmail.Defaults === 1) {
51 return { pinnedKeys: [], isContact };
53 // pick the first contact with the desired email. The API returns them ordered by decreasing priority already
54 const { Contact } = await api<{ Contact: tsContact }>(getContact(ContactEmail.ContactID));
55 // all the info we need is in the signed part
56 const signedCard = Contact.Cards.find(({ Type }) => Type === CONTACT_CARD_TYPE.SIGNED);
58 // contacts created by the server are not signed
59 return { pinnedKeys: [], isContact: !!Contact.Cards.length };
61 const { type, data: signedVcard, signatureTimestamp } = await readSigned(signedCard, { publicKeys });
62 isContactSignatureVerified = type === CRYPTO_PROCESSING_TYPES.SUCCESS;
63 contactSignatureTimestamp = signatureTimestamp;
64 const vCardContact = parseToVCard(signedVcard);
65 const emailProperty = (vCardContact.email || []).find(({ field, value }) => {
66 const scheme = isInternal ? CANONICALIZE_SCHEME.PROTON : CANONICALIZE_SCHEME.DEFAULT;
69 canonicalizeEmail(value as string, scheme) === canonicalizeEmail(emailAddress, scheme)
72 if (!emailProperty || !emailProperty.group) {
73 throw new Error('Invalid vcard');
76 ...(await getKeyInfoFromProperties(vCardContact, emailProperty.group)),
78 isContactSignatureVerified,
79 contactSignatureTimestamp,
81 } catch (error: any) {
82 return { pinnedKeys: [], isContact, isContactSignatureVerified, contactSignatureTimestamp, error };
86 export default getPublicKeysVcardHelper;