Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add react native aes support #4764

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand All @@ -418,6 +426,8 @@ export default class UserStorageController extends BaseController<
config,
getMetaMetricsState,
nativeScryptCrypto,
nativeAesGcmEncrypt,
nativeAesGcmDecrypt,
}: {
messenger: UserStorageControllerMessenger;
state?: UserStorageControllerState;
Expand All @@ -428,6 +438,8 @@ export default class UserStorageController extends BaseController<
};
getMetaMetricsState: () => boolean;
nativeScryptCrypto?: NativeScrypt;
nativeAesGcmEncrypt?: NativeAesGcmEncryptProps;
nativeAesGcmDecrypt?: NativeAesGcmDecryptProps;
}) {
super({
messenger,
Expand All @@ -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
Expand All @@ -459,6 +473,8 @@ export default class UserStorageController extends BaseController<
storageKey,
bearerToken,
nativeScryptCrypto: this.#nativeScryptCrypto,
nativeAesGcmEncrypt: this.#nativeAesGcmEncrypt,
nativeAesGcmDecrypt: this.#nativeAesGcmDecrypt,
};
},
});
Expand Down Expand Up @@ -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<string | null> {
this.#assertProfileSyncingEnabled();

Expand All @@ -598,6 +618,9 @@ export default class UserStorageController extends BaseController<
bearerToken,
storageKey,
nativeScryptCrypto: this.#nativeScryptCrypto,
nativeAesGcmDecrypt: this.#nativeAesGcmDecrypt,
iv,
tag,
});

return result;
Expand All @@ -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<string[] | null> {
this.#assertProfileSyncingEnabled();

Expand All @@ -623,6 +650,9 @@ export default class UserStorageController extends BaseController<
bearerToken,
storageKey,
nativeScryptCrypto: this.#nativeScryptCrypto,
nativeAesGcmDecrypt: this.#nativeAesGcmDecrypt,
iv,
tag,
});

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -58,7 +68,15 @@ export async function getUserStorage(
opts: UserStorageOptions,
): Promise<string | null> {
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}`);
Expand Down Expand Up @@ -91,6 +109,9 @@ export async function getUserStorage(
encryptedData,
opts.storageKey,
nativeScryptCrypto,
nativeAesGcmDecrypt,
iv,
tag,
);

return decryptedData;
Expand All @@ -110,7 +131,14 @@ export async function getUserStorageAllFeatureEntries(
opts: UserStorageAllFeatureEntriesOptions,
): Promise<string[] | null> {
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(), {
Expand Down Expand Up @@ -148,6 +176,9 @@ export async function getUserStorageAllFeatureEntries(
entry.Data,
opts.storageKey,
nativeScryptCrypto,
nativeAesGcmDecrypt,
iv,
tag,
);
decryptedData.push(data);
} catch {
Expand All @@ -172,12 +203,19 @@ export async function upsertUserStorage(
data: string,
opts: UserStorageOptions,
): Promise<void> {
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}`);
Expand Down Expand Up @@ -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,
),
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -54,12 +58,14 @@ class EncryptorDecryptor {
plaintext: string,
password: string,
nativeScryptCrypto?: NativeScrypt,
nativeAesGcmEncrypt?: NativeAesGcmEncryptProps,
): Promise<string> {
try {
return await this.#encryptStringV1(
plaintext,
password,
nativeScryptCrypto,
nativeAesGcmEncrypt,
);
} catch (e) {
const errorMessage = e instanceof Error ? e.message : JSON.stringify(e);
Expand All @@ -71,6 +77,9 @@ class EncryptorDecryptor {
encryptedDataStr: string,
password: string,
nativeScryptCrypto?: NativeScrypt,
nativeAesGcmDecrypt?: NativeAesGcmDecryptProps,
iv?: string,
tag?: string,
): Promise<string> {
try {
const encryptedData: EncryptedPayload = JSON.parse(encryptedDataStr);
Expand All @@ -80,6 +89,9 @@ class EncryptorDecryptor {
encryptedData,
password,
nativeScryptCrypto,
nativeAesGcmDecrypt,
iv,
tag,
);
}
}
Expand All @@ -96,6 +108,7 @@ class EncryptorDecryptor {
plaintext: string,
password: string,
nativeScryptCrypto?: NativeScrypt,
nativeAesGcmEncrypt?: NativeAesGcmEncryptProps,
): Promise<string> {
const { key, salt } = await this.#getOrGenerateScryptKey(
password,
Expand All @@ -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
Expand All @@ -139,6 +152,9 @@ class EncryptorDecryptor {
data: EncryptedPayload,
password: string,
nativeScryptCrypto?: NativeScrypt,
nativeAesGcmDecrypt?: NativeAesGcmDecryptProps,
iv?: string,
tag?: string,
): Promise<string> {
const { o, d: base64CiphertextAndNonceAndSalt, saltLen } = data;

Expand Down Expand Up @@ -168,19 +184,44 @@ 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<Uint8Array> {
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);

return concatBytes(nonce, ciphertext);
}

#decrypt(ciphertextAndNonce: Uint8Array, key: Uint8Array): Uint8Array {
async #decrypt(
ciphertextAndNonce: Uint8Array,
key: Uint8Array,
nativeAesGcmDecrypt?: NativeAesGcmDecryptProps,
iv?: string,
tag?: string,
): Promise<Uint8Array> {
// Create buffers of nonce and ciphertext.
const nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
const ciphertext = ciphertextAndNonce.slice(
Expand All @@ -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);
}

Expand Down
Loading
Loading