UNPKG

@hazae41/kdbx

Version:

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

598 lines (597 loc) 24.3 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; }); export * from "./cipher/mod.js"; export * from "./compression/mod.js"; import { Bytes } from "../../../../libs/bytes/mod.js"; import { BytesAsUuid, StringAsUuid } from "../../../../libs/uuid/mod.js"; import { Dictionary, Entries, Value } from "../../dictionary/mod.js"; import { PreHmacKey } from "../../hmac/mod.js"; import { DerivedKey, MasterKeys, PreHmacMasterKey, PreMasterKey } from "../../mod.js"; import { Vector } from "../../vector/mod.js"; import { argon2 } from "@hazae41/argon2"; import { Readable, Unknown, Writable } from "@hazae41/binary"; import { Cipher } from "./cipher/mod.js"; import { Compression } from "./compression/mod.js"; export class Version { major; minor; constructor(major, minor) { this.major = major; this.minor = minor; } sizeOrThrow() { return 2 + 2; } writeOrThrow(cursor) { cursor.writeUint16OrThrow(this.minor, true); cursor.writeUint16OrThrow(this.major, true); } cloneOrThrow() { return this; } } export class MagicAndVersionAndHeadersWithBytesWithHashAndHmacWithKeys { data; keys; constructor(data, keys) { this.data = data; this.keys = keys; } sizeOrThrow() { return this.data.sizeOrThrow(); } writeOrThrow(cursor) { this.data.writeOrThrow(cursor); } async rotateOrThrow(composite) { const data = this.data.data.rotateOrThrow(); const keys = await data.deriveOrThrow(composite); const hash = await MagicAndVersionAndHeadersWithBytesWithHashAndHmac.computeOrThrow(data, keys); return new MagicAndVersionAndHeadersWithBytesWithHashAndHmacWithKeys(hash, keys); } } export class MagicAndVersionAndHeadersWithBytesWithHashAndHmac { data; hash; hmac; constructor(data, hash, hmac) { this.data = data; this.hash = hash; this.hmac = hmac; } static async computeOrThrow(data, keys) { const index = 0xffffffffffffffffn; const major = keys.authifier.bytes; const key = await new PreHmacKey(index, major).digestOrThrow(); const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", data.bytes.bytes)); const hmac = new Uint8Array(await key.signOrThrow(data.bytes.bytes)); return new MagicAndVersionAndHeadersWithBytesWithHashAndHmac(data, new Unknown(hash), new Unknown(hmac)); } async verifyOrThrow(keys) { const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", this.data.bytes.bytes)); if (!Bytes.equals(hash, this.hash.bytes)) throw new Error(); const index = 0xffffffffffffffffn; const major = keys.authifier.bytes; const key = await new PreHmacKey(index, major).digestOrThrow(); await key.verifyOrThrow(this.data.bytes.bytes, this.hmac.bytes); } sizeOrThrow() { return this.data.sizeOrThrow() + 32 + 32; } writeOrThrow(cursor) { this.data.writeOrThrow(cursor); cursor.writeOrThrow(this.hash.bytes); cursor.writeOrThrow(this.hmac.bytes); } cloneOrThrow() { const data = this.data.cloneOrThrow(); const hash = this.hash.cloneOrThrow(); const hmac = this.hmac.cloneOrThrow(); return new MagicAndVersionAndHeadersWithBytesWithHashAndHmac(data, hash, hmac); } async deriveOrThrow(composite) { return await this.data.deriveOrThrow(composite); } } (function (MagicAndVersionAndHeadersWithBytesWithHashAndHmac) { function readOrThrow(cursor) { const data = MagicAndVersionAndHeadersWithBytes.readOrThrow(cursor); const hash = new Unknown(cursor.readOrThrow(32)); const hmac = new Unknown(cursor.readOrThrow(32)); return new MagicAndVersionAndHeadersWithBytesWithHashAndHmac(data, hash, hmac); } MagicAndVersionAndHeadersWithBytesWithHashAndHmac.readOrThrow = readOrThrow; })(MagicAndVersionAndHeadersWithBytesWithHashAndHmac || (MagicAndVersionAndHeadersWithBytesWithHashAndHmac = {})); export class MagicAndVersionAndHeadersWithBytes { value; bytes; constructor(value, bytes) { this.value = value; this.bytes = bytes; } static computeOrThrow(value) { const bytes = new Unknown(Writable.writeToBytesOrThrow(value)); return new MagicAndVersionAndHeadersWithBytes(value, bytes); } rotateOrThrow() { return MagicAndVersionAndHeadersWithBytes.computeOrThrow(this.value.rotateOrThrow()); } sizeOrThrow() { return this.bytes.bytes.length; } writeOrThrow(cursor) { cursor.writeOrThrow(this.bytes.bytes); } cloneOrThrow() { const value = this.value.cloneOrThrow(); const bytes = this.bytes.cloneOrThrow(); return new MagicAndVersionAndHeadersWithBytes(value, bytes); } async deriveOrThrow(composite) { return await this.value.deriveOrThrow(composite); } } (function (MagicAndVersionAndHeadersWithBytes) { function readOrThrow(cursor) { const start = cursor.offset; const value = MagicAndVersionAndHeaders.readOrThrow(cursor); const bytes = new Unknown(cursor.bytes.subarray(start, cursor.offset)); return new MagicAndVersionAndHeadersWithBytes(value, bytes); } MagicAndVersionAndHeadersWithBytes.readOrThrow = readOrThrow; })(MagicAndVersionAndHeadersWithBytes || (MagicAndVersionAndHeadersWithBytes = {})); export class MagicAndVersionAndHeaders { version; headers; constructor(version, headers) { this.version = version; this.headers = headers; } rotateOrThrow() { const { version } = this; const headers = this.headers.rotateOrThrow(); return new MagicAndVersionAndHeaders(version, headers); } sizeOrThrow() { return 4 + 4 + this.version.sizeOrThrow() + this.headers.sizeOrThrow(); } writeOrThrow(cursor) { cursor.writeUint32OrThrow(0x9AA2D903, true); cursor.writeUint32OrThrow(0xB54BFB67, true); this.version.writeOrThrow(cursor); this.headers.writeOrThrow(cursor); } cloneOrThrow() { const version = this.version.cloneOrThrow(); const headers = this.headers.cloneOrThrow(); return new MagicAndVersionAndHeaders(version, headers); } async deriveOrThrow(composite) { return await this.headers.deriveOrThrow(composite); } } (function (MagicAndVersionAndHeaders) { function readOrThrow(cursor) { const alpha = cursor.readUint32OrThrow(true); if (alpha !== 0x9AA2D903) throw new Error(); const beta = cursor.readUint32OrThrow(true); if (beta !== 0xB54BFB67) throw new Error(); const minor = cursor.readUint16OrThrow(true); const major = cursor.readUint16OrThrow(true); const version = new Version(major, minor); if (major !== 4) throw new Error(); const headers = Headers.readOrThrow(cursor); return new MagicAndVersionAndHeaders(version, headers); } MagicAndVersionAndHeaders.readOrThrow = readOrThrow; })(MagicAndVersionAndHeaders || (MagicAndVersionAndHeaders = {})); export class Headers { value; constructor(value) { this.value = value; } get cipher() { return this.value.value[2][0]; } get compression() { return this.value.value[3][0]; } get seed() { return this.value.value[4][0]; } get iv() { return this.value.value[7][0]; } get kdf() { return this.value.value[11][0]; } get custom() { return this.value.value[12]?.[0]; } rotateOrThrow() { const { cipher, compression, custom } = this; const seed = new Unknown(crypto.getRandomValues(new Uint8Array(32))); const iv = new Unknown(crypto.getRandomValues(new Uint8Array(cipher.IV.length))); const kdf = this.kdf.rotateOrThrow(); return Headers.initOrThrow({ cipher, compression, seed, iv, kdf, custom }); } sizeOrThrow() { return this.value.sizeOrThrow(); } writeOrThrow(cursor) { this.value.writeOrThrow(cursor); } cloneOrThrow() { return Readable.readFromBytesOrThrow(Headers, Writable.writeToBytesOrThrow(this)); } async deriveOrThrow(composite) { const { seed } = this; const derived = this.kdf.deriveOrThrow(composite); const encrypter = await new PreMasterKey(seed, derived).digestOrThrow(); const authifier = await new PreHmacMasterKey(seed, derived).digestOrThrow(); return new MasterKeys(encrypter, authifier); } } (function (Headers) { function initOrThrow(init) { const { cipher, compression, seed, iv, kdf, custom } = init; if (iv.bytes.length !== cipher.IV.length) throw new Error(); const indexed = { 2: [cipher], 3: [compression], 4: [new Unknown(seed.bytes)], 7: [new Unknown(iv.bytes)], 11: [kdf], 12: custom != null ? [custom] : undefined }; return new Headers(Vector.initOrThrow(indexed)); } Headers.initOrThrow = initOrThrow; function readOrThrow(cursor) { const vector = Vector.readOrThrow(cursor); if (vector.value[2].length !== 1) throw new Error(); if (vector.value[3].length !== 1) throw new Error(); if (vector.value[4].length !== 1) throw new Error(); if (vector.value[4][0].bytes.length !== 32) throw new Error(); if (vector.value[7].length !== 1) throw new Error(); if (vector.value[11].length !== 1) throw new Error(); if (vector.value[12] != null && vector.value[12].length !== 1) throw new Error(); const indexed = { 2: [vector.value[2][0].readIntoOrThrow(Cipher)], 3: [vector.value[3][0].readIntoOrThrow(Compression)], 4: [vector.value[4][0]], 7: [vector.value[7][0]], 11: [vector.value[11][0].readIntoOrThrow(KdfParameters)], 12: vector.value[12] != null ? [vector.value[12][0].readIntoOrThrow(Dictionary)] : undefined }; return new Headers(new Vector(vector.bytes, indexed)); } Headers.readOrThrow = readOrThrow; })(Headers || (Headers = {})); export class Seed { bytes; constructor(bytes) { this.bytes = bytes; } static readOrThrow(cursor) { return new Seed(new Unknown(cursor.readOrThrow(32))); } } export var KdfParameters; (function (KdfParameters) { class AesKdf { value; constructor(value) { this.value = value; } get seed() { return this.value.entries.value["S"].value; } get rounds() { return this.value.entries.value["R"].value; } rotateOrThrow() { const { version } = this.value; const $UUID = this.value.entries.value["$UUID"]; const R = this.value.entries.value["R"]; const S = new Value.Bytes(new Unknown(crypto.getRandomValues(new Uint8Array(32)))); const value = Dictionary.initOrThrow(version, { $UUID, R, S }); return new AesKdf(value); } deriveOrThrow(key) { throw new Error(); } sizeOrThrow() { return this.value.sizeOrThrow(); } writeOrThrow(cursor) { this.value.writeOrThrow(cursor); } cloneOrThrow() { return new AesKdf(this.value.cloneOrThrow()); } } KdfParameters.AesKdf = AesKdf; (function (AesKdf) { AesKdf.$UUID = "c9d9f39a-628a-4460-bf74-0d08c18a4fea"; function parseOrThrow(dictionary) { const { version, entries } = dictionary; if (entries.value["$UUID"] instanceof Value.Bytes === false) throw new Error(); const $UUID = entries.value["$UUID"]; if (entries.value.R instanceof Value.UInt32 === false) throw new Error(); const R = entries.value.R; if (entries.value.S instanceof Value.Bytes === false) throw new Error(); const S = entries.value.S; return new KdfParameters.AesKdf(new Dictionary(version, new Entries(entries.bytes, { $UUID, R, S }))); } AesKdf.parseOrThrow = parseOrThrow; })(AesKdf = KdfParameters.AesKdf || (KdfParameters.AesKdf = {})); class Argon2d { value; constructor(value) { this.value = value; } get salt() { return this.value.entries.value["S"].value; } get parallelism() { return this.value.entries.value["P"].value; } get memory() { return this.value.entries.value["M"].value; } get iterations() { return this.value.entries.value["I"].value; } get version() { return this.value.entries.value["V"].value; } rotateOrThrow() { const { version } = this.value; const $UUID = this.value.entries.value["$UUID"]; const S = new Value.Bytes(new Unknown(crypto.getRandomValues(new Uint8Array(32)))); const P = this.value.entries.value.P; const M = this.value.entries.value.M; const I = this.value.entries.value.I; const V = this.value.entries.value.V; const value = Dictionary.initOrThrow(version, { $UUID, S, P, M, I, V }); return new Argon2d(value); } deriveOrThrow(key) { const env_1 = { stack: [], error: void 0, hasError: false }; try { const { version, iterations, parallelism, memory, salt } = this; const { Memory, Argon2Deriver } = argon2.get().getOrThrow(); const mkey = __addDisposableResource(env_1, Memory.fromOrThrow(key.value.bytes), false); const msalt = __addDisposableResource(env_1, Memory.fromOrThrow(salt.bytes), false); const deriver = __addDisposableResource(env_1, Argon2Deriver.createOrThrow("argon2d", version, Number(memory) / 1024, Number(iterations), parallelism), false); const derived = __addDisposableResource(env_1, deriver.deriveOrThrow(mkey, msalt), false); return new DerivedKey(new Unknown(new Uint8Array(derived.bytes))); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { __disposeResources(env_1); } } sizeOrThrow() { return this.value.sizeOrThrow(); } writeOrThrow(cursor) { this.value.writeOrThrow(cursor); } cloneOrThrow() { return new Argon2d(this.value.cloneOrThrow()); } } KdfParameters.Argon2d = Argon2d; (function (Argon2d) { Argon2d.$UUID = "ef636ddf-8c29-444b-91f7-a9a403e30a0c"; function createOrThrow() { const version = new Dictionary.Version(0, 1); const $UUID = new Value.Bytes(new Unknown(BytesAsUuid.from(Argon2d.$UUID))); const S = new Value.Bytes(new Unknown(crypto.getRandomValues(new Uint8Array(32)))); const P = new Value.UInt32(2); const M = new Value.UInt64(16777216n); const I = new Value.UInt64(12n); const V = new Value.UInt32(0x13); const value = Dictionary.initOrThrow(version, { $UUID, S, P, M, I, V }); return new Argon2d(value); } Argon2d.createOrThrow = createOrThrow; function parseOrThrow(dictionary) { const { version, entries } = dictionary; if (dictionary.entries.value["$UUID"] instanceof Value.Bytes === false) throw new Error(); const $UUID = dictionary.entries.value["$UUID"]; if (dictionary.entries.value.S instanceof Value.Bytes === false) throw new Error(); const S = dictionary.entries.value.S; if (dictionary.entries.value.P instanceof Value.UInt32 === false) throw new Error(); const P = dictionary.entries.value.P; if (dictionary.entries.value.M instanceof Value.UInt64 === false) throw new Error(); const M = dictionary.entries.value.M; if (dictionary.entries.value.I instanceof Value.UInt64 === false) throw new Error(); const I = dictionary.entries.value.I; if (dictionary.entries.value.V instanceof Value.UInt32 === false) throw new Error(); const V = dictionary.entries.value.V; return new KdfParameters.Argon2d(new Dictionary(version, new Entries(entries.bytes, { $UUID, S, P, M, I, V }))); } Argon2d.parseOrThrow = parseOrThrow; })(Argon2d = KdfParameters.Argon2d || (KdfParameters.Argon2d = {})); class Argon2id { value; constructor(value) { this.value = value; } get salt() { return this.value.entries.value["S"].value; } get parallelism() { return this.value.entries.value["P"].value; } get memory() { return this.value.entries.value["M"].value; } get iterations() { return this.value.entries.value["I"].value; } get version() { return this.value.entries.value["V"].value; } rotateOrThrow() { const { version } = this.value; const $UUID = this.value.entries.value["$UUID"]; const S = new Value.Bytes(new Unknown(crypto.getRandomValues(new Uint8Array(32)))); const P = this.value.entries.value.P; const M = this.value.entries.value.M; const I = this.value.entries.value.I; const V = this.value.entries.value.V; const value = Dictionary.initOrThrow(version, { $UUID, S, P, M, I, V }); return new Argon2d(value); } deriveOrThrow(key) { const env_2 = { stack: [], error: void 0, hasError: false }; try { const { version, iterations, parallelism, memory, salt } = this; const { Memory, Argon2Deriver } = argon2.get().getOrThrow(); const mkey = __addDisposableResource(env_2, Memory.fromOrThrow(key.value.bytes), false); const msalt = __addDisposableResource(env_2, Memory.fromOrThrow(salt.bytes), false); const deriver = __addDisposableResource(env_2, Argon2Deriver.createOrThrow("argon2id", version, Number(memory) / 1024, Number(iterations), parallelism), false); const derived = __addDisposableResource(env_2, deriver.deriveOrThrow(mkey, msalt), false); return new DerivedKey(new Unknown(new Uint8Array(derived.bytes))); } catch (e_2) { env_2.error = e_2; env_2.hasError = true; } finally { __disposeResources(env_2); } } sizeOrThrow() { return this.value.sizeOrThrow(); } writeOrThrow(cursor) { this.value.writeOrThrow(cursor); } cloneOrThrow() { return new Argon2id(this.value.cloneOrThrow()); } } KdfParameters.Argon2id = Argon2id; (function (Argon2id) { Argon2id.$UUID = "9e298b19-56db-4773-b23d-fc3ec6f0a1e6"; function parseOrThrow(dictionary) { const { version, entries } = dictionary; if (entries.value["$UUID"] instanceof Value.Bytes === false) throw new Error(); const $UUID = entries.value["$UUID"]; if (entries.value.S instanceof Value.Bytes === false) throw new Error(); const S = entries.value.S; if (entries.value.P instanceof Value.UInt32 === false) throw new Error(); const P = entries.value.P; if (entries.value.M instanceof Value.UInt64 === false) throw new Error(); const M = entries.value.M; if (entries.value.I instanceof Value.UInt64 === false) throw new Error(); const I = entries.value.I; if (entries.value.V instanceof Value.UInt32 === false) throw new Error(); const V = entries.value.V; return new KdfParameters.Argon2id(new Dictionary(version, new Entries(entries.bytes, { $UUID, S, P, M, I, V }))); } Argon2id.parseOrThrow = parseOrThrow; })(Argon2id = KdfParameters.Argon2id || (KdfParameters.Argon2id = {})); function readOrThrow(cursor) { const dictionary = Dictionary.readOrThrow(cursor); if (dictionary.entries.value["$UUID"] instanceof Value.Bytes === false) throw new Error(); const $UUID = StringAsUuid.from(dictionary.entries.value["$UUID"].value.bytes); if (![KdfParameters.AesKdf.$UUID, KdfParameters.Argon2d.$UUID, KdfParameters.Argon2id.$UUID].includes($UUID)) throw new Error(); if ($UUID === KdfParameters.AesKdf.$UUID) return KdfParameters.AesKdf.parseOrThrow(dictionary); if ($UUID === KdfParameters.Argon2d.$UUID) return KdfParameters.Argon2d.parseOrThrow(dictionary); if ($UUID === KdfParameters.Argon2id.$UUID) return KdfParameters.Argon2id.parseOrThrow(dictionary); throw new Error(); } KdfParameters.readOrThrow = readOrThrow; })(KdfParameters || (KdfParameters = {}));