@aikdna/kdna-core 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/package.json +3 -2
- package/src/crypto-profile.js +124 -1
- package/src/v1/index.js +15 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.15.1 (2026-06-27)
|
|
4
|
+
- Fix: package.json `files` array now includes `CHANGELOG.md`. The
|
|
5
|
+
v0.15.0 tarball was published without the changelog, leaving
|
|
6
|
+
consumers with no way to read release notes from `npm install`.
|
|
7
|
+
Adding it now is a no-op for code but resolves the docs gap.
|
|
8
|
+
|
|
9
|
+
## v0.15.0 (2026-06-27)
|
|
10
|
+
- B2: scrypt password profile `kdna-password-protected-v1-scrypt` (ADR-007)
|
|
11
|
+
- encryptProtectedEntryScrypt / decryptProtectedEntryScrypt (zero new deps, Node crypto.scryptSync)
|
|
12
|
+
- AES-256-GCM + AES-256-KW envelope encryption, same CEK model as RFC-0008
|
|
13
|
+
- scrypt N=32768 r=8 p=1, 32B KEK, 16B random salt
|
|
14
|
+
- planLoad inferEntitlementProfile detects scrypt profile
|
|
15
|
+
- decrypt dispatch routes Argon2id vs scrypt by envelope profile field
|
|
16
|
+
- recovery slot deferred to v0.2; v0.1: single password slot
|
|
17
|
+
- B5: conformance:canonical npm script (threshold #8)
|
|
18
|
+
|
|
19
|
+
## v0.14.0 (2026-06-25)
|
|
20
|
+
- B1: unified container dispatcher (readAsset — v1/v2/ dir → CanonicalAssetModel)
|
|
21
|
+
- B4: loadAuthorized decryption orchestration (opts.password, opts.decryptEntry)
|
|
22
|
+
- B4: encrypted payload detection in runValidate (backward compatible)
|
|
23
|
+
- B8: cross-language golden vectors (test-vectors/golden-vectors.js — 8/8)
|
|
24
|
+
- Wave 4: canonical conformance (conformance/canonical-conformance.mjs — 13/13)
|
|
25
|
+
- Wave 2.5: clear deprecated registry/domains.json private entries
|
|
26
|
+
- Wave 3a: CQ-T2/T3 symmetric compact rendering, scenario null consistency
|
|
27
|
+
- Add: engines.node >=18.0.0
|
|
28
|
+
|
|
29
|
+
## v0.13.3 (2026-06-22)
|
|
30
|
+
- Fix: index profile includes max_tokens_hint
|
|
31
|
+
- Fix: compact profile falls back to full_statement for TBD placeholders
|
|
32
|
+
|
|
33
|
+
Packages: `@aikdna/kdna-core`
|
|
34
|
+
|
|
35
|
+
## v0.13.2 (2026-06-21)
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Compact boundary projection now correctly preserves `applies_when`, `does_not_apply_when`, and `failure_risk` fields across all load profiles. These axiom governance fields are required by `kdna-loader` for domain routing decisions and were being dropped in compact mode under certain manifest configurations.
|
|
39
|
+
- Cross-platform test stability: Node test glob patterns are now compatible with both macOS and Linux filesystem sort orders.
|
|
40
|
+
- Hash library compatibility: `@noble/hashes` replaces Node-only crypto for digest operations, maintaining Node 18 compatibility without native module binding requirements.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- Public API boundary hardened: `loadKDNA`, `loadKDNASync`, `inspectKDNA`, and `inspectKDNASync` now enforce the asset-first contract — callers must pass `.kdna` files or asset handles, not raw directory paths.
|
|
44
|
+
- Examples in README and inline docs reference the packaged `.kdna` path rather than source directories.
|
|
45
|
+
- First-run contract tightened: exported symbols, error codes, and validation gate names are now part of the stable API surface.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## v0.13.1 (2026-06-21)
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- **Compact boundary projection preserved.** The `planLoad` function and `loadAuthorized` shim now pass through the full axiom governance projection (applies_when, does_not_apply_when, failure_risk) even when the load contract is not present in the manifest. Prior versions could strip these fields under compact profile, breaking domain routing for `kdna-loader`.
|
|
53
|
+
- Digest matching now tolerates `sha256:` prefix in both `manifest_digest` and `payload_digest` checksums entries. Previously, a leading `sha256:` prefix was treated as part of the hex digest, causing false mismatches.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- Public API provenance and profile split clarified: `loadKDNA` / `loadKDNASync` are the stable entry points for runtime consumers. Lower-level `readV1Layout`, `runValidate`, and `planLoad` are exported for advanced tooling but carry no backward-compatibility guarantee.
|
|
57
|
+
- Access enum values normalized everywhere: `public`, `licensed`, `remote`. Legacy aliases (`open` → `public`, `protected` → `licensed`, `runtime` → `remote`) are mapped transparently with an info-level issue in the LoadPlan.
|
|
58
|
+
- `verifyAsset` / `verifyAssetSync`, `verifyDigest` / `verifyDigestSync`, and `verifySignature` / `verifySignatureSync` are now documented as the stable verification API surface.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## v0.13.0 (2026-06-19)
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- **Authorization LoadPlan contract.** The `planLoad(inputPath, opts)` function returns a structured LoadPlan before any judgment content is decrypted or emitted. The LoadPlan reports:
|
|
66
|
+
- `asset_id`, `asset_uid`, `title`, `version`, `judgment_version`
|
|
67
|
+
- `access` model (`public` | `licensed` | `remote`)
|
|
68
|
+
- `entitlement_profile` (`password`, `local_receipt`, `account`, `org`, `purchase_receipt`, `device_bound`)
|
|
69
|
+
- `state` (`ready` | `needs_password` | `needs_license` | `needs_account` | `needs_org_auth` | `expired_grace` | `offline_grace` | `denied` | `invalid` | `needs_runtime`)
|
|
70
|
+
- `required_action` (`load` | `enter_password` | `install_receipt` | `sign_in_or_activate` | `renew_entitlement` | `sync` | `contact_issuer` | `connect_runtime` | `block`)
|
|
71
|
+
- `can_load_now` boolean — the single decision point for product consumers
|
|
72
|
+
- `projection_policy` (`none` | `minimal` | `remote`)
|
|
73
|
+
- `checks` block with per-gate validation results
|
|
74
|
+
- `issues` array with structured error codes (`KDNA_FORMAT_INVALID`, `KDNA_INTEGRITY_DIGEST_FAILED`, `KDNA_AUTH_PASSWORD_REQUIRED`, `KDNA_AUTH_ENTITLEMENT_REQUIRED`, `KDNA_ACCESS_MODE_UNKNOWN`, etc.)
|
|
75
|
+
- `input_fingerprint` capturing the source digest, has-password signal, and entitlement status for cache keying
|
|
76
|
+
- `source.kind` and `source.path` indicating whether the input was a source directory or `.kdna` container
|
|
77
|
+
- **`loadAuthorized(inputPath, opts)`** — a higher-level loader that calls `planLoad` first and blocks load when `can_load_now` is false. Throws `KDNA_LOAD_NOT_AUTHORIZED` with the first issue's code. This is the recommended entry point for agent runtimes.
|
|
78
|
+
- **`loadV1(inputPath, opts)`** — renders judgment content into agent-ready prompt text. Supports profiles (`index`, `compact`, `scenario`, `full`) and output formats (`json`, `prompt`). The `prompt` format produces a flat text block suitable for agent context windows with proper axiom applicability rendering.
|
|
79
|
+
- **KDNA Core v1 inspect module.** `inspect(inputPath)` returns a content-neutral manifest summary including `asset_id`, `asset_uid`, `kdna_version`, `payload_encrypted`, `profile`, `load_contract_default_profile`, and signature count. Output is always JSON. Banned terms (`trusted`, `recommended`, `high_quality`, `officially_approved`) are enforced by an automatic assertion.
|
|
80
|
+
- **KDNA Core v1 validate module.** `validate(inputPath)` runs four independent gates:
|
|
81
|
+
1. **Format gate:** required entries present (`mimetype`, `kdna.json`, `payload.kdnab`), mimetype content correct, lineage is an object not an array
|
|
82
|
+
2. **Schema gate:** `kdna.json` against `manifest.schema.json` via AJV 2020-12
|
|
83
|
+
3. **Payload gate:** `payload.kdnab` against `payload-profile-v1.schema.json`
|
|
84
|
+
4. **Checksums gate:** when `checksums.json` is present, computed digests are compared against declared values for `kdna.json`, `payload.kdnab`, and the combined asset digest
|
|
85
|
+
- **KDNA Core v1 pack module.** `pack(sourceDir, outputPath)` produces a deterministic `.kdna` container: fixed DOS epoch timestamps, alphabetical entry order, mimetype first (STORED, method 0). Same source → byte-identical output.
|
|
86
|
+
- **KDNA Core v1 unpack module.** `unpack(inputPath, outputDir)` extracts a `.kdna` container to a directory with path-traversal protection.
|
|
87
|
+
- **Container security hardening.** ZIP reader enforces:
|
|
88
|
+
- Maximum container size (25 MiB), max entries (128), max entry size (5 MiB), max total uncompressed (12 MiB)
|
|
89
|
+
- Max compression ratio (100:1) to prevent zip bombs
|
|
90
|
+
- Max JSON depth (64), max array length (10,000), max string length (1 MiB)
|
|
91
|
+
- Entry name normalization (NFC Unicode, no backslash separators, no absolute paths, no `..` traversal)
|
|
92
|
+
- Duplicate entry rejection
|
|
93
|
+
- Symlink and device/special file rejection
|
|
94
|
+
- **Digest matching verification.** When `checksums.json` includes `manifest_digest`, `payload_digest`, or `asset_digest`, the validate module computes actual SHA-256 hashes and compares them. Mismatch produces a `KDNA_INTEGRITY_DIGEST_FAILED` issue.
|
|
95
|
+
- **Load contract validation.** When the manifest includes a `load_contract` block, it is validated against `load-contract.schema.json`. A missing load contract is not an error — it simply means no specialization.
|
|
96
|
+
- **Dual CJS/ESM exports.** `src/index.js` (CommonJS) and `src/index.mjs` (ES module) with matching `exports` map. The v1 module is also available at `@aikdna/kdna-core/v1`.
|
|
97
|
+
- **TypeScript declarations.** `src/types.d.ts` exports type definitions for the stable public API surface.
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
- **V1 is the only active format path.** The v2 reader (`readDataMapSync`, `readDataMap`) remains in the codebase for migration compatibility but is no longer the default path. All new integrations should use the v1 API: `inspect`, `validate`, `planLoad`, `loadAuthorized`.
|
|
101
|
+
- `validate` and `planLoad` reject v2 containers (`application/vnd.aikdna.kdna+zip`) with a format error.
|
|
102
|
+
- Access model normalized to the three-tier `public` | `licensed` | `remote` enum. The LoadPlan maps legacy values transparently with an info-level issue.
|
|
103
|
+
- Container format detection (`detectContainerFormat`) reads only the first central-directory entry to determine v1 vs v2, preventing malicious later entries from causing format confusion.
|
|
104
|
+
|
|
105
|
+
### Removed
|
|
106
|
+
- Legacy v1 compatibility shims removed. v2 is the only legacy format and is handled through the existing `asset-reader.js` module.
|
|
107
|
+
- `readDataMapSync` / `readDataMap` deprecated for new use. Use `planLoad` + `loadV1` for runtime loading or `validate` + `inspect` for diagnostics.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "KDNA Core library for validating, planning, and loading local .kdna judgment assets.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -25,13 +25,14 @@
|
|
|
25
25
|
"types": "src/types.d.ts",
|
|
26
26
|
"scripts": {
|
|
27
27
|
"test": "node ../../scripts/run-node-tests.js test/*.test.js && node -e \"const m = require('./src/index.js'); console.log('kdna-core exports:', Object.keys(m).join(', '));\"",
|
|
28
|
-
"prepublishOnly": "npm run release:check
|
|
28
|
+
"prepublishOnly": "npm run release:check",
|
|
29
29
|
"release:check": "node scripts/check-release-readiness.js"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"src/",
|
|
33
33
|
"schema/",
|
|
34
34
|
"README.md",
|
|
35
|
+
"CHANGELOG.md",
|
|
35
36
|
"LICENSE",
|
|
36
37
|
"NOTICE"
|
|
37
38
|
],
|
package/src/crypto-profile.js
CHANGED
|
@@ -420,6 +420,121 @@ function createRecoveryDecryptEntry(options = {}) {
|
|
|
420
420
|
decryptProtectedEntry(ciphertext, { entryName, manifest, recoveryCode });
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
// ── B2: scrypt-based password profile (v0.1 write profile) ─────────
|
|
424
|
+
// Uses Node built-in crypto.scryptSync — zero additional dependencies.
|
|
425
|
+
// v0.2 will promote the Argon2id profile (above) as the default write
|
|
426
|
+
// profile; this scrypt profile will remain a read-only legacy profile.
|
|
427
|
+
|
|
428
|
+
const PASSWORD_PROTECTED_SCRYPT_PROFILE = 'kdna-password-protected-v1-scrypt';
|
|
429
|
+
const SCRYPT_KDF = 'scrypt-sha256';
|
|
430
|
+
|
|
431
|
+
function derivePasswordKeyScrypt(password, params = {}) {
|
|
432
|
+
const { salt, N = 32768, r = 8, p = 1 } = params;
|
|
433
|
+
if (!salt) throw new Error('salt is required for scrypt KDF');
|
|
434
|
+
const saltBuf = decodeBase64(salt, 'salt');
|
|
435
|
+
const passwordBuf = toBuffer(password, 'password');
|
|
436
|
+
// N=32768, r=8, p=1 → 32 MiB memory. Default maxmem is 32 MiB exactly;
|
|
437
|
+
// bump to 64 MiB to avoid boundary rejection on some Node versions.
|
|
438
|
+
return crypto.scryptSync(passwordBuf, saltBuf, 32, { N, r, p, maxmem: 64 * 1024 * 1024 });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Encrypt a protected entry using the scrypt-based password profile.
|
|
443
|
+
*
|
|
444
|
+
* 1. Generate random CEK (32 bytes)
|
|
445
|
+
* 2. Derive KEK from password via scrypt-sha256
|
|
446
|
+
* 3. Encrypt content with CEK (AES-256-GCM)
|
|
447
|
+
* 4. Wrap CEK with KEK (AES-256-KW)
|
|
448
|
+
* 5. Store wrapped_key in envelope (single password slot, no recovery)
|
|
449
|
+
*/
|
|
450
|
+
function encryptProtectedEntryScrypt(plaintext, options = {}) {
|
|
451
|
+
const { entryName, manifest = {}, password } = options;
|
|
452
|
+
if (!entryName) throw new Error('entryName is required');
|
|
453
|
+
if (!password) throw new Error('password is required for scrypt encryption');
|
|
454
|
+
|
|
455
|
+
const cek = generateCEK();
|
|
456
|
+
|
|
457
|
+
// Password slot
|
|
458
|
+
const salt = crypto.randomBytes(16);
|
|
459
|
+
const passwordKey = derivePasswordKeyScrypt(password, { salt: salt.toString('base64') });
|
|
460
|
+
const passwordWrappedKey = wrapCEK(cek, passwordKey);
|
|
461
|
+
|
|
462
|
+
const iv = crypto.randomBytes(12);
|
|
463
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', cek, iv);
|
|
464
|
+
cipher.setAAD(encryptedEntryAad(entryName, manifest, PASSWORD_PROTECTED_SCRYPT_PROFILE));
|
|
465
|
+
const ciphertext = Buffer.concat([cipher.update(toBuffer(plaintext, 'plaintext')), cipher.final()]);
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
profile: PASSWORD_PROTECTED_SCRYPT_PROFILE,
|
|
469
|
+
alg: ALG,
|
|
470
|
+
kdf: SCRYPT_KDF,
|
|
471
|
+
key_wrapping: RFC_KEY_WRAPPING,
|
|
472
|
+
scrypt_params: {
|
|
473
|
+
N: 32768,
|
|
474
|
+
r: 8,
|
|
475
|
+
p: 1,
|
|
476
|
+
salt: salt.toString('base64'),
|
|
477
|
+
},
|
|
478
|
+
key_slots: [
|
|
479
|
+
{
|
|
480
|
+
slot: 'password',
|
|
481
|
+
wrap: RFC_KEY_WRAPPING,
|
|
482
|
+
wrapped_key: passwordWrappedKey.toString('base64'),
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
iv: iv.toString('base64'),
|
|
486
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
487
|
+
ciphertext: ciphertext.toString('base64'),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Decrypt a protected entry encoded with the scrypt-based password profile.
|
|
493
|
+
*
|
|
494
|
+
* 1. Derive KEK from password via scrypt-sha256
|
|
495
|
+
* 2. Unwrap CEK from password slot's wrapped_key (AES-256-KW)
|
|
496
|
+
* 3. Decrypt content with CEK (AES-256-GCM)
|
|
497
|
+
*/
|
|
498
|
+
function decryptProtectedEntryScrypt(envelopeValue, options = {}) {
|
|
499
|
+
const { entryName, manifest = {}, password } = options;
|
|
500
|
+
if (!entryName) throw new Error('entryName is required');
|
|
501
|
+
if (!password) throw new Error('password is required for scrypt decryption');
|
|
502
|
+
|
|
503
|
+
const envelope = normalizeEnvelope(envelopeValue);
|
|
504
|
+
if (envelope.profile !== PASSWORD_PROTECTED_SCRYPT_PROFILE) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`unsupported encrypted entry profile: ${envelope.profile || 'unknown'} (expected ${PASSWORD_PROTECTED_SCRYPT_PROFILE})`,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
if (envelope.alg !== ALG) throw new Error(`unsupported encrypted entry alg: ${envelope.alg}`);
|
|
510
|
+
if (envelope.kdf !== SCRYPT_KDF) throw new Error(`unsupported encrypted entry kdf: ${envelope.kdf}`);
|
|
511
|
+
if (envelope.key_wrapping !== RFC_KEY_WRAPPING) {
|
|
512
|
+
throw new Error(`unsupported encrypted entry key_wrapping: ${envelope.key_wrapping}`);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const passwordKey = derivePasswordKeyScrypt(password, {
|
|
516
|
+
salt: envelope.scrypt_params.salt,
|
|
517
|
+
});
|
|
518
|
+
const passwordSlot = envelope.key_slots.find((s) => s.slot === 'password');
|
|
519
|
+
if (!passwordSlot) throw new Error('password slot missing from envelope');
|
|
520
|
+
|
|
521
|
+
const cek = unwrapCEK(decodeBase64(passwordSlot.wrapped_key, 'wrapped_key'), passwordKey);
|
|
522
|
+
|
|
523
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', cek, decodeBase64(envelope.iv, 'iv'));
|
|
524
|
+
decipher.setAAD(encryptedEntryAad(entryName, manifest, PASSWORD_PROTECTED_SCRYPT_PROFILE));
|
|
525
|
+
decipher.setAuthTag(decodeBase64(envelope.tag, 'tag'));
|
|
526
|
+
return Buffer.concat([
|
|
527
|
+
decipher.update(decodeBase64(envelope.ciphertext, 'ciphertext')),
|
|
528
|
+
decipher.final(),
|
|
529
|
+
]);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function createPasswordDecryptEntryScrypt(options = {}) {
|
|
533
|
+
const { password } = options;
|
|
534
|
+
return ({ entryName, ciphertext, manifest }) =>
|
|
535
|
+
decryptProtectedEntryScrypt(ciphertext, { entryName, manifest, password });
|
|
536
|
+
}
|
|
537
|
+
|
|
423
538
|
// ── Legacy / experimental profile (pre-RFC, backward compat) ───────
|
|
424
539
|
|
|
425
540
|
function deriveLicensedEntryKeyLegacy(options = {}) {
|
|
@@ -504,11 +619,13 @@ module.exports = {
|
|
|
504
619
|
LICENSED_ENTRY_PROFILE,
|
|
505
620
|
LICENSED_EXPERIMENTAL_PROFILE,
|
|
506
621
|
PASSWORD_PROTECTED_PROFILE,
|
|
622
|
+
PASSWORD_PROTECTED_SCRYPT_PROFILE,
|
|
507
623
|
ALG,
|
|
508
624
|
RFC_KDF,
|
|
509
625
|
RFC_KEY_WRAPPING,
|
|
510
626
|
LEGACY_KDF,
|
|
511
627
|
PASSWORD_KDF,
|
|
628
|
+
SCRYPT_KDF,
|
|
512
629
|
|
|
513
630
|
// RFC-0008 compliant
|
|
514
631
|
deriveWrappingKey,
|
|
@@ -518,7 +635,7 @@ module.exports = {
|
|
|
518
635
|
encryptLicensedEntryV1,
|
|
519
636
|
decryptLicensedEntryV1,
|
|
520
637
|
|
|
521
|
-
// RFC-0009 compliant
|
|
638
|
+
// RFC-0009 compliant (Argon2id)
|
|
522
639
|
derivePasswordKey,
|
|
523
640
|
generateRecoveryCode,
|
|
524
641
|
decodeRecoveryCode,
|
|
@@ -527,6 +644,12 @@ module.exports = {
|
|
|
527
644
|
createPasswordDecryptEntry,
|
|
528
645
|
createRecoveryDecryptEntry,
|
|
529
646
|
|
|
647
|
+
// B2 scrypt password profile (v0.1 write profile)
|
|
648
|
+
derivePasswordKeyScrypt,
|
|
649
|
+
encryptProtectedEntryScrypt,
|
|
650
|
+
decryptProtectedEntryScrypt,
|
|
651
|
+
createPasswordDecryptEntryScrypt,
|
|
652
|
+
|
|
530
653
|
// Legacy
|
|
531
654
|
deriveLicensedEntryKey: deriveLicensedEntryKeyLegacy,
|
|
532
655
|
encryptLicensedEntryLegacy,
|
package/src/v1/index.js
CHANGED
|
@@ -1014,6 +1014,9 @@ function inferEntitlementProfile(manifest) {
|
|
|
1014
1014
|
if (manifest.encryption && manifest.encryption.profile === 'kdna-password-protected-v1') {
|
|
1015
1015
|
return 'password';
|
|
1016
1016
|
}
|
|
1017
|
+
if (manifest.encryption && manifest.encryption.profile === 'kdna-password-protected-v1-scrypt') {
|
|
1018
|
+
return 'password';
|
|
1019
|
+
}
|
|
1017
1020
|
if (manifest.access === 'protected') return 'password';
|
|
1018
1021
|
return null;
|
|
1019
1022
|
}
|
|
@@ -1609,11 +1612,20 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1609
1612
|
throw err;
|
|
1610
1613
|
}
|
|
1611
1614
|
} else if (opts.password) {
|
|
1612
|
-
// Password-based decryption via kdna-password-protected-v1
|
|
1615
|
+
// Password-based decryption via kdna-password-protected-v1*
|
|
1616
|
+
// profiles. Detects Argon2id vs scrypt from the envelope profile.
|
|
1613
1617
|
try {
|
|
1614
|
-
const {
|
|
1618
|
+
const {
|
|
1619
|
+
decryptProtectedEntry,
|
|
1620
|
+
decryptProtectedEntryScrypt,
|
|
1621
|
+
PASSWORD_PROTECTED_SCRYPT_PROFILE,
|
|
1622
|
+
} = require('../crypto-profile.js');
|
|
1615
1623
|
const encryptedEnvelope = v1.map['payload.kdnab'].toString('utf8');
|
|
1616
|
-
const
|
|
1624
|
+
const envelope = JSON.parse(encryptedEnvelope);
|
|
1625
|
+
const decryptFn = (envelope.profile === PASSWORD_PROTECTED_SCRYPT_PROFILE)
|
|
1626
|
+
? decryptProtectedEntryScrypt
|
|
1627
|
+
: decryptProtectedEntry;
|
|
1628
|
+
const decryptedBuf = decryptFn(encryptedEnvelope, {
|
|
1617
1629
|
entryName: 'payload.kdnab',
|
|
1618
1630
|
manifest: m,
|
|
1619
1631
|
password: opts.password,
|