From bbc2fe30bf3b9534d21f1809fc61ba45a9c7a83b Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 00:55:36 -0300 Subject: [PATCH 01/17] refactor: encryptor to typescript --- app/core/Encryptor/Encryptor.ts | 133 ++++++++++++++++++++++++++++++++ app/core/Encryptor/index.ts | 3 + 2 files changed, 136 insertions(+) create mode 100644 app/core/Encryptor/Encryptor.ts create mode 100644 app/core/Encryptor/index.ts diff --git a/app/core/Encryptor/Encryptor.ts b/app/core/Encryptor/Encryptor.ts new file mode 100644 index 00000000000..2df733b36d6 --- /dev/null +++ b/app/core/Encryptor/Encryptor.ts @@ -0,0 +1,133 @@ +import { NativeModules } from 'react-native'; +const Aes = NativeModules.Aes; +const AesForked = NativeModules.AesForked; + +/** + * Class that exposes two public methods: Encrypt and Decrypt + * This is used by the KeyringController to encrypt / decrypt the state + * which contains sensitive seed words and addresses + */ +class Encryptor { + key = null; + + _generateSalt(byteCount = 32) { + const view = new Uint8Array(byteCount); + global.crypto.getRandomValues(view); + const b64encoded = btoa(String.fromCharCode.apply(null, Array.from(view))); + return b64encoded; + } + + _generateKey = ({ + password, + salt, + lib, + }: { + password: string; + salt: string; + lib: string; + }) => + lib === 'original' + ? Aes.pbkdf2(password, salt, 5000, 256) + : AesForked.pbkdf2(password, salt); + + _keyFromPassword = ({ + password, + salt, + lib, + }: { + password: string; + salt: string; + lib: string; + }) => this._generateKey({ password, salt, lib }); + + _encryptWithKey = async ({ + text, + keyBase64, + }: { + text: string; + keyBase64: string; + }) => { + const iv = await Aes.randomKey(16); + return Aes.encrypt(text, keyBase64, iv).then((cipher: string) => ({ + cipher, + iv, + })); + }; + + _decryptWithKey = ({ + encryptedData, + key, + lib, + }: { + encryptedData: { cipher: string; iv: string }; + key: string; + lib: string; + }) => + lib === 'original' + ? Aes.decrypt(encryptedData.cipher, key, encryptedData.iv) + : AesForked.decrypt(encryptedData.cipher, key, encryptedData.iv); + + /** + * Asynchronously encrypts a given object using AES encryption. + * The encryption process involves generating a salt, deriving a key from the provided password and salt, + * and then using the key to encrypt the object. The result includes the encrypted data, the salt used, + * and the library version ('original' in this case). + * + * @param params.password - The password used for generating the encryption key. + * @param params.object - The data object to encrypt. It can be of any type, as it will be stringified during the encryption process. + * @returns A promise that resolves to a string. The string is a JSON representation of an object containing the encrypted data, the salt used for encryption, and the library version. + */ + encrypt = async ({ + password, + object, + }: { + password: string; + object: unknown; + }): Promise => { + const salt = this._generateSalt(16); + const key = await this._keyFromPassword({ + password, + salt, + lib: 'original', + }); + const result = await this._encryptWithKey({ + text: JSON.stringify(object), + keyBase64: key, + }); + result.salt = salt; + result.lib = 'original'; + return JSON.stringify(result); + }; + + /** + * Decrypts an encrypted JS object (encryptedString) + * using a password (and AES decryption with native libraries) + * + * @param {string} password - Password used for decryption + * @param {string} encryptedString - String to decrypt + * @returns - Promise resolving to decrypted data object + */ + decrypt = async ({ + password, + encryptedString, + }: { + password: string; + encryptedString: string; + }) => { + const encryptedData = JSON.parse(encryptedString); + const key = await this._keyFromPassword({ + password, + salt: encryptedData.salt, + lib: encryptedData.lib, + }); + const data = await this._decryptWithKey({ + encryptedData, + key, + lib: encryptedData.lib, + }); + return JSON.parse(data); + }; +} + +// eslint-disable-next-line import/prefer-default-export +export { Encryptor }; diff --git a/app/core/Encryptor/index.ts b/app/core/Encryptor/index.ts new file mode 100644 index 00000000000..5d6d4b40aad --- /dev/null +++ b/app/core/Encryptor/index.ts @@ -0,0 +1,3 @@ +import { Encryptor } from './Encryptor'; + +export default Encryptor; From 337bf71d2cfe91359fe7f1f8634cb10a5016096e Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 00:55:59 -0300 Subject: [PATCH 02/17] test: add unit test to encryptor --- app/core/Encryptor/Encryptor.test.ts | 46 ++++++++++++++++++++++++++++ app/util/test/testSetup.js | 25 +++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 app/core/Encryptor/Encryptor.test.ts diff --git a/app/core/Encryptor/Encryptor.test.ts b/app/core/Encryptor/Encryptor.test.ts new file mode 100644 index 00000000000..f577b58161e --- /dev/null +++ b/app/core/Encryptor/Encryptor.test.ts @@ -0,0 +1,46 @@ +import { Encryptor } from './Encryptor'; + +describe('Encryptor', () => { + let encryptor: Encryptor; + + beforeEach(() => { + encryptor = new Encryptor(); + }); + + describe('encrypt', () => { + it('should encrypt an object correctly', async () => { + const password = 'testPassword'; + const objectToEncrypt = { key: 'value' }; + + const encryptedString = await encryptor.encrypt({ + password, + object: objectToEncrypt, + }); + const encryptedObject = JSON.parse(encryptedString); + + expect(encryptedObject).toHaveProperty('cipher'); + expect(encryptedObject).toHaveProperty('iv'); + expect(encryptedObject).toHaveProperty('salt'); + expect(encryptedObject).toHaveProperty('lib', 'original'); + }); + }); + + describe('decrypt', () => { + it('should decrypt a string correctly', async () => { + const password = 'testPassword'; + const encryptedString = JSON.stringify({ + cipher: 'mockedCipher', + iv: 'mockedIV', + salt: 'mockedSalt', + lib: 'original', + }); + + const decryptedObject = await encryptor.decrypt({ + password, + encryptedString, + }); + + expect(decryptedObject).toEqual(expect.any(Object)); + }); + }); +}); diff --git a/app/util/test/testSetup.js b/app/util/test/testSetup.js index d6267801861..40b3f56aee9 100644 --- a/app/util/test/testSetup.js +++ b/app/util/test/testSetup.js @@ -182,6 +182,23 @@ NativeModules.Aes = { const hashBase = '012345678987654'; return Promise.resolve(hashBase + uniqueAddressChar); }), + pbkdf2: jest + .fn() + .mockImplementation((_password, _salt, _iterations, _keyLength) => + Promise.resolve('mockedKey'), + ), + randomKey: jest.fn().mockResolvedValue('mockedIV'), + encrypt: jest.fn().mockResolvedValue('mockedCipher'), + decrypt: jest.fn().mockResolvedValue('{"mockData": "mockedPlainText"}'), +}; + +NativeModules.AesForked = { + pbkdf2: jest + .fn() + .mockImplementation((_password, _salt) => + Promise.resolve('mockedKeyForked'), + ), + decrypt: jest.fn().mockResolvedValue('mockedPlainTextForked'), }; jest.mock( @@ -290,3 +307,11 @@ afterEach(() => { jest.restoreAllMocks(); global.gc && global.gc(true); }); + +global.crypto = { + getRandomValues: (arr) => { + for (let i = 0; i < arr.length; i++) { + arr[i] = Math.floor(Math.random() * 256); + } + }, +}; From b7bf8159df2d92b510999ee246c0c2b9ab6569ae Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 01:04:56 -0300 Subject: [PATCH 03/17] chore: add docs to encryptor class --- app/core/Encryptor/Encryptor.ts | 55 ++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/app/core/Encryptor/Encryptor.ts b/app/core/Encryptor/Encryptor.ts index 2df733b36d6..d8ad2c4b4b3 100644 --- a/app/core/Encryptor/Encryptor.ts +++ b/app/core/Encryptor/Encryptor.ts @@ -3,21 +3,34 @@ const Aes = NativeModules.Aes; const AesForked = NativeModules.AesForked; /** - * Class that exposes two public methods: Encrypt and Decrypt - * This is used by the KeyringController to encrypt / decrypt the state - * which contains sensitive seed words and addresses + * The Encryptor class provides methods for encrypting and + * decrypting data objects using AES encryption with native libraries. + * It supports generating a salt, deriving an encryption key from a + * password and salt, and performing the encryption and decryption processes. */ class Encryptor { - key = null; + key: string | null = null; - _generateSalt(byteCount = 32) { + /** + * Generates a base64-encoded salt string. + * @param byteCount - The number of bytes for the salt. Defaults to 32. + * @returns The base64-encoded salt string. + */ + private _generateSalt(byteCount = 32) { const view = new Uint8Array(byteCount); global.crypto.getRandomValues(view); const b64encoded = btoa(String.fromCharCode.apply(null, Array.from(view))); return b64encoded; } - _generateKey = ({ + /** + * Generates an encryption key based on the provided password, salt, and library choice. + * @param params.password - The password used for key derivation. + * @param params.salt - The salt used for key derivation. + * @param params.lib - The library to use ('original' or forked version). + * @returns A promise that resolves to the derived encryption key. + */ + private _generateKey = ({ password, salt, lib, @@ -30,7 +43,14 @@ class Encryptor { ? Aes.pbkdf2(password, salt, 5000, 256) : AesForked.pbkdf2(password, salt); - _keyFromPassword = ({ + /** + * Wrapper method for key generation from a password. + * @param params.password - The password used for key derivation. + * @param params.salt - The salt used for key derivation. + * @param params.lib - The library to use ('original' or forked version). + * @returns A promise that resolves to the derived encryption key. + */ + private _keyFromPassword = ({ password, salt, lib, @@ -40,7 +60,13 @@ class Encryptor { lib: string; }) => this._generateKey({ password, salt, lib }); - _encryptWithKey = async ({ + /** + * Encrypts a text string using the provided key. + * @param params.text - The text to encrypt. + * @param params.keyBase64 - The base64-encoded encryption key. + * @returns A promise that resolves to an object containing the cipher text and initialization vector (IV). + */ + private _encryptWithKey = async ({ text, keyBase64, }: { @@ -54,7 +80,14 @@ class Encryptor { })); }; - _decryptWithKey = ({ + /** + * Decrypts encrypted data using the provided key. + * @param params.encryptedData - The encrypted data object containing the cipher text and IV. + * @param params.key - The decryption key. + * @param params.lib - The library to use ('original' or forked version) for decryption. + * @returns A promise that resolves to the decrypted text. + */ + private _decryptWithKey = ({ encryptedData, key, lib, @@ -103,8 +136,8 @@ class Encryptor { * Decrypts an encrypted JS object (encryptedString) * using a password (and AES decryption with native libraries) * - * @param {string} password - Password used for decryption - * @param {string} encryptedString - String to decrypt + * @param password - Password used for decryption + * @param encryptedString - String to decrypt * @returns - Promise resolving to decrypted data object */ decrypt = async ({ From 82427146b0a6c4253cdd08426d7235cda58e9123 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 02:41:30 -0300 Subject: [PATCH 04/17] refactor: add constants and types files for Encryptor --- app/core/Encryptor/constants.ts | 6 ++++++ app/core/Encryptor/types.ts | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 app/core/Encryptor/constants.ts create mode 100644 app/core/Encryptor/types.ts diff --git a/app/core/Encryptor/constants.ts b/app/core/Encryptor/constants.ts new file mode 100644 index 00000000000..2597a9aa73d --- /dev/null +++ b/app/core/Encryptor/constants.ts @@ -0,0 +1,6 @@ +export const SHA256_DIGEST_LENGTH = 256; +export const OLD_NUMBER_ITERATIONS = 1_000; +export const MINIMUM_NUMBER_ITERATIONS = 600_000; +export const ENCRYPTION_LIBRARY = { + original: 'original', +}; diff --git a/app/core/Encryptor/types.ts b/app/core/Encryptor/types.ts new file mode 100644 index 00000000000..1a5877fdf04 --- /dev/null +++ b/app/core/Encryptor/types.ts @@ -0,0 +1,6 @@ +export interface EncryptionResult { + data: string; + iv: string; + salt?: string; + lib?: string; +} From e32348d799a722df7e99ebb3ec5be36b1db48f4c Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 02:41:49 -0300 Subject: [PATCH 05/17] chore: replace Encryptor file --- app/core/Encryptor.js | 77 --------------------------------- app/core/Encryptor/Encryptor.ts | 59 +++++++++++++------------ 2 files changed, 32 insertions(+), 104 deletions(-) delete mode 100644 app/core/Encryptor.js diff --git a/app/core/Encryptor.js b/app/core/Encryptor.js deleted file mode 100644 index 1eda492b402..00000000000 --- a/app/core/Encryptor.js +++ /dev/null @@ -1,77 +0,0 @@ -import { NativeModules } from 'react-native'; -const Aes = NativeModules.Aes; -const AesForked = NativeModules.AesForked; - -/** - * Class that exposes two public methods: Encrypt and Decrypt - * This is used by the KeyringController to encrypt / decrypt the state - * which contains sensitive seed words and addresses - */ -export default class Encryptor { - key = null; - - _generateSalt(byteCount = 32) { - const view = new Uint8Array(byteCount); - global.crypto.getRandomValues(view); - // eslint-disable-next-line no-undef - const b64encoded = btoa(String.fromCharCode.apply(null, view)); - return b64encoded; - } - - _generateKey = (password, salt, lib) => - lib === 'original' - ? Aes.pbkdf2(password, salt, 5000, 256) - : AesForked.pbkdf2(password, salt); - - _keyFromPassword = (password, salt, lib) => - this._generateKey(password, salt, lib); - - _encryptWithKey = async (text, keyBase64) => { - const iv = await Aes.randomKey(16); - return Aes.encrypt(text, keyBase64, iv).then((cipher) => ({ cipher, iv })); - }; - - _decryptWithKey = (encryptedData, key, lib) => - lib === 'original' - ? Aes.decrypt(encryptedData.cipher, key, encryptedData.iv) - : AesForked.decrypt(encryptedData.cipher, key, encryptedData.iv); - - /** - * Encrypts a JS object using a password (and AES encryption with native libraries) - * - * @param {string} password - Password used for encryption - * @param {object} object - Data object to encrypt - * @returns - Promise resolving to stringified data - */ - encrypt = async (password, object) => { - const salt = this._generateSalt(16); - const key = await this._keyFromPassword(password, salt, 'original'); - const result = await this._encryptWithKey(JSON.stringify(object), key); - result.salt = salt; - result.lib = 'original'; - return JSON.stringify(result); - }; - - /** - * Decrypts an encrypted JS object (encryptedString) - * using a password (and AES decryption with native libraries) - * - * @param {string} password - Password used for decryption - * @param {string} encryptedString - String to decrypt - * @returns - Promise resolving to decrypted data object - */ - decrypt = async (password, encryptedString) => { - const encryptedData = JSON.parse(encryptedString); - const key = await this._keyFromPassword( - password, - encryptedData.salt, - encryptedData.lib, - ); - const data = await this._decryptWithKey( - encryptedData, - key, - encryptedData.lib, - ); - return JSON.parse(data); - }; -} diff --git a/app/core/Encryptor/Encryptor.ts b/app/core/Encryptor/Encryptor.ts index d8ad2c4b4b3..31cba27b3cb 100644 --- a/app/core/Encryptor/Encryptor.ts +++ b/app/core/Encryptor/Encryptor.ts @@ -1,4 +1,11 @@ import { NativeModules } from 'react-native'; +import { + SHA256_DIGEST_LENGTH, + OLD_NUMBER_ITERATIONS, + ENCRYPTION_LIBRARY, +} from './constants'; +import type { EncryptionResult } from './types'; + const Aes = NativeModules.Aes; const AesForked = NativeModules.AesForked; @@ -33,14 +40,16 @@ class Encryptor { private _generateKey = ({ password, salt, + rounds, lib, }: { password: string; salt: string; + rounds: number; lib: string; }) => - lib === 'original' - ? Aes.pbkdf2(password, salt, 5000, 256) + lib === ENCRYPTION_LIBRARY.original + ? Aes.pbkdf2(password, salt, rounds, SHA256_DIGEST_LENGTH) : AesForked.pbkdf2(password, salt); /** @@ -53,12 +62,14 @@ class Encryptor { private _keyFromPassword = ({ password, salt, + rounds, lib, }: { password: string; salt: string; + rounds: number; lib: string; - }) => this._generateKey({ password, salt, lib }); + }): Promise => this._generateKey({ password, salt, rounds, lib }); /** * Encrypts a text string using the provided key. @@ -72,7 +83,7 @@ class Encryptor { }: { text: string; keyBase64: string; - }) => { + }): Promise => { const iv = await Aes.randomKey(16); return Aes.encrypt(text, keyBase64, iv).then((cipher: string) => ({ cipher, @@ -95,8 +106,8 @@ class Encryptor { encryptedData: { cipher: string; iv: string }; key: string; lib: string; - }) => - lib === 'original' + }): Promise => + lib === ENCRYPTION_LIBRARY.original ? Aes.decrypt(encryptedData.cipher, key, encryptedData.iv) : AesForked.decrypt(encryptedData.cipher, key, encryptedData.iv); @@ -110,25 +121,20 @@ class Encryptor { * @param params.object - The data object to encrypt. It can be of any type, as it will be stringified during the encryption process. * @returns A promise that resolves to a string. The string is a JSON representation of an object containing the encrypted data, the salt used for encryption, and the library version. */ - encrypt = async ({ - password, - object, - }: { - password: string; - object: unknown; - }): Promise => { + encrypt = async (password: string, object: unknown): Promise => { const salt = this._generateSalt(16); const key = await this._keyFromPassword({ password, salt, - lib: 'original', + rounds: OLD_NUMBER_ITERATIONS, + lib: ENCRYPTION_LIBRARY.original, }); const result = await this._encryptWithKey({ text: JSON.stringify(object), keyBase64: key, }); result.salt = salt; - result.lib = 'original'; + result.lib = ENCRYPTION_LIBRARY.original; return JSON.stringify(result); }; @@ -140,23 +146,22 @@ class Encryptor { * @param encryptedString - String to decrypt * @returns - Promise resolving to decrypted data object */ - decrypt = async ({ - password, - encryptedString, - }: { - password: string; - encryptedString: string; - }) => { - const encryptedData = JSON.parse(encryptedString); + decrypt = async ( + password: string, + encryptedString: string, + ): Promise => { + console.log({ password, encryptedString }); + const payload = JSON.parse(encryptedString); const key = await this._keyFromPassword({ password, - salt: encryptedData.salt, - lib: encryptedData.lib, + salt: payload.salt, + rounds: OLD_NUMBER_ITERATIONS, + lib: payload.lib, }); const data = await this._decryptWithKey({ - encryptedData, + encryptedData: payload, key, - lib: encryptedData.lib, + lib: payload.lib, }); return JSON.parse(data); }; From d2d3eb67b615cf3c1f76f881f55be6dbfa9d0524 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 02:45:40 -0300 Subject: [PATCH 06/17] refactor: rename constants --- app/core/Encryptor/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/Encryptor/constants.ts b/app/core/Encryptor/constants.ts index 2597a9aa73d..5d4bffd95d2 100644 --- a/app/core/Encryptor/constants.ts +++ b/app/core/Encryptor/constants.ts @@ -1,6 +1,6 @@ export const SHA256_DIGEST_LENGTH = 256; -export const OLD_NUMBER_ITERATIONS = 1_000; -export const MINIMUM_NUMBER_ITERATIONS = 600_000; +export const OLD_ITERATIONS_NUMBER = 1_000; +export const MINIMUM_ITERATIONS_NUMBER = 600_000; export const ENCRYPTION_LIBRARY = { original: 'original', }; From f2f3e0e8e5921af723c621d444bd6d787fdbe3bd Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 02:45:59 -0300 Subject: [PATCH 07/17] refactor: rename params --- app/core/Encryptor/Encryptor.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/core/Encryptor/Encryptor.ts b/app/core/Encryptor/Encryptor.ts index 31cba27b3cb..469fc117dd1 100644 --- a/app/core/Encryptor/Encryptor.ts +++ b/app/core/Encryptor/Encryptor.ts @@ -1,7 +1,7 @@ import { NativeModules } from 'react-native'; import { SHA256_DIGEST_LENGTH, - OLD_NUMBER_ITERATIONS, + OLD_ITERATIONS_NUMBER, ENCRYPTION_LIBRARY, } from './constants'; import type { EncryptionResult } from './types'; @@ -40,16 +40,16 @@ class Encryptor { private _generateKey = ({ password, salt, - rounds, + iterations, lib, }: { password: string; salt: string; - rounds: number; + iterations: number; lib: string; }) => lib === ENCRYPTION_LIBRARY.original - ? Aes.pbkdf2(password, salt, rounds, SHA256_DIGEST_LENGTH) + ? Aes.pbkdf2(password, salt, iterations, SHA256_DIGEST_LENGTH) : AesForked.pbkdf2(password, salt); /** @@ -62,14 +62,14 @@ class Encryptor { private _keyFromPassword = ({ password, salt, - rounds, + iterations, lib, }: { password: string; salt: string; - rounds: number; + iterations: number; lib: string; - }): Promise => this._generateKey({ password, salt, rounds, lib }); + }): Promise => this._generateKey({ password, salt, iterations, lib }); /** * Encrypts a text string using the provided key. @@ -126,7 +126,7 @@ class Encryptor { const key = await this._keyFromPassword({ password, salt, - rounds: OLD_NUMBER_ITERATIONS, + iterations: OLD_ITERATIONS_NUMBER, lib: ENCRYPTION_LIBRARY.original, }); const result = await this._encryptWithKey({ @@ -155,7 +155,7 @@ class Encryptor { const key = await this._keyFromPassword({ password, salt: payload.salt, - rounds: OLD_NUMBER_ITERATIONS, + iterations: OLD_ITERATIONS_NUMBER, lib: payload.lib, }); const data = await this._decryptWithKey({ From 4ad11d64e00ba4eed40c9bb04a2fba22ae95973f Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 03:13:36 -0300 Subject: [PATCH 08/17] chore: remove log --- app/core/Encryptor/Encryptor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/Encryptor/Encryptor.ts b/app/core/Encryptor/Encryptor.ts index 469fc117dd1..67cc0734051 100644 --- a/app/core/Encryptor/Encryptor.ts +++ b/app/core/Encryptor/Encryptor.ts @@ -150,7 +150,6 @@ class Encryptor { password: string, encryptedString: string, ): Promise => { - console.log({ password, encryptedString }); const payload = JSON.parse(encryptedString); const key = await this._keyFromPassword({ password, From 3311c12470b0be48a120737dce88f32da9bdf73e Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 03:13:53 -0300 Subject: [PATCH 09/17] test: improve unit tests --- app/core/Encryptor/Encryptor.test.ts | 32 +++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/core/Encryptor/Encryptor.test.ts b/app/core/Encryptor/Encryptor.test.ts index f577b58161e..97129f52b13 100644 --- a/app/core/Encryptor/Encryptor.test.ts +++ b/app/core/Encryptor/Encryptor.test.ts @@ -1,4 +1,9 @@ +import { NativeModules } from 'react-native'; import { Encryptor } from './Encryptor'; +import { ENCRYPTION_LIBRARY } from './constants'; + +const Aes = NativeModules.Aes; +// const AesForked = NativeModules.AesForked; describe('Encryptor', () => { let encryptor: Encryptor; @@ -12,10 +17,10 @@ describe('Encryptor', () => { const password = 'testPassword'; const objectToEncrypt = { key: 'value' }; - const encryptedString = await encryptor.encrypt({ + const encryptedString = await encryptor.encrypt( password, - object: objectToEncrypt, - }); + objectToEncrypt, + ); const encryptedObject = JSON.parse(encryptedString); expect(encryptedObject).toHaveProperty('cipher'); @@ -26,21 +31,28 @@ describe('Encryptor', () => { }); describe('decrypt', () => { - it('should decrypt a string correctly', async () => { + it('decrypts a string correctly with original library', async () => { const password = 'testPassword'; - const encryptedString = JSON.stringify({ + const encryptedString = { cipher: 'mockedCipher', iv: 'mockedIV', salt: 'mockedSalt', - lib: 'original', - }); + lib: ENCRYPTION_LIBRARY.original, + }; + + const decryptFromAES = jest.spyOn(Aes, 'decrypt'); - const decryptedObject = await encryptor.decrypt({ + const decryptedObject = await encryptor.decrypt( password, - encryptedString, - }); + JSON.stringify(encryptedString), + ); expect(decryptedObject).toEqual(expect.any(Object)); + expect(decryptFromAES).toHaveBeenCalledWith( + encryptedString.cipher, + 'mockedKey', + encryptedString.iv, + ); }); }); }); From 2938ac989922588dcca29daf9d991bf7b29f5c29 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 16:47:08 -0300 Subject: [PATCH 10/17] test: add AesForked decryption case --- app/core/Encryptor/Encryptor.test.ts | 64 +++++++++++++++++++--------- app/util/test/testSetup.js | 2 +- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/app/core/Encryptor/Encryptor.test.ts b/app/core/Encryptor/Encryptor.test.ts index 97129f52b13..81daf8f290f 100644 --- a/app/core/Encryptor/Encryptor.test.ts +++ b/app/core/Encryptor/Encryptor.test.ts @@ -3,7 +3,7 @@ import { Encryptor } from './Encryptor'; import { ENCRYPTION_LIBRARY } from './constants'; const Aes = NativeModules.Aes; -// const AesForked = NativeModules.AesForked; +const AesForked = NativeModules.AesForked; describe('Encryptor', () => { let encryptor: Encryptor; @@ -31,28 +31,52 @@ describe('Encryptor', () => { }); describe('decrypt', () => { - it('decrypts a string correctly with original library', async () => { - const password = 'testPassword'; - const encryptedString = { - cipher: 'mockedCipher', - iv: 'mockedIV', - salt: 'mockedSalt', + it.each([ + { lib: ENCRYPTION_LIBRARY.original, - }; + expectedDecryptFunction: 'decrypt', + expectedKey: 'mockedKey', + description: 'with original library', + }, + { + lib: 'random-lib', // Assuming not using "original" should lead to AesForked + expectedDecryptFunction: 'decrypt', + expectedKey: 'mockedKeyForked', + description: 'with library different to "original"', + }, + ])( + 'decrypts a string correctly $description', + async ({ lib, expectedDecryptFunction, expectedKey }) => { + const password = 'testPassword'; + const encryptedString = { + cipher: 'mockedCipher', + iv: 'mockedIV', + salt: 'mockedSalt', + lib, + }; - const decryptFromAES = jest.spyOn(Aes, 'decrypt'); + // Determine which AES module to spy on based on the lib value + const aesModuleToSpyOn = + lib === ENCRYPTION_LIBRARY.original ? Aes : AesForked; + const decryptSpy = jest.spyOn( + aesModuleToSpyOn, + expectedDecryptFunction, + ); - const decryptedObject = await encryptor.decrypt( - password, - JSON.stringify(encryptedString), - ); + const decryptedObject = await encryptor.decrypt( + password, + JSON.stringify(encryptedString), + ); - expect(decryptedObject).toEqual(expect.any(Object)); - expect(decryptFromAES).toHaveBeenCalledWith( - encryptedString.cipher, - 'mockedKey', - encryptedString.iv, - ); - }); + expect(decryptedObject).toEqual(expect.any(Object)); + expect(decryptSpy).toHaveBeenCalledWith( + encryptedString.cipher, + expectedKey, + encryptedString.iv, + ); + + decryptSpy.mockRestore(); + }, + ); }); }); diff --git a/app/util/test/testSetup.js b/app/util/test/testSetup.js index 40b3f56aee9..04003b92095 100644 --- a/app/util/test/testSetup.js +++ b/app/util/test/testSetup.js @@ -198,7 +198,7 @@ NativeModules.AesForked = { .mockImplementation((_password, _salt) => Promise.resolve('mockedKeyForked'), ), - decrypt: jest.fn().mockResolvedValue('mockedPlainTextForked'), + decrypt: jest.fn().mockResolvedValue('{"mockData": "mockedPlainTextForked"}'), }; jest.mock( From 22e81119886472e805377a94a841c905543d1476 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 17:39:30 -0300 Subject: [PATCH 11/17] fix: update OLD_ITERATIONS_NUMBER value --- app/core/Encryptor/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/Encryptor/constants.ts b/app/core/Encryptor/constants.ts index 5d4bffd95d2..ad0978d1737 100644 --- a/app/core/Encryptor/constants.ts +++ b/app/core/Encryptor/constants.ts @@ -1,5 +1,5 @@ export const SHA256_DIGEST_LENGTH = 256; -export const OLD_ITERATIONS_NUMBER = 1_000; +export const OLD_ITERATIONS_NUMBER = 5_000; export const MINIMUM_ITERATIONS_NUMBER = 600_000; export const ENCRYPTION_LIBRARY = { original: 'original', From 2e537afdc47561a1aefcedcf61c2439b6171dd2c Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 21:10:10 -0300 Subject: [PATCH 12/17] feat: bump keyring-controller to v9.0.0 --- package.json | 2 +- yarn.lock | 217 ++++++++++++++++++++++++--------------------------- 2 files changed, 105 insertions(+), 114 deletions(-) diff --git a/package.json b/package.json index 74c48ad5731..5f7ce393f09 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "@metamask/gas-fee-controller": "6.1.2", "@metamask/key-tree": "^9.0.0", "@metamask/keyring-api": "^4.0.0", - "@metamask/keyring-controller": "^8.1.0", + "@metamask/keyring-controller": "^9.0.0", "@metamask/logging-controller": "^1.0.1", "@metamask/network-controller": "12.2.0", "@metamask/permission-controller": "7.1.0", diff --git a/yarn.lock b/yarn.lock index 0585357e78c..c318954ac96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1879,7 +1879,7 @@ "@ethereumjs/util" "^8.1.0" ethereum-cryptography "^2.0.0" -"@ethereumjs/util@^8.0.0", "@ethereumjs/util@^8.0.2", "@ethereumjs/util@^8.0.6", "@ethereumjs/util@^8.1.0": +"@ethereumjs/util@^8.0.0", "@ethereumjs/util@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== @@ -3698,7 +3698,7 @@ "@metamask/utils" "^8.3.0" immer "^9.0.6" -"@metamask/browser-passworder@^4.1.0": +"@metamask/browser-passworder@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@metamask/browser-passworder/-/browser-passworder-4.3.0.tgz#62c200750efcea864bd31d685120331859e1ab1e" integrity sha512-RU1TVVV5DkbZRr6zPYg0NkexZ0/T2LCKNvF3A50jvUweyxDFuoNbSTN6z8K3Fy8O6/X2JQ1yyAbVzxZLq0qrGg== @@ -3833,15 +3833,16 @@ resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-9.0.0.tgz#22d4911b705f7e4e566efbdda0e37912da33e30f" integrity sha512-mWlLGQKjXXFOj9EtDClKSoTLeQuPW2kM1w3EpUMf4goYAQ+kLXCCa8pEff6h8ApWAnjhYmXydA1znQ2J4XvD+A== -"@metamask/eth-hd-keyring@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-6.0.0.tgz#a46788d4bbc7aa5d8263ed6e4348101a80519a69" - integrity sha512-dEj/I6Ag9FyCmjPcRXeXCkRXkVJE/uElhDVRcLBU6mT/GsXKgzVWXC/k0dhE8rEDrQbidhl+8wEElSJ2LI1InA== +"@metamask/eth-hd-keyring@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-7.0.1.tgz#799006d8fd57c5580dc5843f74a7343eeb2985f3" + integrity sha512-EVdaZxsBgDBIcUAHIDYLI10TepQwUuwzBMJ2GQjNwiCwkARYHeckKnvlCkPcYWhwlnjhkfFg+iJn24cA5poulw== dependencies: - "@ethereumjs/util" "^8.0.2" - "@metamask/eth-sig-util" "^5.0.2" - "@metamask/scure-bip39" "^2.0.3" - ethereum-cryptography "^1.1.2" + "@ethereumjs/util" "^8.1.0" + "@metamask/eth-sig-util" "^7.0.0" + "@metamask/scure-bip39" "^2.1.0" + "@metamask/utils" "^8.1.0" + ethereum-cryptography "^2.1.2" "@metamask/eth-json-rpc-infura@^8.1.1": version "8.1.1" @@ -3913,18 +3914,18 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.2.0" -"@metamask/eth-keyring-controller@^13.0.1": - version "13.0.1" - resolved "https://registry.yarnpkg.com/@metamask/eth-keyring-controller/-/eth-keyring-controller-13.0.1.tgz#9756a70ed2ea4f4dc6a8c335ac55b6322990e435" - integrity sha512-zxULUAAR4CUiIw5lYeELfkyKqfxoepbRjxpEJ9OaSPMZz66oVQGCxIyzKLgXe5i782WfGEihTHLHj338A0RLZw== +"@metamask/eth-keyring-controller@^15.0.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-keyring-controller/-/eth-keyring-controller-15.1.0.tgz#dc7c5ec3a075a2eb3f90b1f452cd67232714451d" + integrity sha512-FL6bVet2Rp3n6z+tKM9Lp0dBhTNj7wPKFLBvTTqJq7wBEbXir/AdN7JtnSXWC4BzbfsonXtnGuX353z0x3+8lw== dependencies: "@ethereumjs/tx" "^4.2.0" - "@metamask/browser-passworder" "^4.1.0" - "@metamask/eth-hd-keyring" "^6.0.0" - "@metamask/eth-sig-util" "^6.0.0" - "@metamask/eth-simple-keyring" "^5.0.0" + "@metamask/browser-passworder" "^4.3.0" + "@metamask/eth-hd-keyring" "^7.0.1" + "@metamask/eth-sig-util" "^7.0.0" + "@metamask/eth-simple-keyring" "^6.0.1" "@metamask/obs-store" "^8.1.0" - "@metamask/utils" "^8.1.0" + "@metamask/utils" "^8.2.0" "@metamask/eth-query@^3.0.1": version "3.0.1" @@ -3953,18 +3954,6 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" -"@metamask/eth-sig-util@^5.0.1", "@metamask/eth-sig-util@^5.0.2": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-5.1.0.tgz#a47f62800ee1917fef976ba67544a0ccd7d1bd6b" - integrity sha512-mlgziIHYlA9pi/XZerChqg4NocdOgBPB9NmxgXWQO2U2hH8RGOJQrz6j/AIKkYxgCMIE2PY000+joOwXfzeTDQ== - dependencies: - "@ethereumjs/util" "^8.0.6" - bn.js "^4.12.0" - ethereum-cryptography "^2.0.0" - ethjs-util "^0.1.6" - tweetnacl "^1.0.3" - tweetnacl-util "^0.15.1" - "@metamask/eth-sig-util@^6.0.0": version "6.0.2" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-6.0.2.tgz#d81dc87e0cd5a6580010911501976b48821746ad" @@ -3990,14 +3979,15 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" -"@metamask/eth-simple-keyring@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-5.0.0.tgz#307772d1aa3298e41a2444b428cd8f4522b7bf5c" - integrity sha512-UJfP36Z9g1eeD8mSHWaVqUvkgbgYm3S7YuzlMzQi+WgPnWu81CdbldMMtvreTlu4I1mTyljXLDMjIp65P0bygQ== +"@metamask/eth-simple-keyring@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-6.0.1.tgz#2c914c51746aba3588530a256842ce0436481ab5" + integrity sha512-UEMOojkcyPAJF30lRUesZvqESeUZIOHQgWyHQv3FzwE4ekXgJMfvvW+TuGydcGY2aeIdJy9l9wJQF0/+V9wNhg== dependencies: - "@ethereumjs/util" "^8.0.0" - "@metamask/eth-sig-util" "^5.0.1" - ethereum-cryptography "^1.1.2" + "@ethereumjs/util" "^8.1.0" + "@metamask/eth-sig-util" "^7.0.0" + "@metamask/utils" "^8.1.0" + ethereum-cryptography "^2.1.2" randombytes "^2.1.0" "@metamask/eth-snap-keyring@^2.1.1": @@ -4062,6 +4052,16 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/json-rpc-middleware-stream@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-6.0.2.tgz#75852ce481f8f9f091edbfc04ffdf964f8f3cabd" + integrity sha512-jtyx3PRfc1kqoLpYveIVQNwsxYKefc64/LCl9h9Da1m3nUKEvypbYuXSIwi237qvOjKmNHQKsDOZg6f4uBf62Q== + dependencies: + "@metamask/json-rpc-engine" "^7.3.2" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + readable-stream "^3.6.2" + "@metamask/key-tree@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/key-tree/-/key-tree-9.0.0.tgz#ce880a79f35af5b3b540b44be16ff98cc77be4c1" @@ -4087,28 +4087,28 @@ uuid "^9.0.0" "@metamask/keyring-api@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-4.0.0.tgz#5e9eba65d6db56c2a64d4eaae2ba26d0ded036e2" - integrity sha512-wkApdp5UOwmPW4SuX+bj9vu73hxNzzrFBnwvDA0HN7oIiCDSH/97o8C5LsZ7Hmn6YplMEwOl28AhuQgFjOR2+w== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-4.0.2.tgz#dc7ba93cf5cfd381c091dc231895dcfef72affc8" + integrity sha512-5Jc376i2ULXEopi5S6y/BZzloW4lrNOatMczKrsYVMiJdTHYZh9nPZY41ggZVxM1zwETvCQAX+QrAV2FXZQxAw== dependencies: - "@metamask/providers" "^14.0.1" - "@metamask/snaps-sdk" "^1.3.2" - "@metamask/utils" "^8.1.0" + "@metamask/providers" "^15.0.0" + "@metamask/snaps-sdk" "^3.1.1" + "@metamask/utils" "^8.3.0" "@types/uuid" "^9.0.1" superstruct "^1.0.3" uuid "^9.0.0" -"@metamask/keyring-controller@^8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-8.1.0.tgz#e8c9a9a4e4689492b1a4e440ece321a9eea3d279" - integrity sha512-b5T2tULBoYjuc/xeP7JxsVPo2zABJsTR3JkboKUkcptL72qGHgMix1eQgPeU4msMHrDC4hc5Bgc8ZD2PxtjzEw== +"@metamask/keyring-controller@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-9.0.0.tgz#d0b3790ed43b8626edd6c4e092c14a9e3c8a3de4" + integrity sha512-KSD59AiYvChFt2NyIuHslU6IW5F5pMxyEWKQ3FqjmNEtbtk36DCvXay//8IK+ZURWh6TdWanu2O/+9bn/UcUVQ== dependencies: "@keystonehq/metamask-airgapped-keyring" "^0.13.1" "@metamask/base-controller" "^3.2.3" - "@metamask/eth-keyring-controller" "^13.0.1" + "@metamask/eth-keyring-controller" "^15.0.0" "@metamask/message-manager" "^7.3.5" "@metamask/preferences-controller" "^4.4.3" - "@metamask/utils" "^8.1.0" + "@metamask/utils" "^8.2.0" async-mutex "^0.2.6" ethereumjs-util "^7.0.10" ethereumjs-wallet "^1.0.1" @@ -4354,6 +4354,42 @@ readable-stream "^3.6.2" webextension-polyfill "^0.10.0" +"@metamask/providers@^15.0.0": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-15.0.0.tgz#e8957bb89d2f3379b32b60117d79a141e44db2bc" + integrity sha512-FXvL1NQNl6I7fMOJTfQYcBlBZ33vSlm6w80cMpmn8sJh0Lb7wcBpe02UwBsNlARnI+Qsr26XeDs6WHUHQh8CuA== + dependencies: + "@metamask/json-rpc-engine" "^7.3.2" + "@metamask/json-rpc-middleware-stream" "^6.0.2" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + detect-browser "^5.2.0" + extension-port-stream "^3.0.0" + fast-deep-equal "^3.1.3" + is-stream "^2.0.0" + readable-stream "^3.6.2" + webextension-polyfill "^0.10.0" + +"@metamask/providers@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-16.0.0.tgz#5ba3bcbdc59b334958f1dcf6ed9b7f68de55eff1" + integrity sha512-zqZpn7Tg+QuvhL9x1WYCdXjFclZrMR2LOU2a3kHVFms5pOfizzFApB492VFgHuv/5xKohSxDkhXpAZ/TNGmM4g== + dependencies: + "@metamask/json-rpc-engine" "^7.3.2" + "@metamask/json-rpc-middleware-stream" "^6.0.2" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + detect-browser "^5.2.0" + extension-port-stream "^3.0.0" + fast-deep-equal "^3.1.3" + is-stream "^2.0.0" + readable-stream "^3.6.2" + webextension-polyfill "^0.10.0" + "@metamask/react-native-button@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@metamask/react-native-button/-/react-native-button-3.0.0.tgz#4af8affd11e2b285cfc1b1752280797e1b33e62b" @@ -4387,7 +4423,7 @@ resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.0.0.tgz#8c2b9073fe0722d48693143b0dc8448840daa3bd" integrity sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ== -"@metamask/scure-bip39@^2.0.3", "@metamask/scure-bip39@^2.1.0": +"@metamask/scure-bip39@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@metamask/scure-bip39/-/scure-bip39-2.1.0.tgz#13456884736e56ede15e471bd93c0aa0acdedd0b" integrity sha512-Ndwdnld0SI6YaftEUUVq20sdoWcWNXsJXxvQkbiY42FKmrA16U6WoSh9Eq+NpugpKKwK6f5uvaTDusjndiEDGQ== @@ -4504,6 +4540,18 @@ fast-xml-parser "^4.3.4" superstruct "^1.0.3" +"@metamask/snaps-sdk@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@metamask/snaps-sdk/-/snaps-sdk-3.2.0.tgz#66d60869697a479a3484adc9532d82c3b568fc19" + integrity sha512-Xfsc6seyucs7TP2JLXoKYrWm5FbrttdHnMOTfuzTb4T+qmdmoc3wdw83RAGjRFiOOaHGFc6JJSCdP33fBmw4Hg== + dependencies: + "@metamask/key-tree" "^9.0.0" + "@metamask/providers" "^16.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + fast-xml-parser "^4.3.4" + superstruct "^1.0.3" + "@metamask/snaps-utils@^5.1.2": version "5.2.0" resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-5.2.0.tgz#ff43b97ff176846230d8bdedb1769b269effc4d8" @@ -4697,11 +4745,6 @@ resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123" integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ== -"@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" - integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== - "@noble/hashes@1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" @@ -4717,7 +4760,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ== -"@noble/secp256k1@1.7.1", "@noble/secp256k1@^1.5.5", "@noble/secp256k1@~1.7.0": +"@noble/secp256k1@^1.5.5": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== @@ -5668,15 +5711,6 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== -"@scure/bip32@1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" - integrity sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw== - dependencies: - "@noble/hashes" "~1.2.0" - "@noble/secp256k1" "~1.7.0" - "@scure/base" "~1.1.0" - "@scure/bip32@1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" @@ -5686,14 +5720,6 @@ "@noble/hashes" "~1.3.1" "@scure/base" "~1.1.0" -"@scure/bip39@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" - integrity sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg== - dependencies: - "@noble/hashes" "~1.2.0" - "@scure/base" "~1.1.0" - "@scure/bip39@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" @@ -11974,7 +12000,7 @@ bn.js@5.2.1, bn.js@^5.0.0, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.12.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -15680,16 +15706,6 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" -ethereum-cryptography@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz#5ccfa183e85fdaf9f9b299a79430c044268c9b3a" - integrity sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw== - dependencies: - "@noble/hashes" "1.2.0" - "@noble/secp256k1" "1.7.1" - "@scure/bip32" "1.1.5" - "@scure/bip39" "1.1.1" - ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" @@ -26128,7 +26144,7 @@ string-range@~1.2, string-range@~1.2.1: resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd" integrity sha1-qJPtNH5yKZvIO++78qaSqNI51d0= -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -26146,15 +26162,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -26269,14 +26276,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -28168,7 +28168,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -28203,15 +28203,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From ec5c5af96b2c80dcb425984eef35b430cae3bb7b Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 3 Mar 2024 21:12:14 -0300 Subject: [PATCH 13/17] feat: improvements --- app/core/Encryptor/Encryptor.test.ts | 213 +++++++++++++++--- app/core/Encryptor/Encryptor.ts | 190 ++++++++++++---- app/core/Encryptor/constants.ts | 18 +- app/core/Encryptor/index.ts | 3 +- app/core/Encryptor/types.ts | 70 ++++++ app/core/Engine.ts | 6 +- app/core/SecureKeychain.js | 6 +- app/util/test/testSetup.js | 3 +- app/util/validators/index.js | 6 +- ... @metamask+keyring-controller+9.0.0.patch} | 4 +- 10 files changed, 435 insertions(+), 84 deletions(-) rename patches/{@metamask+keyring-controller+8.1.0.patch => @metamask+keyring-controller+9.0.0.patch} (95%) diff --git a/app/core/Encryptor/Encryptor.test.ts b/app/core/Encryptor/Encryptor.test.ts index 81daf8f290f..dd9795bd309 100644 --- a/app/core/Encryptor/Encryptor.test.ts +++ b/app/core/Encryptor/Encryptor.test.ts @@ -1,6 +1,10 @@ import { NativeModules } from 'react-native'; import { Encryptor } from './Encryptor'; -import { ENCRYPTION_LIBRARY } from './constants'; +import { + ENCRYPTION_LIBRARY, + DEFAULT_DERIVATION_PARAMS, + KeyDerivationIteration, +} from './constants'; const Aes = NativeModules.Aes; const AesForked = NativeModules.AesForked; @@ -9,10 +13,32 @@ describe('Encryptor', () => { let encryptor: Encryptor; beforeEach(() => { - encryptor = new Encryptor(); + encryptor = new Encryptor({ derivationParams: DEFAULT_DERIVATION_PARAMS }); + }); + + describe('constructor', () => { + it('throws an error if the provided iterations do not meet the minimum required', () => { + expect( + () => + new Encryptor({ + derivationParams: { + algorithm: 'PBKDF2', + params: { + iterations: 100, + }, + }, + }), + ).toThrowError( + `Invalid key derivation iterations: 100. Recommended number of iterations is ${KeyDerivationIteration.Default}. Minimum required is ${KeyDerivationIteration.Minimum}.`, + ); + }); }); describe('encrypt', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should encrypt an object correctly', async () => { const password = 'testPassword'; const objectToEncrypt = { key: 'value' }; @@ -31,52 +57,187 @@ describe('Encryptor', () => { }); describe('decrypt', () => { + let decryptAesSpy: jest.SpyInstance, + pbkdf2AesSpy: jest.SpyInstance, + decryptAesForkedSpy: jest.SpyInstance, + pbkdf2AesForkedSpy: jest.SpyInstance; + + beforeEach(() => { + decryptAesSpy = jest + .spyOn(Aes, 'decrypt') + .mockResolvedValue('{"mockData": "mockedPlainText"}'); + pbkdf2AesSpy = jest + .spyOn(Aes, 'pbkdf2') + .mockResolvedValue('mockedAesKey'); + decryptAesForkedSpy = jest + .spyOn(AesForked, 'decrypt') + .mockResolvedValue('{"mockData": "mockedPlainText"}'); + pbkdf2AesForkedSpy = jest + .spyOn(AesForked, 'pbkdf2') + .mockResolvedValue('mockedAesForkedKey'); + }); + + afterEach(() => { + decryptAesSpy.mockRestore(); + pbkdf2AesSpy.mockRestore(); + decryptAesForkedSpy.mockRestore(); + pbkdf2AesForkedSpy.mockRestore(); + }); + it.each([ { lib: ENCRYPTION_LIBRARY.original, - expectedDecryptFunction: 'decrypt', - expectedKey: 'mockedKey', - description: 'with original library', + expectedKey: 'mockedAesKey', + expectedPBKDF2Args: ['testPassword', 'mockedSalt', 900000, 256], + description: + 'with original library and default iterations number for key generation', + keyMetadata: DEFAULT_DERIVATION_PARAMS, + }, + { + lib: ENCRYPTION_LIBRARY.original, + expectedKey: 'mockedAesKey', + expectedPBKDF2Args: ['testPassword', 'mockedSalt', 5000, 256], + description: + 'with original library and old iterations number for key generation', + }, + { + lib: 'random-lib', // Assuming not using "original" should lead to AesForked + expectedKey: 'mockedAesForkedKey', + expectedPBKDF2Args: ['testPassword', 'mockedSalt'], + description: + 'with library different to "original" and default iterations number for key generation', + keyMetadata: DEFAULT_DERIVATION_PARAMS, }, { lib: 'random-lib', // Assuming not using "original" should lead to AesForked - expectedDecryptFunction: 'decrypt', - expectedKey: 'mockedKeyForked', - description: 'with library different to "original"', + expectedKey: 'mockedAesForkedKey', + expectedPBKDF2Args: ['testPassword', 'mockedSalt'], + description: + 'with library different to "original" and old iterations number for key generation', }, ])( 'decrypts a string correctly $description', - async ({ lib, expectedDecryptFunction, expectedKey }) => { + async ({ lib, expectedKey, expectedPBKDF2Args, keyMetadata }) => { const password = 'testPassword'; - const encryptedString = { + const mockVault = { cipher: 'mockedCipher', iv: 'mockedIV', salt: 'mockedSalt', lib, }; - // Determine which AES module to spy on based on the lib value - const aesModuleToSpyOn = - lib === ENCRYPTION_LIBRARY.original ? Aes : AesForked; - const decryptSpy = jest.spyOn( - aesModuleToSpyOn, - expectedDecryptFunction, - ); - const decryptedObject = await encryptor.decrypt( password, - JSON.stringify(encryptedString), + JSON.stringify( + keyMetadata !== undefined + ? { ...mockVault, keyMetadata } + : mockVault, + ), ); expect(decryptedObject).toEqual(expect.any(Object)); - expect(decryptSpy).toHaveBeenCalledWith( - encryptedString.cipher, - expectedKey, - encryptedString.iv, - ); - - decryptSpy.mockRestore(); + expect( + lib === ENCRYPTION_LIBRARY.original + ? decryptAesSpy + : decryptAesForkedSpy, + ).toHaveBeenCalledWith(mockVault.cipher, expectedKey, mockVault.iv); + expect( + lib === ENCRYPTION_LIBRARY.original + ? pbkdf2AesSpy + : pbkdf2AesForkedSpy, + ).toHaveBeenCalledWith(...expectedPBKDF2Args); }, ); }); + + describe('isVaultUpdated', () => { + it('returns true if a vault has the correct format', () => { + expect( + encryptor.isVaultUpdated( + JSON.stringify({ + cipher: 'mockedCipher', + iv: 'mockedIV', + salt: 'mockedSalt', + lib: 'original', + keyMetadata: DEFAULT_DERIVATION_PARAMS, + }), + ), + ).toBe(true); + }); + + it('returns false if a vault has the incorrect format', () => { + expect( + encryptor.isVaultUpdated( + JSON.stringify({ + cipher: 'mockedCipher', + iv: 'mockedIV', + salt: 'mockedSalt', + lib: 'original', + }), + ), + ).toBe(false); + }); + }); + + describe('updateVault', () => { + let encryptSpy: jest.SpyInstance, decryptSpy: jest.SpyInstance; + const expectedKeyMetadata = DEFAULT_DERIVATION_PARAMS; + + beforeEach(() => { + encryptSpy = jest + .spyOn(Aes, 'encrypt') + .mockResolvedValue(() => Promise.resolve('mockedCipher')); + decryptSpy = jest + .spyOn(Aes, 'decrypt') + .mockResolvedValue('{"mockData": "mockedPlainText"}'); + }); + + afterEach(() => { + encryptSpy.mockRestore(); + decryptSpy.mockRestore(); + }); + + it('updates a vault correctly if keyMetadata is not present', async () => { + const mockVault = { + cipher: 'mockedCipher', + iv: 'mockedIV', + salt: 'mockedSalt', + lib: 'original', + }; + + const updatedVault = await encryptor.updateVault( + JSON.stringify(mockVault), + 'mockPassword', + ); + + const vault = JSON.parse(updatedVault); + + expect(encryptSpy).toBeCalledTimes(1); + expect(decryptSpy).toBeCalledTimes(1); + expect(vault).toHaveProperty('keyMetadata'); + expect(vault.keyMetadata).toStrictEqual(expectedKeyMetadata); + }); + + it('does not update a vault if algorithm is PBKDF2 and the number of iterations is 900000', async () => { + const mockVault = { + cipher: 'mockedCipher', + iv: 'mockedIV', + salt: 'mockedSalt', + lib: 'original', + keyMetadata: DEFAULT_DERIVATION_PARAMS, + }; + + const updatedVault = await encryptor.updateVault( + JSON.stringify(mockVault), + 'mockPassword', + ); + + const vault = JSON.parse(updatedVault); + + expect(encryptSpy).toBeCalledTimes(0); + expect(decryptSpy).toBeCalledTimes(0); + expect(vault).toHaveProperty('keyMetadata'); + expect(vault.keyMetadata).toStrictEqual(expectedKeyMetadata); + }); + }); }); diff --git a/app/core/Encryptor/Encryptor.ts b/app/core/Encryptor/Encryptor.ts index 67cc0734051..575c424da14 100644 --- a/app/core/Encryptor/Encryptor.ts +++ b/app/core/Encryptor/Encryptor.ts @@ -1,56 +1,104 @@ import { NativeModules } from 'react-native'; +import { hasProperty, isPlainObject, Json } from '@metamask/utils'; import { + SALT_BYTES_COUNT, SHA256_DIGEST_LENGTH, - OLD_ITERATIONS_NUMBER, ENCRYPTION_LIBRARY, + KeyDerivationIteration, } from './constants'; -import type { EncryptionResult } from './types'; +import type { + EncryptionResult, + KeyDerivationOptions, + GenericEncryptor, +} from './types'; const Aes = NativeModules.Aes; const AesForked = NativeModules.AesForked; +/** + * Checks if the provided object is a `KeyDerivationOptions`. + * + * @param derivationOptions - The object to check. + * @returns Whether or not the object is a `KeyDerivationOptions`. + */ +const isKeyDerivationOptions = ( + derivationOptions: unknown, +): derivationOptions is KeyDerivationOptions => + isPlainObject(derivationOptions) && + hasProperty(derivationOptions, 'algorithm') && + hasProperty(derivationOptions, 'params'); + /** * The Encryptor class provides methods for encrypting and * decrypting data objects using AES encryption with native libraries. * It supports generating a salt, deriving an encryption key from a * password and salt, and performing the encryption and decryption processes. */ -class Encryptor { - key: string | null = null; +class Encryptor implements GenericEncryptor { + /** + * The key derivation parameters used for encryption and decryption operations. + * These parameters include the algorithm and its specific parameters, for example, number of iterations for key derivation. + * They are set during the construction of the Encryptor instance and used for generating encryption keys. + * @property derivationParams - The key derivation options. + */ + private derivationParams: KeyDerivationOptions; /** - * Generates a base64-encoded salt string. - * @param byteCount - The number of bytes for the salt. Defaults to 32. - * @returns The base64-encoded salt string. + * Constructs an instance of the Encryptor class. + * @param params - An object containing key derivation parameters. + * @param params.derivationParams - The key derivation options to use for encryption and decryption operations. + * @throws Error if the provided iterations in `derivationParams` do not meet the minimum required. */ - private _generateSalt(byteCount = 32) { - const view = new Uint8Array(byteCount); - global.crypto.getRandomValues(view); - const b64encoded = btoa(String.fromCharCode.apply(null, Array.from(view))); - return b64encoded; + constructor({ + derivationParams, + }: { + derivationParams: KeyDerivationOptions; + }) { + this.checkMinimalRequiredIterations(derivationParams.params.iterations); + this.derivationParams = derivationParams; } /** - * Generates an encryption key based on the provided password, salt, and library choice. - * @param params.password - The password used for key derivation. - * @param params.salt - The salt used for key derivation. - * @param params.lib - The library to use ('original' or forked version). - * @returns A promise that resolves to the derived encryption key. + * Throws an error if the provided number of iterations does not meet the minimum required for key derivation. + * This method ensures that the key derivation process is secure by enforcing a minimum number of iterations. + * @param iterations - The number of iterations to check. + * @throws Error if the number of iterations is less than the minimum required. */ - private _generateKey = ({ - password, - salt, - iterations, - lib, - }: { - password: string; - salt: string; - iterations: number; - lib: string; - }) => - lib === ENCRYPTION_LIBRARY.original - ? Aes.pbkdf2(password, salt, iterations, SHA256_DIGEST_LENGTH) - : AesForked.pbkdf2(password, salt); + private checkMinimalRequiredIterations = (iterations: number): void => { + if (!this.isMinimalRequiredIterationsMet(iterations)) { + throw new Error( + `Invalid key derivation iterations: ${iterations}. Recommended number of iterations is ${KeyDerivationIteration.Default}. Minimum required is ${KeyDerivationIteration.Minimum}.`, + ); + } + }; + + /** + * Checks if the provided number of iterations meets the minimum required for key derivation. + * @param iterations - The number of iterations to check. + * @returns A boolean indicating whether the minimum required iterations are met. + */ + private isMinimalRequiredIterationsMet = (iterations: number): boolean => + iterations >= KeyDerivationIteration.Minimum; + + /** + * Generates a random base64-encoded salt string. + * @param byteCount - The number of bytes for the salt. Defaults to `constant.SALT_BYTES_COUNT`. + * @returns The base64-encoded salt string. + */ + private generateSalt = (saltBytesCount = SALT_BYTES_COUNT) => { + const salt = new Uint8Array(saltBytesCount); + // @ts-expect-error - globalThis is not recognized by TypeScript + global.crypto.getRandomValues(salt); + return salt; + }; + + /** + * Encodes a byte array to a base64 string. + * @param byteArray The byte array to encode. + * @returns The base64-encoded string. + */ + private encodeByteArrayToBase64 = (byteArray: Uint8Array): string => + btoa(String.fromCharCode.apply(null, Array.from(byteArray))); /** * Wrapper method for key generation from a password. @@ -59,7 +107,7 @@ class Encryptor { * @param params.lib - The library to use ('original' or forked version). * @returns A promise that resolves to the derived encryption key. */ - private _keyFromPassword = ({ + private generateKeyFromPassword = ({ password, salt, iterations, @@ -69,7 +117,10 @@ class Encryptor { salt: string; iterations: number; lib: string; - }): Promise => this._generateKey({ password, salt, iterations, lib }); + }): Promise => + lib === ENCRYPTION_LIBRARY.original + ? Aes.pbkdf2(password, salt, iterations, SHA256_DIGEST_LENGTH) + : AesForked.pbkdf2(password, salt); /** * Encrypts a text string using the provided key. @@ -77,7 +128,7 @@ class Encryptor { * @param params.keyBase64 - The base64-encoded encryption key. * @returns A promise that resolves to an object containing the cipher text and initialization vector (IV). */ - private _encryptWithKey = async ({ + private encryptWithKey = async ({ text, keyBase64, }: { @@ -98,7 +149,7 @@ class Encryptor { * @param params.lib - The library to use ('original' or forked version) for decryption. * @returns A promise that resolves to the decrypted text. */ - private _decryptWithKey = ({ + private decryptWithKey = ({ encryptedData, key, lib, @@ -121,20 +172,22 @@ class Encryptor { * @param params.object - The data object to encrypt. It can be of any type, as it will be stringified during the encryption process. * @returns A promise that resolves to a string. The string is a JSON representation of an object containing the encrypted data, the salt used for encryption, and the library version. */ - encrypt = async (password: string, object: unknown): Promise => { - const salt = this._generateSalt(16); - const key = await this._keyFromPassword({ + encrypt = async (password: string, object: Json): Promise => { + const salt = this.generateSalt(16); + const base64salt = this.encodeByteArrayToBase64(salt); + const key = await this.generateKeyFromPassword({ password, - salt, - iterations: OLD_ITERATIONS_NUMBER, + salt: base64salt, + iterations: this.derivationParams.params.iterations, lib: ENCRYPTION_LIBRARY.original, }); - const result = await this._encryptWithKey({ + const result = await this.encryptWithKey({ text: JSON.stringify(object), keyBase64: key, }); - result.salt = salt; + result.salt = base64salt; result.lib = ENCRYPTION_LIBRARY.original; + result.keyMetadata = this.derivationParams; return JSON.stringify(result); }; @@ -149,21 +202,66 @@ class Encryptor { decrypt = async ( password: string, encryptedString: string, - ): Promise => { + ): Promise => { const payload = JSON.parse(encryptedString); - const key = await this._keyFromPassword({ + const key = await this.generateKeyFromPassword({ password, salt: payload.salt, - iterations: OLD_ITERATIONS_NUMBER, + iterations: + payload.keyMetadata?.params.iterations || KeyDerivationIteration.Legacy, lib: payload.lib, }); - const data = await this._decryptWithKey({ + const data = await this.decryptWithKey({ encryptedData: payload, key, lib: payload.lib, }); + return JSON.parse(data); }; + + /** + * Checks if the provided vault is an updated encryption format. + * + * @param vault - The vault to check. + * @param targetDerivationParams - The options to use for key derivation. + * @returns Whether or not the vault is an updated encryption format. + */ + isVaultUpdated = ( + vault: string, + targetDerivationParams = this.derivationParams, + ): boolean => { + const { keyMetadata } = JSON.parse(vault); + return ( + isKeyDerivationOptions(keyMetadata) && + keyMetadata.algorithm === targetDerivationParams.algorithm && + keyMetadata.params.iterations === targetDerivationParams.params.iterations + ); + }; + + /** + * Updates the provided vault, re-encrypting + * data with a safer algorithm if one is available. + * + * If the provided vault is already using the latest available encryption method, + * it is returned as is. + * + * @param vault - The vault to update. + * @param password - The password to use for encryption. + * @param targetDerivationParams - The options to use for key derivation. + * @returns A promise resolving to the updated vault. + */ + updateVault = async ( + vault: string, + password: string, + targetDerivationParams = this.derivationParams, + ): Promise => { + if (this.isVaultUpdated(vault, targetDerivationParams)) { + return vault; + } + + return this.encrypt(password, await this.decrypt(password, vault)); + }; } // eslint-disable-next-line import/prefer-default-export diff --git a/app/core/Encryptor/constants.ts b/app/core/Encryptor/constants.ts index ad0978d1737..78cf9addd32 100644 --- a/app/core/Encryptor/constants.ts +++ b/app/core/Encryptor/constants.ts @@ -1,6 +1,20 @@ +import { KeyDerivationOptions } from './types'; + +export const SALT_BYTES_COUNT = 32; export const SHA256_DIGEST_LENGTH = 256; -export const OLD_ITERATIONS_NUMBER = 5_000; -export const MINIMUM_ITERATIONS_NUMBER = 600_000; export const ENCRYPTION_LIBRARY = { original: 'original', }; + +export enum KeyDerivationIteration { + Legacy = 5_000, + Minimum = 600_000, + Default = 900_000, +} + +export const DEFAULT_DERIVATION_PARAMS: KeyDerivationOptions = { + algorithm: 'PBKDF2', + params: { + iterations: KeyDerivationIteration.Default, + }, +}; diff --git a/app/core/Encryptor/index.ts b/app/core/Encryptor/index.ts index 5d6d4b40aad..7ae0aca4be6 100644 --- a/app/core/Encryptor/index.ts +++ b/app/core/Encryptor/index.ts @@ -1,3 +1,4 @@ import { Encryptor } from './Encryptor'; +import { DEFAULT_DERIVATION_PARAMS } from './constants'; -export default Encryptor; +export { Encryptor, DEFAULT_DERIVATION_PARAMS }; diff --git a/app/core/Encryptor/types.ts b/app/core/Encryptor/types.ts index 1a5877fdf04..6abe41891c1 100644 --- a/app/core/Encryptor/types.ts +++ b/app/core/Encryptor/types.ts @@ -1,6 +1,76 @@ +import type { Json } from '@metamask/utils'; + +/** + * Parameters used for key derivation. + * @interface KeyParams + * @property iterations - The number of iterations to use in the key derivation process. + */ +export interface KeyParams { + iterations: number; +} + +/** + * Options for key derivation, specifying the algorithm and parameters to use. + * @interface KeyDerivationOptions + * @property algorithm - The name of the algorithm to use for key derivation. + * @property params - The parameters to use with the specified algorithm. + */ +export interface KeyDerivationOptions { + algorithm: string; + params: KeyParams; +} + +/** + * The result of an encryption operation. + * @interface EncryptionResult + * @property data - The encrypted data. + * @property iv - The initialization vector used in the encryption process. + * @property [salt] - The salt used in the encryption process, if applicable. + * @property [lib] - The library or algorithm used for encryption, if applicable. + * @property [keyMetadata] - Metadata about the key derivation, if key derivation was used. + */ export interface EncryptionResult { data: string; iv: string; salt?: string; lib?: string; + keyMetadata?: KeyDerivationOptions; +} + +/** + * Defines the structure for a generic encryption utility. + * This utility provides methods for encrypting and decrypting objects + * using a specified password. It may also include an optional method + * for checking if an encrypted vault is up to date with the desired + * encryption algorithm and parameters. + */ +export interface GenericEncryptor { + /** + * Encrypts the given object with the given password. + * + * @param password - The password to encrypt with. + * @param object - The object to encrypt. + * @returns The encrypted string. + */ + encrypt: (password: string, object: Json) => Promise; + /** + * Decrypts the given encrypted string with the given password. + * + * @param password - The password to decrypt with. + * @param encryptedString - The encrypted string to decrypt. + * @returns The decrypted object. + */ + decrypt: (password: string, encryptedString: string) => Promise; + /** + * Optional vault migration helper. Checks if the provided vault is up to date + * with the desired encryption algorithm. + * + * @param vault - The encrypted string to check. + * @param targetDerivationParams - The desired target derivation params. + * @returns The updated encrypted string. + */ + isVaultUpdated?: ( + vault: string, + targetDerivationParams?: KeyDerivationOptions, + ) => boolean; } diff --git a/app/core/Engine.ts b/app/core/Engine.ts index d4917cb9222..f20b69663b6 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -125,7 +125,7 @@ import { LoggingControllerActions, } from '@metamask/logging-controller'; import LedgerKeyring from '@consensys/ledgerhq-metamask-keyring'; -import Encryptor from './Encryptor'; +import { Encryptor, DEFAULT_DERIVATION_PARAMS } from './Encryptor'; import { isMainnetByChainId, getDecimalChainId, @@ -193,7 +193,9 @@ import { lowerCase } from 'lodash'; const NON_EMPTY = 'NON_EMPTY'; -const encryptor = new Encryptor(); +const encryptor = new Encryptor({ + derivationParams: DEFAULT_DERIVATION_PARAMS, +}); let currentChainId: any; ///: BEGIN:ONLY_INCLUDE_IF(snaps) diff --git a/app/core/SecureKeychain.js b/app/core/SecureKeychain.js index 09fb8c14382..5b1ecf8f65c 100644 --- a/app/core/SecureKeychain.js +++ b/app/core/SecureKeychain.js @@ -1,5 +1,5 @@ import * as Keychain from 'react-native-keychain'; // eslint-disable-line import/no-namespace -import Encryptor from './Encryptor'; +import { Encryptor, DEFAULT_DERIVATION_PARAMS } from './Encryptor'; import { strings } from '../../locales/i18n'; import AsyncStorage from '../store/async-storage-wrapper'; import { Platform } from 'react-native'; @@ -14,7 +14,9 @@ import { import Device from '../util/device'; const privates = new WeakMap(); -const encryptor = new Encryptor(); +const encryptor = new Encryptor({ + derivationParams: DEFAULT_DERIVATION_PARAMS, +}); const defaultOptions = { service: 'com.metamask', authenticationPromptTitle: strings('authentication.auth_prompt_title'), diff --git a/app/util/test/testSetup.js b/app/util/test/testSetup.js index 04003b92095..8fb20c59f5d 100644 --- a/app/util/test/testSetup.js +++ b/app/util/test/testSetup.js @@ -310,8 +310,9 @@ afterEach(() => { global.crypto = { getRandomValues: (arr) => { + const uint8Max = 255; for (let i = 0; i < arr.length; i++) { - arr[i] = Math.floor(Math.random() * 256); + arr[i] = Math.floor(Math.random() * (uint8Max + 1)); } }, }; diff --git a/app/util/validators/index.js b/app/util/validators/index.js index dddc41cc44a..a754b8ed9c1 100644 --- a/app/util/validators/index.js +++ b/app/util/validators/index.js @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import Encryptor from '../../core/Encryptor'; +import { Encryptor, DEFAULT_DERIVATION_PARAMS } from '../../core/Encryptor'; import { regex } from '../regex'; export const failedSeedPhraseRequirements = (seed) => { @@ -26,7 +26,9 @@ export const parseVaultValue = async (password, vault) => { seedObject?.iv && seedObject?.lib ) { - const encryptor = new Encryptor(); + const encryptor = new Encryptor({ + derivationParams: DEFAULT_DERIVATION_PARAMS, + }); const result = await encryptor.decrypt(password, vault); vaultSeed = result[0]?.data?.mnemonic; } diff --git a/patches/@metamask+keyring-controller+8.1.0.patch b/patches/@metamask+keyring-controller+9.0.0.patch similarity index 95% rename from patches/@metamask+keyring-controller+8.1.0.patch rename to patches/@metamask+keyring-controller+9.0.0.patch index 2373eba1e7e..fb5bc2538ee 100644 --- a/patches/@metamask+keyring-controller+8.1.0.patch +++ b/patches/@metamask+keyring-controller+9.0.0.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@metamask/keyring-controller/dist/KeyringController.js b/node_modules/@metamask/keyring-controller/dist/KeyringController.js -index 08a8714..a096388 100644 +index da9523a..2cb136d 100644 --- a/node_modules/@metamask/keyring-controller/dist/KeyringController.js +++ b/node_modules/@metamask/keyring-controller/dist/KeyringController.js -@@ -773,15 +773,25 @@ class KeyringController extends base_controller_1.BaseControllerV2 { +@@ -785,15 +785,25 @@ class KeyringController extends base_controller_1.BaseControllerV2 { return (yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringForAccount(account)).type; }); } From 18fbdedefcb07d1ac06d89d4d3293bf1efaa067d Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 26 Mar 2024 20:00:42 -0300 Subject: [PATCH 14/17] chore: dedupe --- yarn.lock | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c318954ac96..c3207f56cdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26144,7 +26144,7 @@ string-range@~1.2, string-range@~1.2.1: resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd" integrity sha1-qJPtNH5yKZvIO++78qaSqNI51d0= -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -26162,6 +26162,15 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -26276,7 +26285,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -28168,7 +28184,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -28203,6 +28219,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From d4cf781b1d87a41b025dafb8f7f8611fb9c3bf74 Mon Sep 17 00:00:00 2001 From: Owen Craston Date: Tue, 26 Mar 2024 18:20:17 -0700 Subject: [PATCH 15/17] fix: TypeScript lint issue (#9068) Co-authored-by: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> From 417254f670b6f23130519d29806bd0eb565f8d85 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 26 Mar 2024 23:19:00 -0300 Subject: [PATCH 16/17] Revert "chore: update yarn.lock" This reverts commit 4a4fbe6fdbb4bbfc30f5aa58b49adf96167d0b13. --- yarn.lock | 82 ------------------------------------------------------- 1 file changed, 82 deletions(-) diff --git a/yarn.lock b/yarn.lock index c3207f56cdd..0133ef1f525 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4052,16 +4052,6 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" -"@metamask/json-rpc-middleware-stream@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-6.0.2.tgz#75852ce481f8f9f091edbfc04ffdf964f8f3cabd" - integrity sha512-jtyx3PRfc1kqoLpYveIVQNwsxYKefc64/LCl9h9Da1m3nUKEvypbYuXSIwi237qvOjKmNHQKsDOZg6f4uBf62Q== - dependencies: - "@metamask/json-rpc-engine" "^7.3.2" - "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^8.3.0" - readable-stream "^3.6.2" - "@metamask/key-tree@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/key-tree/-/key-tree-9.0.0.tgz#ce880a79f35af5b3b540b44be16ff98cc77be4c1" @@ -4074,30 +4064,6 @@ "@noble/secp256k1" "^1.5.5" "@scure/base" "^1.0.0" -"@metamask/keyring-api@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-3.0.0.tgz#66e6d9de01ba652f0f7959d04d8d4d0c6a3dd040" - integrity sha512-hvBH55P0ajhHOU8lkUFLCs/HdXJORDHzBT6BSVh7nxUmxf8gdDIYX9iG/+2PYWHo1qvUlnojzfROMS1/E6MQgQ== - dependencies: - "@metamask/providers" "^14.0.1" - "@metamask/snaps-sdk" "^1.3.2" - "@metamask/utils" "^8.1.0" - "@types/uuid" "^9.0.1" - superstruct "^1.0.3" - uuid "^9.0.0" - -"@metamask/keyring-api@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-4.0.2.tgz#dc7ba93cf5cfd381c091dc231895dcfef72affc8" - integrity sha512-5Jc376i2ULXEopi5S6y/BZzloW4lrNOatMczKrsYVMiJdTHYZh9nPZY41ggZVxM1zwETvCQAX+QrAV2FXZQxAw== - dependencies: - "@metamask/providers" "^15.0.0" - "@metamask/snaps-sdk" "^3.1.1" - "@metamask/utils" "^8.3.0" - "@types/uuid" "^9.0.1" - superstruct "^1.0.3" - uuid "^9.0.0" - "@metamask/keyring-controller@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-9.0.0.tgz#d0b3790ed43b8626edd6c4e092c14a9e3c8a3de4" @@ -4354,42 +4320,6 @@ readable-stream "^3.6.2" webextension-polyfill "^0.10.0" -"@metamask/providers@^15.0.0": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-15.0.0.tgz#e8957bb89d2f3379b32b60117d79a141e44db2bc" - integrity sha512-FXvL1NQNl6I7fMOJTfQYcBlBZ33vSlm6w80cMpmn8sJh0Lb7wcBpe02UwBsNlARnI+Qsr26XeDs6WHUHQh8CuA== - dependencies: - "@metamask/json-rpc-engine" "^7.3.2" - "@metamask/json-rpc-middleware-stream" "^6.0.2" - "@metamask/object-multiplex" "^2.0.0" - "@metamask/rpc-errors" "^6.2.1" - "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^8.3.0" - detect-browser "^5.2.0" - extension-port-stream "^3.0.0" - fast-deep-equal "^3.1.3" - is-stream "^2.0.0" - readable-stream "^3.6.2" - webextension-polyfill "^0.10.0" - -"@metamask/providers@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-16.0.0.tgz#5ba3bcbdc59b334958f1dcf6ed9b7f68de55eff1" - integrity sha512-zqZpn7Tg+QuvhL9x1WYCdXjFclZrMR2LOU2a3kHVFms5pOfizzFApB492VFgHuv/5xKohSxDkhXpAZ/TNGmM4g== - dependencies: - "@metamask/json-rpc-engine" "^7.3.2" - "@metamask/json-rpc-middleware-stream" "^6.0.2" - "@metamask/object-multiplex" "^2.0.0" - "@metamask/rpc-errors" "^6.2.1" - "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^8.3.0" - detect-browser "^5.2.0" - extension-port-stream "^3.0.0" - fast-deep-equal "^3.1.3" - is-stream "^2.0.0" - readable-stream "^3.6.2" - webextension-polyfill "^0.10.0" - "@metamask/react-native-button@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@metamask/react-native-button/-/react-native-button-3.0.0.tgz#4af8affd11e2b285cfc1b1752280797e1b33e62b" @@ -4540,18 +4470,6 @@ fast-xml-parser "^4.3.4" superstruct "^1.0.3" -"@metamask/snaps-sdk@^3.1.1": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@metamask/snaps-sdk/-/snaps-sdk-3.2.0.tgz#66d60869697a479a3484adc9532d82c3b568fc19" - integrity sha512-Xfsc6seyucs7TP2JLXoKYrWm5FbrttdHnMOTfuzTb4T+qmdmoc3wdw83RAGjRFiOOaHGFc6JJSCdP33fBmw4Hg== - dependencies: - "@metamask/key-tree" "^9.0.0" - "@metamask/providers" "^16.0.0" - "@metamask/rpc-errors" "^6.2.1" - "@metamask/utils" "^8.3.0" - fast-xml-parser "^4.3.4" - superstruct "^1.0.3" - "@metamask/snaps-utils@^5.1.2": version "5.2.0" resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-5.2.0.tgz#ff43b97ff176846230d8bdedb1769b269effc4d8" From a91de7335430c6e7d26f5325347f2872e6cd8c24 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 26 Mar 2024 23:26:26 -0300 Subject: [PATCH 17/17] chore: updte yarn.lock --- yarn.lock | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/yarn.lock b/yarn.lock index 0133ef1f525..c3207f56cdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4052,6 +4052,16 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/json-rpc-middleware-stream@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-6.0.2.tgz#75852ce481f8f9f091edbfc04ffdf964f8f3cabd" + integrity sha512-jtyx3PRfc1kqoLpYveIVQNwsxYKefc64/LCl9h9Da1m3nUKEvypbYuXSIwi237qvOjKmNHQKsDOZg6f4uBf62Q== + dependencies: + "@metamask/json-rpc-engine" "^7.3.2" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + readable-stream "^3.6.2" + "@metamask/key-tree@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/key-tree/-/key-tree-9.0.0.tgz#ce880a79f35af5b3b540b44be16ff98cc77be4c1" @@ -4064,6 +4074,30 @@ "@noble/secp256k1" "^1.5.5" "@scure/base" "^1.0.0" +"@metamask/keyring-api@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-3.0.0.tgz#66e6d9de01ba652f0f7959d04d8d4d0c6a3dd040" + integrity sha512-hvBH55P0ajhHOU8lkUFLCs/HdXJORDHzBT6BSVh7nxUmxf8gdDIYX9iG/+2PYWHo1qvUlnojzfROMS1/E6MQgQ== + dependencies: + "@metamask/providers" "^14.0.1" + "@metamask/snaps-sdk" "^1.3.2" + "@metamask/utils" "^8.1.0" + "@types/uuid" "^9.0.1" + superstruct "^1.0.3" + uuid "^9.0.0" + +"@metamask/keyring-api@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-4.0.2.tgz#dc7ba93cf5cfd381c091dc231895dcfef72affc8" + integrity sha512-5Jc376i2ULXEopi5S6y/BZzloW4lrNOatMczKrsYVMiJdTHYZh9nPZY41ggZVxM1zwETvCQAX+QrAV2FXZQxAw== + dependencies: + "@metamask/providers" "^15.0.0" + "@metamask/snaps-sdk" "^3.1.1" + "@metamask/utils" "^8.3.0" + "@types/uuid" "^9.0.1" + superstruct "^1.0.3" + uuid "^9.0.0" + "@metamask/keyring-controller@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-9.0.0.tgz#d0b3790ed43b8626edd6c4e092c14a9e3c8a3de4" @@ -4320,6 +4354,42 @@ readable-stream "^3.6.2" webextension-polyfill "^0.10.0" +"@metamask/providers@^15.0.0": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-15.0.0.tgz#e8957bb89d2f3379b32b60117d79a141e44db2bc" + integrity sha512-FXvL1NQNl6I7fMOJTfQYcBlBZ33vSlm6w80cMpmn8sJh0Lb7wcBpe02UwBsNlARnI+Qsr26XeDs6WHUHQh8CuA== + dependencies: + "@metamask/json-rpc-engine" "^7.3.2" + "@metamask/json-rpc-middleware-stream" "^6.0.2" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + detect-browser "^5.2.0" + extension-port-stream "^3.0.0" + fast-deep-equal "^3.1.3" + is-stream "^2.0.0" + readable-stream "^3.6.2" + webextension-polyfill "^0.10.0" + +"@metamask/providers@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-16.0.0.tgz#5ba3bcbdc59b334958f1dcf6ed9b7f68de55eff1" + integrity sha512-zqZpn7Tg+QuvhL9x1WYCdXjFclZrMR2LOU2a3kHVFms5pOfizzFApB492VFgHuv/5xKohSxDkhXpAZ/TNGmM4g== + dependencies: + "@metamask/json-rpc-engine" "^7.3.2" + "@metamask/json-rpc-middleware-stream" "^6.0.2" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + detect-browser "^5.2.0" + extension-port-stream "^3.0.0" + fast-deep-equal "^3.1.3" + is-stream "^2.0.0" + readable-stream "^3.6.2" + webextension-polyfill "^0.10.0" + "@metamask/react-native-button@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@metamask/react-native-button/-/react-native-button-3.0.0.tgz#4af8affd11e2b285cfc1b1752280797e1b33e62b" @@ -4470,6 +4540,18 @@ fast-xml-parser "^4.3.4" superstruct "^1.0.3" +"@metamask/snaps-sdk@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@metamask/snaps-sdk/-/snaps-sdk-3.2.0.tgz#66d60869697a479a3484adc9532d82c3b568fc19" + integrity sha512-Xfsc6seyucs7TP2JLXoKYrWm5FbrttdHnMOTfuzTb4T+qmdmoc3wdw83RAGjRFiOOaHGFc6JJSCdP33fBmw4Hg== + dependencies: + "@metamask/key-tree" "^9.0.0" + "@metamask/providers" "^16.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + fast-xml-parser "^4.3.4" + superstruct "^1.0.3" + "@metamask/snaps-utils@^5.1.2": version "5.2.0" resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-5.2.0.tgz#ff43b97ff176846230d8bdedb1769b269effc4d8"