Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / keys / driveKeys.ts
blob13555b09e1d4e0b2c63c026d583b203d54801fac
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({
19         [dataType]: data,
20         stripTrailingSpaces: dataType === 'textData',
21         signingKeys: privateKeys,
22         detached: true,
23     });
24     return signature;
27 export const encryptUnsigned = async ({ message, publicKey }: UnsignedEncryptionPayload) => {
28     const dataType = message instanceof Uint8Array ? 'binaryData' : 'textData';
29     const { message: encryptedToken } = await CryptoProxy.encryptMessage({
30         [dataType]: message,
31         stripTrailingSpaces: dataType === 'textData',
32         encryptionKeys: publicKey,
33     });
34     return encryptedToken;
37 export const encryptName = async (
38     name: string,
39     parentPublicKey: PublicKeyReference,
40     addressPrivateKey: PrivateKeyReference
41 ) => {
42     const { message: Name } = await CryptoProxy.encryptMessage({
43         textData: name,
44         stripTrailingSpaces: true,
45         encryptionKeys: parentPublicKey,
46         signingKeys: addressPrivateKey,
47     });
49     return Name;
52 interface UnsignedDecryptionPayload {
53     armoredMessage: string;
54     privateKey: PrivateKeyReference | PrivateKeyReference[];
57 interface SignedDecryptionPayload<F extends 'utf8' | 'binary'> extends UnsignedDecryptionPayload {
58     publicKey: PublicKeyReference | PublicKeyReference[];
59     format?: F;
62 export const decryptSigned = async <F extends 'utf8' | 'binary' = 'utf8'>({
63     armoredMessage,
64     privateKey,
65     publicKey,
66     format,
67 }: SignedDecryptionPayload<F>) => {
68     const { data, verified } = await CryptoProxy.decryptMessage({
69         armoredMessage,
70         decryptionKeys: privateKey,
71         verificationKeys: publicKey,
72         format,
73     });
74     return { data, verified };
77 /**
78  * Decrypts unsigned armored message, in the context of drive it's share's passphrase and folder's contents.
79  */
80 export const decryptUnsigned = async ({ armoredMessage, privateKey }: UnsignedDecryptionPayload) => {
81     const { data: decryptedMessage } = await CryptoProxy.decryptMessage({
82         armoredMessage,
83         decryptionKeys: privateKey,
84     });
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' }],
93         ...keyGenConfigs,
94     });
96     const privateKeyArmored = await CryptoProxy.exportPrivateKey({
97         privateKey,
98         passphrase: rawPassphrase,
99     });
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,
119     });
121     return { NodeHashKey };
124 export const encryptPassphrase = async (
125     parentKey: PrivateKeyReference | PrivateKeyReference[],
126     addressKey: PrivateKeyReference | PrivateKeyReference[] = parentKey,
127     rawPassphrase = generatePassphrase(),
128     passphraseSessionKey?: SessionKey
129 ) => {
130     const sessionKey = passphraseSessionKey
131         ? passphraseSessionKey
132         : await CryptoProxy.generateSessionKey({ recipientKeys: parentKey });
133     const { message: NodePassphrase, signature: NodePassphraseSignature } = await CryptoProxy.encryptMessage({
134         textData: rawPassphrase,
135         sessionKey,
136         signingKeys: addressKey,
137         encryptionKeys: parentKey,
138         detached: true,
139     });
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
156         addressKey,
157         rawPassphrase
158     );
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) => {
177     const {
178         NodeKey: ShareKey,
179         NodePassphrase: SharePassphrase,
180         privateKey: sharePrivateKey,
181         NodePassphraseSignature: SharePassphraseSignature,
182     } = await generateNodeKeys(addressPrivateKey);
184     const {
185         NodeKey: FolderKey,
186         NodePassphrase: FolderPassphrase,
187         privateKey: folderPrivateKey,
188         NodePassphraseSignature: FolderPassphraseSignature,
189     } = await generateNodeKeys(sharePrivateKey, addressPrivateKey);
191     const FolderName = await encryptName('root', sharePrivateKey, addressPrivateKey);
193     return {
194         bootstrap: {
195             SharePassphrase,
196             SharePassphraseSignature,
197             FolderPassphrase,
198             FolderPassphraseSignature,
199             ShareKey,
200             FolderKey,
201             FolderName,
202         },
203         sharePrivateKey,
204         folderPrivateKey,
205     };