Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / keys / memberKeys.ts
blob04f6e0504155d958e6f3dead0035b8aa724d42d8
1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import { getDecryptedAddressKeys } from '@proton/shared/lib/keys/getDecryptedAddressKeys';
4 import { getDecryptedUserKeys } from '@proton/shared/lib/keys/getDecryptedUserKeys';
5 import { getPrimaryKey } from '@proton/shared/lib/keys/getPrimaryKey';
6 import { getDefaultKeyFlags } from '@proton/shared/lib/keys/keyFlags';
7 import isTruthy from '@proton/utils/isTruthy';
9 import { createMemberKeyRoute, setupMemberKeyRoute } from '../api/memberKeys';
10 import { MEMBER_PRIVATE } from '../constants';
11 import type {
12     Api,
13     CachedOrganizationKey,
14     DecryptedKey,
15     KeyGenConfig,
16     KeyPair,
17     KeyTransparencyVerify,
18     UserModel,
19     Address as tsAddress,
20     Key as tsKey,
21     Member as tsMember,
22 } from '../interfaces';
23 import { srpVerify } from '../srp';
24 import { generateAddressKey, generateAddressKeyTokens } from './addressKeys';
25 import {
26     getActiveAddressKeys,
27     getActiveKeyObject,
28     getNormalizedActiveAddressKeys,
29     getPrimaryFlag,
30 } from './getActiveKeys';
31 import { generateKeySaltAndPassphrase } from './keys';
32 import { decryptMemberToken, encryptMemberToken, generateMemberToken } from './memberToken';
33 import { generateMemberAddressKey } from './organizationKeys';
34 import { getSignedKeyListWithDeferredPublish } from './signedKeyList';
35 import { generateUserKey } from './userKeys';
37 export const getDecryptedMemberKey = async (
38     { Token, PrivateKey }: tsKey,
39     organizationKey: PrivateKeyReference
40 ): Promise<PrivateKeyReference> => {
41     if (!Token) {
42         throw new Error('Member token invalid');
43     }
44     const decryptedToken = await decryptMemberToken(Token, [organizationKey], [organizationKey]);
45     return CryptoProxy.importPrivateKey({ armoredKey: PrivateKey, passphrase: decryptedToken });
48 interface SetupMemberKeySharedArguments {
49     api: Api;
50     member: tsMember;
51     memberAddresses: tsAddress[];
52     password: string;
53     organizationKey: PrivateKeyReference;
54     keyGenConfig: KeyGenConfig; // pqc: TODO no v6 support
55     keyTransparencyVerify: KeyTransparencyVerify;
58 export const setupMemberKeyV2 = async ({
59     api,
60     member,
61     memberAddresses,
62     password,
63     organizationKey,
64     keyGenConfig,
65     keyTransparencyVerify,
66 }: SetupMemberKeySharedArguments) => {
67     const { salt: keySalt, passphrase: memberKeyPassword } = await generateKeySaltAndPassphrase(password);
69     const { privateKey: userPrivateKey, privateKeyArmored: userPrivateKeyArmored } = await generateUserKey({
70         passphrase: memberKeyPassword,
71         keyGenConfig,
72     });
73     const memberKeyToken = generateMemberToken();
74     const privateKeyArmoredOrganization = await CryptoProxy.exportPrivateKey({
75         privateKey: userPrivateKey,
76         passphrase: memberKeyToken,
77     });
78     const organizationToken = await encryptMemberToken(memberKeyToken, organizationKey);
80     const AddressKeysWithOnSKLPublish = await Promise.all(
81         memberAddresses.map(async (address) => {
82             const { token, signature, organizationSignature, encryptedToken } = await generateAddressKeyTokens(
83                 userPrivateKey,
84                 organizationKey
85             );
87             const { privateKey: addressPrivateKey, privateKeyArmored: addressPrivateKeyArmored } =
88                 await generateAddressKey({
89                     email: address.Email,
90                     passphrase: token,
91                     keyGenConfig,
92                 });
94             const newActiveKey = await getActiveKeyObject(addressPrivateKey, {
95                 ID: 'tmp',
96                 primary: 1,
97                 flags: getDefaultKeyFlags(address),
98             });
99             const updatedActiveKeys = getNormalizedActiveAddressKeys(address, { v4: [newActiveKey], v6: [] });
100             const [SignedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
101                 updatedActiveKeys,
102                 address,
103                 keyTransparencyVerify
104             );
106             return {
107                 addressKey: {
108                     AddressID: address.ID,
109                     SignedKeyList,
110                     PrivateKey: addressPrivateKeyArmored,
111                     Token: encryptedToken,
112                     Signature: signature,
113                     OrgSignature: organizationSignature,
114                 },
115                 onSKLPublishSuccess,
116             };
117         })
118     );
119     const AddressKeys = AddressKeysWithOnSKLPublish.map(({ addressKey }) => addressKey);
121     const { Member } = await srpVerify<{ Member: tsMember }>({
122         api,
123         credentials: { password },
124         config: setupMemberKeyRoute({
125             MemberID: member.ID,
126             AddressKeys,
127             UserKey: {
128                 PrivateKey: userPrivateKeyArmored,
129                 OrgPrivateKey: privateKeyArmoredOrganization,
130                 OrgToken: organizationToken,
131             },
132             KeySalt: keySalt,
133         }),
134     });
136     await Promise.all(
137         AddressKeysWithOnSKLPublish.map(({ onSKLPublishSuccess }) =>
138             onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
139         )
140     );
142     return { Member, userPrivateKey };
145 export const getCanGenerateMemberKeys = (member: tsMember | undefined) => {
146     return member?.Private === MEMBER_PRIVATE.READABLE && !member.SSO;
149 export const getShouldSetupMemberKeys = (member: tsMember | undefined) => {
150     return !member?.Self && member?.Keys.length === 0 && getCanGenerateMemberKeys(member);
153 export const getCanGenerateMemberKeysPermissions = (
154     user: UserModel,
155     organizationKey: CachedOrganizationKey | undefined
156 ) => {
157     return !!organizationKey?.privateKey && user.isAdmin && !user.isSubUser;
160 export const getCanGenerateMemberAddressKeys = ({
161     user,
162     member,
163     organizationKey,
164     address,
165 }: {
166     user: UserModel;
167     member: tsMember | undefined;
168     organizationKey: CachedOrganizationKey | undefined;
169     address: tsAddress;
170 }) => {
171     /*
172      * Keys can be generated if the organisation key is decrypted, and you are an admin,
173      * and the member is readable, you're not an admin signed in to a readable member.
174      */
175     return (
176         getCanGenerateMemberKeysPermissions(user, organizationKey) &&
177         getCanGenerateMemberKeys(member) &&
178         !address.HasKeys
179     );
182 interface SetupMemberKeyArguments extends SetupMemberKeySharedArguments {
183     ownerAddresses: tsAddress[];
186 export const setupMemberKeys = async ({ ownerAddresses, ...rest }: SetupMemberKeyArguments) => {
187     return setupMemberKeyV2(rest);
190 interface CreateMemberAddressKeysLegacyArguments {
191     api: Api;
192     member: tsMember;
193     memberAddress: tsAddress;
194     memberAddressKeys: DecryptedKey[];
195     memberUserKey: PrivateKeyReference;
196     organizationKey: PrivateKeyReference;
197     keyGenConfig: KeyGenConfig;
198     keyTransparencyVerify: KeyTransparencyVerify;
201 export const createMemberAddressKeysLegacy = async ({
202     api,
203     member,
204     memberAddress,
205     memberAddressKeys,
206     memberUserKey,
207     organizationKey,
208     keyGenConfig,
209     keyTransparencyVerify,
210 }: CreateMemberAddressKeysLegacyArguments) => {
211     const { privateKey, activationToken, privateKeyArmored, privateKeyArmoredOrganization, organizationToken } =
212         await generateMemberAddressKey({
213             email: memberAddress.Email,
214             primaryKey: memberUserKey,
215             organizationKey,
216             keyGenConfig,
217         });
219     const activeKeys = await getActiveAddressKeys(
220         memberAddress,
221         memberAddress.SignedKeyList,
222         memberAddress.Keys,
223         memberAddressKeys
224     );
225     const newActiveKey = await getActiveKeyObject(privateKey, {
226         ID: 'tmp',
227         primary: getPrimaryFlag(activeKeys.v4),
228         flags: getDefaultKeyFlags(memberAddress),
229     });
230     const updatedActiveKeys = getNormalizedActiveAddressKeys(memberAddress, {
231         v4: [...activeKeys.v4, newActiveKey],
232         v6: [],
233     });
234     const [SignedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
235         updatedActiveKeys,
236         memberAddress,
237         keyTransparencyVerify
238     );
240     const { primary } = newActiveKey;
242     const { MemberKey } = await api(
243         createMemberKeyRoute({
244             MemberID: member.ID,
245             AddressID: memberAddress.ID,
246             Activation: activationToken,
247             UserKey: privateKeyArmored,
248             Token: organizationToken,
249             MemberKey: privateKeyArmoredOrganization,
250             Primary: primary,
251             SignedKeyList,
252         })
253     );
255     await onSKLPublishSuccess();
257     newActiveKey.ID = MemberKey.ID;
259     return updatedActiveKeys;
262 interface CreateMemberAddressKeysV2Arguments {
263     api: Api;
264     member: tsMember;
265     memberAddress: tsAddress;
266     memberAddressKeys: DecryptedKey[];
267     memberUserKey: PrivateKeyReference;
268     organizationKey: PrivateKeyReference;
269     keyGenConfig: KeyGenConfig;
270     keyTransparencyVerify: KeyTransparencyVerify;
273 export const createMemberAddressKeysV2 = async ({
274     api,
275     member,
276     memberAddress,
277     memberAddressKeys,
278     memberUserKey,
279     organizationKey,
280     keyGenConfig,
281     keyTransparencyVerify,
282 }: CreateMemberAddressKeysV2Arguments) => {
283     const { token, signature, organizationSignature, encryptedToken } = await generateAddressKeyTokens(
284         memberUserKey,
285         organizationKey
286     );
288     const { privateKey: addressPrivateKey, privateKeyArmored: addressPrivateKeyArmored } = await generateAddressKey({
289         email: memberAddress.Email,
290         passphrase: token,
291         keyGenConfig,
292     });
294     const activeKeys = await getActiveAddressKeys(
295         memberAddress,
296         memberAddress.SignedKeyList,
297         memberAddress.Keys,
298         memberAddressKeys
299     );
300     const newActiveKey = await getActiveKeyObject(addressPrivateKey, {
301         ID: 'tmp',
302         primary: getPrimaryFlag(activeKeys.v4),
303         flags: getDefaultKeyFlags(memberAddress),
304     });
305     const updatedActiveKeys = getNormalizedActiveAddressKeys(memberAddress, {
306         v4: [...activeKeys.v4, newActiveKey],
307         v6: [],
308     });
309     const [SignedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
310         updatedActiveKeys,
311         memberAddress,
312         keyTransparencyVerify
313     );
315     const { primary } = newActiveKey;
317     const { MemberKey } = await api(
318         createMemberKeyRoute({
319             MemberID: member.ID,
320             AddressID: memberAddress.ID,
321             PrivateKey: addressPrivateKeyArmored,
322             Token: encryptedToken,
323             Signature: signature,
324             OrgSignature: organizationSignature,
325             Primary: primary,
326             SignedKeyList,
327         })
328     );
330     await onSKLPublishSuccess();
332     newActiveKey.ID = MemberKey.ID;
334     return updatedActiveKeys;
337 export const getMemberKeys = async ({
338     member,
339     memberAddresses,
340     organizationKey,
341 }: {
342     member: Pick<tsMember, 'Keys'>;
343     memberAddresses: tsAddress[];
344     organizationKey: KeyPair;
345 }) => {
346     const memberUserKeys = await getDecryptedUserKeys(member.Keys, '', organizationKey);
347     const memberUserKeyPrimary = getPrimaryKey(memberUserKeys)?.privateKey;
348     if (!memberUserKeyPrimary) {
349         throw new Error('Not able to decrypt the primary member user key');
350     }
352     const memberAddressesKeys = (
353         await Promise.all(
354             memberAddresses.map(async (address) => {
355                 const result = {
356                     address,
357                     keys: await getDecryptedAddressKeys(address.Keys, memberUserKeys, '', organizationKey),
358                 };
359                 // Some non-private members don't have keys generated
360                 if (!result.keys.length) {
361                     return;
362                 }
363                 return result;
364             })
365         )
366     ).filter(isTruthy);
368     return {
369         memberUserKeyPrimary,
370         memberUserKeys,
371         memberAddressesKeys,
372     };