1 import { c } from 'ttag';
3 import type { PrivateKeyReference, PublicKeyReference } from '@proton/crypto';
4 import { CryptoProxy, VERIFICATION_STATUS, serverTime } from '@proton/crypto';
5 import { arrayToHexString } from '@proton/crypto/lib/utils';
6 import { getPrimaryKey } from '@proton/shared/lib/keys/getPrimaryKey';
7 import { splitKeys } from '@proton/shared/lib/keys/keys';
8 import isTruthy from '@proton/utils/isTruthy';
10 import { DEFAULT_KEYGEN_TYPE, KEYGEN_CONFIGS } from '../constants';
14 CachedOrganizationKey,
21 AddressKey as tsAddressKey,
22 } from '../interfaces';
23 import { decryptKeyPacket, encryptAndSignKeyPacket } from './keypacket';
24 import { decryptMemberToken } from './memberToken';
26 interface EncryptAddressKeyTokenArguments {
28 userKey: PrivateKeyReference;
29 organizationKey?: PrivateKeyReference;
30 context?: Parameters<typeof CryptoProxy.signMessage<any>>[0]['context'];
33 export const encryptAddressKeyToken = async ({
38 }: EncryptAddressKeyTokenArguments) => {
39 const date = serverTime(); // ensure the signed message and the encrypted one have the same creation time, otherwise verification will fail
40 const textData = token;
41 const [userSignatureResult, organizationSignatureResult] = await Promise.all([
42 CryptoProxy.signMessage({
43 textData, // stripTrailingSpaces: false,
45 signingKeys: [userKey],
50 ? CryptoProxy.signMessage({
51 textData, // stripTrailingSpaces: false,
53 signingKeys: [organizationKey],
60 const { message: encryptedToken } = await CryptoProxy.encryptMessage({
63 encryptionKeys: organizationKey ? [userKey, organizationKey] : [userKey],
69 signature: userSignatureResult,
70 ...(organizationSignatureResult && { organizationSignature: organizationSignatureResult }),
74 interface EncryptAddressKeyUsingOrgKeyTokenArguments {
76 organizationKey: PrivateKeyReference;
77 context?: Parameters<typeof CryptoProxy.signMessage<any>>[0]['context'];
80 export const encryptAddressKeyUsingOrgKeyToken = async ({
84 }: EncryptAddressKeyUsingOrgKeyTokenArguments) => {
85 const date = serverTime(); // ensure the signed message and the encrypted one have the same creation time, otherwise verification will fail
86 const textData = token;
88 const organizationSignatureResult = await CryptoProxy.signMessage({
91 signingKeys: [organizationKey],
96 const { message: encryptedToken } = await CryptoProxy.encryptMessage({
99 encryptionKeys: [organizationKey],
105 signature: organizationSignatureResult,
109 interface DecryptAddressKeyTokenArguments {
112 privateKeys: PrivateKeyReference | PrivateKeyReference[];
113 publicKeys: PublicKeyReference | PublicKeyReference[];
114 context?: Parameters<typeof CryptoProxy.decryptMessage<any>>[0]['context'];
117 export const decryptAddressKeyToken = async ({
123 }: DecryptAddressKeyTokenArguments) => {
124 const { data: decryptedToken, verified } = await CryptoProxy.decryptMessage({
125 armoredMessage: Token,
126 armoredSignature: Signature,
127 decryptionKeys: privateKeys,
128 verificationKeys: publicKeys,
132 if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
133 const error = new Error(c('Error').t`Signature verification failed`);
134 error.name = 'SignatureError';
138 return decryptedToken;
141 interface DectyptAddressKeyUsingOrgKeyTokenArguments {
143 organizationKey: PrivateKeyReference;
147 export const decryptAddressKeyUsingOrgKeyToken = async ({
151 }: DectyptAddressKeyUsingOrgKeyTokenArguments) => {
152 const { data: decryptedToken, verified } = await CryptoProxy.decryptMessage({
153 armoredMessage: Token,
154 armoredSignature: Signature,
155 decryptionKeys: [organizationKey],
156 verificationKeys: [organizationKey],
159 if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
160 const error = new Error(c('Error').t`Signature verification failed`);
161 error.name = 'SignatureError';
165 return Promise.resolve(decryptedToken);
168 interface AddressKeyTokenResult {
170 encryptedToken: string;
172 organizationSignature?: string;
175 interface AddressKeyOrgTokenResult extends AddressKeyTokenResult {
176 organizationSignature: string;
179 export function generateAddressKeyTokens(
180 userKey: PrivateKeyReference,
181 organizationKey: PrivateKeyReference
182 ): Promise<AddressKeyOrgTokenResult>;
183 export function generateAddressKeyTokens(
184 userKey: PrivateKeyReference,
185 organizationKey?: PrivateKeyReference
186 ): Promise<AddressKeyTokenResult>;
188 export async function generateAddressKeyTokens(userKey: PrivateKeyReference, organizationKey?: PrivateKeyReference) {
189 const randomBytes = crypto.getRandomValues(new Uint8Array(32));
190 const token = arrayToHexString(randomBytes);
191 return encryptAddressKeyToken({ token, organizationKey, userKey });
194 export async function generateAddressKeyTokensUsingOrgKey(organizationKey: PrivateKeyReference) {
195 const randomBytes = crypto.getRandomValues(new Uint8Array(32));
196 const token = arrayToHexString(randomBytes);
197 return encryptAddressKeyUsingOrgKeyToken({ token, organizationKey });
200 interface GetAddressKeyTokenArguments {
203 organizationKey?: KeyPair;
204 privateKeys: PrivateKeyReference | PrivateKeyReference[];
205 publicKeys: PublicKeyReference | PublicKeyReference[];
208 export const getAddressKeyToken = ({
214 }: GetAddressKeyTokenArguments) => {
215 // New address key format
217 return decryptAddressKeyToken({
224 if (!organizationKey) {
225 throw new Error('Missing organization key');
227 // Old address key format for an admin signed into a non-private user
228 return decryptMemberToken(Token, [organizationKey.privateKey], [organizationKey.publicKey]);
231 export const getAddressKeyPassword = (
232 { Activation, Token, Signature }: tsAddressKey,
235 organizationKey?: KeyPair
237 // If not decrypting the non-private member keys with the organization key, and
238 // because the activation process is asynchronous in the background, allow the
239 // private key to get decrypted already here so that it can be used
240 if (!organizationKey && Activation) {
241 return decryptMemberToken(Activation, userKeys.privateKeys, userKeys.publicKeys);
245 return getAddressKeyToken({
249 privateKeys: userKeys.privateKeys,
250 publicKeys: userKeys.publicKeys,
254 return Promise.resolve(keyPassword);
257 export const getDecryptedAddressKey = async (
258 { ID, PrivateKey, Flags, Primary }: tsAddressKey,
259 addressKeyPassword: string
260 ): Promise<DecryptedAddressKey> => {
261 const privateKey = await CryptoProxy.importPrivateKey({ armoredKey: PrivateKey, passphrase: addressKeyPassword });
262 const publicKey = await CryptoProxy.importPublicKey({
263 binaryKey: await CryptoProxy.exportPublicKey({ key: privateKey, format: 'binary' }),
274 export interface ReformatAddressKeyArguments {
278 privateKey: PrivateKeyReference;
281 export const reformatAddressKey = async ({
285 privateKey: originalKey,
286 }: ReformatAddressKeyArguments) => {
287 const privateKey = await CryptoProxy.reformatKey({
288 userIDs: [{ name, email }],
289 privateKey: originalKey,
292 const privateKeyArmored = await CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase });
294 return { privateKey, privateKeyArmored };
297 export const getEncryptedArmoredAddressKey = async (
298 privateKey: PrivateKeyReference,
300 newKeyPassword: string
302 return CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase: newKeyPassword });
305 export interface GenerateAddressKeyArguments<C extends KeyGenConfig | KeyGenConfigV6> {
312 // TODOOOOO instead of type inference, create separate generateAddressKeyV6 helper?
313 export const generateAddressKey = async <C extends KeyGenConfig | KeyGenConfigV6>({
317 keyGenConfig = KEYGEN_CONFIGS[DEFAULT_KEYGEN_TYPE] as C,
318 }: GenerateAddressKeyArguments<C>) => {
319 const privateKey = await CryptoProxy.generateKey<C['config']>({
320 userIDs: [{ name, email }],
324 const privateKeyArmored = await CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase });
326 return { privateKey, privateKeyArmored };
329 export const getIsTokenEncryptedToKeys = async ({
333 addressKey: AddressKey;
334 decryptionKeys: PrivateKeyReference[];
337 const sessionKey = await CryptoProxy.decryptSessionKey({
338 armoredMessage: addressKey.Token,
347 interface ReplaceAddressTokens {
348 privateKey: PrivateKeyReference;
349 privateKeys: PrivateKeyReference[];
350 addresses: Address[];
353 export const getReplacedAddressKeyTokens = async ({ addresses, privateKeys, privateKey }: ReplaceAddressTokens) => {
354 const reEncryptedTokens = await Promise.all(
355 addresses.map(async (address) => {
356 const result = await Promise.all(
357 address.Keys.map(async (addressKey) => {
359 // If the address key token is already encrypted to the new key, let's ignore this action.
360 if (await getIsTokenEncryptedToKeys({ addressKey, decryptionKeys: [privateKey] })) {
363 const { sessionKey, message } = await decryptKeyPacket({
364 armoredMessage: addressKey.Token,
365 decryptionKeys: privateKeys,
367 const result = await encryptAndSignKeyPacket({
369 binaryData: message.data,
370 encryptionKey: privateKey,
371 signingKey: privateKey,
377 AddressKeyID: addressKey.ID,
378 KeyPacket: result.keyPacket,
379 Signature: result.signature,
386 return result.filter(isTruthy);
391 AddressKeyTokens: reEncryptedTokens.flat(),
395 interface RenameAddressKeysArguments {
396 userKeys: DecryptedKey[];
397 addressKeys: AddressKey[];
398 organizationKey?: KeyPair;
402 export const getRenamedAddressKeys = async ({
407 }: RenameAddressKeysArguments) => {
408 const splittedUserKeys = splitKeys(userKeys);
410 const cb = async (addressKey: AddressKey) => {
412 const addressKeyPassword = await getAddressKeyPassword(
415 '', // Not using a key password since this function only works for address key migrated users
418 const { privateKey } = await getDecryptedAddressKey(addressKey, addressKeyPassword);
419 const changedPrivateKey = await CryptoProxy.cloneKeyAndChangeUserIDs({
421 userIDs: [{ name: email, email }],
423 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
424 privateKey: changedPrivateKey,
425 passphrase: addressKeyPassword,
427 await CryptoProxy.clearKey({ key: privateKey });
429 PrivateKey: privateKeyArmored,
437 const result = await Promise.all(addressKeys.map(cb));
438 return result.filter(isTruthy);
441 export const getPreviousAddressKeyToken = async ({
446 addressKey: AddressKey;
447 privateKeys: PrivateKeyReference | PrivateKeyReference[];
448 publicKeys: PublicKeyReference | PublicKeyReference[];
450 const encryptedToken = addressKey.Token;
451 if (!encryptedToken) {
452 throw new Error('Missing token');
454 const signature = addressKey.Signature;
455 const token = await getAddressKeyToken({
456 Token: encryptedToken,
457 Signature: signature,
461 return { signature, token, encryptedToken };
464 export const getNewAddressKeyToken = async ({ address, userKeys }: { address: Address; userKeys: KeyPair[] }) => {
465 const userPrimaryKey = getPrimaryKey(userKeys);
466 const userKey = userPrimaryKey?.privateKey;
467 const splitUserKeys = splitKeys(userKeys);
469 throw new Error('Missing primary user key');
471 if (!address.Keys?.length) {
472 return generateAddressKeyTokens(userKey);
474 // When a primary key is available, we prefer re-using the existing token instead of generating a new one.
475 // For non-private members we can generate a new key without requiring access to the org key
476 // For future shared addresses, reusing the token will ensure other users can decrypt the new key as well
477 const [primaryAddressKey] = address.Keys;
479 return await getPreviousAddressKeyToken({
480 addressKey: primaryAddressKey,
481 privateKeys: splitUserKeys.privateKeys,
482 publicKeys: splitUserKeys.publicKeys,
485 return generateAddressKeyTokens(userKey);
489 export const getNewAddressKeyTokenFromOrgKey = async ({
494 organizationKey: CachedOrganizationKey;
496 const { privateKey: decryptedOrganizationKey, publicKey } = organizationKey;
497 if (!decryptedOrganizationKey) {
498 throw new Error('Missing organization primary key');
500 if (!address.Keys?.length) {
501 return generateAddressKeyTokensUsingOrgKey(decryptedOrganizationKey);
503 // When a primary key is available, we prefer re-using the existing token instead of generating a new one.
504 // For non-private members we can generate a new key without requiring access to the org key
505 // For future shared addresses, reusing the token will ensure other users can decrypt the new key as well
506 const [primaryAddressKey] = address.Keys;
508 return await getPreviousAddressKeyToken({
509 addressKey: primaryAddressKey,
510 privateKeys: decryptedOrganizationKey,
511 publicKeys: publicKey,
514 return generateAddressKeyTokensUsingOrgKey(decryptedOrganizationKey);