From 46d69cc0d84d747ce1a614ea03bd8cb790761758 Mon Sep 17 00:00:00 2001 From: JSoufer Date: Fri, 4 Oct 2024 14:51:53 +0100 Subject: [PATCH 1/2] add react native gcm aes optionality --- .../src/shared/encryption/encryption.ts | 55 +++++++++++++++++-- .../src/shared/types/encryption.ts | 17 ++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/packages/profile-sync-controller/src/shared/encryption/encryption.ts b/packages/profile-sync-controller/src/shared/encryption/encryption.ts index 969f613751..a52b39d65e 100644 --- a/packages/profile-sync-controller/src/shared/encryption/encryption.ts +++ b/packages/profile-sync-controller/src/shared/encryption/encryption.ts @@ -4,7 +4,11 @@ import { scryptAsync } from '@noble/hashes/scrypt'; import { sha256 } from '@noble/hashes/sha256'; import { utf8ToBytes, concatBytes, bytesToHex } from '@noble/hashes/utils'; -import type { NativeScrypt } from '../types/encryption'; +import type { + NativeAesGcmDecryptProps, + NativeAesGcmEncryptProps, + NativeScrypt, +} from '../types/encryption'; import { getAnyCachedKey, getCachedKeyBySalt, setCachedKey } from './cache'; import { base64ToByteArray, @@ -54,12 +58,14 @@ class EncryptorDecryptor { plaintext: string, password: string, nativeScryptCrypto?: NativeScrypt, + nativeAesGcmEncrypt?: NativeAesGcmEncryptProps, ): Promise { try { return await this.#encryptStringV1( plaintext, password, nativeScryptCrypto, + nativeAesGcmEncrypt, ); } catch (e) { const errorMessage = e instanceof Error ? e.message : JSON.stringify(e); @@ -71,6 +77,9 @@ class EncryptorDecryptor { encryptedDataStr: string, password: string, nativeScryptCrypto?: NativeScrypt, + nativeAesGcmDecrypt?: NativeAesGcmDecryptProps, + iv?: string, + tag?: string, ): Promise { try { const encryptedData: EncryptedPayload = JSON.parse(encryptedDataStr); @@ -80,6 +89,9 @@ class EncryptorDecryptor { encryptedData, password, nativeScryptCrypto, + nativeAesGcmDecrypt, + iv, + tag, ); } } @@ -96,6 +108,7 @@ class EncryptorDecryptor { plaintext: string, password: string, nativeScryptCrypto?: NativeScrypt, + nativeAesGcmEncrypt?: NativeAesGcmEncryptProps, ): Promise { const { key, salt } = await this.#getOrGenerateScryptKey( password, @@ -113,7 +126,7 @@ class EncryptorDecryptor { const plaintextRaw = utf8ToBytes(plaintext); const ciphertextAndNonceAndSalt = concatBytes( salt, - this.#encrypt(plaintextRaw, key), + await this.#encrypt(plaintextRaw, key, nativeAesGcmEncrypt), ); // Convert to Base64 @@ -139,6 +152,9 @@ class EncryptorDecryptor { data: EncryptedPayload, password: string, nativeScryptCrypto?: NativeScrypt, + nativeAesGcmDecrypt?: NativeAesGcmDecryptProps, + iv?: string, + tag?: string, ): Promise { const { o, d: base64CiphertextAndNonceAndSalt, saltLen } = data; @@ -168,11 +184,30 @@ class EncryptorDecryptor { ); // Decrypt and return result. - return bytesToUtf8(this.#decrypt(ciphertextAndNonce, key)); + return bytesToUtf8( + await this.#decrypt( + ciphertextAndNonce, + key, + nativeAesGcmDecrypt, + iv, + tag, + ), + ); } - #encrypt(plaintext: Uint8Array, key: Uint8Array): Uint8Array { + async #encrypt( + plaintext: Uint8Array, + key: Uint8Array, + nativeAesGcmEncrypt?: NativeAesGcmEncryptProps, + ): Promise { const nonce = randomBytes(ALGORITHM_NONCE_SIZE); + if (nativeAesGcmEncrypt) { + console.log('using native aes gcm encrypt'); + return concatBytes( + nonce, + (await nativeAesGcmEncrypt(key, plaintext)).content, + ); + } // Encrypt and prepend nonce. const ciphertext = gcm(key, nonce).encrypt(plaintext); @@ -180,7 +215,13 @@ class EncryptorDecryptor { return concatBytes(nonce, ciphertext); } - #decrypt(ciphertextAndNonce: Uint8Array, key: Uint8Array): Uint8Array { + async #decrypt( + ciphertextAndNonce: Uint8Array, + key: Uint8Array, + nativeAesGcmDecrypt?: NativeAesGcmDecryptProps, + iv?: string, + tag?: string, + ): Promise { // Create buffers of nonce and ciphertext. const nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE); const ciphertext = ciphertextAndNonce.slice( @@ -189,6 +230,10 @@ class EncryptorDecryptor { ); // Decrypt and return result. + if (nativeAesGcmDecrypt && iv && tag) { + console.log('using native aes gcm decrypt'); + return nativeAesGcmDecrypt(key, ciphertext, iv, tag, false); + } return gcm(key, nonce).decrypt(ciphertext); } diff --git a/packages/profile-sync-controller/src/shared/types/encryption.ts b/packages/profile-sync-controller/src/shared/types/encryption.ts index e95ec41fbd..623f85ff96 100644 --- a/packages/profile-sync-controller/src/shared/types/encryption.ts +++ b/packages/profile-sync-controller/src/shared/types/encryption.ts @@ -6,3 +6,20 @@ export declare type NativeScrypt = ( p: number, size: number, ) => Promise; + +export declare type NativeAesGcmEncryptProps = ( + key: Uint8Array, + text: Uint8Array, +) => Promise<{ + content: Uint8Array; + iv: string; + tag: string; +}>; + +export declare type NativeAesGcmDecryptProps = ( + key: Uint8Array, + text: Uint8Array, + iv: string, + tag: string, + isBinary: boolean, +) => Promise; From 3c9fa83f4fa5ff6fc02b9547271807ccc9076780 Mon Sep 17 00:00:00 2001 From: JSoufer Date: Fri, 4 Oct 2024 14:52:34 +0100 Subject: [PATCH 2/2] accept optional native params --- .../user-storage/UserStorageController.ts | 32 +++++++++- .../src/controllers/user-storage/services.ts | 61 +++++++++++++++++-- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/packages/profile-sync-controller/src/controllers/user-storage/UserStorageController.ts b/packages/profile-sync-controller/src/controllers/user-storage/UserStorageController.ts index 5a4deb1a5a..fdf5cff322 100644 --- a/packages/profile-sync-controller/src/controllers/user-storage/UserStorageController.ts +++ b/packages/profile-sync-controller/src/controllers/user-storage/UserStorageController.ts @@ -28,7 +28,11 @@ import type { UserStoragePathWithFeatureOnly, UserStoragePathWithKeyOnly, } from '../../shared/storage-schema'; -import type { NativeScrypt } from '../../shared/types/encryption'; +import type { + NativeAesGcmEncryptProps, + NativeAesGcmDecryptProps, + NativeScrypt, +} from '../../shared/types/encryption'; import { createSnapSignMessageRequest } from '../authentication/auth-snap-requests'; import type { AuthenticationControllerGetBearerToken, @@ -409,6 +413,10 @@ export default class UserStorageController extends BaseController< #nativeScryptCrypto: NativeScrypt | undefined = undefined; + #nativeAesGcmEncrypt: NativeAesGcmEncryptProps | undefined = undefined; + + #nativeAesGcmDecrypt: NativeAesGcmDecryptProps | undefined = undefined; + getMetaMetricsState: () => boolean; constructor({ @@ -418,6 +426,8 @@ export default class UserStorageController extends BaseController< config, getMetaMetricsState, nativeScryptCrypto, + nativeAesGcmEncrypt, + nativeAesGcmDecrypt, }: { messenger: UserStorageControllerMessenger; state?: UserStorageControllerState; @@ -428,6 +438,8 @@ export default class UserStorageController extends BaseController< }; getMetaMetricsState: () => boolean; nativeScryptCrypto?: NativeScrypt; + nativeAesGcmEncrypt?: NativeAesGcmEncryptProps; + nativeAesGcmDecrypt?: NativeAesGcmDecryptProps; }) { super({ messenger, @@ -446,6 +458,8 @@ export default class UserStorageController extends BaseController< this.#keyringController.setupLockedStateSubscriptions(); this.#registerMessageHandlers(); this.#nativeScryptCrypto = nativeScryptCrypto; + this.#nativeAesGcmEncrypt = nativeAesGcmEncrypt; + this.#nativeAesGcmDecrypt = nativeAesGcmDecrypt; this.#accounts.setupAccountSyncingSubscriptions(); // Network Syncing @@ -459,6 +473,8 @@ export default class UserStorageController extends BaseController< storageKey, bearerToken, nativeScryptCrypto: this.#nativeScryptCrypto, + nativeAesGcmEncrypt: this.#nativeAesGcmEncrypt, + nativeAesGcmDecrypt: this.#nativeAesGcmDecrypt, }; }, }); @@ -583,10 +599,14 @@ export default class UserStorageController extends BaseController< * Developers can extend the entry path and entry name through the `schema.ts` file. * * @param path - string in the form of `${feature}.${key}` that matches schema + * @param iv - optional iv + * @param tag - optional tag * @returns the decrypted string contents found from user storage (or null if not found) */ public async performGetStorage( path: UserStoragePathWithFeatureAndKey, + iv?: string, + tag?: string, ): Promise { this.#assertProfileSyncingEnabled(); @@ -598,6 +618,9 @@ export default class UserStorageController extends BaseController< bearerToken, storageKey, nativeScryptCrypto: this.#nativeScryptCrypto, + nativeAesGcmDecrypt: this.#nativeAesGcmDecrypt, + iv, + tag, }); return result; @@ -608,10 +631,14 @@ export default class UserStorageController extends BaseController< * Developers can extend the entry path through the `schema.ts` file. * * @param path - string in the form of `${feature}` that matches schema + * @param iv - optional iv + * @param tag - optional tag * @returns the array of decrypted string contents found from user storage (or null if not found) */ public async performGetStorageAllFeatureEntries( path: UserStoragePathWithFeatureOnly, + iv?: string, + tag?: string, ): Promise { this.#assertProfileSyncingEnabled(); @@ -623,6 +650,9 @@ export default class UserStorageController extends BaseController< bearerToken, storageKey, nativeScryptCrypto: this.#nativeScryptCrypto, + nativeAesGcmDecrypt: this.#nativeAesGcmDecrypt, + iv, + tag, }); return result; diff --git a/packages/profile-sync-controller/src/controllers/user-storage/services.ts b/packages/profile-sync-controller/src/controllers/user-storage/services.ts index 9794824e5e..f5a019ef14 100644 --- a/packages/profile-sync-controller/src/controllers/user-storage/services.ts +++ b/packages/profile-sync-controller/src/controllers/user-storage/services.ts @@ -8,7 +8,11 @@ import type { UserStoragePathWithKeyOnly, } from '../../shared/storage-schema'; import { createEntryPath } from '../../shared/storage-schema'; -import type { NativeScrypt } from '../../shared/types/encryption'; +import type { + NativeScrypt, + NativeAesGcmEncryptProps, + NativeAesGcmDecryptProps, +} from '../../shared/types/encryption'; const ENV_URLS = getEnvUrls(Env.PRD); @@ -36,14 +40,20 @@ export type UserStorageBaseOptions = { bearerToken: string; storageKey: string; nativeScryptCrypto?: NativeScrypt; + nativeAesGcmEncrypt?: NativeAesGcmEncryptProps; + nativeAesGcmDecrypt?: NativeAesGcmDecryptProps; }; export type UserStorageOptions = UserStorageBaseOptions & { path: UserStoragePathWithFeatureAndKey; + iv?: string; + tag?: string; }; export type UserStorageAllFeatureEntriesOptions = UserStorageBaseOptions & { path: UserStoragePathWithFeatureOnly; + iv?: string; + tag?: string; }; export type UserStorageBatchUpsertOptions = UserStorageAllFeatureEntriesOptions; @@ -58,7 +68,15 @@ export async function getUserStorage( opts: UserStorageOptions, ): Promise { try { - const { bearerToken, path, storageKey, nativeScryptCrypto } = opts; + const { + bearerToken, + path, + storageKey, + nativeScryptCrypto, + nativeAesGcmDecrypt, + iv, + tag, + } = opts; const encryptedPath = createEntryPath(path, storageKey); const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`); @@ -91,6 +109,9 @@ export async function getUserStorage( encryptedData, opts.storageKey, nativeScryptCrypto, + nativeAesGcmDecrypt, + iv, + tag, ); return decryptedData; @@ -110,7 +131,14 @@ export async function getUserStorageAllFeatureEntries( opts: UserStorageAllFeatureEntriesOptions, ): Promise { try { - const { bearerToken, path, nativeScryptCrypto } = opts; + const { + bearerToken, + path, + nativeScryptCrypto, + nativeAesGcmDecrypt, + iv, + tag, + } = opts; const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`); const userStorageResponse = await fetch(url.toString(), { @@ -148,6 +176,9 @@ export async function getUserStorageAllFeatureEntries( entry.Data, opts.storageKey, nativeScryptCrypto, + nativeAesGcmDecrypt, + iv, + tag, ); decryptedData.push(data); } catch { @@ -172,12 +203,19 @@ export async function upsertUserStorage( data: string, opts: UserStorageOptions, ): Promise { - const { bearerToken, path, storageKey, nativeScryptCrypto } = opts; + const { + bearerToken, + path, + storageKey, + nativeScryptCrypto, + nativeAesGcmEncrypt, + } = opts; const encryptedData = await encryption.encryptString( data, opts.storageKey, nativeScryptCrypto, + nativeAesGcmEncrypt, ); const encryptedPath = createEntryPath(path, storageKey); const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`); @@ -211,14 +249,25 @@ export async function batchUpsertUserStorage( return; } - const { bearerToken, path, storageKey, nativeScryptCrypto } = opts; + const { + bearerToken, + path, + storageKey, + nativeScryptCrypto, + nativeAesGcmEncrypt, + } = opts; const encryptedData: string[][] = []; for (const d of data) { encryptedData.push([ createSHA256Hash(d[0] + storageKey), - await encryption.encryptString(d[1], opts.storageKey, nativeScryptCrypto), + await encryption.encryptString( + d[1], + opts.storageKey, + nativeScryptCrypto, + nativeAesGcmEncrypt, + ), ]); }