UNPKG

@hazae41/kdbx

Version:

Rust-like KeePass (KDBX 4) file format for TypeScript

403 lines (402 loc) 14.8 kB
// deno-lint-ignore-file no-namespace var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { if (value !== null && value !== void 0) { if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); var dispose, inner; if (async) { if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); dispose = value[Symbol.asyncDispose]; } if (dispose === void 0) { if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); dispose = value[Symbol.dispose]; if (async) inner = dispose; } if (typeof dispose !== "function") throw new TypeError("Object not disposable."); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; env.stack.push({ value: value, dispose: dispose, async: async }); } else if (async) { env.stack.push({ async: true }); } return value; }; var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { return function (env) { function fail(e) { env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; env.hasError = true; } var r, s = 0; function next() { while (r = env.stack.pop()) { try { if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); if (r.dispose) { var result = r.dispose.call(r.value); if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); } else s |= 1; } catch (e) { fail(e); } } if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); if (env.hasError) throw env.error; } return next(); }; })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }); var _a, _b, _c, _d, _e, _f, _g, _h; export * from "./dictionary/mod.js"; export * from "./headers/mod.js"; import { gunzip, gzip } from "../../libs/gzip/mod.js"; import { Readable, Unknown, Writable } from "@hazae41/binary"; import { Cursor } from "@hazae41/cursor"; import { Cursors } from "../../libs/cursors/mod.js"; import { ContentWithBytes } from "./headers/inner/mod.js"; import { Inner, Outer } from "./headers/mod.js"; import { Compression, MagicAndVersionAndHeadersWithBytesWithHashAndHmac } from "./headers/outer/mod.js"; import { PreHmacKey } from "./hmac/mod.js"; export class PasswordKey { value; #class = _a; constructor(value) { this.value = value; } static async digestOrThrow(password) { const array = await crypto.subtle.digest("SHA-256", password); const bytes = new Uint8Array(array); return new _a(new Unknown(bytes)); } } _a = PasswordKey; export class CompositeKey { value; #class = _b; constructor(value) { this.value = value; } static async digestOrThrow(password) { const array = await crypto.subtle.digest("SHA-256", password.value.bytes); const bytes = new Uint8Array(array); return new _b(new Unknown(bytes)); } } _b = CompositeKey; export class DerivedKey { value; #class = _c; constructor(value) { this.value = value; } } _c = DerivedKey; export class PreMasterKey { seed; hash; #class = _d; constructor(seed, hash) { this.seed = seed; this.hash = hash; } sizeOrThrow() { return 32 + 32; } writeOrThrow(cursor) { cursor.writeOrThrow(this.seed.bytes); cursor.writeOrThrow(this.hash.value.bytes); } async digestOrThrow() { const input = Writable.writeToBytesOrThrow(this); const digest = await crypto.subtle.digest("SHA-256", input); const output = new Uint8Array(digest); return new MasterKey(new Unknown(output)); } } _d = PreMasterKey; export class MasterKey { value; #class = _e; constructor(value) { this.value = value; } } _e = MasterKey; export class PreHmacMasterKey { seed; hash; #class = _f; constructor(seed, hash) { this.seed = seed; this.hash = hash; } sizeOrThrow() { return 32 + 32 + 1; } writeOrThrow(cursor) { cursor.writeOrThrow(this.seed.bytes); cursor.writeOrThrow(this.hash.value.bytes); cursor.writeUint8OrThrow(1); } async digestOrThrow() { const input = Writable.writeToBytesOrThrow(this); const digest = await crypto.subtle.digest("SHA-512", input); const output = new Uint8Array(digest); return new HmacMasterKey(new Unknown(output)); } } _f = PreHmacMasterKey; export class HmacMasterKey { bytes; #class = _g; constructor(bytes) { this.bytes = bytes; } } _g = HmacMasterKey; export class MasterKeys { encrypter; authifier; #class = _h; constructor(encrypter, authifier) { this.encrypter = encrypter; this.authifier = authifier; } } _h = MasterKeys; export var Database; (function (Database) { class Decrypted { outer; inner; constructor(outer, inner) { this.outer = outer; this.inner = inner; } async encryptOrThrow(composite) { const headers = this.inner.headers.rotateOrThrow(); const $file = this.inner.content.value.cloneOrThrow(); const $list = $file.document.querySelectorAll("Value[Protected='True']"); const cipher = await headers.getCipherOrThrow(); for (let i = 0; i < $list.length; i++) { const env_1 = { stack: [], error: void 0, hasError: false }; try { const $value = $list[i]; const decrypted = new TextEncoder().encode($value.textContent); const encrypted = __addDisposableResource(env_1, cipher.applyOrThrow(decrypted), false); $value.textContent = encrypted.bytes.toBase64(); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { __disposeResources(env_1); } } const content = ContentWithBytes.computeOrThrow($file); const inner = new Inner.HeadersAndContentWithBytes(headers, content); const outer = await this.outer.rotateOrThrow(composite); { const { cipher, iv, compression } = outer.data.data.value.headers; const degzipped = Writable.writeToBytesOrThrow(inner); const engzipped = compression === Compression.Gzip ? new Uint8Array(await gzip(degzipped)) : degzipped; const encrypted = await cipher.encryptOrThrow(outer.keys.encrypter.value.bytes, iv.bytes, engzipped); const blocks = new Array(); const cursor = new Cursor(encrypted); const splits = Cursors.splitOrThrow(cursor, 1048576); let index = 0n; for (let x = splits.next(); true; index++, x = splits.next()) { if (x.done === true) break; blocks.push(await BlockWithIndex.fromOrThrow(outer.keys, index, x.value)); continue; } blocks.push(await BlockWithIndex.fromOrThrow(outer.keys, index, new Uint8Array(0))); return new Encrypted(outer.data, new Blocks(blocks)); } } } Database.Decrypted = Decrypted; class Encrypted { outer; inner; constructor(outer, inner) { this.outer = outer; this.inner = inner; } sizeOrThrow() { return this.outer.sizeOrThrow() + this.inner.sizeOrThrow(); } writeOrThrow(cursor) { this.outer.writeOrThrow(cursor); this.inner.writeOrThrow(cursor); } cloneOrThrow() { return new Encrypted(this.outer.cloneOrThrow(), this.inner.cloneOrThrow()); } async decryptOrThrow(composite) { const { cipher, iv, compression } = this.outer.data.value.headers; const keys = await this.outer.deriveOrThrow(composite); await this.outer.verifyOrThrow(keys); const length = this.inner.blocks.reduce((a, b) => a + b.block.data.bytes.length, 0); const cursor = new Cursor(new Uint8Array(length)); for (const block of this.inner.blocks) { await block.verifyOrThrow(keys); cursor.writeOrThrow(block.block.data.bytes); continue; } const decrypted = await cipher.decryptOrThrow(keys.encrypter.value.bytes, iv.bytes, cursor.bytes); const degzipped = compression === Compression.Gzip ? await gunzip(decrypted) : decrypted; const inner = Readable.readFromBytesOrThrow(Inner.HeadersAndContentWithBytes, degzipped); const outer = new Outer.MagicAndVersionAndHeadersWithBytesWithHashAndHmacWithKeys(this.outer, keys); { const cipher = await inner.headers.getCipherOrThrow(); const $$values = inner.content.value.document.querySelectorAll("Value[Protected='True']"); for (let i = 0; i < $$values.length; i++) { const env_2 = { stack: [], error: void 0, hasError: false }; try { const $value = $$values[i]; const encrypted = Uint8Array.fromBase64($value.textContent); const decrypted = __addDisposableResource(env_2, cipher.applyOrThrow(encrypted), false); $value.textContent = new TextDecoder().decode(decrypted.bytes); } catch (e_2) { env_2.error = e_2; env_2.hasError = true; } finally { __disposeResources(env_2); } } return new Decrypted(outer, inner); } } } Database.Encrypted = Encrypted; (function (Encrypted) { function readOrThrow(cursor) { const head = MagicAndVersionAndHeadersWithBytesWithHashAndHmac.readOrThrow(cursor); const body = Blocks.readOrThrow(cursor); return new Encrypted(head, body); } Encrypted.readOrThrow = readOrThrow; })(Encrypted = Database.Encrypted || (Database.Encrypted = {})); })(Database || (Database = {})); export class Blocks { blocks; constructor(blocks) { this.blocks = blocks; } sizeOrThrow() { return this.blocks.reduce((a, b) => a + b.sizeOrThrow(), 0); } writeOrThrow(cursor) { for (const block of this.blocks) block.writeOrThrow(cursor); return; } cloneOrThrow() { return new Blocks(this.blocks.map(block => block.cloneOrThrow())); } } (function (Blocks) { function readOrThrow(cursor) { const blocks = new Array(); for (let index = 0n; true; index++) { const block = Block.readOrThrow(cursor); blocks.push(new BlockWithIndex(index, block)); if (block.data.bytes.length === 0) break; continue; } return new Blocks(blocks); } Blocks.readOrThrow = readOrThrow; })(Blocks || (Blocks = {})); export class BlockWithIndexPreHmacData { index; block; constructor(index, block) { this.index = index; this.block = block; } sizeOrThrow() { return 8 + 4 + this.block.bytes.length; } writeOrThrow(cursor) { cursor.writeBigUint64OrThrow(this.index, true); cursor.writeUint32OrThrow(this.block.bytes.length, true); cursor.writeOrThrow(this.block.bytes); } } export class BlockWithIndex { index; block; constructor(index, block) { this.index = index; this.block = block; } sizeOrThrow() { return this.block.sizeOrThrow(); } writeOrThrow(cursor) { this.block.writeOrThrow(cursor); } cloneOrThrow() { return new BlockWithIndex(this.index, this.block.cloneOrThrow()); } async verifyOrThrow(keys) { const { index } = this; const major = keys.authifier.bytes; const key = await new PreHmacKey(index, major).digestOrThrow(); const preimage = new BlockWithIndexPreHmacData(this.index, this.block.data); const prebytes = Writable.writeToBytesOrThrow(preimage); await key.verifyOrThrow(prebytes, this.block.hmac.bytes); } } (function (BlockWithIndex) { async function fromOrThrow(keys, index, data) { const major = keys.authifier.bytes; const key = await new PreHmacKey(index, major).digestOrThrow(); const preimage = new BlockWithIndexPreHmacData(index, new Unknown(data)); const prebytes = Writable.writeToBytesOrThrow(preimage); const hmac = new Unknown(await key.signOrThrow(prebytes)); const block = new Block(hmac, new Unknown(data)); return new BlockWithIndex(index, block); } BlockWithIndex.fromOrThrow = fromOrThrow; })(BlockWithIndex || (BlockWithIndex = {})); export class Block { hmac; data; constructor(hmac, data) { this.hmac = hmac; this.data = data; } sizeOrThrow() { return 32 + 4 + this.data.bytes.length; } writeOrThrow(cursor) { cursor.writeOrThrow(this.hmac.bytes); cursor.writeUint32OrThrow(this.data.bytes.length, true); cursor.writeOrThrow(this.data.bytes); } cloneOrThrow() { return new Block(this.hmac.cloneOrThrow(), this.data.cloneOrThrow()); } } (function (Block) { function readOrThrow(cursor) { const hmac = new Unknown(cursor.readOrThrow(32)); const size = cursor.readUint32OrThrow(true); const data = new Unknown(cursor.readOrThrow(size)); return new Block(hmac, data); } Block.readOrThrow = readOrThrow; })(Block || (Block = {}));