@@ -3,15 +3,18 @@ import { useNavigate } from 'react-router-dom';
33
44import * as config from "../config" ;
55import { useClearStorages , useLocalStorage , useSessionStorage } from "../hooks/useStorage" ;
6- import { toBase64Url } from "../util" ;
6+ import { fromBase64Url , jsonStringifyTaggedBinary , toBase64Url } from "../util" ;
77import { useIndexedDb } from "../hooks/useIndexedDb" ;
88import { useOnUserInactivity } from "../hooks/useOnUserInactivity" ;
99
1010import * as keystore from "./keystore" ;
1111import type { AsymmetricEncryptedContainer , AsymmetricEncryptedContainerKeys , EncryptedContainer , OpenedContainer , PrivateData , UnlockSuccess , WebauthnPrfEncryptionKeyInfo , WebauthnPrfSaltInfo , WrappedKeyInfo } from "./keystore" ;
1212import { MDoc } from "@auth0/mdl" ;
1313import { WalletStateUtils } from "./WalletStateUtils" ;
14- import { addAlterSettingsEvent , addDeleteCredentialEvent , addDeleteCredentialIssuanceSessionEvent , addDeleteKeypairEvent , addNewCredentialEvent , addNewPresentationEvent , addSaveCredentialIssuanceSessionEvent , CurrentSchema , foldOldEventsIntoBaseState } from "./WalletStateSchema" ;
14+ import { addAlterSettingsEvent , addDeleteCredentialEvent , addDeleteCredentialIssuanceSessionEvent , addDeleteKeypairEvent , addNewCredentialEvent , addNewPresentationEvent , addSaveCredentialIssuanceSessionEvent , CurrentSchema , foldOldEventsIntoBaseState , foldState , mergeEventHistories } from "./WalletStateSchema" ;
15+ import { UserId } from "@/api/types" ;
16+ import { getItem } from "@/indexedDB" ;
17+ import { WalletStateContainerGeneric } from "./WalletStateSchemaCommon" ;
1518
1619type WalletState = CurrentSchema . WalletState ;
1720type WalletStateCredential = CurrentSchema . WalletStateCredential ;
@@ -314,14 +317,73 @@ export function useLocalStorageKeystore(eventTarget: EventTarget): LocalStorageK
314317 } , [ setPrivateData , setMainKey , assertKeystoreOpen , userHandleB64u , writePrivateDataOnIdb ] ) ;
315318
316319 const finishUnlock = useCallback ( async (
317- { mainKey , privateData } : UnlockSuccess ,
320+ unlockSuccess : UnlockSuccess ,
318321 user : CachedUser | UserData | null ,
319- ) : Promise < void > => {
322+ credential : PublicKeyCredential | null ,
323+ promptForPrfRetry : ( ) => Promise < boolean | AbortSignal > ,
324+ ) : Promise < keystore . EncryptedContainer > => {
320325 if ( user ) {
321326 const userHandleB64u = ( "prfKeys" in user
322327 ? user . userHandleB64u
323328 : toBase64Url ( user . userHandle )
324329 ) ;
330+ let newEncryptedContainer : keystore . EncryptedContainer ;
331+
332+ if ( privateData ) { // keystore is already opened
333+ const [ localPrivateData , localMainKey ] = await assertKeystoreOpen ( ) ;
334+ const [ remoteContainer , remoteMainKey , ] = await keystore . openPrivateData ( unlockSuccess . mainKey , unlockSuccess . privateData ) ;
335+ const [ localContainer , , ] = await keystore . openPrivateData ( localMainKey , localPrivateData ) ;
336+ const mergedContainer = await mergeEventHistories ( remoteContainer , localContainer ) ;
337+ const { newContainer } = await keystore . updateWalletState ( [
338+ keystore . assertAsymmetricEncryptedContainer ( unlockSuccess . privateData ) ,
339+ remoteMainKey ,
340+ ] , mergedContainer as CurrentSchema . WalletStateContainer ) ;
341+ const [ newPrivateDataEncryptedContainer , newMainKey ] = newContainer ;
342+ await writePrivateDataOnIdb ( newPrivateDataEncryptedContainer , userHandleB64u ) ;
343+ setPrivateData ( newPrivateDataEncryptedContainer ) ;
344+ newEncryptedContainer = newPrivateDataEncryptedContainer ;
345+ setMainKey ( await keystore . exportMainKey ( newMainKey ) ) ;
346+ const foldedState = foldState ( mergedContainer as CurrentSchema . WalletStateContainer ) ;
347+ setCalculatedWalletState ( foldedState ) ;
348+ }
349+ else {
350+ async function mergeWithLocalEncryptedPrivateData ( container : [ EncryptedContainer , CryptoKey , WalletStateContainerGeneric ] ) : Promise < [ EncryptedContainer , CryptoKey , WalletStateContainerGeneric ] > {
351+ const userId = UserId . fromUserHandle ( fromBase64Url ( userHandleB64u ) ) ;
352+ const localUser = await getItem ( "users" , userId . id ) ;
353+ if ( ! localUser ) {
354+ return container ;
355+ }
356+ const localPrivateData : Uint8Array = localUser . privateData ;
357+ const parsedLocalEncryptedPrivateData = await keystore . parsePrivateData ( localPrivateData ) ;
358+ const stringifiedLocalPrivateData = jsonStringifyTaggedBinary ( localPrivateData ) ;
359+ const stringifiedSerializedNewlyUnlockedPrivateData = jsonStringifyTaggedBinary ( keystore . serializePrivateData ( unlockSuccess . privateData ) ) ;
360+ if ( stringifiedLocalPrivateData !== stringifiedSerializedNewlyUnlockedPrivateData ) { // local and remote are different
361+ // decryption of local is required
362+ const [ unlockPrfResult , ] = await keystore . unlockPrf ( parsedLocalEncryptedPrivateData , credential , promptForPrfRetry ) ;
363+ const { privateData, mainKey } = unlockPrfResult ;
364+ const [ localContainer , , ] = await keystore . openPrivateData ( mainKey , privateData ) ;
365+ const mergedContainer = await mergeEventHistories ( unlockedContainer , localContainer ) ;
366+ const { newContainer } = await keystore . updateWalletState ( [
367+ keystore . assertAsymmetricEncryptedContainer ( unlockSuccess . privateData ) ,
368+ unlockSuccess . mainKey ,
369+ ] , mergedContainer as CurrentSchema . WalletStateContainer ) ;
370+ const [ newPrivateDataEncryptedContainer , newMainKey ] = newContainer ;
371+ return [ newPrivateDataEncryptedContainer , newMainKey , mergedContainer ] ;
372+ }
373+ return container ;
374+ }
375+ const { privateData, mainKey } = unlockSuccess ;
376+ const [ unlockedContainer , , ] = await keystore . openPrivateData ( mainKey , privateData ) ;
377+ const [ encryptedContainer , newMainKey , decryptedWalletState ] = await mergeWithLocalEncryptedPrivateData ( [ privateData , mainKey , unlockedContainer ] ) ;
378+ const foldedState = foldState ( decryptedWalletState as CurrentSchema . WalletStateContainer ) ;
379+ newEncryptedContainer = encryptedContainer ;
380+ setPrivateData ( encryptedContainer ) ;
381+ setMainKey ( await keystore . exportMainKey ( newMainKey ) ) ;
382+ await writePrivateDataOnIdb ( encryptedContainer , userHandleB64u ) ;
383+ // after private data update, the calculated wallet state must be re-computed
384+ setCalculatedWalletState ( foldedState ) ;
385+ }
386+
325387 const newUser = ( "prfKeys" in user
326388 ? user
327389 : {
@@ -341,20 +403,15 @@ export function useLocalStorageKeystore(eventTarget: EventTarget): LocalStorageK
341403 // useEffect updating cachedUsers from corrupting cache entries for other
342404 // users logged in in other tabs.
343405 setGlobalUserHandleB64u ( userHandleB64u ) ;
344- await writePrivateDataOnIdb ( privateData , userHandleB64u ) ;
345406
346407 setCachedUsers ( ( cachedUsers ) => {
347408 // Move most recently used user to front of list
348409 const otherUsers = ( cachedUsers || [ ] ) . filter ( ( cu ) => cu . userHandleB64u !== newUser . userHandleB64u ) ;
349410 return [ newUser , ...otherUsers ] ;
350411 } ) ;
351- }
352412
353- setMainKey ( await keystore . exportMainKey ( mainKey ) ) ;
354- setPrivateData ( privateData ) ;
355- // after private data update, the calculated wallet state must be re-computed
356- const [ , , newCalculatedWalletState ] = await keystore . openPrivateData ( mainKey , privateData ) ;
357- setCalculatedWalletState ( newCalculatedWalletState ) ;
413+ return newEncryptedContainer ;
414+ }
358415 } , [
359416 setUserHandleB64u ,
360417 setGlobalUserHandleB64u ,
@@ -365,7 +422,9 @@ export function useLocalStorageKeystore(eventTarget: EventTarget): LocalStorageK
365422 setTabId ,
366423 setGlobalTabId ,
367424 tabId ,
368- writePrivateDataOnIdb
425+ writePrivateDataOnIdb ,
426+ assertKeystoreOpen ,
427+ privateData ,
369428 ] ) ;
370429
371430
@@ -385,8 +444,7 @@ export function useLocalStorageKeystore(eventTarget: EventTarget): LocalStorageK
385444 user : UserData ,
386445 ) : Promise < EncryptedContainer > => {
387446 const unlocked = await keystore . init ( mainKey , keyInfo ) ;
388- await finishUnlock ( unlocked , user ) ;
389- const { privateData } = unlocked ;
447+ const privateData = await finishUnlock ( unlocked , user , null ) ;
390448 return privateData ;
391449 } ,
392450 [ finishUnlock ]
@@ -399,7 +457,7 @@ export function useLocalStorageKeystore(eventTarget: EventTarget): LocalStorageK
399457 user : UserData ,
400458 ) : Promise < [ EncryptedContainer , CommitCallback ] | null > => {
401459 const [ unlockResult , newPrivateData ] = await keystore . unlockPassword ( privateData , password ) ;
402- await finishUnlock ( unlockResult , user ) ;
460+ await finishUnlock ( unlockResult , user , null ) ;
403461 return (
404462 newPrivateData
405463 ?
@@ -492,26 +550,25 @@ export function useLocalStorageKeystore(eventTarget: EventTarget): LocalStorageK
492550
493551 const unlockPrf = useCallback (
494552 async (
495- privateData : EncryptedContainer ,
553+ encryptedPrivateData : EncryptedContainer ,
496554 credential : PublicKeyCredential ,
497555 promptForPrfRetry : ( ) => Promise < boolean | AbortSignal > ,
498556 user : CachedUser | UserData | null ,
499557 ) : Promise < [ EncryptedContainer , CommitCallback ] | null > => {
500- const [ unlockPrfResult , newPrivateData ] = await keystore . unlockPrf ( privateData , credential , promptForPrfRetry ) ;
501- await finishUnlock ( unlockPrfResult , user ) ;
558+ const [ unlockPrfResult , ] = await keystore . unlockPrf ( encryptedPrivateData , credential , promptForPrfRetry ) ;
559+ const updatedPrivateData = await finishUnlock ( unlockPrfResult , user , credential , promptForPrfRetry ) ;
502560 return (
503- newPrivateData
561+ updatedPrivateData
504562 ?
505- [ newPrivateData ,
563+ [ updatedPrivateData ,
506564 async ( ) => {
507- await writePrivateDataOnIdb ( newPrivateData , userHandleB64u ) ;
508- setPrivateData ( newPrivateData ) ;
565+
509566 } ,
510567 ]
511568 : null
512569 ) ;
513570 } ,
514- [ finishUnlock , setPrivateData , writePrivateDataOnIdb , userHandleB64u ]
571+ [ finishUnlock ]
515572 ) ;
516573
517574 const getPasswordOrPrfKeyFromSession = useCallback (
0 commit comments