@@ -100,7 +100,11 @@ export type KeyringControllerState = {
100100 /**
101101 * The salt used to derive the encryption key from the password.
102102 */
103- encryptionSalt ?: string ;
103+ encryptionSalt ?: string ; // TODO why is this stored here?
104+ /**
105+ * The encrypted encryption key. If set, envelope encryption is used.
106+ */
107+ encryptedEncryptionKey ?: string ;
104108} ;
105109
106110export type KeyringControllerMemState = Omit <
@@ -327,6 +331,7 @@ export type SerializedKeyring = {
327331type SessionState = {
328332 keyrings : SerializedKeyring [ ] ;
329333 password ?: string ;
334+ encryptedEncryptionKey ?: string ;
330335} ;
331336
332337/**
@@ -543,6 +548,20 @@ function assertIsValidPassword(password: unknown): asserts password is string {
543548 }
544549}
545550
551+ /**
552+ * Assert that the provided cacheEncryptionKey is true.
553+ *
554+ * @param cacheEncryptionKey - The cacheEncryptionKey to check.
555+ * @throws If the cacheEncryptionKey is not true.
556+ */
557+ function assertIsCacheEncryptionKeyTrue (
558+ cacheEncryptionKey : boolean ,
559+ ) : asserts cacheEncryptionKey is true {
560+ if ( ! cacheEncryptionKey ) {
561+ throw new Error ( KeyringControllerError . CacheEncryptionKeyDisabled ) ;
562+ }
563+ }
564+
546565/**
547566 * Checks if the provided value is a serialized keyrings array.
548567 *
@@ -679,6 +698,7 @@ export class KeyringController extends BaseController<
679698 keyrings : { persist : false , anonymous : false } ,
680699 encryptionKey : { persist : false , anonymous : false } ,
681700 encryptionSalt : { persist : false , anonymous : false } ,
701+ encryptedEncryptionKey : { persist : true , anonymous : false } ,
682702 } ,
683703 messenger,
684704 state : {
@@ -816,19 +836,26 @@ export class KeyringController extends BaseController<
816836 /**
817837 * Create a new vault and primary keyring.
818838 *
819- * This only works if keyrings are empty. If there is a pre-existing unlocked vault, calling this will have no effect.
820- * If there is a pre-existing locked vault, it will be replaced.
839+ * This only works if keyrings are empty. If there is a pre-existing unlocked
840+ * vault, calling this will have no effect. If there is a pre-existing locked
841+ * vault, it will be replaced.
821842 *
822843 * @param password - Password to unlock the new vault.
844+ * @param encryptionKey - Optional encryption key to encrypt the new vault. If
845+ * set, envelope encryption will be used.
823846 * @returns Promise resolving when the operation ends successfully.
824847 */
825- async createNewVaultAndKeychain ( password : string ) {
848+ async createNewVaultAndKeychain ( password : string , encryptionKey ?: string ) {
826849 return this . #persistOrRollback( async ( ) => {
827850 const accounts = await this . #getAccountsFromKeyrings( ) ;
828851 if ( ! accounts . length ) {
829- await this . #createNewVaultWithKeyring( password , {
830- type : KeyringTypes . hd ,
831- } ) ;
852+ await this . #createNewVaultWithKeyring(
853+ password ,
854+ {
855+ type : KeyringTypes . hd ,
856+ } ,
857+ encryptionKey ,
858+ ) ;
832859 }
833860 } ) ;
834861 }
@@ -1415,6 +1442,21 @@ export class KeyringController extends BaseController<
14151442 * @returns Promise resolving when the operation completes.
14161443 */
14171444 changePassword ( password : string ) : Promise < void > {
1445+ return this . changePasswordAndEncryptionKey ( password ) ;
1446+ }
1447+
1448+ /**
1449+ * Changes the password and encryption key used to encrypt the vault.
1450+ *
1451+ * @param password - The new password.
1452+ * @param encryptionKey - The new encryption key. If omitted, the encryption
1453+ * key will not be changed.
1454+ * @returns Promise resolving when the operation completes.
1455+ */
1456+ changePasswordAndEncryptionKey (
1457+ password : string ,
1458+ encryptionKey ?: string ,
1459+ ) : Promise < void > {
14181460 this . #assertIsUnlocked( ) ;
14191461
14201462 return this . #persistOrRollback( async ( ) => {
@@ -1430,9 +1472,27 @@ export class KeyringController extends BaseController<
14301472 delete state . encryptionSalt ;
14311473 } ) ;
14321474 }
1475+
1476+ await this . #updateEncryptedEncryptionKey( password , encryptionKey ) ;
14331477 } ) ;
14341478 }
14351479
1480+ async #updateEncryptedEncryptionKey(
1481+ password : string ,
1482+ encryptionKey ?: string ,
1483+ ) {
1484+ if ( encryptionKey ) {
1485+ assertIsCacheEncryptionKeyTrue ( this . #cacheEncryptionKey) ;
1486+ const encryptedEncryptionKey = await this . #encryptor. encrypt (
1487+ password ,
1488+ encryptionKey ,
1489+ ) ;
1490+ this . update ( ( state ) => {
1491+ state . encryptedEncryptionKey = encryptedEncryptionKey ;
1492+ } ) ;
1493+ }
1494+ }
1495+
14361496 /**
14371497 * Attempts to decrypt the current vault and load its keyrings,
14381498 * using the given encryption key and salt.
@@ -2071,6 +2131,7 @@ export class KeyringController extends BaseController<
20712131 * @param keyring - A object containing the params to instantiate a new keyring.
20722132 * @param keyring.type - The keyring type.
20732133 * @param keyring.opts - Optional parameters required to instantiate the keyring.
2134+ * @param encryptionKey - Optional encryption key to encrypt the vault.
20742135 * @returns A promise that resolves to the state.
20752136 */
20762137 async #createNewVaultWithKeyring(
@@ -2079,6 +2140,7 @@ export class KeyringController extends BaseController<
20792140 type : string ;
20802141 opts ?: unknown ;
20812142 } ,
2143+ encryptionKey ?: string ,
20822144 ) : Promise < void > {
20832145 this . #assertControllerMutexIsLocked( ) ;
20842146
@@ -2093,6 +2155,8 @@ export class KeyringController extends BaseController<
20932155
20942156 this . #password = password ;
20952157
2158+ await this . #updateEncryptedEncryptionKey( password , encryptionKey ) ;
2159+
20962160 await this . #clearKeyrings( ) ;
20972161 await this . #createKeyringWithFirstAccount( keyring . type , keyring . opts ) ;
20982162 this . #setUnlocked( ) ;
@@ -2204,6 +2268,7 @@ export class KeyringController extends BaseController<
22042268 return {
22052269 keyrings : await this . #getSerializedKeyrings( ) ,
22062270 password : this . #password,
2271+ encryptedEncryptionKey : this . state . encryptedEncryptionKey ,
22072272 } ;
22082273 }
22092274
@@ -2255,7 +2320,7 @@ export class KeyringController extends BaseController<
22552320 newMetadata : boolean ;
22562321 } > {
22572322 return this . #withVaultLock( async ( ) => {
2258- const encryptedVault = this . state . vault ;
2323+ const { vault : encryptedVault , encryptedEncryptionKey } = this . state ;
22592324 if ( ! encryptedVault ) {
22602325 throw new Error ( KeyringControllerError . VaultError ) ;
22612326 }
@@ -2267,15 +2332,31 @@ export class KeyringController extends BaseController<
22672332 assertIsExportableKeyEncryptor ( this . #encryptor) ;
22682333
22692334 if ( password ) {
2270- const result = await this . #encryptor. decryptWithDetail (
2271- password ,
2272- encryptedVault ,
2273- ) ;
2274- vault = result . vault ;
2275- this . #password = password ;
2335+ if ( encryptedEncryptionKey ) {
2336+ const key = ( await this . #encryptor. decrypt (
2337+ password ,
2338+ encryptedEncryptionKey ,
2339+ ) ) as string ;
2340+
2341+ const importedKey = await this . #encryptor. importKey ( key ) ;
2342+
2343+ vault = await this . #encryptor. decryptWithKey (
2344+ importedKey ,
2345+ encryptedVault ,
2346+ ) ;
2347+ this . #password = password ;
2348+ updatedState . encryptionKey = key ;
2349+ } else {
2350+ const result = await this . #encryptor. decryptWithDetail (
2351+ password ,
2352+ encryptedVault ,
2353+ ) ;
2354+ vault = result . vault ;
2355+ this . #password = password ;
22762356
2277- updatedState . encryptionKey = result . exportedKeyString ;
2278- updatedState . encryptionSalt = result . salt ;
2357+ updatedState . encryptionKey = result . exportedKeyString ;
2358+ updatedState . encryptionSalt = result . salt ;
2359+ }
22792360 } else {
22802361 const parsedEncryptedVault = JSON . parse ( encryptedVault ) ;
22812362
@@ -2341,7 +2422,9 @@ export class KeyringController extends BaseController<
23412422 // Ensure no duplicate accounts are persisted.
23422423 await this . #assertNoDuplicateAccounts( ) ;
23432424
2344- const { encryptionKey, encryptionSalt, vault } = this . state ;
2425+ const { encryptionKey, encryptionSalt, vault, encryptedEncryptionKey } =
2426+ this . state ;
2427+
23452428 // READ THIS CAREFULLY:
23462429 // We do check if the vault is still considered up-to-date, if not, we would not re-use the
23472430 // cached key and we will re-generate a new one (based on the password).
@@ -2379,14 +2462,30 @@ export class KeyringController extends BaseController<
23792462 vaultJSON . salt = encryptionSalt ;
23802463 updatedState . vault = JSON . stringify ( vaultJSON ) ;
23812464 } else if ( this . #password) {
2382- const { vault : newVault , exportedKeyString } =
2383- await this . #encryptor. encryptWithDetail (
2465+ if ( encryptedEncryptionKey ) {
2466+ const decryptedKey = ( await this . #encryptor. decrypt (
23842467 this . #password,
2468+ encryptedEncryptionKey ,
2469+ ) ) as string ;
2470+
2471+ const importedKey = await this . #encryptor. importKey ( decryptedKey ) ;
2472+
2473+ const vaultJSON = await this . #encryptor. encryptWithKey (
2474+ importedKey ,
23852475 serializedKeyrings ,
23862476 ) ;
2387-
2388- updatedState . vault = newVault ;
2389- updatedState . encryptionKey = exportedKeyString ;
2477+ vaultJSON . salt = encryptionSalt ;
2478+ updatedState . vault = JSON . stringify ( vaultJSON ) ;
2479+ updatedState . encryptionKey = decryptedKey ;
2480+ } else {
2481+ const { vault : newVault , exportedKeyString } =
2482+ await this . #encryptor. encryptWithDetail (
2483+ this . #password,
2484+ serializedKeyrings ,
2485+ ) ;
2486+ updatedState . vault = newVault ;
2487+ updatedState . encryptionKey = exportedKeyString ;
2488+ }
23902489 }
23912490 } else {
23922491 assertIsValidPassword ( this . #password) ;
0 commit comments