Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / contacts / keyPinning.ts
blob3e686c26e829d686ac35bc6bfad1e9ee14b249a8
1 import { c } from 'ttag';
3 import type { PrivateKeyReference, PublicKeyReference } from '@proton/crypto';
4 import { CryptoProxy } from '@proton/crypto';
5 import isTruthy from '@proton/utils/isTruthy';
7 import { CONTACT_CARD_TYPE } from '../constants';
8 import { CANONICALIZE_SCHEME, canonicalizeEmail } from '../helpers/email';
9 import { generateProtonWebUID } from '../helpers/uid';
10 import type { ContactCard } from '../interfaces/contacts';
11 import type { VCardProperty } from '../interfaces/contacts/VCard';
12 import { CRYPTO_PROCESSING_TYPES } from './constants';
13 import { readSigned } from './decrypt';
14 import { toKeyProperty } from './keyProperties';
15 import { createContactPropertyUid, getVCardProperties } from './properties';
16 import { parseToVCard, vCardPropertiesToICAL } from './vcard';
18 /**
19  * Pin a public key in a contact. Give to it the highest preference
20  * Public keys need to be passed to check signature validity of signed contact cards
21  * Private keys (typically only the primary one) need to be passed to sign the new contact card with the new pinned key
22  */
23 interface ParamsUpdate {
24     contactCards: ContactCard[];
25     emailAddress: string;
26     isInternal: boolean;
27     bePinnedPublicKey: PublicKeyReference;
28     publicKeys: PublicKeyReference[];
29     privateKeys: PrivateKeyReference[];
31 export const pinKeyUpdateContact = async ({
32     contactCards,
33     emailAddress,
34     isInternal,
35     bePinnedPublicKey,
36     publicKeys,
37     privateKeys,
38 }: ParamsUpdate): Promise<ContactCard[]> => {
39     // get the signed card of the contact that contains the key properties. Throw if there are errors
40     const [signedCard, ...otherCards] = contactCards.reduce<ContactCard[]>((acc, card) => {
41         if (card.Type === CONTACT_CARD_TYPE.SIGNED) {
42             acc.unshift(card);
43         } else {
44             acc.push(card);
45         }
46         return acc;
47     }, []);
48     const readSignedCard = await readSigned(signedCard, { publicKeys });
49     if (readSignedCard.type !== CRYPTO_PROCESSING_TYPES.SUCCESS) {
50         if (readSignedCard.error) {
51             throw readSignedCard.error;
52         }
53         throw new Error('Unknown error');
54     }
55     const signedVcard = readSignedCard.data;
57     // get the key properties that correspond to the email address
58     const signedVCard = parseToVCard(signedVcard);
59     const signedProperties = getVCardProperties(signedVCard);
60     const emailProperty = signedProperties.find(({ field, value }) => {
61         const scheme = isInternal ? CANONICALIZE_SCHEME.PROTON : CANONICALIZE_SCHEME.DEFAULT;
62         return (
63             field === 'email' && canonicalizeEmail(value as string, scheme) === canonicalizeEmail(emailAddress, scheme)
64         );
65     });
66     const emailGroup = emailProperty?.group as string;
67     const keyProperties = emailGroup
68         ? signedProperties.filter((prop) => {
69               return prop.field === 'key' && prop.group === emailGroup;
70           })
71         : undefined;
72     if (!keyProperties) {
73         throw new Error(c('Error').t`The key properties for ${emailAddress} could not be extracted`);
74     }
76     // add the new key as the preferred one
77     const shiftedPrefKeyProperties = keyProperties.map((property) => ({
78         ...property,
79         params: {
80             ...property.params,
81             pref: String(Number(property.params?.pref) + 1),
82         },
83     }));
84     const newKeyProperties = [
85         await toKeyProperty({ publicKey: bePinnedPublicKey, group: emailGroup, index: 0 }),
86         ...shiftedPrefKeyProperties,
87     ];
88     const untouchedSignedProperties = signedProperties.filter(
89         ({ field, group }) => field !== 'key' || group !== emailGroup
90     );
91     const newSignedProperties = [...untouchedSignedProperties, ...newKeyProperties];
93     // sign the new properties
94     const toSignVcard: string = vCardPropertiesToICAL(newSignedProperties).toString();
95     const signature = await CryptoProxy.signMessage({
96         textData: toSignVcard,
97         stripTrailingSpaces: true,
98         signingKeys: privateKeys,
99         detached: true,
100     });
101     const newSignedCard = {
102         Type: CONTACT_CARD_TYPE.SIGNED,
103         Data: toSignVcard,
104         Signature: signature,
105     };
106     return [newSignedCard, ...otherCards];
110  * Create a contact with a pinned key. Set encrypt flag to true
111  * Private keys (typically only the primary one) need to be passed to sign the new contact card with the new pinned key
112  */
113 interface ParamsCreate {
114     emailAddress: string;
115     name?: string;
116     isInternal: boolean;
117     bePinnedPublicKey: PublicKeyReference;
118     privateKeys: PrivateKeyReference[];
120 export const pinKeyCreateContact = async ({
121     emailAddress,
122     name,
123     isInternal,
124     bePinnedPublicKey,
125     privateKeys,
126 }: ParamsCreate): Promise<ContactCard[]> => {
127     const properties: VCardProperty[] = [
128         { field: 'fn', value: name || emailAddress, uid: createContactPropertyUid() },
129         { field: 'uid', value: generateProtonWebUID(), uid: createContactPropertyUid() },
130         { field: 'email', value: emailAddress, group: 'item1', uid: createContactPropertyUid() },
131         !isInternal && { field: 'x-pm-encrypt', value: 'true', group: 'item1', uid: createContactPropertyUid() },
132         !isInternal && { field: 'x-pm-sign', value: 'true', group: 'item1', uid: createContactPropertyUid() },
133         await toKeyProperty({ publicKey: bePinnedPublicKey, group: 'item1', index: 0 }),
134     ].filter(isTruthy);
135     // sign the properties
136     const toSignVcard: string = vCardPropertiesToICAL(properties).toString();
137     const signature = await CryptoProxy.signMessage({
138         textData: toSignVcard,
139         stripTrailingSpaces: true,
140         signingKeys: privateKeys,
141         detached: true,
142     });
143     const newSignedCard = {
144         Type: CONTACT_CARD_TYPE.SIGNED,
145         Data: toSignVcard,
146         Signature: signature,
147     };
148     return [newSignedCard];