Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / crypto / lib / proxy / proxy.ts
blob8cbc9b5757547eb7b0bae96a52522ee35b2019ee
1 import {
2     DEFAULT_KEY_GENERATION_OFFSET,
3     DEFAULT_SIGNATURE_VERIFICATION_OFFSET,
4     VERIFICATION_STATUS,
5 } from 'pmcrypto-v6-canary/lib/constants';
7 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
9 import { serverTime } from '../serverTime';
10 import type { ApiInterface } from '../worker/api';
11 import type { WorkerVerifyOptions } from '../worker/api.models';
13 export type CryptoApiInterface = ApiInterface;
15 const assertNotNull = (value: CryptoApiInterface | null): CryptoApiInterface => {
16     if (value === null) {
17         throw new Error('CryptoProxy: endpoint not initialized');
18     }
19     return value;
22 let endpoint: CryptoApiInterface | null = null;
23 let onEndpointRelease: (endpoint?: any) => Promise<void> = async () => {};
25 interface CryptoProxyInterface extends CryptoApiInterface {
26     /**
27      * Set proxy endpoint.
28      * The provided instance must be already initialised and ready to resolve requests.
29      * @param endpoint
30      * @param onRelease - callback called after `this.releaseEndpoint()` is invoked and endpoint is released
31      */
32     setEndpoint<T extends CryptoApiInterface>(endpoint: T, onRelease?: (endpoint: T) => Promise<void>): void;
34     /**
35      * Release endpoint. Afterwards, a different one may be set via `setEndpoint()`.
36      * If a `onRelease` callback was passed to `setEndpoint()`, the callback is called before returning.
37      * Note that this function does not have any other side effects, e.g. it does not clear the key store automatically.
38      * Any endpoint-specific clean up logic should be done inside the `onRelease` callback.
39      */
40     releaseEndpoint(): Promise<void>;
43 /**
44  * Prior to OpenPGP.js v5.4.0, trailing spaces were not properly stripped with \r\n line endings (see https://github.com/openpgpjs/openpgpjs/pull/1548).
45  * In order to verify the signatures generated over the incorrectly normalised data, we fallback to not normalising the input.
46  * Currently, this is done inside the CryptoProxy, to transparently track the number of signatures that are affected throughout the apps.
47  * @param options - verification options, with `date` already set to server time
48  */
49 async function verifyMessageWithFallback<
50     DataType extends string | Uint8Array,
51     FormatType extends WorkerVerifyOptions<DataType>['format'] = 'utf8',
52 >(options: WorkerVerifyOptions<DataType> & { format?: FormatType }) {
53     const verificationResult = await assertNotNull(endpoint).verifyMessage<DataType, FormatType>(options);
55     const { textData, stripTrailingSpaces } = options;
56     if (
57         verificationResult.verified === VERIFICATION_STATUS.SIGNED_AND_INVALID &&
58         stripTrailingSpaces &&
59         textData &&
60         verificationResult.data !== textData // detect whether some normalisation was applied
61     ) {
62         const fallbackverificationResult = await assertNotNull(endpoint).verifyMessage<string, FormatType>({
63             ...options,
64             binaryData: undefined,
65             stripTrailingSpaces: false,
66         });
68         if (fallbackverificationResult.verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
69             captureMessage('Fallback verification needed', {
70                 level: 'info',
71             });
72             return fallbackverificationResult;
73         }
75         // detect whether the message has trailing spaces followed by a mix of \r\n and \n line endings
76         const legacyRemoveTrailingSpaces = (text: string) => {
77             return text
78                 .split('\n')
79                 .map((line) => {
80                     let i = line.length - 1;
81                     for (; i >= 0 && (line[i] === ' ' || line[i] === '\t'); i--) {}
82                     return line.substr(0, i + 1);
83                 })
84                 .join('\n');
85         };
87         if (textData !== legacyRemoveTrailingSpaces(textData)) {
88             captureMessage('Fallback verification insufficient', {
89                 level: 'info',
90             });
91         }
92     }
94     return verificationResult;
97 /**
98  * CryptoProxy relays crypto requests to the specified endpoint, which is typically a worker(s), except if
99  * CryptoProxy is already called (also indirectly) from inside a worker.
100  * In such a case, the endpoint can be set to a `new WorkerApi()` instance, or to tbe worker instance itself,
101  * provided it implements/extends WorkerApi.
102  */
103 export const CryptoProxy: CryptoProxyInterface = {
104     setEndpoint(endpointInstance, onRelease = onEndpointRelease) {
105         if (endpoint) {
106             throw new Error('already initialised');
107         }
108         endpoint = endpointInstance;
109         onEndpointRelease = onRelease;
110     },
112     releaseEndpoint() {
113         const tmp = endpoint;
114         endpoint = null;
115         return onEndpointRelease(assertNotNull(tmp));
116     },
118     encryptMessage: async ({ date = serverTime(), ...opts }) =>
119         assertNotNull(endpoint).encryptMessage({ ...opts, date }),
120     decryptMessage: async ({ date = new Date(+serverTime() + DEFAULT_SIGNATURE_VERIFICATION_OFFSET), ...opts }) =>
121         assertNotNull(endpoint).decryptMessage({ ...opts, date }),
122     signMessage: async ({ date = serverTime(), ...opts }) => assertNotNull(endpoint).signMessage({ ...opts, date }),
123     verifyMessage: async ({ date = new Date(+serverTime() + DEFAULT_SIGNATURE_VERIFICATION_OFFSET), ...opts }) =>
124         verifyMessageWithFallback({ ...opts, date }),
125     verifyCleartextMessage: async ({ date = serverTime(), ...opts }) =>
126         assertNotNull(endpoint).verifyCleartextMessage({ ...opts, date }),
127     processMIME: async ({ date = serverTime(), ...opts }) => assertNotNull(endpoint).processMIME({ ...opts, date }),
129     generateSessionKey: async ({ date = serverTime(), ...opts }) =>
130         assertNotNull(endpoint).generateSessionKey({ ...opts, date }),
131     generateSessionKeyForAlgorithm: async (opts) => assertNotNull(endpoint).generateSessionKeyForAlgorithm(opts),
132     encryptSessionKey: async ({ date = serverTime(), ...opts }) =>
133         assertNotNull(endpoint).encryptSessionKey({ ...opts, date }),
134     decryptSessionKey: async ({ date = serverTime(), ...opts }) =>
135         assertNotNull(endpoint).decryptSessionKey({ ...opts, date }),
137     importPrivateKey: async (opts) => assertNotNull(endpoint).importPrivateKey(opts),
138     importPublicKey: async (opts) => assertNotNull(endpoint).importPublicKey(opts),
139     generateKey: async ({ date = new Date(+serverTime() + DEFAULT_KEY_GENERATION_OFFSET), ...opts }) =>
140         assertNotNull(endpoint).generateKey({ ...opts, date }),
141     reformatKey: async ({ privateKey, date = privateKey.getCreationTime(), ...opts }) =>
142         assertNotNull(endpoint).reformatKey({ ...opts, privateKey, date }),
143     exportPublicKey: async (opts) => assertNotNull(endpoint).exportPublicKey(opts),
144     exportPrivateKey: async (opts) => assertNotNull(endpoint).exportPrivateKey(opts),
145     clearKeyStore: () => assertNotNull(endpoint).clearKeyStore(),
146     clearKey: async (opts) => assertNotNull(endpoint).clearKey(opts),
147     replaceUserIDs: async (opts) => assertNotNull(endpoint).replaceUserIDs(opts),
148     cloneKeyAndChangeUserIDs: async (opts) => assertNotNull(endpoint).cloneKeyAndChangeUserIDs(opts),
149     generateE2EEForwardingMaterial: async ({ date = serverTime(), ...opts }) =>
150         assertNotNull(endpoint).generateE2EEForwardingMaterial({ ...opts, date }),
151     doesKeySupportE2EEForwarding: async ({ date = serverTime(), ...opts }) =>
152         assertNotNull(endpoint).doesKeySupportE2EEForwarding({ ...opts, date }),
153     isE2EEForwardingKey: async ({ date = serverTime(), ...opts }) =>
154         assertNotNull(endpoint).isE2EEForwardingKey({ ...opts, date }),
156     isRevokedKey: async ({ date = serverTime(), ...opts }) => assertNotNull(endpoint).isRevokedKey({ ...opts, date }),
157     isExpiredKey: async ({ date = serverTime(), ...opts }) => assertNotNull(endpoint).isExpiredKey({ ...opts, date }),
158     canKeyEncrypt: async ({ date = serverTime(), ...opts }) => assertNotNull(endpoint).canKeyEncrypt({ ...opts, date }),
159     getSHA256Fingerprints: async (opts) => assertNotNull(endpoint).getSHA256Fingerprints(opts),
160     computeHash: async (opts) => assertNotNull(endpoint).computeHash(opts),
161     computeHashStream: async (opts) => assertNotNull(endpoint).computeHashStream(opts),
162     computeArgon2: (opts) => assertNotNull(endpoint).computeArgon2(opts),
164     getArmoredMessage: async (opts) => assertNotNull(endpoint).getArmoredMessage(opts),
165     getArmoredKeys: async (opts) => assertNotNull(endpoint).getArmoredKeys(opts),
166     getArmoredSignature: async (opts) => assertNotNull(endpoint).getArmoredSignature(opts),
167     getSignatureInfo: async (opts) => assertNotNull(endpoint).getSignatureInfo(opts),
168     getMessageInfo: async (opts) => assertNotNull(endpoint).getMessageInfo(opts),
169     getKeyInfo: async (opts) => assertNotNull(endpoint).getKeyInfo(opts),