1 import type { PrivateKeyReference, PublicKeyReference } from '@proton/crypto';
2 import { CryptoProxy, VERIFICATION_STATUS } from '@proton/crypto';
3 import { uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
4 import isTruthy from '@proton/utils/isTruthy';
5 import mergeUint8Arrays from '@proton/utils/mergeUint8Arrays';
7 import type { APP_NAMES } from '../constants';
8 import { APPS, RECOVERY_FILE_FILE_NAME } from '../constants';
9 import downloadFile from '../helpers/downloadFile';
10 import type { Address, DecryptedKey, Key, KeyWithRecoverySecret, User } from '../interfaces';
11 import type { ArmoredKeyWithInfo } from '../keys';
12 import { getHasMigratedAddressKeys, getPrimaryKey } from '../keys';
14 const decryptRecoveryFile = (recoverySecrets: KeyWithRecoverySecret[]) => async (file: string) => {
16 return await Promise.any(
17 recoverySecrets.map(async ({ RecoverySecret }) => {
18 const { data } = await CryptoProxy.decryptMessage({
20 passwords: RecoverySecret,
27 } catch (error: any) {
32 export const parseRecoveryFiles = async (filesAsStrings: string[] = [], recoverySecrets: KeyWithRecoverySecret[]) => {
33 const decryptedFiles = (await Promise.all(filesAsStrings.map(decryptRecoveryFile(recoverySecrets)))).filter(
36 const decryptedArmoredKeys = (
38 decryptedFiles.map((concatenatedBinaryKeys) =>
39 CryptoProxy.getArmoredKeys({ binaryKeys: concatenatedBinaryKeys })
45 decryptedArmoredKeys.map(
46 async (armoredKey): Promise<ArmoredKeyWithInfo> => ({
47 ...(await CryptoProxy.getKeyInfo({ armoredKey })),
54 export const generateRecoverySecret = async (privateKey: PrivateKeyReference) => {
56 const randomValues = crypto.getRandomValues(new Uint8Array(length));
57 const recoverySecret = uint8ArrayToBase64String(randomValues);
59 const signature = await CryptoProxy.signMessage({
60 textData: recoverySecret,
61 stripTrailingSpaces: true,
62 signingKeys: privateKey,
72 export const generateRecoveryFileMessage = async ({
76 recoverySecret: string;
77 privateKeys: PrivateKeyReference[];
79 const userKeysArray = await Promise.all(
80 privateKeys.map((privateKey) =>
81 CryptoProxy.exportPrivateKey({ privateKey: privateKey, passphrase: null, format: 'binary' })
85 const { message } = await CryptoProxy.encryptMessage({
86 binaryData: mergeUint8Arrays(userKeysArray),
87 passwords: [recoverySecret],
93 export const exportRecoveryFile = async ({
97 recoverySecret: string;
98 userKeys: DecryptedKey[];
100 const message = await generateRecoveryFileMessage({
102 privateKeys: userKeys.map(({ privateKey }) => privateKey),
104 const blob = new Blob([message], { type: 'text/plain' });
105 downloadFile(blob, RECOVERY_FILE_FILE_NAME);
108 export const validateRecoverySecret = async (recoverySecret: KeyWithRecoverySecret, publicKey: PublicKeyReference) => {
109 const { RecoverySecret, RecoverySecretSignature } = recoverySecret;
111 const { verified } = await CryptoProxy.verifyMessage({
112 textData: RecoverySecret,
113 stripTrailingSpaces: true,
114 verificationKeys: publicKey,
115 armoredSignature: RecoverySecretSignature,
118 return verified === VERIFICATION_STATUS.SIGNED_AND_VALID;
121 export const getKeyWithRecoverySecret = (key: Key | undefined) => {
122 if (!key?.RecoverySecret || !key?.RecoverySecretSignature) {
125 return key as KeyWithRecoverySecret;
128 export const getRecoverySecrets = (Keys: Key[] = []): KeyWithRecoverySecret[] => {
129 return Keys.map(getKeyWithRecoverySecret).filter(isTruthy);
132 export const getPrimaryRecoverySecret = (Keys: Key[] = []): KeyWithRecoverySecret | undefined => {
133 return getKeyWithRecoverySecret(Keys?.[0]);
136 export const getHasOutdatedRecoveryFile = (keys: Key[] = []) => {
137 const primaryRecoverySecret = getPrimaryRecoverySecret(keys);
138 const recoverySecrets = getRecoverySecrets(keys);
140 return recoverySecrets?.length > 0 && !primaryRecoverySecret;
143 export const getIsRecoveryFileAvailable = ({
150 addresses: Address[];
151 userKeys: DecryptedKey[];
154 const hasMigratedKeys = getHasMigratedAddressKeys(addresses);
155 const primaryKey = getPrimaryKey(userKeys);
157 const isPrivateUser = Boolean(user.Private);
159 return !!primaryKey?.privateKey && hasMigratedKeys && isPrivateUser && appName !== APPS.PROTONVPN_SETTINGS;