1 import type { PrivateKeyReference, PublicKeyReference, SessionKey } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import { signData as computeHmacSignature, importKey as importHmacKey } from '@proton/crypto/lib/subtle/hmac';
4 import { arrayToHexString, stringToUtf8Array } from '@proton/crypto/lib/utils';
6 import { createSessionKey, getEncryptedSessionKey } from '../calendar/crypto/encrypt';
7 import { generatePassphrase } from '../calendar/crypto/keys/calendarKeys';
8 import { KEYGEN_CONFIGS, KEYGEN_TYPES } from '../constants';
9 import { uint8ArrayToBase64String } from '../helpers/encoding';
11 interface UnsignedEncryptionPayload {
12 message: string | Uint8Array;
13 publicKey: PublicKeyReference;
16 export const sign = async (data: string | Uint8Array, privateKeys: PrivateKeyReference | PrivateKeyReference[]) => {
17 const dataType = data instanceof Uint8Array ? 'binaryData' : 'textData';
18 const signature = await CryptoProxy.signMessage({
20 stripTrailingSpaces: dataType === 'textData',
21 signingKeys: privateKeys,
27 export const encryptUnsigned = async ({ message, publicKey }: UnsignedEncryptionPayload) => {
28 const dataType = message instanceof Uint8Array ? 'binaryData' : 'textData';
29 const { message: encryptedToken } = await CryptoProxy.encryptMessage({
31 stripTrailingSpaces: dataType === 'textData',
32 encryptionKeys: publicKey,
34 return encryptedToken;
37 export const encryptName = async (
39 parentPublicKey: PublicKeyReference,
40 addressPrivateKey: PrivateKeyReference
42 const { message: Name } = await CryptoProxy.encryptMessage({
44 stripTrailingSpaces: true,
45 encryptionKeys: parentPublicKey,
46 signingKeys: addressPrivateKey,
52 interface UnsignedDecryptionPayload {
53 armoredMessage: string;
54 privateKey: PrivateKeyReference | PrivateKeyReference[];
57 interface SignedDecryptionPayload<F extends 'utf8' | 'binary'> extends UnsignedDecryptionPayload {
58 publicKey: PublicKeyReference | PublicKeyReference[];
62 export const decryptSigned = async <F extends 'utf8' | 'binary' = 'utf8'>({
67 }: SignedDecryptionPayload<F>) => {
68 const { data, verified } = await CryptoProxy.decryptMessage({
70 decryptionKeys: privateKey,
71 verificationKeys: publicKey,
74 return { data, verified };
78 * Decrypts unsigned armored message, in the context of drive it's share's passphrase and folder's contents.
80 export const decryptUnsigned = async ({ armoredMessage, privateKey }: UnsignedDecryptionPayload) => {
81 const { data: decryptedMessage } = await CryptoProxy.decryptMessage({
83 decryptionKeys: privateKey,
86 return decryptedMessage;
89 export const generateDriveKey = async (rawPassphrase: string) => {
90 const keyGenConfigs = KEYGEN_CONFIGS[KEYGEN_TYPES.CURVE25519];
91 const privateKey = await CryptoProxy.generateKey({
92 userIDs: [{ name: 'Drive key' }],
96 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
98 passphrase: rawPassphrase,
101 return { privateKey, privateKeyArmored };
104 export const generateLookupHash = async (name: string, parentHashKey: Uint8Array) => {
105 const key = await importHmacKey(parentHashKey);
107 const signature = await computeHmacSignature(key, stringToUtf8Array(name));
108 return arrayToHexString(signature);
111 export const generateNodeHashKey = async (publicKey: PublicKeyReference, addressPrivateKey: PrivateKeyReference) => {
112 const { message: NodeHashKey } = await CryptoProxy.encryptMessage({
113 // Once all clients can use non-ascii bytes, switch to simple
114 // generating of random bytes without encoding it into base64:
115 //binaryData: crypto.getRandomValues(new Uint8Array(32)),
116 textData: generatePassphrase(),
117 encryptionKeys: publicKey,
118 signingKeys: addressPrivateKey,
121 return { NodeHashKey };
124 export const encryptPassphrase = async (
125 parentKey: PrivateKeyReference | PrivateKeyReference[],
126 addressKey: PrivateKeyReference | PrivateKeyReference[] = parentKey,
127 rawPassphrase = generatePassphrase(),
128 passphraseSessionKey?: SessionKey
130 const sessionKey = passphraseSessionKey
131 ? passphraseSessionKey
132 : await CryptoProxy.generateSessionKey({ recipientKeys: parentKey });
133 const { message: NodePassphrase, signature: NodePassphraseSignature } = await CryptoProxy.encryptMessage({
134 textData: rawPassphrase,
136 signingKeys: addressKey,
137 encryptionKeys: parentKey,
141 return { NodePassphrase, NodePassphraseSignature, sessionKey };
144 export const generateNodeKeys = async (parentKey: PrivateKeyReference, addressKey: PrivateKeyReference = parentKey) => {
145 const rawPassphrase = generatePassphrase();
146 const [{ NodePassphrase, NodePassphraseSignature, sessionKey }, { privateKey, privateKeyArmored: NodeKey }] =
147 await Promise.all([encryptPassphrase(parentKey, addressKey, rawPassphrase), generateDriveKey(rawPassphrase)]);
148 return { privateKey, NodeKey, NodePassphrase, NodePassphraseSignature, sessionKey };
151 export const generateShareKeys = async (linkNodeKey: PrivateKeyReference, addressKey: PrivateKeyReference) => {
152 const rawPassphrase = generatePassphrase();
153 const { privateKey, privateKeyArmored: NodeKey } = await generateDriveKey(rawPassphrase);
154 const { NodePassphrase, NodePassphraseSignature, sessionKey } = await encryptPassphrase(
155 [linkNodeKey, addressKey], // NodeKey always needs to be first
159 return { privateKey, NodeKey, NodePassphrase, NodePassphraseSignature, sessionKey };
162 export const generateContentHash = async (content: Uint8Array) => {
163 const data = await CryptoProxy.computeHash({ algorithm: 'SHA256', data: content });
164 return { HashType: 'sha256', BlockHash: data };
167 export const generateContentKeys = async (nodeKey: PrivateKeyReference) => {
168 const publicKey: PublicKeyReference = nodeKey; // no need to get a separate public key reference in this case
169 const sessionKey = await createSessionKey(publicKey);
170 const sessionKeySignature = await sign(sessionKey.data, nodeKey);
171 const contentKeys = await getEncryptedSessionKey(sessionKey, publicKey);
172 const ContentKeyPacket = uint8ArrayToBase64String(contentKeys);
173 return { sessionKey, ContentKeyPacket, ContentKeyPacketSignature: sessionKeySignature };
176 export const generateDriveBootstrap = async (addressPrivateKey: PrivateKeyReference) => {
179 NodePassphrase: SharePassphrase,
180 privateKey: sharePrivateKey,
181 NodePassphraseSignature: SharePassphraseSignature,
182 } = await generateNodeKeys(addressPrivateKey);
186 NodePassphrase: FolderPassphrase,
187 privateKey: folderPrivateKey,
188 NodePassphraseSignature: FolderPassphraseSignature,
189 } = await generateNodeKeys(sharePrivateKey, addressPrivateKey);
191 const FolderName = await encryptName('root', sharePrivateKey, addressPrivateKey);
196 SharePassphraseSignature,
198 FolderPassphraseSignature,