From a34dbeeb3235ac2e7be13e5d3f56f37b346255ef Mon Sep 17 00:00:00 2001 From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com> Date: Mon, 18 Oct 2021 14:57:31 +0200 Subject: [PATCH] Save identity in local storage (#80) --- packages/react-chat/src/hooks/useMessenger.ts | 10 ++- .../react-chat/src/utils/identityStorage.ts | 86 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 packages/react-chat/src/utils/identityStorage.ts diff --git a/packages/react-chat/src/hooks/useMessenger.ts b/packages/react-chat/src/hooks/useMessenger.ts index 78fa4bde..5a690979 100644 --- a/packages/react-chat/src/hooks/useMessenger.ts +++ b/packages/react-chat/src/hooks/useMessenger.ts @@ -6,6 +6,7 @@ import { ApplicationMetadataMessage } from "status-communities/dist/cjs"; import { uintToImgUrl } from "../helpers/uintToImgUrl"; import { ChatMessage } from "../models/ChatMessage"; +import { loadIdentity, saveIdentity } from "../utils/identityStorage"; const _MS_PER_DAY = 1000 * 60 * 60 * 24; @@ -136,8 +137,13 @@ export function useMessenger(chatId: string, chatIdList: string[]) { useEffect(() => { const createMessenger = async () => { - const identity = Identity.generate(); - + // Test password for now + // Need design for password input + let identity = await loadIdentity("test"); + if (!identity) { + identity = Identity.generate(); + await saveIdentity(identity, "test"); + } const messenger = await Messenger.create(identity, { libp2p: { config: { diff --git a/packages/react-chat/src/utils/identityStorage.ts b/packages/react-chat/src/utils/identityStorage.ts new file mode 100644 index 00000000..fbe69c93 --- /dev/null +++ b/packages/react-chat/src/utils/identityStorage.ts @@ -0,0 +1,86 @@ +import { bufToHex, hexToBuf } from "js-waku/build/main/lib/utils"; +import { Identity } from "status-communities/dist/cjs"; + +export async function saveIdentity(identity: Identity, password: string) { + const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const wrapKey = await getWrapKey(password, salt); + + const iv = window.crypto.getRandomValues(new Uint8Array(12)); + const cipher = await window.crypto.subtle.encrypt( + { + name: "AES-GCM", + iv: iv, + }, + wrapKey, + identity.privateKey + ); + + const data = { + salt: bufToHex(salt), + iv: bufToHex(iv), + cipher: bufToHex(cipher), + }; + + localStorage.setItem("cipherIdentity", JSON.stringify(data)); +} + +export async function loadIdentity( + password: string +): Promise { + const str = localStorage.getItem("cipherIdentity"); + if (!str) return; + const data = JSON.parse(str); + + const salt = hexToBuf(data.salt); + const iv = hexToBuf(data.iv); + const cipher = hexToBuf(data.cipher); + + return await decryptIdentity(salt, iv, cipher, password); +} + +async function getWrapKey(password: string, salt: Uint8Array) { + const enc = new TextEncoder(); + const keyMaterial = await window.crypto.subtle.importKey( + "raw", + enc.encode(password), + { name: "PBKDF2" }, + false, + ["deriveBits", "deriveKey"] + ); + return await window.crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt, + iterations: 100000, + hash: "SHA-256", + }, + keyMaterial, + { name: "AES-GCM", length: 256 }, + true, + ["encrypt", "decrypt"] + ); +} + +async function decryptIdentity( + salt: Buffer, + iv: Buffer, + cipherKeyPair: Buffer, + password: string +): Promise { + const key = await getWrapKey(password, salt); + + try { + const decrypted = await window.crypto.subtle.decrypt( + { + name: "AES-GCM", + iv: iv, + }, + key, + cipherKeyPair + ); + + return new Identity(new Uint8Array(decrypted)); + } catch (e) { + return; + } +}