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,
20 AddressKey as tsAddressKey,
21 } from '../interfaces';
22 import { decryptKeyPacket, encryptAndSignKeyPacket } from './keypacket';
23 import { decryptMemberToken } from './memberToken';
25 interface EncryptAddressKeyTokenArguments {
27 userKey: PrivateKeyReference;
28 organizationKey?: PrivateKeyReference;
29 context?: Parameters<typeof CryptoProxy.signMessage<any>>[0]['context'];
32 export const encryptAddressKeyToken = async ({
37 }: EncryptAddressKeyTokenArguments) => {
38 const date = serverTime(); // ensure the signed message and the encrypted one have the same creation time, otherwise verification will fail
39 const textData = token;
40 const [userSignatureResult, organizationSignatureResult] = await Promise.all([
41 CryptoProxy.signMessage({
42 textData, // stripTrailingSpaces: false,
44 signingKeys: [userKey],
49 ? CryptoProxy.signMessage({
50 textData, // stripTrailingSpaces: false,
52 signingKeys: [organizationKey],
59 const { message: encryptedToken } = await CryptoProxy.encryptMessage({
62 encryptionKeys: organizationKey ? [userKey, organizationKey] : [userKey],
68 signature: userSignatureResult,
69 ...(organizationSignatureResult && { organizationSignature: organizationSignatureResult }),
73 interface EncryptAddressKeyUsingOrgKeyTokenArguments {
75 organizationKey: PrivateKeyReference;
76 context?: Parameters<typeof CryptoProxy.signMessage<any>>[0]['context'];
79 export const encryptAddressKeyUsingOrgKeyToken = async ({
83 }: EncryptAddressKeyUsingOrgKeyTokenArguments) => {
84 const date = serverTime(); // ensure the signed message and the encrypted one have the same creation time, otherwise verification will fail
85 const textData = token;
87 const organizationSignatureResult = await CryptoProxy.signMessage({
90 signingKeys: [organizationKey],
95 const { message: encryptedToken } = await CryptoProxy.encryptMessage({
98 encryptionKeys: [organizationKey],
104 signature: organizationSignatureResult,
108 interface DecryptAddressKeyTokenArguments {
111 privateKeys: PrivateKeyReference | PrivateKeyReference[];
112 publicKeys: PublicKeyReference | PublicKeyReference[];
113 context?: Parameters<typeof CryptoProxy.decryptMessage<any>>[0]['context'];
116 export const decryptAddressKeyToken = async ({
122 }: DecryptAddressKeyTokenArguments) => {
123 const { data: decryptedToken, verified } = await CryptoProxy.decryptMessage({
124 armoredMessage: Token,
125 armoredSignature: Signature,
126 decryptionKeys: privateKeys,
127 verificationKeys: publicKeys,
131 if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
132 const error = new Error(c('Error').t`Signature verification failed`);
133 error.name = 'SignatureError';
137 return decryptedToken;
140 interface DectyptAddressKeyUsingOrgKeyTokenArguments {
142 organizationKey: PrivateKeyReference;
146 export const decryptAddressKeyUsingOrgKeyToken = async ({
150 }: DectyptAddressKeyUsingOrgKeyTokenArguments) => {
151 const { data: decryptedToken, verified } = await CryptoProxy.decryptMessage({
152 armoredMessage: Token,
153 armoredSignature: Signature,
154 decryptionKeys: [organizationKey],
155 verificationKeys: [organizationKey],
158 if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
159 const error = new Error(c('Error').t`Signature verification failed`);
160 error.name = 'SignatureError';
164 return Promise.resolve(decryptedToken);
167 interface AddressKeyTokenResult {
169 encryptedToken: string;
171 organizationSignature?: string;
174 interface AddressKeyOrgTokenResult extends AddressKeyTokenResult {
175 organizationSignature: string;
178 export function generateAddressKeyTokens(
179 userKey: PrivateKeyReference,
180 organizationKey: PrivateKeyReference
181 ): Promise<AddressKeyOrgTokenResult>;
182 export function generateAddressKeyTokens(
183 userKey: PrivateKeyReference,
184 organizationKey?: PrivateKeyReference
185 ): Promise<AddressKeyTokenResult>;
187 export async function generateAddressKeyTokens(userKey: PrivateKeyReference, organizationKey?: PrivateKeyReference) {
188 const randomBytes = crypto.getRandomValues(new Uint8Array(32));
189 const token = arrayToHexString(randomBytes);
190 return encryptAddressKeyToken({ token, organizationKey, userKey });
193 export async function generateAddressKeyTokensUsingOrgKey(organizationKey: PrivateKeyReference) {
194 const randomBytes = crypto.getRandomValues(new Uint8Array(32));
195 const token = arrayToHexString(randomBytes);
196 return encryptAddressKeyUsingOrgKeyToken({ token, organizationKey });
199 interface GetAddressKeyTokenArguments {
202 organizationKey?: KeyPair;
203 privateKeys: PrivateKeyReference | PrivateKeyReference[];
204 publicKeys: PublicKeyReference | PublicKeyReference[];
207 export const getAddressKeyToken = ({
213 }: GetAddressKeyTokenArguments) => {
214 // New address key format
216 return decryptAddressKeyToken({
223 if (!organizationKey) {
224 throw new Error('Missing organization key');
226 // Old address key format for an admin signed into a non-private user
227 return decryptMemberToken(Token, [organizationKey.privateKey], [organizationKey.publicKey]);
230 export const getAddressKeyPassword = (
231 { Activation, Token, Signature }: tsAddressKey,
234 organizationKey?: KeyPair
236 // If not decrypting the non-private member keys with the organization key, and
237 // because the activation process is asynchronous in the background, allow the
238 // private key to get decrypted already here so that it can be used
239 if (!organizationKey && Activation) {
240 return decryptMemberToken(Activation, userKeys.privateKeys, userKeys.publicKeys);
244 return getAddressKeyToken({
248 privateKeys: userKeys.privateKeys,
249 publicKeys: userKeys.publicKeys,
253 return Promise.resolve(keyPassword);
256 export const getDecryptedAddressKey = async (
257 { ID, PrivateKey, Flags, Primary }: tsAddressKey,
258 addressKeyPassword: string
259 ): Promise<DecryptedAddressKey> => {
260 const privateKey = await CryptoProxy.importPrivateKey({ armoredKey: PrivateKey, passphrase: addressKeyPassword });
261 const publicKey = await CryptoProxy.importPublicKey({
262 binaryKey: await CryptoProxy.exportPublicKey({ key: privateKey, format: 'binary' }),
273 export interface ReformatAddressKeyArguments {
277 privateKey: PrivateKeyReference;
280 export const reformatAddressKey = async ({
284 privateKey: originalKey,
285 }: ReformatAddressKeyArguments) => {
286 const privateKey = await CryptoProxy.reformatKey({
287 userIDs: [{ name, email }],
288 privateKey: originalKey,
291 const privateKeyArmored = await CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase });
293 return { privateKey, privateKeyArmored };
296 export const getEncryptedArmoredAddressKey = async (
297 privateKey: PrivateKeyReference,
299 newKeyPassword: string
301 return CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase: newKeyPassword });
304 export interface GenerateAddressKeyArguments {
308 keyGenConfig?: KeyGenConfig;
311 export const generateAddressKey = async ({
315 keyGenConfig = KEYGEN_CONFIGS[DEFAULT_KEYGEN_TYPE],
316 }: GenerateAddressKeyArguments) => {
317 const privateKey = await CryptoProxy.generateKey({
318 userIDs: [{ name, email }],
322 const privateKeyArmored = await CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase });
324 return { privateKey, privateKeyArmored };
327 export const getIsTokenEncryptedToKeys = async ({
331 addressKey: AddressKey;
332 decryptionKeys: PrivateKeyReference[];
335 const sessionKey = await CryptoProxy.decryptSessionKey({
336 armoredMessage: addressKey.Token,
345 interface ReplaceAddressTokens {
346 privateKey: PrivateKeyReference;
347 privateKeys: PrivateKeyReference[];
348 addresses: Address[];
351 export const getReplacedAddressKeyTokens = async ({ addresses, privateKeys, privateKey }: ReplaceAddressTokens) => {
352 const reEncryptedTokens = await Promise.all(
353 addresses.map(async (address) => {
354 const result = await Promise.all(
355 address.Keys.map(async (addressKey) => {
357 // If the address key token is already encrypted to the new key, let's ignore this action.
358 if (await getIsTokenEncryptedToKeys({ addressKey, decryptionKeys: [privateKey] })) {
361 const { sessionKey, message } = await decryptKeyPacket({
362 armoredMessage: addressKey.Token,
363 decryptionKeys: privateKeys,
365 const result = await encryptAndSignKeyPacket({
367 binaryData: message.data,
368 encryptionKey: privateKey,
369 signingKey: privateKey,
375 AddressKeyID: addressKey.ID,
376 KeyPacket: result.keyPacket,
377 Signature: result.signature,
384 return result.filter(isTruthy);
389 AddressKeyTokens: reEncryptedTokens.flat(),
393 interface RenameAddressKeysArguments {
394 userKeys: DecryptedKey[];
395 addressKeys: AddressKey[];
396 organizationKey?: KeyPair;
400 export const getRenamedAddressKeys = async ({
405 }: RenameAddressKeysArguments) => {
406 const splittedUserKeys = splitKeys(userKeys);
408 const cb = async (addressKey: AddressKey) => {
410 const addressKeyPassword = await getAddressKeyPassword(
413 '', // Not using a key password since this function only works for address key migrated users
416 const { privateKey } = await getDecryptedAddressKey(addressKey, addressKeyPassword);
417 const changedPrivateKey = await CryptoProxy.cloneKeyAndChangeUserIDs({
419 userIDs: [{ name: email, email }],
421 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
422 privateKey: changedPrivateKey,
423 passphrase: addressKeyPassword,
425 await CryptoProxy.clearKey({ key: privateKey });
427 PrivateKey: privateKeyArmored,
435 const result = await Promise.all(addressKeys.map(cb));
436 return result.filter(isTruthy);
439 export const getPreviousAddressKeyToken = async ({
444 addressKey: AddressKey;
445 privateKeys: PrivateKeyReference | PrivateKeyReference[];
446 publicKeys: PublicKeyReference | PublicKeyReference[];
448 const encryptedToken = addressKey.Token;
449 if (!encryptedToken) {
450 throw new Error('Missing token');
452 const signature = addressKey.Signature;
453 const token = await getAddressKeyToken({
454 Token: encryptedToken,
455 Signature: signature,
459 return { signature, token, encryptedToken };
462 export const getNewAddressKeyToken = async ({ address, userKeys }: { address: Address; userKeys: KeyPair[] }) => {
463 const userPrimaryKey = getPrimaryKey(userKeys);
464 const userKey = userPrimaryKey?.privateKey;
465 const splitUserKeys = splitKeys(userKeys);
467 throw new Error('Missing primary user key');
469 if (!address.Keys?.length) {
470 return generateAddressKeyTokens(userKey);
472 // When a primary key is available, we prefer re-using the existing token instead of generating a new one.
473 // For non-private members we can generate a new key without requiring access to the org key
474 // For future shared addresses, reusing the token will ensure other users can decrypt the new key as well
475 const [primaryAddressKey] = address.Keys;
477 return await getPreviousAddressKeyToken({
478 addressKey: primaryAddressKey,
479 privateKeys: splitUserKeys.privateKeys,
480 publicKeys: splitUserKeys.publicKeys,
483 return generateAddressKeyTokens(userKey);
487 export const getNewAddressKeyTokenFromOrgKey = async ({
492 organizationKey: CachedOrganizationKey;
494 const { privateKey: decryptedOrganizationKey, publicKey } = organizationKey;
495 if (!decryptedOrganizationKey) {
496 throw new Error('Missing organization primary key');
498 if (!address.Keys?.length) {
499 return generateAddressKeyTokensUsingOrgKey(decryptedOrganizationKey);
501 // When a primary key is available, we prefer re-using the existing token instead of generating a new one.
502 // For non-private members we can generate a new key without requiring access to the org key
503 // For future shared addresses, reusing the token will ensure other users can decrypt the new key as well
504 const [primaryAddressKey] = address.Keys;
506 return await getPreviousAddressKeyToken({
507 addressKey: primaryAddressKey,
508 privateKeys: decryptedOrganizationKey,
509 publicKeys: publicKey,
512 return generateAddressKeyTokensUsingOrgKey(decryptedOrganizationKey);