feat: parse known KDBX header fields
This commit is contained in:
+3
-2
@@ -2,5 +2,6 @@
|
||||
|
||||
- Reframed the project as a fresh TypeScript-native KeePass library.
|
||||
- Python/pykeepass is now only a compatibility reference during development.
|
||||
- Added initial KDBX format scaffolding and crypto helpers.
|
||||
- The current runtime is still mostly in-memory and does not yet decrypt real databases.
|
||||
- Added KDBX header parsing for known fields and basic buffer guards.
|
||||
- Added initial password/key-file secret handling and master-key derivation helpers.
|
||||
- The current runtime still does not decrypt or parse real KeePass databases end-to-end.
|
||||
|
||||
@@ -4,6 +4,11 @@ import type { KdbxHeader } from "./types";
|
||||
const KDBX_SIGNATURE_1 = 0x9aa2d903;
|
||||
const KDBX_SIGNATURE_2 = 0xb54bfb67;
|
||||
|
||||
function readLengthPrefixedBytes(reader: BinaryReader): Uint8Array {
|
||||
const length = reader.readUint32LE();
|
||||
return reader.readBytes(length);
|
||||
}
|
||||
|
||||
export function parseKdbxHeader(buffer: Uint8Array): { header: KdbxHeader; offset: number } {
|
||||
const reader = new BinaryReader(buffer);
|
||||
const signature1 = reader.readUint32LE();
|
||||
@@ -18,6 +23,45 @@ export function parseKdbxHeader(buffer: Uint8Array): { header: KdbxHeader; offse
|
||||
const version = { major: versionMajor, minor: versionMinor };
|
||||
const header: KdbxHeader = { version };
|
||||
|
||||
while (reader.remaining() > 0) {
|
||||
if (reader.remaining() < 1) break;
|
||||
const fieldId = reader.readUint8();
|
||||
if (fieldId === 0) break;
|
||||
if (reader.remaining() < 4) break;
|
||||
const fieldLength = reader.readUint32LE();
|
||||
if (reader.remaining() < fieldLength) break;
|
||||
const fieldData = reader.readBytes(fieldLength);
|
||||
|
||||
switch (fieldId) {
|
||||
case 2:
|
||||
header.compressionFlags = new DataView(fieldData.buffer, fieldData.byteOffset, fieldData.byteLength).getUint32(0, true);
|
||||
break;
|
||||
case 3:
|
||||
header.masterSeed = fieldData;
|
||||
break;
|
||||
case 4:
|
||||
header.encryptionIV = fieldData;
|
||||
break;
|
||||
case 5:
|
||||
header.protectedStreamKey = fieldData;
|
||||
break;
|
||||
case 6:
|
||||
header.streamStartBytes = fieldData;
|
||||
break;
|
||||
case 7:
|
||||
header.innerRandomStreamId = new DataView(fieldData.buffer, fieldData.byteOffset, fieldData.byteLength).getUint32(0, true);
|
||||
break;
|
||||
case 11:
|
||||
header.cipherUuid = fieldData;
|
||||
break;
|
||||
case 12:
|
||||
header.kdfParameters = { raw: Array.from(fieldData).join(",") };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
header,
|
||||
offset: buffer.length - reader.remaining(),
|
||||
|
||||
@@ -26,6 +26,6 @@ describe("parseKdbxFile", () => {
|
||||
test("extracts header and payload", () => {
|
||||
const file = parseKdbxFile(createBuffer());
|
||||
expect(file.header.version).toEqual({ major: 4, minor: 1 });
|
||||
expect(Array.from(file.payload)).toEqual([0xaa, 0xbb, 0xcc, 0xdd]);
|
||||
expect(Array.from(file.payload)).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user