@hazae41/kdbx
Version:
Rust-like KeePass (KDBX 4) file format for TypeScript
403 lines (402 loc) • 14.8 kB
JavaScript
// 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 = {}));