Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / crypto / README.md
blobe88aae1213bce8d0a279fd3fbb17808bbe3b1802
1 The crypto package interfaces the apps with the underlying OpenPGP crypto libs of pmcrypto and OpenPGP.js, as well as the browser's native WebCrypto API.
3 > **`pmcrypto` no longer needs to be directly imported by the apps and other packages, you should always use `@proton/crypto` instead.**
5 ## Usage
7 The utils functions that `pmcrypto` exported (e.g. `arrayToBinaryString`) are now accessible under `@proton/crypto/lib/utils`.
9 Crypto-related functions runnable in web workers are handled by the `CryptoProxy`, which is initialized together with the apps (see [this section](web-worker-integration) for more info on the setup).
11 ### Examples
13 <details>
14 <summary><b>Importing/exporting public and private keys</b></summary>
16 #### Importing/exporting public and private keys
18 `OpenPGPKey` objects have been replaced by `PrivateKeyReference` and `PublicKeyReference` ones, as key material stored away from main thread.
20 To import keys:
22 ```js
23 const recipientPublicKey = await CryptoProxy.importPublicKey({ armoredKey: '...' }); // or `binaryKey`
24 // To import a private key, the passphrase must be known
25 // (otherwise, either wait for it to be available, or import as public key)
26 const senderPrivateKey = await CryptoProxy.importPrivateKey({
27     armoredKey: '...', // or `binaryKey`
28     passphrase: 'key decryption passphrase', // If the key is expected to be already decrypted (rare, but it can happen for keys uploaded by the user), you have to pass `passphrase: null`.
29 });
30 ```
32 To export keys to be able to transfer them:
34 ```js
35 // on public key export, if a private key is given, only the public key material is extracted and serialized
36 const armoredPublicKey = await CryptoProxy.exportPublicKey({
37     key: senderPrivateKey,
38     format: 'armored', // or 'binary'
39 });
40 // on private key export, the key will be encrypted before serialization, using the given `passhrapse`
41 const armoredPrivateKey = await CryptoProxy.exportPrivateKey({
42     key: senderPrivateKey,
43     passphrase: 'key encryption passphrase',
44     format: 'armored', // or 'binary'
45 });
46 ```
48 To delete the keys from memory once they are no longer needed:
50 ```js
51 // invalidate a specific key reference
52 await CryptoProxy.clearKey({ key: senderPrivateKey }); // after this, passing `senderPrivateKey` to the `CryptoProxy` will result in an error
54 // invalidate all keys previously imported and generated using the `CryptoProxy`
55 await CryptoProxy.clearKeyStore();
56 ```
58 </details>
60 <details>
61 <summary><b>Encrypt/sign and decrypt/verify string or binary data using keys</b></summary>
63 #### Encrypt/sign and decrypt/verify string or binary data using keys
65 To encrypt and sign:
67 ```js
68 // import the required keys
69 const senderPublicKey = await CryptoProxy.importPublicKey(...);
70 const recipientPrivateKey = await CryptoProxy.importPrivateKey(...);
72 const {
73   message: armoredMessage,
74   signature: armoredSignature,
75   encryptedSignature: armoredEncryptedSignature,
76 } = await CryptoProxy.encryptMessage({
77   textData: 'text data to encrypt', // or `binaryData` for Uint8Arrays
78   encryptionKeys: recipientPublicKey, // and/or `passwords`
79   signingKeys: senderPrivateKey,
80   detached: true,
81   format: 'armored' // or 'binary' to output a binary message and signature
82 });
84 // share `armoredMessage`
85 ```
87 To decrypt and verify:
89 ```js
90 // import the required keys
91 const senderPublicKey = await CryptoProxy.importPublicKey(...);
92 const recipientPrivateKey = await CryptoProxy.importPrivateKey(...);
94 const { data: decryptedData, verified, verificationErrors } = await CryptoProxy.decryptMessage({
95   armoredMessage, // or `binaryMessage`
96   armoredEncryptedSignature, // or 'binaryEncryptedSignature'/'armoredSignature'/'binarySignature'
97   decryptionKeys: recipientPrivateKey // and/or 'passwords'/'sessionKey'
98   verificationKeys: senderPublicKey
99 });
101 if (verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
102   console.log(decryptedData)
103 } else if (verified === VERIFICATION_STATUS.SIGNED_AND_INVALID) {
104   console.log(verificationErrors)
108 </details>
110 <details>
111 <summary><b>Encrypt/decrypt using the session key</b></summary>
113 #### Encrypt/decrypt using the session key directly
115 ```js
116 // First generate the session key
117 const sessionKey = await CryptoProxy.generateSessionKey({ recipientKeys: recipientPublicKey });
119 // Then encrypt the data with it
120 const { message: armoredMessage } = await CryptoProxy.encryptMessage({
121     textData: 'text data to encrypt', // or `binaryData` for Uint8Arrays
122     sessionKey,
123     encryptionKeys: recipientPublicKey, // and/or `passwords`, used to encrypt the session key
124     signingKeys: senderPrivateKey,
128 To decrypt, you can again provide the session key directly:
130 ```js
131 // Then encrypt the data with it
132 const { data } = await CryptoProxy.decryptMessage({
133     armoredMessage, // or `binaryMessage`
134     sessionKeys: sessionKey,
135     verificationKeys: senderPublicKey,
139 You can also encrypt the session key on its own:
141 ```js
142 const armoredEncryptedSessionKey = await encryptSessionKey({
143     ...sessionKey,
144     encryptionKeys, // and/or passwords
145     format: 'armored', // or 'binary'
148 // And decrypt it with:
149 const sessionKey = await CryptoProxy.decryptSessionKey({
150     armoredMessage: armoredEncryptedSessionKey, // or `binaryMessage`
151     decryptionsKeys, // or `passwords`
155 </details>
157 ## Web Worker Integration
159 The CryptoProxy redirects crypto request to whatever endpoint is set via `CryptoProxy.setEndpoint`. Only one endpoint can be set at a time. To release an endpoint and possibly set a new one, call `CryptoProxy.releaseEndpoint`.
161 This package implements a worker pool `CryptoWorkerPool` that the apps can use as endpoint, out of the box:
163 ```js
164 import { CryptoWorkerPool } from '@proton/crypto/lib/worker/workerPool';
166 async function setupCryptoWorker() {
167     await CryptoWorkerPool.init(); // CryptoWorkerPool is a singleton
168     CryptoProxy.setEndpoint(
169         CryptoWorkerPool,
170         (endpoint) => endpoint.destroy() // destroy the CryptoWorkerPool when the CryptoProxy endpoint is released
171     );
175 Using workers is necessary since crypto operations are likely to freeze the UI if run in the main thread.
177 However, if you have an existing app-specific worker, you might not need to spawn separate workers, as described below.
179 <!-- ## App-specific workers -->
181 ### Setting up CryptoProxy inside a worker (with separate key store than the main thread)
183 If a custom app worker needs to call the CryptoProxy (even indirectly, to e.g. use `@proton/shared` functions), it can create and use a CryptoApi instance directly, thus avoiding going through a separate worker to resolve the requests:
185 ```js
186 import { Api: CryptoApi } from '@proton/crypto/lib/worker/api'
187 CryptoProxy.setEndpoint(new CryptoApi(), endpoint => endpoint.clearKeyStore());
190 Note that the CryptoApi imports OpenPGP.js, and it should not be used or imported in the main thread, but only inside workers (you might want to use dynamic imports in this sense).
192 The CryptoProxy initialized in this way is totally separate from the CryptoProxy initialized in the main thread, and it will not share key store with it. If you need a shared key store (which is preferable than trasferring keys manually to and from the worker), see the next section.
194 ### Using custom worker as CryptoProxy endpoint for the main thread (with shared key store)
196 To have a single app-specific worker that takes care of some app-specific requests, as well as the CryptoProxy ones from the main thread, it's possible to extend the CryptoApi.
198 Example setup:
200 ```js
201 // in `customWorker.ts`:
202 import { expose, transferHandlers } from 'comlink';
204 import { CryptoProxy, PrivateKeyReference, PublicKeyReference } from '@proton/crypto';
205 import { Api as CryptoApi } from '@proton/crypto/lib/worker/api';
206 import { workerTransferHandlers } from '@proton/crypto/lib/worker/transferHandlers';
208 class CustomWorkerApi extends CryptoApi {
209     constructor() {
210         super();
211         CryptoProxy.setEndpoint(this); // if needed, set endpoint (e.g. for @proton/shared) in the worker itself
212     }
214     // decrypt and encrypt to a different key, saving some communication overhead
215     async reEncryptMessage({
216         armoredMessage,
217         decryptionKeys,
218         encryptionKeys,
219     }: {
220         armoredMessage: string,
221         decryptionKeys: PrivateKeyReference[],
222         encryptionKeys: PublicKeyReference[],
223     }) {
224         const { data: binaryData } = await this.decryptMessage({ armoredMessage, decryptionKeys, format: 'binary' });
225         return this.encryptMessage({ binaryData, encryptionKeys });
226     }
229 // set up transfer handlers for the CryptoApi (you might have to set up your own as well)
230 workerTransferHandlers.forEach(({ name, handler }) => transferHandlers.set(name, handler));
231 // initialize underlying crypto libraries
232 CustomWorkerApi.init();
234 expose(CustomWorkerApi);
237 ```js
238 // in main thread:
239 import { wrap, transferHandlers } from 'comlink';
240 import { mainThreadTransferHandlers } from '@proton/crypto/lib/worker/transferHandlers';
241 import { CryptoProxy } from '@proton/crypto';
243 const RemoteCustomWorker = wrap<typeof CustomWorkerApi>(new Worker(new URL('./customWorker.ts', import.meta.url)));
244 // set up transfer handlers for the CryptoApi (you might have to set up your own as well)
245 mainThreadTransferHandlers.forEach(({ name, handler }) => transferHandlers.set(name, handler));
247 async function doStuff() {
248   // start the worker
249   const customWorkerInstance = await new RemoteCustomWorker();
250   // set it as CryptoProxy endpoint
251   CryptoProxy.setEndpoint(customWorkerInstance);
253   // the CryptoProxy requests will now be directed to your custom worker
254   const oldKey = await CryptoProxy.importPrivateKey(...); // or `customWorkerInstance.importPrivateKey`
255   const newKey = await CryptoProxy.generateKey(...); // or `customWorkerInstance.generateKey`
257   // the custom functions need to be referenced directly, since the CryptoProxy is not aware of them
258   await customWorkerInstance.reEncryptMessage({
259     armoredMessage: '...',
260     decryptionKeys: [oldKey],
261     encryptionKeys: [newKey]
262   });
266 ## Testing
268 Chrome and Firefox are used for tests. With Chrome and Firefox installed, running test should work out of the box. To use a different Chromium-based browser, set the environment variable CHROME_BIN to point to the corresponding executable.