'use strict'
import { WorkerInterface } from './worker-interface'
-import { default as Convert, bytes } from '#src/lib/convert.js'
+import { default as Convert, base32, bytes } from '#src/lib/convert.js'
import { Entropy } from '#src/lib/entropy.js'
import { Data, Headers, SafeRecord } from '#types'
static DB_NAME = 'libnemo'
static STORE_NAME = 'Safe'
static ERR_MSG = 'Failed to store item in Safe'
+ static #decoder: TextDecoder = new TextDecoder()
+ static #encoder: TextEncoder = new TextEncoder()
static #storage: IDBDatabase
static {
result = `unknown Safe method ${method}`
}
}
- } catch (err) {
- console.log(err)
+ } catch {
result = false
}
return result
static async destroy (name: string): Promise<boolean> {
try {
return await this.#delete(name)
- } catch (err) {
+ } catch {
throw new Error(this.ERR_MSG)
}
}
static async set (name: string, data: Data): Promise<boolean> {
const { password } = data
delete data.password
- let passkey: CryptoKey
- try {
- if (await this.#exists(name)) throw new Error('Record is already locked')
- passkey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
- } catch {
- throw new Error(this.ERR_MSG)
- } finally {
- bytes.erase(password)
- }
- if (this.#isInvalid(name, passkey, data)) {
- throw new Error(this.ERR_MSG)
- }
try {
- const iv = await Entropy.create()
+ if (await this.#exists(name)) {
+ throw null
+ }
+ const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
+ if (this.#isInvalid(name, derivationKey, data)) {
+ throw null
+ }
+
+ const base32: { [key: string]: string } = {}
+ for (const d of Object.keys(data)) {
+ base32[d] = bytes.toBase32(new Uint8Array(data[d]))
+ }
+ const serialized = JSON.stringify(base32)
+ const encoded = this.#encoder.encode(serialized)
+
+ const salt = await Entropy.create()
const derivationAlgorithm: Pbkdf2Params = {
name: 'PBKDF2',
hash: 'SHA-512',
- salt: iv.bytes,
+ salt: salt.bytes,
iterations: 210000
}
const derivedKeyType: AesKeyGenParams = {
name: 'AES-GCM',
length: 256
}
- passkey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, passkey, derivedKeyType, false, ['encrypt'])
- for (const d of Object.keys(data)) {
- data[d] = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv.buffer }, passkey, data[d])
- }
+ const encryptionKey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, ['encrypt'])
+
+ const iv = await Entropy.create()
+ const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv.buffer }, encryptionKey, encoded)
const record: SafeRecord = {
iv: iv.hex,
- data
+ salt: salt.hex,
+ encrypted
}
return await this.#add(record, name)
} catch (err) {
throw new Error(this.ERR_MSG)
+ } finally {
+ bytes.erase(password)
}
}
static async get (name: string, data: Data): Promise<Data | null> {
const { password } = data
delete data.password
- let passkey: CryptoKey
- try {
- passkey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
- } catch {
- return null
- } finally {
- bytes.erase(password)
- }
- if (this.#isInvalid(name, passkey)) {
- return null
- }
- let record: SafeRecord
try {
- record = await this.#get(name)
- } catch {
- return null
- }
- if (record == null) {
- return null
- }
+ const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
+ if (this.#isInvalid(name, derivationKey)) {
+ throw null
+ }
- try {
- const iv = await Entropy.import(record.iv)
- const { data } = record
+ const record: SafeRecord = await this.#get(name)
+ if (record == null) {
+ throw null
+ }
+ const { encrypted } = record
+
+ const salt = await Entropy.import(record.salt)
const derivationAlgorithm: Pbkdf2Params = {
name: 'PBKDF2',
hash: 'SHA-512',
- salt: iv.bytes,
+ salt: salt.bytes,
iterations: 210000
}
const derivedKeyType: AesKeyGenParams = {
name: 'AES-GCM',
length: 256
}
- passkey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, passkey, derivedKeyType, false, ['decrypt'])
- for (const d of Object.keys(data)) {
- const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, passkey, data[d])
- data[d] = decrypted
+ const decryptionKey = await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, ['decrypt'])
+
+ const iv = await Entropy.import(record.iv)
+ const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKey, encrypted)
+ const decoded = this.#decoder.decode(decrypted)
+ const deserialized: { [key: string]: string } = JSON.parse(decoded)
+
+ const bytes: Data = {}
+ for (const d of Object.keys(deserialized)) {
+ bytes[d] = new Uint8Array(base32.toBytes(deserialized[d])).buffer
}
await this.destroy(name)
- return data
+ return bytes
} catch (err) {
return null
+ } finally {
+ bytes.erase(password)
}
}
if (typeof data === 'object') {
try {
JSON.stringify(data, (k, v) => typeof v === 'bigint' ? v.toString() : v)
- } catch (err) {
+ } catch {
return true
}
}