}
static async work (headers: Headers, data: Data): Promise<any> {
- const { method, name, password, store } = headers
+ const { method, name, store, password } = headers
this.#storage = await this.#open(this.DB_NAME)
let result
try {
switch (method) {
case 'set': {
- result = await this.set(store, password, data)
+ result = await this.set(data, store, password)
break
}
case 'get': {
- result = await this.get(store, password, name)
+ result = await this.get(name, store, password)
break
}
case 'destroy': {
- result = await this.destroy(store, name)
+ result = await this.destroy(name, store)
break
}
default: {
}
}
} catch (err) {
+ console.log(err)
result = false
}
return result
/**
* Removes data from the Safe without decrypting.
*/
- static async destroy (store: string, name: string): Promise<boolean> {
+ static async destroy (name: string, store: string): Promise<boolean> {
try {
- return await this.#delete(store, name)
- } catch {
+ return await this.#delete(name, store)
+ } catch (err) {
+ console.log(err)
throw new Error(this.ERR_MSG)
}
}
/**
* Encrypts data with a password byte array and stores it in the Safe.
*/
- static async set (store: string, password: ArrayBuffer, data: Data): Promise<boolean> {
- try {
- const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
- if (this.#isInvalid(store, derivationKey, data)) {
- throw new Error('Failed to import key')
- }
+ static async set (data: Data | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<boolean> {
+ this.#isDataValid(data)
+ if (typeof store !== 'string' || store === '') {
+ throw new Error('Invalid database store name')
+ }
+ if (!(password instanceof ArrayBuffer)) {
+ throw new Error('Invalid password')
+ }
+ try {
const records: SafeRecord[] = []
for (const label of Object.keys(data)) {
const salt = await Entropy.create()
- const derivationAlgorithm: Pbkdf2Params = {
- name: 'PBKDF2',
- hash: 'SHA-512',
- salt: salt.bytes,
- iterations: 210000
- }
- const derivedKeyType: AesKeyGenParams = {
- name: 'AES-GCM',
- length: 256
- }
- const encryptionKey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, ['encrypt'])
-
+ const encryptionKey = await Safe.#createAesKey('encrypt', password, salt.buffer)
const iv = await Entropy.create()
const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv.buffer }, encryptionKey, data[label])
const record: SafeRecord = {
}
records.push(record)
}
-
return await this.#put(records, store)
} catch (err) {
throw new Error(this.ERR_MSG)
/**
* Retrieves data from the Safe and decrypts it with a password byte array.
*/
- static async get (store: string, password: ArrayBuffer, name: string): Promise<Data | null> {
- try {
- const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
- if (this.#isInvalid(store, derivationKey)) {
- throw new Error('Failed to import key')
- }
+ static async get (name: string | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<Data | null> {
+ if (typeof name !== 'string' || name === '') {
+ throw new Error('Invalid database field name')
+ }
+ if (typeof store !== 'string' || store === '') {
+ throw new Error('Invalid database store name')
+ }
+ if (!(password instanceof ArrayBuffer)) {
+ throw new Error('Invalid password')
+ }
+ try {
const record: SafeRecord = await this.#get(name, store)
if (record == null) {
throw new Error('Failed to find record')
}
- const { label, encrypted } = record
-
const salt = await Entropy.import(record.salt)
- const derivationAlgorithm: Pbkdf2Params = {
- name: 'PBKDF2',
- hash: 'SHA-512',
- salt: salt.bytes,
- iterations: 210000
- }
- const derivedKeyType: AesKeyGenParams = {
- name: 'AES-GCM',
- length: 256
- }
- const decryptionKey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, ['decrypt'])
-
+ const decryptionKey = await Safe.#createAesKey('decrypt', password, salt.buffer)
const iv = await Entropy.import(record.iv)
- const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKey, encrypted)
-
- await this.destroy(store, name)
- return { [label]: decrypted }
+ const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKey, record.encrypted)
+ return { [record.label]: decrypted }
} catch (err) {
+ console.log(err)
return null
} finally {
bytes.erase(password)
}
}
- static #isInvalid (name: string, passkey: CryptoKey, data?: any): boolean {
- if (typeof name !== 'string' || name === '') {
- return true
- }
- if (!(passkey instanceof CryptoKey)) {
- return true
+ static async #createAesKey (purpose: 'encrypt' | 'decrypt', password: ArrayBuffer, salt: ArrayBuffer): Promise<CryptoKey> {
+ const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
+ const derivationAlgorithm: Pbkdf2Params = {
+ name: 'PBKDF2',
+ hash: 'SHA-512',
+ iterations: 210000,
+ salt
}
- if (typeof data === 'object') {
- try {
- JSON.stringify(data, (k, v) => typeof v === 'bigint' ? v.toString() : v)
- } catch {
- return true
- }
+ const derivedKeyType: AesKeyGenParams = {
+ name: 'AES-GCM',
+ length: 256
}
- return false
+ return await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose])
}
- static async #delete (store: string, name: string): Promise<boolean> {
+ static async #delete (name: string, store: string): Promise<boolean> {
const transaction = this.#storage.transaction(store, 'readwrite')
const db = transaction.objectStore(store)
return new Promise((resolve, reject) => {
})
}
+ static #isDataValid (data: unknown): asserts data is Data {
+ if (typeof data !== 'object') {
+ throw new Error('Invalid data')
+ }
+ const dataObject = data as { [key: string]: unknown }
+ if (Object.keys(dataObject).some(k => !(dataObject[k] instanceof ArrayBuffer))) {
+ throw new Error('Invalid data')
+ }
+ }
+
static async #open (database: string): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(database, 1)