mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 22:49:45 +01:00
web: use safeStorage to encrypt secrets on desktop
This commit is contained in:
@@ -22,6 +22,7 @@ import { IKVStore, IndexedDBKVStore, MemoryKVStore } from "./key-value";
|
|||||||
import { NNCrypto } from "./nncrypto";
|
import { NNCrypto } from "./nncrypto";
|
||||||
import { isFeatureSupported } from "../utils/feature-check";
|
import { isFeatureSupported } from "../utils/feature-check";
|
||||||
import { isCipher } from "@notesnook/core/dist/database/crypto";
|
import { isCipher } from "@notesnook/core/dist/database/crypto";
|
||||||
|
import { desktop } from "../common/desktop-bridge";
|
||||||
|
|
||||||
type BaseCredential = { id: string };
|
type BaseCredential = { id: string };
|
||||||
type PasswordCredential = BaseCredential & {
|
type PasswordCredential = BaseCredential & {
|
||||||
@@ -92,12 +93,11 @@ class KeyStore {
|
|||||||
private key?: CryptoKey;
|
private key?: CryptoKey;
|
||||||
|
|
||||||
constructor(dbName: string) {
|
constructor(dbName: string) {
|
||||||
console.log("key store", dbName);
|
|
||||||
this.metadataStore = isFeatureSupported("indexedDB")
|
this.metadataStore = isFeatureSupported("indexedDB")
|
||||||
? new IndexedDBKVStore(dbName, "metadata")
|
? new IndexedDBKVStore(`${dbName}-metadata`, "metadata")
|
||||||
: new MemoryKVStore();
|
: new MemoryKVStore();
|
||||||
this.secretStore = isFeatureSupported("indexedDB")
|
this.secretStore = isFeatureSupported("indexedDB")
|
||||||
? new IndexedDBKVStore(dbName, "secrets")
|
? new IndexedDBKVStore(`${dbName}-secrets`, "secrets")
|
||||||
: new MemoryKVStore();
|
: new MemoryKVStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ class KeyStore {
|
|||||||
for (const credential of await this.getCredentials()) {
|
for (const credential of await this.getCredentials()) {
|
||||||
await this.metadataStore.delete(this.getCredentialKey(credential));
|
await this.metadataStore.delete(this.getCredentialKey(credential));
|
||||||
}
|
}
|
||||||
await this.metadataStore.set(this.keyId, key);
|
await this.storeKey(key);
|
||||||
this.key = undefined;
|
this.key = undefined;
|
||||||
} else this.key = key;
|
} else this.key = key;
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ class KeyStore {
|
|||||||
public async get(name: string): Promise<string | undefined> {
|
public async get(name: string): Promise<string | undefined> {
|
||||||
if (await this.isLocked())
|
if (await this.isLocked())
|
||||||
throw new Error("Please unlock the key store to get values.");
|
throw new Error("Please unlock the key store to get values.");
|
||||||
|
console.log("GETTING", name);
|
||||||
const blob = await this.secretStore.get<EncryptedData>(name);
|
const blob = await this.secretStore.get<EncryptedData>(name);
|
||||||
if (!blob) return;
|
if (!blob) return;
|
||||||
return this.decrypt(blob, await this.getKey());
|
return this.decrypt(blob, await this.getKey());
|
||||||
@@ -301,18 +301,54 @@ class KeyStore {
|
|||||||
if ((await this.getCredentials()).length > 0)
|
if ((await this.getCredentials()).length > 0)
|
||||||
throw new Error("Key store is locked.");
|
throw new Error("Key store is locked.");
|
||||||
|
|
||||||
let key = await this.metadataStore.get<CryptoKey>(this.keyId);
|
const key = await this.metadataStore.get<Uint8Array | CryptoKey>(
|
||||||
if (!key) {
|
this.keyId
|
||||||
key = await window.crypto.subtle.generateKey(
|
);
|
||||||
|
|
||||||
|
if (key instanceof Uint8Array) {
|
||||||
|
if (!desktop)
|
||||||
|
throw new Error("Cannot decrypt key: no safe storage found.");
|
||||||
|
const decrypted = Buffer.from(
|
||||||
|
await desktop.safeStorage.decryptString.query(
|
||||||
|
Buffer.from(key).toString("base64")
|
||||||
|
),
|
||||||
|
"base64"
|
||||||
|
);
|
||||||
|
return window.crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
decrypted,
|
||||||
|
{ name: "AES-GCM", length: 256 },
|
||||||
|
true,
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
);
|
||||||
|
} else if (key instanceof CryptoKey) return key;
|
||||||
|
else return this.storeKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async storeKey(key?: CryptoKey) {
|
||||||
|
key =
|
||||||
|
key ||
|
||||||
|
(await window.crypto.subtle.generateKey(
|
||||||
{
|
{
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
length: 256
|
length: 256
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
["encrypt", "decrypt"]
|
["encrypt", "decrypt"]
|
||||||
|
));
|
||||||
|
|
||||||
|
if (IS_DESKTOP_APP && desktop) {
|
||||||
|
const encrypted = Buffer.from(
|
||||||
|
await desktop.safeStorage.encryptString.query(
|
||||||
|
Buffer.from(
|
||||||
|
await window.crypto.subtle.exportKey("raw", key)
|
||||||
|
).toString("base64")
|
||||||
|
),
|
||||||
|
"base64"
|
||||||
);
|
);
|
||||||
await this.metadataStore.set(this.keyId, key);
|
await this.metadataStore.set(this.keyId, encrypted);
|
||||||
}
|
} else await this.metadataStore.set(this.keyId, key);
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +386,5 @@ class KeyStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.trace("key store!");
|
|
||||||
export const KeyChain = new KeyStore("KeyChain");
|
export const KeyChain = new KeyStore("KeyChain");
|
||||||
export type IKeyStore = typeof KeyStore.prototype;
|
export type IKeyStore = typeof KeyStore.prototype;
|
||||||
|
|||||||
Reference in New Issue
Block a user