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: improve password encryption methods #675

Merged
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
15 changes: 14 additions & 1 deletion packages/adena-extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { MemoryProvider } from '@common/provider/memory/memory-provider';
import { ChromeLocalStorage } from '@common/storage';
import { CommandHandler } from '@inject/message/command-handler';
import { isCommandMessageData } from '@inject/message/command-message';
import { MessageHandler } from './inject/message';

const inMemoryProvider = new MemoryProvider();
inMemoryProvider.init();

function existsWallet(): Promise<boolean> {
const storage = new ChromeLocalStorage();
return storage
Expand Down Expand Up @@ -44,4 +50,11 @@ chrome.action.onClicked.addListener(async () => {
});
});

chrome.runtime.onMessage.addListener(MessageHandler.createHandler);
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (isCommandMessageData(message)) {
CommandHandler.createHandler(inMemoryProvider, message, sender, sendResponse);
return true;
}

return MessageHandler.createHandler(message, sender, sendResponse);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const COMMAND_KEYS = {
encryptPassword: 'ENCRYPT_PASSWORD',
decryptPassword: 'DECRYPT_PASSWORD',
clearEncryptKey: 'CLEAR_ENCRYPT_KEY',
} as const;
export type CommandKeyType = keyof typeof COMMAND_KEYS;
export type CommandValueType = (typeof COMMAND_KEYS)[keyof typeof COMMAND_KEYS];
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class MemoryProvider {
private memory: Map<string, any> = new Map();

public get = <T = any>(key: string): T => {
return this.memory.get(key) as T;
};

public set = <T = any>(key: string, value: T): void => {
this.memory.set(key, value);
};

public async init(): Promise<void> {
this.memory = new Map();
}
}
57 changes: 33 additions & 24 deletions packages/adena-extension/src/common/utils/crypto-utils.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,46 @@
import { CommandMessage, CommandMessageData } from '@inject/message/command-message';
import CryptoJS from 'crypto-js';
import { v4 as uuidv4 } from 'uuid';

// Static cipher key used for encrypting the cryptographic key
const ENCRYPT_CIPHER_KEY = 'r3v4';

export const encryptSha256Password = (password: string): string => {
return CryptoJS.SHA256(password).toString();
};

// Encrypts a password with a dynamically generated key and returns the encrypted key and password
export const encryptPassword = (
// Sends a message to the background script to encrypt a password
export const encryptPassword = async (
password: string,
): { encryptedKey: string; encryptedPassword: string } => {
const cryptKey = uuidv4();
const adenaKey = ENCRYPT_CIPHER_KEY;
const encryptedKey = CryptoJS.AES.encrypt(cryptKey, adenaKey).toString();
const encryptedPassword = CryptoJS.AES.encrypt(password, cryptKey).toString();
): Promise<{ encryptedKey: string; encryptedPassword: string }> => {
const result = await sendMessage(CommandMessage.command('encryptPassword', { password }));
if (!result.data) {
throw new Error('Encryption key not initialized.');
}

return {
encryptedKey,
encryptedPassword,
encryptedKey: result.data.encryptedKey,
encryptedPassword: result.data.encryptedPassword,
};
};

// Decrypts a password using the encrypted key and password
export const decryptPassword = (encryptedKey: string, encryptedPassword: string): string => {
const adenaKey = ENCRYPT_CIPHER_KEY;
const key = CryptoJS.AES.decrypt(encryptedKey, adenaKey).toString(CryptoJS.enc.Utf8);
if (key === '') {
throw new Error('CipherKey Decryption Failed');
}
const password = CryptoJS.AES.decrypt(encryptedPassword, key).toString(CryptoJS.enc.Utf8);
if (password === '') {
throw new Error('Password Decryption Failed');
// Sends a message to the background script to encrypt a password
export const decryptPassword = async (iv: string, encryptedPassword: string): Promise<string> => {
const result = await sendMessage(
CommandMessage.command('decryptPassword', {
iv,
encryptedPassword,
}),
);
if (!result.data) {
throw new Error('Encryption key not initialized.');
}
return password;

return result.data.password;
};

export const clearInMemoryKey = async (): Promise<void> => {
await sendMessage(CommandMessage.command('clearEncryptKey'));
};

function sendMessage<T = any>(message: CommandMessageData): Promise<CommandMessageData<T>> {
return new Promise((resolve) => {
chrome.runtime.sendMessage(message, resolve);
});
}
73 changes: 73 additions & 0 deletions packages/adena-extension/src/inject/message/command-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { MemoryProvider } from '@common/provider/memory/memory-provider';
import { CommandMessageData } from './command-message';
import {
clearInMemoryKey,
decryptPassword,
encryptPassword,
getInMemoryKey,
} from './commands/encrypt';

export class CommandHandler {
public static createHandler = async (
inMemoryProvider: MemoryProvider,
message: CommandMessageData,
_: chrome.runtime.MessageSender,
sendResponse: (response?: CommandMessageData) => void,
): Promise<void> => {
if (message.code !== 0) {
return;
}

if (message.command === 'encryptPassword') {
const key = await getInMemoryKey(inMemoryProvider);
if (!key) {
sendResponse({
...message,
code: 500,
});
return;
}

const password = message.data.password;
const resultData = await encryptPassword(key, password);

sendResponse({
...message,
code: 200,
data: resultData,
});

return;
}

if (message.command === 'decryptPassword') {
const key = await getInMemoryKey(inMemoryProvider);
if (!key) {
sendResponse({
...message,
code: 500,
});
return;
}

const iv = message.data.iv;
const encryptedPassword = message.data.encryptedPassword;
const decryptedPassword = await decryptPassword(key, iv, encryptedPassword);

sendResponse({
...message,
code: 200,
data: {
password: decryptedPassword,
},
});
return;
}

if (message.command === 'clearEncryptKey') {
await clearInMemoryKey(inMemoryProvider);
sendResponse({ ...message, code: 200 });
return;
}
};
}
61 changes: 61 additions & 0 deletions packages/adena-extension/src/inject/message/command-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { CommandKeyType } from '@common/constants/command-key.constant';

type StatusType = 'command';

export function isCommandMessageData(data: any): data is CommandMessageData {
return data.status === 'command';
}

export interface CommandMessageData<T = any> {
key: string;
code: number;
status: StatusType;
command: CommandKeyType;
data: T;
}

export class CommandMessage {
private code: number;

private key: string;

private status: StatusType;

private command: CommandKeyType;

private data: any;

constructor(command: CommandKeyType, data?: any, key?: string) {
this.code = 0;
this.key = key ?? '';
this.command = command;
this.status = 'command';
this.data = data;
}

public get message(): CommandMessageData {
return {
code: this.code,
key: this.key,
status: this.status,
command: this.command,
data: this.data,
};
}

public getCommand = (): CommandKeyType => {
return this.command;
};

public getStatus = (): 'command' => {
return this.status;
};

public getData = (): any => {
return this.data;
};

public static command = (command: CommandKeyType, data?: any): CommandMessageData => {
return new CommandMessage(command, data).message;
};
}
77 changes: 77 additions & 0 deletions packages/adena-extension/src/inject/message/commands/encrypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { MemoryProvider } from '@common/provider/memory/memory-provider';

const MEMORY_KEY = 'encryptKey';

const KEY_LENGTH = 256; // AES-256 key length
const IV_LENGTH = 12; // GCM nonce length (12 bytes is recommended)

export async function getInMemoryKey(memoryProvider: MemoryProvider): Promise<CryptoKey | null> {
const key = memoryProvider.get(MEMORY_KEY) || null;
if (!key) {
const generated = await generateInMemoryKey();
memoryProvider.set(MEMORY_KEY, generated);
}

return memoryProvider.get(MEMORY_KEY) || null;
}

export async function clearInMemoryKey(memoryProvider: MemoryProvider): Promise<void> {
const random = await generateInMemoryKey();
memoryProvider.set(MEMORY_KEY, random);
memoryProvider.set(MEMORY_KEY, null);
}

// Encrypts a password using AES-GCM
export const encryptPassword = async (
key: CryptoKey,
password: string,
): Promise<{ encryptedKey: string; encryptedPassword: string }> => {
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const enc = new TextEncoder();
const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
key,
enc.encode(password),
);

return {
encryptedKey: Buffer.from(iv).toString('base64'),
encryptedPassword: Buffer.from(encrypted).toString('base64'),
};
};

// Decrypts a password using AES-GCM
export const decryptPassword = async (
key: CryptoKey,
iv: string,
encryptedPassword: string,
): Promise<string> => {
const encryptedData = Buffer.from(encryptedPassword, 'base64');
const ivBytes = Buffer.from(iv, 'base64');
const dec = new TextDecoder();

const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: ivBytes,
},
key,
encryptedData,
);

return dec.decode(decrypted);
};

const generateInMemoryKey = async (): Promise<CryptoKey> => {
return crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: KEY_LENGTH,
},
true,
['encrypt', 'decrypt'],
);
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { WalletResponseFailureType } from '@adena-wallet/sdk';
import { HandlerMethod } from '.';
import { CommandMessageData } from './command-message';
import { InjectionMessage, InjectionMessageInstance } from './message';
import { existsPopups, removePopups } from './methods';
import { InjectCore } from './methods/core';

export class MessageHandler {
public static createHandler = (
message: InjectionMessage | any,
message: InjectionMessage | CommandMessageData | any,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: InjectionMessage | any) => void,
sendResponse: (response?: InjectionMessage | CommandMessageData | any) => void,
): boolean => {
try {
if (message?.status) {
Expand Down
Loading