From 634eee11920a16531dcff77307d059afee1a4341 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Fri, 1 Sep 2023 20:29:11 +0530 Subject: [PATCH 1/3] added new crypto utils --- apps/photos/src/utils/crypto/libsodium.ts | 13 +++++++++---- apps/photos/src/worker/crypto.worker.ts | 7 +++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/photos/src/utils/crypto/libsodium.ts b/apps/photos/src/utils/crypto/libsodium.ts index 71ce4d2ba..958a2af0b 100644 --- a/apps/photos/src/utils/crypto/libsodium.ts +++ b/apps/photos/src/utils/crypto/libsodium.ts @@ -202,7 +202,7 @@ export async function generateKeyAndEncryptToB64(data: string) { } export async function encryptUTF8(data: string, key: string) { - const b64Data = await toB64(await fromString(data)); + const b64Data = await toB64(await fromUTF8(data)); return await encryptToB64(b64Data, key); } @@ -281,7 +281,7 @@ export async function deriveKey( return await toB64( sodium.crypto_pwhash( sodium.crypto_secretbox_KEYBYTES, - await fromString(passphrase), + await fromUTF8(passphrase), await fromB64(salt), opsLimit, memLimit, @@ -315,7 +315,7 @@ export async function deriveInteractiveKey(passphrase: string, salt: string) { const key = await toB64( sodium.crypto_pwhash( sodium.crypto_secretbox_KEYBYTES, - await fromString(passphrase), + await fromUTF8(passphrase), await fromB64(salt), sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, @@ -402,10 +402,15 @@ export async function toURLSafeB64(input: Uint8Array) { return sodium.to_base64(input, sodium.base64_variants.URLSAFE); } -export async function fromString(input: string) { +export async function fromUTF8(input: string) { await sodium.ready; return sodium.from_string(input); } + +export async function toUTF8(input: string) { + await sodium.ready; + return sodium.to_string(await fromB64(input)); +} export async function toHex(input: string) { await sodium.ready; return sodium.to_hex(await fromB64(input)); diff --git a/apps/photos/src/worker/crypto.worker.ts b/apps/photos/src/worker/crypto.worker.ts index f0d38b827..7a51bec98 100644 --- a/apps/photos/src/worker/crypto.worker.ts +++ b/apps/photos/src/worker/crypto.worker.ts @@ -151,8 +151,11 @@ export class DedicatedCryptoWorker { return libsodium.generateSubKey(key, subKeyLength, subKeyID, context); } - async fromString(string: string) { - return libsodium.fromString(string); + async fromUTF8(string: string) { + return libsodium.fromUTF8(string); + } + async toUTF8(data: string) { + return libsodium.toUTF8(data); } async toB64(data: Uint8Array) { From 732ccc816fe505ebe0e2c3b86ed6f355645bd253 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Fri, 1 Sep 2023 20:46:24 +0530 Subject: [PATCH 2/3] fix two-factor enabled login --- .../components/VerifyMasterPasswordForm.tsx | 12 ++- apps/photos/src/pages/credentials/index.tsx | 94 +++++++++++++------ apps/photos/src/utils/error/index.ts | 1 + .../src/utils/storage/sessionStorage.ts | 1 + 4 files changed, 77 insertions(+), 31 deletions(-) diff --git a/apps/photos/src/components/VerifyMasterPasswordForm.tsx b/apps/photos/src/components/VerifyMasterPasswordForm.tsx index 10eacc2be..3593be38a 100644 --- a/apps/photos/src/components/VerifyMasterPasswordForm.tsx +++ b/apps/photos/src/components/VerifyMasterPasswordForm.tsx @@ -22,7 +22,10 @@ export interface VerifyMasterPasswordFormProps { ) => void; buttonText: string; submitButtonProps?: ButtonProps; - getKeyAttributes?: (kek: string) => Promise; + getKeyAttributes?: ( + kek: string, + passphrase: string + ) => Promise; srpAttributes?: SRPAttributes; } @@ -63,7 +66,7 @@ export default function VerifyMasterPasswordForm({ throw Error(CustomError.WEAK_DEVICE); } if (!keyAttributes && typeof getKeyAttributes === 'function') { - keyAttributes = await getKeyAttributes(kek); + keyAttributes = await getKeyAttributes(kek, passphrase); } if (!keyAttributes) { throw Error("couldn't get key attributes"); @@ -80,6 +83,11 @@ export default function VerifyMasterPasswordForm({ throw Error(CustomError.INCORRECT_PASSWORD); } } catch (e) { + if (e.message === CustomError.TWO_FACTOR_ENABLED) { + // two factor enabled, user has been redirected to two factor page + return; + } + logError(e, 'failed to verify passphrase'); switch (e.message) { case CustomError.WEAK_DEVICE: setFieldError(t('WEAK_DEVICE')); diff --git a/apps/photos/src/pages/credentials/index.tsx b/apps/photos/src/pages/credentials/index.tsx index fcde91788..13b95f629 100644 --- a/apps/photos/src/pages/credentials/index.tsx +++ b/apps/photos/src/pages/credentials/index.tsx @@ -42,6 +42,7 @@ import { APPS, getAppName } from 'constants/apps'; import { addLocalLog } from 'utils/logging'; import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker'; import { B64EncryptionResult } from 'types/crypto'; +import { CustomError } from 'utils/error'; export default function Credentials() { const router = useRouter(); @@ -75,23 +76,36 @@ export default function Credentials() { const kekEncryptedAttributes: B64EncryptionResult = getKey( SESSION_KEYS.KEY_ENCRYPTION_KEY ); + const passphraseEncryptedAttributes: B64EncryptionResult = getKey( + SESSION_KEYS.PASSPHRASE_ENCRYPTION_KEY + ); const keyAttributes: KeyAttributes = getData( LS_KEYS.KEY_ATTRIBUTES ); - if (kekEncryptedAttributes && keyAttributes) { + if ( + kekEncryptedAttributes && + keyAttributes && + passphraseEncryptedAttributes + ) { const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const kek = await cryptoWorker.decryptB64( kekEncryptedAttributes.encryptedData, kekEncryptedAttributes.nonce, kekEncryptedAttributes.key ); + const passphrase = await cryptoWorker.toUTF8( + await cryptoWorker.decryptB64( + passphraseEncryptedAttributes.encryptedData, + passphraseEncryptedAttributes.nonce, + passphraseEncryptedAttributes.key + ) + ); const key = await cryptoWorker.decryptB64( keyAttributes.encryptedKey, keyAttributes.keyDecryptionNonce, kek ); - await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key); - router.push(PAGES.GALLERY); + useMasterPassword(key, passphrase, kek, keyAttributes); return; } @@ -122,34 +136,56 @@ export default function Credentials() { appContext.showNavBar(true); }, []); - const getKeyAttributes = async (kek: string) => { - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const { keyAttributes, encryptedToken, token, id, twoFactorSessionID } = - await loginViaSRP(srpAttributes, kek); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); - if (twoFactorSessionID) { - const sessionKeyAttributes = - await cryptoWorker.generateKeyAndEncryptToB64(kek); - setKey(SESSION_KEYS.KEY_ENCRYPTION_KEY, sessionKeyAttributes); - const user = getData(LS_KEYS.USER); - setData(LS_KEYS.USER, { - ...user, - twoFactorSessionID, - isTwoFactorEnabled: true, - }); - setIsFirstLogin(true); - router.push(PAGES.TWO_FACTOR_VERIFY); - return null; - } else { - const user = getData(LS_KEYS.USER); - setData(LS_KEYS.USER, { - ...user, - token, + const getKeyAttributes = async (kek: string, passphrase: string) => { + try { + const cryptoWorker = await ComlinkCryptoWorker.getInstance(); + const { + keyAttributes, encryptedToken, + token, id, - isTwoFactorEnabled: false, - }); - return keyAttributes; + twoFactorSessionID, + } = await loginViaSRP(srpAttributes, kek); + setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); + if (twoFactorSessionID) { + const sessionKeyAttributes = + await cryptoWorker.generateKeyAndEncryptToB64(kek); + const passphraseKeyAttributes = + await cryptoWorker.generateKeyAndEncryptToB64( + await cryptoWorker.toB64( + await cryptoWorker.fromUTF8(passphrase) + ) + ); + setKey(SESSION_KEYS.KEY_ENCRYPTION_KEY, sessionKeyAttributes); + setKey( + SESSION_KEYS.PASSPHRASE_ENCRYPTION_KEY, + passphraseKeyAttributes + ); + const user = getData(LS_KEYS.USER); + setData(LS_KEYS.USER, { + ...user, + twoFactorSessionID, + isTwoFactorEnabled: true, + }); + setIsFirstLogin(true); + router.push(PAGES.TWO_FACTOR_VERIFY); + throw Error(CustomError.TWO_FACTOR_ENABLED); + } else { + const user = getData(LS_KEYS.USER); + setData(LS_KEYS.USER, { + ...user, + token, + encryptedToken, + id, + isTwoFactorEnabled: false, + }); + return keyAttributes; + } + } catch (e) { + if (e.message !== CustomError.TWO_FACTOR_ENABLED) { + logError(e, 'getKeyAttributes failed'); + } + throw e; } }; diff --git a/apps/photos/src/utils/error/index.ts b/apps/photos/src/utils/error/index.ts index 603ca50f4..926518f1d 100644 --- a/apps/photos/src/utils/error/index.ts +++ b/apps/photos/src/utils/error/index.ts @@ -71,6 +71,7 @@ export const CustomError = { NON_PREVIEWABLE_FILE: 'non previewable file', PROCESSING_FAILED: 'processing failed', EXPORT_RECORD_JSON_PARSING_FAILED: 'export record json parsing failed', + TWO_FACTOR_ENABLED: 'two factor enabled', }; export function parseUploadErrorCodes(error) { diff --git a/apps/photos/src/utils/storage/sessionStorage.ts b/apps/photos/src/utils/storage/sessionStorage.ts index e91ea8080..b32c2a8e8 100644 --- a/apps/photos/src/utils/storage/sessionStorage.ts +++ b/apps/photos/src/utils/storage/sessionStorage.ts @@ -1,6 +1,7 @@ export enum SESSION_KEYS { ENCRYPTION_KEY = 'encryptionKey', KEY_ENCRYPTION_KEY = 'keyEncryptionKey', + PASSPHRASE_ENCRYPTION_KEY = 'passphraseEncryptionKey', } export const setKey = (key: SESSION_KEYS, value: object) => { From 50281296214094796443fdb7d17d5e2ea0fc7108 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 3 Sep 2023 09:06:21 +0530 Subject: [PATCH 3/3] remove session storage passpharse storage --- .../components/VerifyMasterPasswordForm.tsx | 13 +- apps/photos/src/pages/credentials/index.tsx | 124 ++++++++---------- .../src/utils/storage/sessionStorage.ts | 8 +- 3 files changed, 67 insertions(+), 78 deletions(-) diff --git a/apps/photos/src/components/VerifyMasterPasswordForm.tsx b/apps/photos/src/components/VerifyMasterPasswordForm.tsx index 3593be38a..39d30f068 100644 --- a/apps/photos/src/components/VerifyMasterPasswordForm.tsx +++ b/apps/photos/src/components/VerifyMasterPasswordForm.tsx @@ -16,16 +16,13 @@ export interface VerifyMasterPasswordFormProps { keyAttributes: KeyAttributes; callback: ( key: string, - passphrase: string, kek: string, - keyAttributes: KeyAttributes + keyAttributes: KeyAttributes, + passphrase?: string ) => void; buttonText: string; submitButtonProps?: ButtonProps; - getKeyAttributes?: ( - kek: string, - passphrase: string - ) => Promise; + getKeyAttributes?: (kek: string) => Promise; srpAttributes?: SRPAttributes; } @@ -66,7 +63,7 @@ export default function VerifyMasterPasswordForm({ throw Error(CustomError.WEAK_DEVICE); } if (!keyAttributes && typeof getKeyAttributes === 'function') { - keyAttributes = await getKeyAttributes(kek, passphrase); + keyAttributes = await getKeyAttributes(kek); } if (!keyAttributes) { throw Error("couldn't get key attributes"); @@ -77,7 +74,7 @@ export default function VerifyMasterPasswordForm({ keyAttributes.keyDecryptionNonce, kek ); - callback(key, passphrase, kek, keyAttributes); + callback(key, kek, keyAttributes, passphrase); } catch (e) { logError(e, 'user entered a wrong password'); throw Error(CustomError.INCORRECT_PASSWORD); diff --git a/apps/photos/src/pages/credentials/index.tsx b/apps/photos/src/pages/credentials/index.tsx index 13b95f629..e7e3d2ccb 100644 --- a/apps/photos/src/pages/credentials/index.tsx +++ b/apps/photos/src/pages/credentials/index.tsx @@ -10,7 +10,12 @@ import { } from 'utils/storage/localStorage'; import { useRouter } from 'next/router'; import { PAGES } from 'constants/pages'; -import { SESSION_KEYS, getKey, setKey } from 'utils/storage/sessionStorage'; +import { + SESSION_KEYS, + getKey, + removeKey, + setKey, +} from 'utils/storage/sessionStorage'; import { decryptAndStoreToken, generateAndSaveIntermediateKeyAttributes, @@ -76,36 +81,23 @@ export default function Credentials() { const kekEncryptedAttributes: B64EncryptionResult = getKey( SESSION_KEYS.KEY_ENCRYPTION_KEY ); - const passphraseEncryptedAttributes: B64EncryptionResult = getKey( - SESSION_KEYS.PASSPHRASE_ENCRYPTION_KEY - ); const keyAttributes: KeyAttributes = getData( LS_KEYS.KEY_ATTRIBUTES ); - if ( - kekEncryptedAttributes && - keyAttributes && - passphraseEncryptedAttributes - ) { + if (kekEncryptedAttributes && keyAttributes) { + removeKey(SESSION_KEYS.KEY_ENCRYPTION_KEY); const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const kek = await cryptoWorker.decryptB64( kekEncryptedAttributes.encryptedData, kekEncryptedAttributes.nonce, kekEncryptedAttributes.key ); - const passphrase = await cryptoWorker.toUTF8( - await cryptoWorker.decryptB64( - passphraseEncryptedAttributes.encryptedData, - passphraseEncryptedAttributes.nonce, - passphraseEncryptedAttributes.key - ) - ); const key = await cryptoWorker.decryptB64( keyAttributes.encryptedKey, keyAttributes.keyDecryptionNonce, kek ); - useMasterPassword(key, passphrase, kek, keyAttributes); + useMasterPassword(key, kek, keyAttributes, kek); return; } @@ -136,67 +128,61 @@ export default function Credentials() { appContext.showNavBar(true); }, []); - const getKeyAttributes = async (kek: string, passphrase: string) => { - try { - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const { - keyAttributes, - encryptedToken, - token, - id, - twoFactorSessionID, - } = await loginViaSRP(srpAttributes, kek); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); - if (twoFactorSessionID) { - const sessionKeyAttributes = - await cryptoWorker.generateKeyAndEncryptToB64(kek); - const passphraseKeyAttributes = - await cryptoWorker.generateKeyAndEncryptToB64( - await cryptoWorker.toB64( - await cryptoWorker.fromUTF8(passphrase) - ) - ); - setKey(SESSION_KEYS.KEY_ENCRYPTION_KEY, sessionKeyAttributes); - setKey( - SESSION_KEYS.PASSPHRASE_ENCRYPTION_KEY, - passphraseKeyAttributes - ); - const user = getData(LS_KEYS.USER); - setData(LS_KEYS.USER, { - ...user, - twoFactorSessionID, - isTwoFactorEnabled: true, - }); - setIsFirstLogin(true); - router.push(PAGES.TWO_FACTOR_VERIFY); - throw Error(CustomError.TWO_FACTOR_ENABLED); - } else { - const user = getData(LS_KEYS.USER); - setData(LS_KEYS.USER, { - ...user, - token, + const getKeyAttributes: VerifyMasterPasswordFormProps['getKeyAttributes'] = + async (kek: string) => { + try { + const cryptoWorker = await ComlinkCryptoWorker.getInstance(); + const { + keyAttributes, encryptedToken, + token, id, - isTwoFactorEnabled: false, - }); - return keyAttributes; + twoFactorSessionID, + } = await loginViaSRP(srpAttributes, kek); + setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); + if (twoFactorSessionID) { + const sessionKeyAttributes = + await cryptoWorker.generateKeyAndEncryptToB64(kek); + setKey( + SESSION_KEYS.KEY_ENCRYPTION_KEY, + sessionKeyAttributes + ); + const user = getData(LS_KEYS.USER); + setData(LS_KEYS.USER, { + ...user, + twoFactorSessionID, + isTwoFactorEnabled: true, + }); + setIsFirstLogin(true); + router.push(PAGES.TWO_FACTOR_VERIFY); + throw Error(CustomError.TWO_FACTOR_ENABLED); + } else { + const user = getData(LS_KEYS.USER); + setData(LS_KEYS.USER, { + ...user, + token, + encryptedToken, + id, + isTwoFactorEnabled: false, + }); + return keyAttributes; + } + } catch (e) { + if (e.message !== CustomError.TWO_FACTOR_ENABLED) { + logError(e, 'getKeyAttributes failed'); + } + throw e; } - } catch (e) { - if (e.message !== CustomError.TWO_FACTOR_ENABLED) { - logError(e, 'getKeyAttributes failed'); - } - throw e; - } - }; + }; const useMasterPassword: VerifyMasterPasswordFormProps['callback'] = async ( key, - passphrase, kek, - keyAttributes + keyAttributes, + passphrase ) => { try { - if (isFirstLogin()) { + if (isFirstLogin() && passphrase) { await generateAndSaveIntermediateKeyAttributes( passphrase, keyAttributes, diff --git a/apps/photos/src/utils/storage/sessionStorage.ts b/apps/photos/src/utils/storage/sessionStorage.ts index b32c2a8e8..bad871f76 100644 --- a/apps/photos/src/utils/storage/sessionStorage.ts +++ b/apps/photos/src/utils/storage/sessionStorage.ts @@ -1,7 +1,6 @@ export enum SESSION_KEYS { ENCRYPTION_KEY = 'encryptionKey', KEY_ENCRYPTION_KEY = 'keyEncryptionKey', - PASSPHRASE_ENCRYPTION_KEY = 'passphraseEncryptionKey', } export const setKey = (key: SESSION_KEYS, value: object) => { @@ -18,6 +17,13 @@ export const getKey = (key: SESSION_KEYS) => { return JSON.parse(sessionStorage.getItem(key)); }; +export const removeKey = (key: SESSION_KEYS) => { + if (typeof sessionStorage === 'undefined') { + return null; + } + sessionStorage.removeItem(key); +}; + export const clearKeys = () => { if (typeof sessionStorage === 'undefined') { return null;