Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 13 additions & 29 deletions packages/argon2/src/argon2.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
/**
* Argon2d and Argon2id, implemented per
* [RFC 9106](https://www.rfc-editor.org/rfc/rfc9106). See SPEC.md.
*
* This module holds the algorithm core; the public, validated entry points
* live in `index.ts`. The 64-bit arithmetic uses `bigint` for correctness.
*/
/** Argon2d/Argon2id per RFC 9106 (see SPEC.md). Algorithm core only —
validated public entry points live in index.ts; 64-bit math uses bigint. */

import { blake2b } from './blake2b.ts';

const A2_M32 = 0xffffffffn;
const A2_M64 = (1n << 64n) - 1n;

/** Argon2 type code: 0 = Argon2d, 2 = Argon2id (RFC 9106, Section 3.1). */
// Argon2 type code: 0 = Argon2d, 2 = Argon2id (RFC 9106, Section 3.1).
export const ARGON2_D = 0;
export const ARGON2_ID = 2;

/** Argon2 version numbers (RFC 9106). 0x13 (19) is current. */
// Argon2 version numbers (RFC 9106). 0x13 (19) is current.
export const ARGON2_VERSION_10 = 0x10;
export const ARGON2_VERSION_13 = 0x13;

Expand All @@ -29,12 +24,12 @@ export interface CoreParams {
secret: Uint8Array;
associatedData: Uint8Array;
parallelism: number;
/** Memory size in KiB. */
// Memory size in KiB.
memory: number;
iterations: number;
tagLength: number;
version: number;
/** 0 for Argon2d, 2 for Argon2id. */
// 0 for Argon2d, 2 for Argon2id.
type: number;
}

Expand Down Expand Up @@ -86,9 +81,7 @@ function a2_blockToBytes(block: BigUint64Array): Uint8Array {
return out;
}

/**
* Variable-length hash function H' (RFC 9106, Section 3.3), built on BLAKE2b.
*/
// Variable-length hash H' (RFC 9106 §3.3), built on BLAKE2b.
function a2_hPrime(outLength: number, input: Uint8Array): Uint8Array {
if (outLength <= 64) {
return blake2b(outLength, a2_concatBytes(a2_le32(outLength), input));
Expand All @@ -107,9 +100,7 @@ function a2_hPrime(outLength: number, input: Uint8Array): Uint8Array {
return out;
}

// Index patterns for the BLAKE2b permutation P over a 1024-byte block, viewed
// as an 8x8 matrix of 16-byte registers (RFC 9106, Section 3.5). Rows are 16
// consecutive words; columns stride through the block.
// P's index patterns over the 1024-byte block as an 8x8 register matrix (RFC 9106 §3.5).
const A2_ROWS: number[][] = [];
const A2_COLS: number[][] = [];
for (let r = 0; r < 8; r++) {
Expand All @@ -126,7 +117,7 @@ for (let r = 0; r < 8; r++) {
A2_COLS.push(col);
}

/** GB, the modified BLAKE2b mixing function with multiplications (Section 3.6). */
// GB, the modified BLAKE2b mixing function with multiplications (Section 3.6).
function a2_gb(v: BigUint64Array, a: number, b: number, c: number, d: number): void {
let va = v[a] as bigint;
let vb = v[b] as bigint;
Expand All @@ -146,7 +137,7 @@ function a2_gb(v: BigUint64Array, a: number, b: number, c: number, d: number): v
v[d] = vd;
}

/** Permutation P applied to the 16 words named by `q` (RFC 9106, Section 3.6). */
// Permutation P applied to the 16 words named by `q` (RFC 9106, Section 3.6).
function a2_permute(v: BigUint64Array, q: number[]): void {
const i = (k: number): number => q[k] as number;
a2_gb(v, i(0), i(4), i(8), i(12));
Expand All @@ -159,11 +150,7 @@ function a2_permute(v: BigUint64Array, q: number[]): void {
a2_gb(v, i(3), i(4), i(9), i(14));
}

/**
* Compression function G (RFC 9106, Section 3.5):
* `next = (with_xor ? next : 0) XOR R XOR P_columns(P_rows(R))`,
* where `R = ref XOR prev`.
*/
// G (RFC 9106 §3.5): next = (with_xor?next:0) XOR R XOR P_columns(P_rows(R)), where R=ref XOR prev.
function a2_fillBlock(
prev: BigUint64Array,
ref: BigUint64Array,
Expand Down Expand Up @@ -193,10 +180,7 @@ function a2_fillBlock(
}
}

/**
* Map a pseudo-random value to a reference block index within a lane
* (RFC 9106, Section 3.4.2; reference implementation `index_alpha`).
*/
// Map a pseudo-random value to a reference block index in a lane (RFC 9106 §3.4.2, index_alpha).
function a2_indexAlpha(
pass: number,
slice: number,
Expand Down Expand Up @@ -232,7 +216,7 @@ function a2_indexAlpha(
return Number((BigInt(startPosition) + relative) % BigInt(laneLength));
}

/** Run the full Argon2 operation and return the tag. */
// Run the full Argon2 operation and return the tag.
export function argon2Core(params: CoreParams): Uint8Array {
const { password, salt, secret, associatedData } = params;
const lanes = params.parallelism;
Expand Down
27 changes: 9 additions & 18 deletions packages/argon2/src/blake2b.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
/**
* BLAKE2b ([RFC 7693](https://www.rfc-editor.org/rfc/rfc7693)), the underlying
* hash function `H` used by Argon2 (RFC 9106, Section 3.2).
*
* This is an unkeyed implementation with a variable output length of 1..64
* bytes, which is all Argon2 requires. The 64-bit arithmetic is implemented
* with `bigint` for correctness; see SPEC.md for the optimization trade-off.
*/
/** BLAKE2b (RFC 7693), Argon2's H (RFC 9106 §3.2) — unkeyed, 1..64-byte
output. 64-bit math uses bigint; see SPEC.md for the trade-off. */

const B2_MASK64 = (1n << 64n) - 1n;

/** BLAKE2b initialization vector (RFC 7693, Section 2.6). */
// BLAKE2b initialization vector (RFC 7693, Section 2.6).
const B2_IV: readonly bigint[] = [
0x6a09e667f3bcc908n,
0xbb67ae8584caa73bn,
Expand All @@ -21,7 +15,7 @@ const B2_IV: readonly bigint[] = [
0x5be0cd19137e2179n,
];

/** Message word schedule per round (RFC 7693, Section 2.7). */
// Message word schedule per round (RFC 7693, Section 2.7).
const B2_SIGMA: readonly (readonly number[])[] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
Expand All @@ -41,7 +35,7 @@ function b2_rotr64(x: bigint, n: bigint): bigint {
return ((x >> n) | (x << (64n - n))) & B2_MASK64;
}

/** The BLAKE2b mixing function G (RFC 7693, Section 3.1). */
// The BLAKE2b mixing function G (RFC 7693, Section 3.1).
function b2_mix(
v: bigint[],
a: number,
Expand Down Expand Up @@ -69,7 +63,7 @@ function b2_mix(
v[d] = vd;
}

/** The BLAKE2b compression function F (RFC 7693, Section 3.2). */
// The BLAKE2b compression function F (RFC 7693, Section 3.2).
function b2_compress(h: bigint[], m: bigint[], counter: bigint, last: boolean): void {
const v = new Array<bigint>(16);
for (let i = 0; i < 8; i++) {
Expand Down Expand Up @@ -98,12 +92,9 @@ function b2_compress(h: bigint[], m: bigint[], counter: bigint, last: boolean):
}
}

/**
* Compute the unkeyed BLAKE2b digest of `input`.
*
* @param outLength desired digest length in bytes, 1..64.
* @param input message to hash.
*/
/** Unkeyed BLAKE2b digest of `input`.
@param outLength digest length in bytes, 1..64.
@param input message to hash. */
export function blake2b(outLength: number, input: Uint8Array): Uint8Array {
if (!Number.isInteger(outLength) || outLength < 1 || outLength > 64) {
throw new RangeError(`BLAKE2b output length must be an integer in 1..64, got ${outLength}`);
Expand Down
35 changes: 15 additions & 20 deletions packages/argon2/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
/**
* `argon2` — Argon2d and Argon2id key derivation per
* [RFC 9106](https://www.rfc-editor.org/rfc/rfc9106).
*
* Argon2i is intentionally not implemented (see README.md); RFC 9106 requires
* only Argon2id, and KDBX 4.x uses Argon2d or Argon2id.
*/
/** `argon2` — Argon2d/Argon2id per RFC 9106. Argon2i isn't implemented (see
README.md): RFC 9106 only requires Argon2id, and that's all KDBX 4.x uses. */

import { ARGON2_D, ARGON2_ID, ARGON2_VERSION_13, argon2Core } from './argon2.ts';

const A2_MAX_U24 = 0xffffff;
const A2_MAX_U32 = 0xffffffff;
const A2_EMPTY = new Uint8Array(0);

/** Argon2 variant. */
// Argon2 variant.
export type Argon2Type = 'argon2d' | 'argon2id';

/** Options for {@link argon2}. Parameter names and bounds follow RFC 9106. */
// Options for {@link argon2}. Parameter names and bounds follow RFC 9106.
export interface Argon2Options {
/** Message P. For KDBX this is the composite key. */
// Message P. For KDBX this is the composite key.
password: Uint8Array;
/** Nonce S (salt). */
// Nonce S (salt).
salt: Uint8Array;
/** Degree of parallelism p (lanes), an integer in 1..2^24-1. */
// Degree of parallelism p (lanes), an integer in 1..2^24-1.
parallelism: number;
/** Memory size m in KiB, an integer in 8*parallelism..2^32-1. */
// Memory size m in KiB, an integer in 8*parallelism..2^32-1.
memory: number;
/** Number of passes t, an integer in 1..2^32-1. */
// Number of passes t, an integer in 1..2^32-1.
iterations: number;
/** Desired tag length T in bytes, an integer in 4..2^32-1. */
// Desired tag length T in bytes, an integer in 4..2^32-1.
tagLength: number;
/** Variant to use. */
// Variant to use.
type: Argon2Type;
/** Optional secret value K. */
// Optional secret value K.
secret?: Uint8Array;
/** Optional associated data X. */
// Optional associated data X.
associatedData?: Uint8Array;
/** Version number; defaults to 0x13 (the current version). */
// Version number; defaults to 0x13 (the current version).
version?: number;
}

/** Options for {@link argon2d} / {@link argon2id} (no `type` field). */
// Options for {@link argon2d} / {@link argon2id} (no `type` field).
export type Argon2VariantOptions = Omit<Argon2Options, 'type'>;

function a2_requireInteger(name: string, value: number, min: number, max: number): void {
Expand Down
29 changes: 8 additions & 21 deletions packages/chacha20/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
/**
* `chacha20` — ChaCha20 (RFC 8439) and Salsa20 (D. J. Bernstein) stream
* ciphers.
*
* Scope is intentionally limited to the raw stream ciphers. Poly1305 and the
* ChaCha20-Poly1305 AEAD construction from RFC 8439 are NOT implemented: KDBX
* authenticates with HMAC-SHA256, so the AEAD is not needed by keepass-web.
*
* Both ciphers expose three layers:
* - a pure 64-byte block function (`chacha20Block` / `salsa20Block`);
* - a one-shot helper (`chacha20` / `salsa20`) for whole buffers;
* - a stateful class (`ChaCha20` / `Salsa20`) whose `encrypt`/`decrypt`
* consume a single continuous keystream across successive calls. The
* stateful form is what KDBX inner-stream (protected-field) processing
* needs, since protected values are XORed against one running keystream in
* document order.
*/
/** `chacha20` — ChaCha20 (RFC 8439) and Salsa20 stream ciphers. No
Poly1305/AEAD: KDBX authenticates with HMAC-SHA256 instead. Each exposes
a block function, a one-shot helper, and a stateful class sharing one
running keystream across calls — what inner-stream processing needs. */

/** A 32-bit unsigned word. */
// A 32-bit unsigned word.
type Word = number;

/** Result of a quarter round: four updated 32-bit words. */
// Result of a quarter round: four updated 32-bit words.
type QuarterRound = [Word, Word, Word, Word];

const CC_KEY_BYTES = 32;
const CC_BLOCK_BYTES = 64;
const CC_CHACHA_NONCE_BYTES = 12;
const CC_SALSA_NONCE_BYTES = 8;

/** "expand 32-byte k" as four little-endian 32-bit words. */
// "expand 32-byte k" as four little-endian 32-bit words.
const CC_SIGMA: readonly [Word, Word, Word, Word] = [
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
];

/** Left-rotate a 32-bit word by `n` bits. */
// Left-rotate a 32-bit word by `n` bits.
const cc_rotl = (x: Word, n: number): Word => ((x << n) | (x >>> (32 - n))) >>> 0;

function cc_assertLength(bytes: Uint8Array, expected: number, name: string): void {
Expand Down
27 changes: 5 additions & 22 deletions packages/embed-protocol/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
/**
* `embed-protocol` — the same-origin postMessage contract between a
* keepass-web implementation (currently only `0x67`) and whatever host page
* embeds it in an iframe (the local-file chooser, the Google Drive
* connector, and future sources).
*
* Message shapes and guards used to be hand-written twice: once inline in
* 0x67/page.ts (the app side) and once in each host's own logic.ts (the host
* side). Nothing but manual care kept those two hand-written copies in sync.
* Centralizing both the guards *and* the builders here means the two ends of
* the protocol are provably using the same wire format, not just similarly
* shaped code.
*
* Six message types, each read by exactly one side and built by the other:
* app → host : kw-ready (built by the app, read by the host)
* host → app : kw-open (built by the host, read by the app)
* app → host : kw-save (built by the app, read by the host)
* host → app : kw-saved (built by the host, read by the app)
* host → app : kw-close-request (built by the host, read by the app)
* app → host : kw-close-ack (built by the app, read by the host)
* app → host : kw-close (built by the app, read by the host)
*/
/** `embed-protocol` — the same-origin postMessage contract between a
keepass-web implementation and whatever host embeds it in an iframe.
Centralizes shapes/guards/builders (previously duplicated per side) so
both ends provably agree on the wire format: kw-ready, kw-open, kw-save,
kw-saved, kw-close-request, kw-close-ack, kw-close. */

export interface ReadyMessage {
type: 'kw-ready';
Expand Down
Loading