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: do not decrypt mnemonic for auth #322

Merged
merged 2 commits into from
Jul 10, 2023
Merged
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
9 changes: 4 additions & 5 deletions apps/extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import {
init as initKeyRing,
SDK_KEY,
PARENT_ACCOUNT_ID_KEY,
UtilityStore,
} from "./keyring";

const store = new IndexedDBKVStore(KVPrefix.IndexedDB);

const activeAccountStore = new IndexedDBKVStore(KVPrefix.ActiveAccount);
const utilityStore = new IndexedDBKVStore<UtilityStore>(KVPrefix.Utility);
// TODO: For now we will be running two stores side by side
const sdkStore = new IndexedDBKVStore(KVPrefix.SDK);
const extensionStore = new ExtensionKVStore(KVPrefix.LocalStorage, {
Expand Down Expand Up @@ -71,9 +72,7 @@ const { REACT_APP_NAMADA_URL = DEFAULT_URL } = process.env;
const sdkData: Record<string, string> | undefined = await sdkStore.get(
SDK_KEY
);
const activeAccount = await activeAccountStore.get<string>(
PARENT_ACCOUNT_ID_KEY
);
const activeAccount = await utilityStore.get<string>(PARENT_ACCOUNT_ID_KEY);

if (sdkData && activeAccount) {
const data = new TextEncoder().encode(sdkData[activeAccount]);
Expand All @@ -84,7 +83,7 @@ const { REACT_APP_NAMADA_URL = DEFAULT_URL } = process.env;
const keyRingService = new KeyRingService(
store,
sdkStore,
activeAccountStore,
utilityStore,
connectedTabsStore,
extensionStore,
defaultChainId,
Expand Down
116 changes: 81 additions & 35 deletions apps/extension/src/background/keyring/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { AES, Argon2, Argon2Params, ByteSize, Rng, Salt } from "@anoma/crypto";
import {
AES,
Argon2,
Argon2Params,
ByteSize,
Rng,
Salt,
VecU8Pointer,
} from "@anoma/crypto";
import { AccountType, Bip44Path } from "@anoma/types";
import { Argon2Config } from "config";
import { KdfType, KeyStore } from "./types";
import { CryptoRecord, KdfType, KeyStore } from "./types";
import { readVecU8Pointer } from "@anoma/crypto/src/utils";

type CryptoArgs = {
Expand Down Expand Up @@ -31,21 +39,10 @@ export class Crypto {
text,
type,
} = args;
const saltInstance = Salt.generate();
const salt = saltInstance.as_string();
const { m_cost, t_cost, p_cost } = Argon2Config;
const argon2Params = new Argon2Params(m_cost, t_cost, p_cost);
const argon2 = new Argon2(password, salt, argon2Params);
const params = argon2.params();
const key = argon2.key();

const iv = Rng.generate_bytes(ByteSize.N12);
const aes = new AES(key, iv);
const encrypted = aes.encrypt(text);

saltInstance.free();
argon2.free();
aes.free();
const { params, key, iv, salt } = this.encryptionParams(password);
const cipherText = this.encryptWithAES(key, iv, text);
const crypto = this.cryptoRecord(cipherText, params, iv, salt);

return {
alias,
Expand All @@ -55,32 +52,46 @@ export class Crypto {
id,
parentId,
path,
crypto: {
cipher: {
type: "aes-256-gcm",
iv,
text: encrypted,
},
kdf: {
type: KdfType.Argon2,
params: {
m_cost: params.m_cost,
t_cost: params.t_cost,
p_cost: params.p_cost,
salt,
},
crypto,
type,
};
}

private cryptoRecord(
cipherText: Uint8Array,
{ m_cost, t_cost, p_cost }: Argon2Params,
iv: Uint8Array,
salt: string
): CryptoRecord {
return {
cipher: {
type: "aes-256-gcm",
iv,
text: cipherText,
},
kdf: {
type: KdfType.Argon2,
params: {
m_cost,
t_cost,
p_cost,
salt,
},
},
type,
};
}

public encryptAuthKey(password: string, uuid: string): CryptoRecord {
const { params, key, iv, salt } = this.encryptionParams(password);
const cipherText = this.encryptWithAES(key, iv, uuid);
return this.cryptoRecord(cipherText, params, iv, salt);
}

public decrypt(
store: KeyStore,
crypto: CryptoRecord,
password: string,
cryptoMemory: WebAssembly.Memory
): string {
const { crypto } = store;
const { cipher, kdf } = crypto;
const { m_cost, t_cost, p_cost, salt } = kdf.params;

Expand All @@ -91,11 +102,46 @@ export class Crypto {
const aes = new AES(newKey, cipher.iv);
const vecU8Pointer = aes.decrypt(cipher.text);
const decrypted = readVecU8Pointer(vecU8Pointer, cryptoMemory);
const phrase = new TextDecoder().decode(decrypted);
const plainText = new TextDecoder().decode(decrypted);

aes.free();
vecU8Pointer.free();

return phrase;
return plainText;
}

private encryptionParams(password: string): {
params: Argon2Params;
key: VecU8Pointer;
salt: string;
iv: Uint8Array;
} {
const saltInstance = Salt.generate();

const salt = saltInstance.as_string();
saltInstance.free();

const { m_cost, t_cost, p_cost } = Argon2Config;
const argon2Params = new Argon2Params(m_cost, t_cost, p_cost);
const argon2 = new Argon2(password, salt, argon2Params);
const params = argon2.params();
const key = argon2.key();
argon2.free();

const iv = Rng.generate_bytes(ByteSize.N12);

return { params, key, salt, iv };
}

private encryptWithAES(
key: VecU8Pointer,
iv: Uint8Array,
plainText: string
): Uint8Array {
const aes = new AES(key, iv);
const cipherText = aes.encrypt(plainText);
aes.free();

return cipherText;
}
}
51 changes: 39 additions & 12 deletions apps/extension/src/background/keyring/keyring.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { v5 as uuid } from "uuid";
import BigNumber from "bignumber.js";
import { v5 as uuid, v4 as uuidV4 } from "uuid";

import {
HDWallet,
Expand Down Expand Up @@ -32,6 +32,8 @@ import {
KeyStore,
ResetPasswordError,
DeleteAccountError,
CryptoRecord,
UtilityStore,
} from "./types";
import {
readVecStringPointer,
Expand Down Expand Up @@ -61,6 +63,7 @@ const getId = (name: string, ...args: (number | string)[]): string => {
export const KEYSTORE_KEY = "key-store";
export const SDK_KEY = "sdk-store";
export const PARENT_ACCOUNT_ID_KEY = "parent-account-id";
export const AUTHKEY_KEY = "auth-key-store";

const crypto = new Crypto();

Expand All @@ -83,7 +86,7 @@ export class KeyRing {
constructor(
protected readonly kvStore: KVStore<KeyStore[]>,
protected readonly sdkStore: KVStore<Record<string, string>>,
protected readonly activeAccountStore: KVStore<string>,
protected readonly utilityStore: KVStore<UtilityStore>,
protected readonly extensionStore: KVStore<number>,
protected readonly chainId: string,
protected readonly sdk: Sdk,
Expand Down Expand Up @@ -115,11 +118,11 @@ export class KeyRing {
}

public async getActiveAccountId(): Promise<string | undefined> {
return await this.activeAccountStore.get(PARENT_ACCOUNT_ID_KEY);
return await this.utilityStore.get(PARENT_ACCOUNT_ID_KEY);
}

public async setActiveAccountId(parentId: string): Promise<void> {
await this.activeAccountStore.set(PARENT_ACCOUNT_ID_KEY, parentId);
await this.utilityStore.set(PARENT_ACCOUNT_ID_KEY, parentId);

// To sync sdk wallet with DB
const sdkData = await this.sdkStore.get(SDK_KEY);
Expand All @@ -134,12 +137,16 @@ export class KeyRing {
): Promise<boolean> {
// default to active account if no account provided
const idToCheck = accountId ?? (await this.getActiveAccountId());
if (!idToCheck) {
throw new Error("No account to check password against");
}

const mnemonic = await this._keyStore.getRecord("id", idToCheck);
// TODO: Generate arbitrary data to check decryption against
if (mnemonic) {
const authKeys = await this.utilityStore.get<{
[id: string]: CryptoRecord;
}>(AUTHKEY_KEY);
if (authKeys) {
try {
crypto.decrypt(mnemonic, password, this._cryptoMemory);
crypto.decrypt(authKeys[idToCheck], password, this._cryptoMemory);
return true;
} catch (error) {
console.warn(error);
Expand Down Expand Up @@ -172,7 +179,7 @@ export class KeyRing {

for (const account of allAccounts) {
const decryptedSecret = crypto.decrypt(
account,
account.crypto,
currentPassword,
this._cryptoMemory
);
Expand Down Expand Up @@ -264,6 +271,7 @@ export class KeyRing {
});

await this._keyStore.append(mnemonicStore);
await this.generateAuthKey(id, password);
// When we are adding new top level account we have to clear the storage
// to prevent adding top level secret key to existing keys
this.sdk.clear_storage();
Expand All @@ -278,6 +286,21 @@ export class KeyRing {
return false;
}

public async generateAuthKey(
accountId: string,
password: string
): Promise<void> {
const id = uuidV4();
const authKey = crypto.encryptAuthKey(password, id);
const entries = await this.utilityStore.get<{ [id: string]: CryptoRecord }>(
AUTHKEY_KEY
);
await this.utilityStore.set(AUTHKEY_KEY, {
...entries,
[accountId]: authKey,
});
}

public deriveTransparentAccount(
seed: VecU8Pointer,
path: Bip44Path,
Expand Down Expand Up @@ -465,7 +488,7 @@ export class KeyRing {
const parentId = storedMnemonic.id;
try {
const phrase = crypto.decrypt(
storedMnemonic,
storedMnemonic.crypto,
password,
this._cryptoMemory
);
Expand Down Expand Up @@ -616,7 +639,7 @@ export class KeyRing {
let pk: string;

try {
pk = crypto.decrypt(account, this._password, this._cryptoMemory);
pk = crypto.decrypt(account.crypto, this._password, this._cryptoMemory);
} catch (e) {
throw new Error(`Could not unlock account for ${address}: ${e}`);
}
Expand Down Expand Up @@ -673,7 +696,11 @@ export class KeyRing {
if (!account) {
throw new Error(`Account not found.`);
}
const text = crypto.decrypt(account, this._password, this._cryptoMemory);
const text = crypto.decrypt(
account.crypto,
this._password,
this._cryptoMemory
);

// For shielded accounts we need to return the spending key as well.
const extendedSpendingKey =
Expand Down
5 changes: 3 additions & 2 deletions apps/extension/src/background/keyring/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
TabStore,
ResetPasswordError,
DeleteAccountError,
UtilityStore,
} from "./types";
import { syncTabs, updateTabStorage } from "./utils";
import { ExtensionRequester, getAnomaRouterId } from "extension";
Expand All @@ -37,7 +38,7 @@ export class KeyRingService {
constructor(
protected readonly kvStore: KVStore<KeyStore[]>,
protected readonly sdkStore: KVStore<Record<string, string>>,
protected readonly accountAccountStore: KVStore<string>,
protected readonly utilityStore: KVStore<UtilityStore>,
protected readonly connectedTabsStore: KVStore<TabStore[]>,
protected readonly extensionStore: KVStore<number>,
protected readonly chainId: string,
Expand All @@ -49,7 +50,7 @@ export class KeyRingService {
this._keyRing = new KeyRing(
kvStore,
sdkStore,
accountAccountStore,
utilityStore,
extensionStore,
chainId,
sdk,
Expand Down
Loading
Loading