@aikdna/kdna-core 0.10.0 → 0.11.0
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/package.json +1 -1
- package/src/v1/index.js +60 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "KDNA core library — load, validate, lint, and render KDNA domain judgment assets. Supports KDNA Container format (payload.kdnab via CBOR).",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "src/index.js",
|
package/src/v1/index.js
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
const fs = require('node:fs');
|
|
38
38
|
const path = require('node:path');
|
|
39
39
|
const zlib = require('node:zlib');
|
|
40
|
+
const crypto = require('node:crypto');
|
|
40
41
|
|
|
41
42
|
const MIMETYPE_V1 = 'application/vnd.kdna.asset';
|
|
42
43
|
const MIMETYPE_V2 = 'application/vnd.aikdna.kdna+zip';
|
|
@@ -431,7 +432,7 @@ function buildInspectOutput(v1) {
|
|
|
431
432
|
load_contract_default_profile: m.load_contract ? m.load_contract.default_profile : null,
|
|
432
433
|
};
|
|
433
434
|
if (m.signatures !== undefined) out.signature_count = Array.isArray(m.signatures) ? m.signatures.length : 0;
|
|
434
|
-
if (v1.map.
|
|
435
|
+
if (v1.map['checksums.json']) out.checksums_present = true;
|
|
435
436
|
return out;
|
|
436
437
|
}
|
|
437
438
|
|
|
@@ -498,10 +499,10 @@ function runValidate(v1) {
|
|
|
498
499
|
}
|
|
499
500
|
|
|
500
501
|
// checksums gate — checksums.json against checksums.schema.json
|
|
501
|
-
if (v1.map.
|
|
502
|
+
if (v1.map['checksums.json']) {
|
|
502
503
|
let checks;
|
|
503
504
|
try {
|
|
504
|
-
checks = JSON.parse(v1.map
|
|
505
|
+
checks = JSON.parse(v1.map['checksums.json'].toString('utf8'));
|
|
505
506
|
} catch (e) {
|
|
506
507
|
result.checksums_valid = false;
|
|
507
508
|
problems.push(`checksums: not valid JSON (${e.message})`);
|
|
@@ -512,6 +513,10 @@ function runValidate(v1) {
|
|
|
512
513
|
problems.push(`checksums: ${err.instancePath || '<root>'} ${err.message}`);
|
|
513
514
|
}
|
|
514
515
|
}
|
|
516
|
+
// Digest matching verification — compute actual hashes and compare.
|
|
517
|
+
if (checks) {
|
|
518
|
+
result.checksums_valid = verifyDigests(checks, v1.map, problems, result);
|
|
519
|
+
}
|
|
515
520
|
}
|
|
516
521
|
|
|
517
522
|
// load_contract gate — only if manifest references a load_contract block
|
|
@@ -532,6 +537,38 @@ function runValidate(v1) {
|
|
|
532
537
|
return finalizeValidate(result, problems);
|
|
533
538
|
}
|
|
534
539
|
|
|
540
|
+
function verifyDigests(checksums, map, problems, result) {
|
|
541
|
+
const algo = checksums.algorithm || 'sha256';
|
|
542
|
+
if (algo !== 'sha256') {
|
|
543
|
+
problems.push(`checksums: unsupported digest algorithm ${algo} (supported: sha256)`);
|
|
544
|
+
result.checksums_valid = false;
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const entryMap = {
|
|
548
|
+
manifest_digest: 'kdna.json',
|
|
549
|
+
payload_digest: 'payload.kdnab',
|
|
550
|
+
};
|
|
551
|
+
let stillValid = true;
|
|
552
|
+
for (const [digestKey, entryName] of Object.entries(entryMap)) {
|
|
553
|
+
const declared = checksums[digestKey];
|
|
554
|
+
if (!declared) continue;
|
|
555
|
+
if (!map[entryName]) {
|
|
556
|
+
problems.push(`checksums: ${digestKey} references missing entry ${entryName}`);
|
|
557
|
+
stillValid = false;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const entryBytes = map[entryName];
|
|
561
|
+
const actual = crypto.createHash('sha256').update(entryBytes).digest('hex');
|
|
562
|
+
const expected = declared.replace(/^sha256:/, '');
|
|
563
|
+
if (actual !== expected) {
|
|
564
|
+
problems.push(`checksums: ${digestKey} mismatch (declared ${expected.slice(0, 8)}..., actual ${actual.slice(0, 8)}...)`);
|
|
565
|
+
stillValid = false;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (!stillValid) result.checksums_valid = false;
|
|
569
|
+
return stillValid;
|
|
570
|
+
}
|
|
571
|
+
|
|
535
572
|
function finalizeValidate(result, problems) {
|
|
536
573
|
result.overall_valid =
|
|
537
574
|
result.format_valid &&
|
|
@@ -735,6 +772,26 @@ function loadV1(inputPath, opts = {}) {
|
|
|
735
772
|
throw err;
|
|
736
773
|
}
|
|
737
774
|
|
|
775
|
+
// Digest verification — refuse to load if checksums.json is present and digests mismatch.
|
|
776
|
+
if (v1.map['checksums.json']) {
|
|
777
|
+
try {
|
|
778
|
+
const checks = JSON.parse(v1.map['checksums.json'].toString('utf8'));
|
|
779
|
+
const problems = [];
|
|
780
|
+
const ok = verifyDigests(checks, v1.map, problems, {});
|
|
781
|
+
if (!ok) {
|
|
782
|
+
const err = new Error(`checksum verification failed: ${problems.join('; ')}`);
|
|
783
|
+
err.code = 'checksum_mismatch';
|
|
784
|
+
throw err;
|
|
785
|
+
}
|
|
786
|
+
} catch (e) {
|
|
787
|
+
if (e.code === 'checksum_mismatch') throw e;
|
|
788
|
+
// Invalid JSON — already caught by validate, but still refuse to load.
|
|
789
|
+
const err = new Error(`checksums.json is not valid: ${e.message}`);
|
|
790
|
+
err.code = 'checksum_parse_error';
|
|
791
|
+
throw err;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
738
795
|
const result = { status: 'loaded', profile, asset_id: m.asset_id, title: m.title };
|
|
739
796
|
|
|
740
797
|
if (profile === 'index') {
|