Merge branch 'IDTEAM-1.26.0' into 'main'
[ProtonMail-WebClient.git] / packages / pass / lib / auth / integrity.ts
blob6465e37b3eef1782fc1c28d16b939f43a47ffd66
1 import { stringToUint8Array, uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
3 import type { AuthSession, EncryptedSessionKeys } from './session';
5 type IntegrityKey = keyof Omit<AuthSession, EncryptedSessionKeys>;
7 export const SESSION_DIGEST_VERSION = 1;
8 const VERSION_SEPARATOR = '.';
10 /** `AuthSession` keys used to verify the integrity of the session
11  * when resuming from a persisted state. This ensures that the
12  * session has not been tampered with, avoiding unintended side-effects
13  * if the persisted session was modified outside of our control.
14  * ⚠️ WARNING: UPDATING KEYS HERE WILL CAUSE EXISTING DIGESTS TO FAIL
15  * VALIDATION - BUMP THE `SESSION_DIGEST_VERSION` WHEN DOING SO.  */
16 export const SESSION_INTEGRITY_KEYS_V1: IntegrityKey[] = [
17     'extraPassword',
18     'LocalID',
19     'lockMode',
20     'lockTTL',
21     'offlineVerifier',
22     'payloadVersion',
23     'persistent',
24     'UID',
25     'UserID',
28 export const getSessionIntegrityKeys = (version: number): IntegrityKey[] => {
29     switch (version) {
30         case 1:
31             return SESSION_INTEGRITY_KEYS_V1;
32         default:
33             return [];
34     }
37 export const getSessionDigestVersion = (digest: string): number => {
38     try {
39         const [versionStr] = digest.split(VERSION_SEPARATOR);
41         if (versionStr.length === digest.length) return SESSION_DIGEST_VERSION;
42         const version = parseInt(versionStr, 10);
43         return Number.isFinite(version) ? version : SESSION_DIGEST_VERSION;
44     } catch {
45         return SESSION_DIGEST_VERSION;
46     }
49 /** Generates a unique digest based on specific session properties to ensure
50  * the integrity of the session when resuming from a persisted state. This
51  * digest can later be used to verify if the session has been tampered with. */
52 export const digestSession = async (
53     session: Omit<AuthSession, EncryptedSessionKeys>,
54     version: number
55 ): Promise<string> => {
56     const integrityKeys = getSessionIntegrityKeys(version);
57     const sessionDigest = integrityKeys.reduce<string>((digest, key) => `${digest}::${session[key] || '-'}`, '');
58     const sessionBuffer = stringToUint8Array(sessionDigest);
59     const digest = await crypto.subtle.digest('SHA-256', sessionBuffer);
61     return `${version}${VERSION_SEPARATOR}${uint8ArrayToBase64String(new Uint8Array(digest))}`;