@aikdna/kdna-core 0.18.0 → 0.19.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +128 -94
  2. package/README.md +109 -215
  3. package/package.json +7 -2
  4. package/schema/agent-host-capabilities.schema.json +62 -0
  5. package/schema/agent-host-receipt.schema.json +371 -0
  6. package/schema/agent-host-request.schema.json +223 -0
  7. package/schema/{bundle-profile-v1.schema.json → bundle-profile.schema.json} +9 -5
  8. package/schema/checksums.schema.json +8 -8
  9. package/schema/consumption-plan.schema.json +428 -0
  10. package/schema/digest-evidence.schema.json +115 -26
  11. package/schema/{kdna-envelope-external-grant-v1.schema.json → external-grant-envelope.schema.json} +17 -4
  12. package/schema/{kdna-key-grant-v1.schema.json → external-key-grant.schema.json} +39 -7
  13. package/schema/judgment-trace.schema.json +1251 -0
  14. package/schema/load-plan.schema.json +5 -3
  15. package/schema/manifest.schema.json +70 -10
  16. package/schema/{payload-profile-v1.schema.json → payload-profile.schema.json} +30 -6
  17. package/schema/{runtime-capsule-2.schema.json → runtime-capsule.schema.json} +7 -31
  18. package/src/asset-reader.js +8 -2
  19. package/src/{v1 → container}/index.js +385 -198
  20. package/src/container/index.mjs +20 -0
  21. package/src/container-dispatcher.js +6 -6
  22. package/src/crypto-profile.js +37 -107
  23. package/src/external-key-grant.js +41 -15
  24. package/src/index.js +11 -8
  25. package/src/index.mjs +49 -45
  26. package/src/loader-compatibility.js +59 -0
  27. package/src/loader.js +1 -1
  28. package/src/public-api.js +5 -5
  29. package/src/remote-runtime.d.ts +13 -0
  30. package/src/remote-runtime.js +27 -0
  31. package/src/remote-runtime.mjs +3 -0
  32. package/src/runtime-api.js +5 -5
  33. package/src/{capsule-v2.js → runtime-capsule.js} +96 -254
  34. package/src/{execution-contract-v1.js → runtime-contract.js} +184 -152
  35. package/src/types.d.ts +200 -277
  36. package/src/workpack-pure.js +1 -1
  37. package/schema/agent-host-2-receipt.schema.json +0 -206
  38. package/schema/agent-host-2-request.schema.json +0 -135
  39. package/schema/agent-host-capabilities-1.schema.json +0 -40
  40. package/schema/consumption-plan-1.schema.json +0 -280
  41. package/schema/judgment-trace-1.schema.json +0 -748
  42. package/schema/runtime-capsule-1.schema.json +0 -68
  43. package/src/lint-pure.js +0 -433
  44. package/src/v1/index.mjs +0 -21
@@ -0,0 +1,20 @@
1
+ import containerApi from './index.js';
2
+
3
+ export const MIMETYPE = containerApi.MIMETYPE;
4
+ export const REQUIRED_DIR_ENTRIES = containerApi.REQUIRED_DIR_ENTRIES;
5
+ export const isKdnaSourceDir = containerApi.isKdnaSourceDir;
6
+ export const detectContainerFormat = containerApi.detectContainerFormat;
7
+ export const readLayout = containerApi.readLayout;
8
+ export const inspect = containerApi.inspect;
9
+ export const validate = containerApi.validate;
10
+ export const planLoad = containerApi.planLoad;
11
+ export const loadAuthorized = containerApi.loadAuthorized;
12
+ export const buildChecksums = containerApi.buildChecksums;
13
+ export const computeRuntimeEntrySetDigest = containerApi.computeRuntimeEntrySetDigest;
14
+ export const pack = containerApi.pack;
15
+ export const unpack = containerApi.unpack;
16
+ export const load = containerApi.load;
17
+ export const loadAsset = containerApi.loadAsset;
18
+ export const FORBIDDEN_OUTPUT_TERMS = containerApi.FORBIDDEN_OUTPUT_TERMS;
19
+
20
+ export default containerApi;
@@ -185,7 +185,7 @@ function readSourceDirectory(absPath, _opts) {
185
185
  }
186
186
 
187
187
  function readKdnaContainer(absPath, _opts) {
188
- const v1Layout = require('./v1/index.js').readLayout(absPath);
188
+ const layout = require('./container/index.js').readLayout(absPath);
189
189
  const containerBuf = fs.readFileSync(absPath);
190
190
  const assetDigest = crypto.createHash('sha256').update(containerBuf).digest();
191
191
 
@@ -194,15 +194,15 @@ function readKdnaContainer(absPath, _opts) {
194
194
  sourcePath: absPath,
195
195
  format: 'kdna',
196
196
  mimetype: MIMETYPE,
197
- manifest: v1Layout.manifest,
198
- payloadRaw: v1Layout.map['payload.kdnab'] || null,
199
- checksums: v1Layout.map['checksums.json']
200
- ? (() => { try { return JSON.parse(v1Layout.map['checksums.json'].toString('utf8')); } catch { return null; } })()
197
+ manifest: layout.manifest,
198
+ payloadRaw: layout.map['payload.kdnab'] || null,
199
+ checksums: layout.map['checksums.json']
200
+ ? (() => { try { return JSON.parse(layout.map['checksums.json'].toString('utf8')); } catch { return null; } })()
201
201
  : null,
202
202
  assetDigest,
203
203
  containerDigest: `sha256:${assetDigest.toString('hex')}`,
204
204
  containerBuffer: containerBuf,
205
- entries: new Map(Object.entries(v1Layout.map)),
205
+ entries: new Map(Object.entries(layout.map)),
206
206
  };
207
207
  }
208
208
 
@@ -17,14 +17,7 @@ try {
17
17
  * - AES-256-GCM content encryption
18
18
  * - Random CEK per asset; license key only unwraps CEK, never touches content
19
19
  */
20
- const LICENSED_ENTRY_PROFILE = 'kdna-licensed-entry-v1';
21
-
22
- /**
23
- * Pre-RFC legacy profile (now experimental).
24
- * Uses scrypt-sha256 with concatenated secret (`licenseKey|machineFingerprint`).
25
- * Retained for backward compatibility only.
26
- */
27
- const LICENSED_EXPERIMENTAL_PROFILE = 'kdna-licensed-entry-experimental';
20
+ const LICENSED_ENTRY_PROFILE = 'kdna.encryption.licensed-entry';
28
21
 
29
22
  /**
30
23
  * RFC-0009 compliant profile.
@@ -33,11 +26,11 @@ const LICENSED_EXPERIMENTAL_PROFILE = 'kdna-licensed-entry-experimental';
33
26
  * - AES-256-GCM content encryption
34
27
  * - Dual key slots: password + recovery
35
28
  */
36
- const PASSWORD_PROTECTED_PROFILE = 'kdna-password-protected-v1';
29
+ const PASSWORD_PROTECTED_PROFILE = 'kdna.encryption.password';
30
+ const ENCRYPTION_PROFILE_VERSION = '0.1.0';
37
31
 
38
32
  const RFC_KDF = 'HKDF-SHA256';
39
33
  const RFC_KEY_WRAPPING = 'AES-256-KW';
40
- const LEGACY_KDF = 'scrypt-sha256';
41
34
  const PASSWORD_KDF = 'Argon2id';
42
35
  const ALG = 'AES-256-GCM';
43
36
 
@@ -63,14 +56,20 @@ function normalizeEnvelope(value) {
63
56
  try { return cbor.decode(Buffer.from(value, 'utf8')); } catch { /* fall through */ }
64
57
  }
65
58
  if (value && typeof value === 'object') return value;
66
- throw new Error('encrypted entry envelope must be CBOR (kdna_version 1.0)');
59
+ throw new Error('encrypted entry envelope must be CBOR (format 0.1.0)');
67
60
  }
68
61
 
69
- function encryptedEntryAad(entryName, manifest = {}, profile = LICENSED_ENTRY_PROFILE) {
62
+ function encryptedEntryAad(
63
+ entryName,
64
+ manifest = {},
65
+ profile = LICENSED_ENTRY_PROFILE,
66
+ profileVersion = ENCRYPTION_PROFILE_VERSION,
67
+ ) {
70
68
  return Buffer.from(
71
69
  [
72
70
  profile,
73
- manifest.name || manifest.asset_id || '',
71
+ profileVersion,
72
+ manifest.asset_id || '',
74
73
  manifest.version || '',
75
74
  entryName,
76
75
  ].join('\n'),
@@ -173,7 +172,7 @@ function aesUnwrap(key, ciphertext) {
173
172
  /**
174
173
  * Derive a key-wrapping key (KWK) from a license key via HKDF-SHA256.
175
174
  */
176
- function deriveWrappingKey(licenseKey, info = 'kdna-licensed-entry-v1-kwk') {
175
+ function deriveWrappingKey(licenseKey, info = 'kdna.encryption.licensed-entry-kwk') {
177
176
  return hkdfSha256(licenseKey, null, info, 32);
178
177
  }
179
178
 
@@ -207,7 +206,7 @@ function unwrapCEK(wrappedCek, wrappingKey) {
207
206
  * 4. Wrap CEK with KWK (AES-256-KW)
208
207
  * 5. Store wrapped_key in envelope
209
208
  */
210
- function encryptLicensedEntryV1(plaintext, options = {}) {
209
+ function encryptLicensedEntry(plaintext, options = {}) {
211
210
  const { entryName, manifest = {}, licenseKey } = options;
212
211
  if (!entryName) throw new Error('entryName is required');
213
212
  if (!licenseKey) throw new Error('licenseKey is required for RFC-0008 encryption');
@@ -223,6 +222,7 @@ function encryptLicensedEntryV1(plaintext, options = {}) {
223
222
 
224
223
  return {
225
224
  profile: LICENSED_ENTRY_PROFILE,
225
+ profile_version: ENCRYPTION_PROFILE_VERSION,
226
226
  alg: ALG,
227
227
  kdf: RFC_KDF,
228
228
  key_wrapping: RFC_KEY_WRAPPING,
@@ -240,7 +240,7 @@ function encryptLicensedEntryV1(plaintext, options = {}) {
240
240
  * 2. Unwrap CEK from wrapped_key (AES-256-KW)
241
241
  * 3. Decrypt content with CEK (AES-256-GCM)
242
242
  */
243
- function decryptLicensedEntryV1(envelopeValue, options = {}) {
243
+ function decryptLicensedEntry(envelopeValue, options = {}) {
244
244
  const { entryName, manifest = {}, licenseKey } = options;
245
245
  if (!entryName) throw new Error('entryName is required');
246
246
  if (!licenseKey) throw new Error('licenseKey is required for RFC-0008 decryption');
@@ -249,6 +249,9 @@ function decryptLicensedEntryV1(envelopeValue, options = {}) {
249
249
  if (envelope.profile !== LICENSED_ENTRY_PROFILE) {
250
250
  throw new Error(`unsupported encrypted entry profile: ${envelope.profile || 'unknown'} (expected ${LICENSED_ENTRY_PROFILE})`);
251
251
  }
252
+ if (envelope.profile_version !== ENCRYPTION_PROFILE_VERSION) {
253
+ throw new Error(`unsupported encrypted entry profile_version: ${envelope.profile_version || 'unknown'}`);
254
+ }
252
255
  if (envelope.alg !== ALG) throw new Error(`unsupported encrypted entry alg: ${envelope.alg}`);
253
256
  if (envelope.kdf !== RFC_KDF) throw new Error(`unsupported encrypted entry kdf: ${envelope.kdf}`);
254
257
  if (envelope.key_wrapping !== RFC_KEY_WRAPPING) throw new Error(`unsupported encrypted entry key_wrapping: ${envelope.key_wrapping}`);
@@ -358,6 +361,7 @@ function encryptProtectedEntry(plaintext, options = {}) {
358
361
 
359
362
  return {
360
363
  profile: PASSWORD_PROTECTED_PROFILE,
364
+ profile_version: ENCRYPTION_PROFILE_VERSION,
361
365
  alg: ALG,
362
366
  kdf: PASSWORD_KDF,
363
367
  key_wrapping: RFC_KEY_WRAPPING,
@@ -382,6 +386,9 @@ function decryptProtectedEntry(envelopeValue, options = {}) {
382
386
  `unsupported encrypted entry profile: ${envelope.profile || 'unknown'} (expected ${PASSWORD_PROTECTED_PROFILE})`,
383
387
  );
384
388
  }
389
+ if (envelope.profile_version !== ENCRYPTION_PROFILE_VERSION) {
390
+ throw new Error(`unsupported encrypted entry profile_version: ${envelope.profile_version || 'unknown'}`);
391
+ }
385
392
  if (envelope.alg !== ALG) throw new Error(`unsupported encrypted entry alg: ${envelope.alg}`);
386
393
  if (envelope.kdf !== PASSWORD_KDF) throw new Error(`unsupported encrypted entry kdf: ${envelope.kdf}`);
387
394
  if (envelope.key_wrapping !== RFC_KEY_WRAPPING) {
@@ -422,12 +429,12 @@ function createRecoveryDecryptEntry(options = {}) {
422
429
  decryptProtectedEntry(ciphertext, { entryName, manifest, recoveryCode });
423
430
  }
424
431
 
425
- // ── B2: scrypt-based password profile (v0.1 write profile) ─────────
432
+ // ── B2: scrypt-based password profile (0.1 write profile) ─────────
426
433
  // Uses Node built-in crypto.scryptSync — zero additional dependencies.
427
- // v0.2 will promote the Argon2id profile (above) as the default write
434
+ // 0.2 will promote the Argon2id profile (above) as the default write
428
435
  // profile; this scrypt profile will remain a read-only legacy profile.
429
436
 
430
- const PASSWORD_PROTECTED_SCRYPT_PROFILE = 'kdna-password-protected-v1-scrypt';
437
+ const PASSWORD_PROTECTED_SCRYPT_PROFILE = 'kdna.encryption.password.scrypt';
431
438
  const SCRYPT_KDF = 'scrypt-sha256';
432
439
 
433
440
  function derivePasswordKeyScrypt(password, params = {}) {
@@ -468,6 +475,7 @@ function encryptProtectedEntryScrypt(plaintext, options = {}) {
468
475
 
469
476
  return {
470
477
  profile: PASSWORD_PROTECTED_SCRYPT_PROFILE,
478
+ profile_version: ENCRYPTION_PROFILE_VERSION,
471
479
  alg: ALG,
472
480
  kdf: SCRYPT_KDF,
473
481
  key_wrapping: RFC_KEY_WRAPPING,
@@ -508,6 +516,9 @@ function decryptProtectedEntryScrypt(envelopeValue, options = {}) {
508
516
  `unsupported encrypted entry profile: ${envelope.profile || 'unknown'} (expected ${PASSWORD_PROTECTED_SCRYPT_PROFILE})`,
509
517
  );
510
518
  }
519
+ if (envelope.profile_version !== ENCRYPTION_PROFILE_VERSION) {
520
+ throw new Error(`unsupported encrypted entry profile_version: ${envelope.profile_version || 'unknown'}`);
521
+ }
511
522
  if (envelope.alg !== ALG) throw new Error(`unsupported encrypted entry alg: ${envelope.alg}`);
512
523
  if (envelope.kdf !== SCRYPT_KDF) throw new Error(`unsupported encrypted entry kdf: ${envelope.kdf}`);
513
524
  if (envelope.key_wrapping !== RFC_KEY_WRAPPING) {
@@ -537,95 +548,21 @@ function createPasswordDecryptEntryScrypt(options = {}) {
537
548
  decryptProtectedEntryScrypt(ciphertext, { entryName, manifest, password });
538
549
  }
539
550
 
540
- // ── Legacy / experimental profile (pre-RFC, backward compat) ───────
541
-
542
- function deriveLicensedEntryKeyLegacy(options = {}) {
543
- const { licenseKey, machineFingerprint, salt, keyLength = 32 } = options;
544
- if (!licenseKey) throw new Error('licenseKey is required');
545
- if (!machineFingerprint) throw new Error('machineFingerprint is required');
546
- const saltBuffer = Buffer.isBuffer(salt) || salt instanceof Uint8Array
547
- ? Buffer.from(salt)
548
- : decodeBase64(salt, 'salt');
549
- const secret = `${licenseKey}|${machineFingerprint}`;
550
- return crypto.scryptSync(secret, saltBuffer, keyLength);
551
- }
552
-
553
- function encryptLicensedEntryLegacy(plaintext, options = {}) {
554
- const { entryName, manifest = {}, licenseKey, machineFingerprint } = options;
555
- if (!entryName) throw new Error('entryName is required');
556
- const salt = crypto.randomBytes(16);
557
- const iv = crypto.randomBytes(12);
558
- const key = deriveLicensedEntryKeyLegacy({ licenseKey, machineFingerprint, salt });
559
- const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
560
- cipher.setAAD(encryptedEntryAad(entryName, manifest, LICENSED_EXPERIMENTAL_PROFILE));
561
- const ciphertext = Buffer.concat([cipher.update(toBuffer(plaintext, 'plaintext')), cipher.final()]);
562
- return {
563
- profile: LICENSED_EXPERIMENTAL_PROFILE,
564
- alg: ALG,
565
- kdf: LEGACY_KDF,
566
- salt: salt.toString('base64'),
567
- iv: iv.toString('base64'),
568
- tag: cipher.getAuthTag().toString('base64'),
569
- ciphertext: ciphertext.toString('base64'),
570
- };
571
- }
572
-
573
- function decryptLicensedEntryLegacy(envelopeValue, options = {}) {
574
- const { entryName, manifest = {}, licenseKey, machineFingerprint } = options;
575
- if (!entryName) throw new Error('entryName is required');
576
- const envelope = normalizeEnvelope(envelopeValue);
577
- if (envelope.profile !== LICENSED_EXPERIMENTAL_PROFILE) {
578
- throw new Error(`unsupported encrypted entry profile: ${envelope.profile || 'unknown'}`);
579
- }
580
- if (envelope.alg !== ALG) throw new Error(`unsupported encrypted entry alg: ${envelope.alg}`);
581
- if (envelope.kdf !== LEGACY_KDF) throw new Error(`unsupported encrypted entry kdf: ${envelope.kdf}`);
582
- const key = deriveLicensedEntryKeyLegacy({
583
- licenseKey,
584
- machineFingerprint,
585
- salt: envelope.salt,
586
- });
587
- const decipher = crypto.createDecipheriv('aes-256-gcm', key, decodeBase64(envelope.iv, 'iv'));
588
- decipher.setAAD(encryptedEntryAad(entryName, manifest, LICENSED_EXPERIMENTAL_PROFILE));
589
- decipher.setAuthTag(decodeBase64(envelope.tag, 'tag'));
590
- return Buffer.concat([
591
- decipher.update(decodeBase64(envelope.ciphertext, 'ciphertext')),
592
- decipher.final(),
593
- ]);
594
- }
595
-
596
- // ── Unified entry points (auto-detect profile) ────────────────────
597
-
598
- function encryptLicensedEntry(plaintext, options = {}) {
599
- return encryptLicensedEntryV1(plaintext, options);
600
- }
601
-
602
- function decryptLicensedEntry(envelopeValue, options = {}) {
603
- const envelope = normalizeEnvelope(envelopeValue);
604
- if (envelope.profile === LICENSED_ENTRY_PROFILE) {
605
- return decryptLicensedEntryV1(envelopeValue, options);
606
- }
607
- if (envelope.profile === LICENSED_EXPERIMENTAL_PROFILE) {
608
- return decryptLicensedEntryLegacy(envelopeValue, options);
609
- }
610
- throw new Error(`unsupported encrypted entry profile: ${envelope.profile || 'unknown'}`);
611
- }
612
-
613
551
  function createLicensedDecryptEntry(options = {}) {
614
- const { licenseKey, machineFingerprint } = options;
552
+ const { licenseKey } = options;
615
553
  return ({ entryName, ciphertext, manifest }) =>
616
- decryptLicensedEntry(ciphertext, { entryName, manifest, licenseKey, machineFingerprint });
554
+ decryptLicensedEntry(ciphertext, { entryName, manifest, licenseKey });
617
555
  }
618
556
 
619
557
  module.exports = {
620
558
  // Profiles
621
559
  LICENSED_ENTRY_PROFILE,
622
- LICENSED_EXPERIMENTAL_PROFILE,
623
560
  PASSWORD_PROTECTED_PROFILE,
624
561
  PASSWORD_PROTECTED_SCRYPT_PROFILE,
562
+ ENCRYPTION_PROFILE_VERSION,
625
563
  ALG,
626
564
  RFC_KDF,
627
565
  RFC_KEY_WRAPPING,
628
- LEGACY_KDF,
629
566
  PASSWORD_KDF,
630
567
  SCRYPT_KDF,
631
568
 
@@ -634,8 +571,8 @@ module.exports = {
634
571
  generateCEK,
635
572
  wrapCEK,
636
573
  unwrapCEK,
637
- encryptLicensedEntryV1,
638
- decryptLicensedEntryV1,
574
+ encryptLicensedEntry,
575
+ decryptLicensedEntry,
639
576
 
640
577
  // RFC-0009 compliant (Argon2id)
641
578
  derivePasswordKey,
@@ -646,20 +583,13 @@ module.exports = {
646
583
  createPasswordDecryptEntry,
647
584
  createRecoveryDecryptEntry,
648
585
 
649
- // B2 scrypt password profile (v0.1 write profile)
586
+ // B2 scrypt password profile (0.1 write profile)
650
587
  derivePasswordKeyScrypt,
651
588
  encryptProtectedEntryScrypt,
652
589
  decryptProtectedEntryScrypt,
653
590
  createPasswordDecryptEntryScrypt,
654
591
 
655
- // Legacy
656
- deriveLicensedEntryKey: deriveLicensedEntryKeyLegacy,
657
- encryptLicensedEntryLegacy,
658
- decryptLicensedEntryLegacy,
659
-
660
- // Unified
661
- encryptLicensedEntry,
662
- decryptLicensedEntry,
592
+ // Consumer adapter
663
593
  createLicensedDecryptEntry,
664
594
 
665
595
  // Low-level
@@ -8,26 +8,27 @@
8
8
 
9
9
  const crypto = require('node:crypto');
10
10
  const path = require('node:path');
11
- const Ajv2020 = require('ajv/dist/2020');
11
+ const JsonSchema2020 = require('ajv/dist/2020');
12
12
  const addFormats = require('ajv-formats');
13
13
  const cbor = require('cbor-x');
14
14
  const { hkdfSha256, aesWrap, aesUnwrap } = require('./crypto-profile');
15
15
 
16
- const EXTERNAL_ENVELOPE_PROFILE = 'kdna-envelope-external-grant-v1';
17
- const EXTERNAL_GRANT_PROFILE = 'kdna-key-grant-v1';
18
- const EXTERNAL_AAD_PROFILE = 'kdna-external-asset-cek-v1';
19
- const DEVICE_KEK_PROFILE = 'kdna-device-grant-kek-v1';
16
+ const EXTERNAL_ENVELOPE_PROFILE = 'kdna.envelope.external-grant';
17
+ const EXTERNAL_GRANT_PROFILE = 'kdna.grant.external-key';
18
+ const EXTERNAL_AAD_PROFILE = 'kdna.key-context.asset-content';
19
+ const DEVICE_KEK_PROFILE = 'kdna.key-context.device-grant';
20
+ const EXTERNAL_GRANT_CONTRACT_VERSION = '0.1.0';
20
21
  const ENVELOPE_ALG = 'A256GCM';
21
22
  const GRANT_WRAP_ALG = 'X25519-HKDF-SHA256+A256KW';
22
23
 
23
24
  const schemaRoot = path.join(__dirname, '..', 'schema');
24
- const ajv = new Ajv2020({ allErrors: true, strict: true });
25
+ const ajv = new JsonSchema2020({ allErrors: true, strict: true });
25
26
  addFormats(ajv);
26
27
  const validateEnvelopeSchema = ajv.compile(
27
- require(path.join(schemaRoot, 'kdna-envelope-external-grant-v1.schema.json')),
28
+ require(path.join(schemaRoot, 'external-grant-envelope.schema.json')),
28
29
  );
29
30
  const validateGrantSchema = ajv.compile(
30
- require(path.join(schemaRoot, 'kdna-key-grant-v1.schema.json')),
31
+ require(path.join(schemaRoot, 'external-key-grant.schema.json')),
31
32
  );
32
33
 
33
34
  const verifiedEntitlements = new WeakSet();
@@ -127,8 +128,9 @@ function externalEnvelopeAad({ manifest, entryName, plaintextDigest, keyRef, iss
127
128
  const entitlementProfile = manifest?.entitlement?.profile || '';
128
129
  const fields = [
129
130
  EXTERNAL_ENVELOPE_PROFILE,
131
+ EXTERNAL_GRANT_CONTRACT_VERSION,
130
132
  manifest?.asset_uid || '',
131
- manifest?.asset_id || manifest?.name || '',
133
+ manifest?.asset_id || '',
132
134
  manifest?.version || '',
133
135
  entryName,
134
136
  plaintextDigest,
@@ -210,6 +212,7 @@ function encryptExternalGrantEntry(plaintextValue, options = {}) {
210
212
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
211
213
  return {
212
214
  profile: EXTERNAL_ENVELOPE_PROFILE,
215
+ contract_version: EXTERNAL_GRANT_CONTRACT_VERSION,
213
216
  alg: ENVELOPE_ALG,
214
217
  cek_derivation: 'HKDF-SHA256',
215
218
  key_ref: keyRef,
@@ -327,9 +330,18 @@ function createExternalKeyGrant(options = {}) {
327
330
  shared = crypto.diffieHellman({ privateKey: ephemeral.privateKey, publicKey: deviceKey });
328
331
  const salt = wrapSalt ? Buffer.from(wrapSalt) : crypto.randomBytes(16);
329
332
  if (salt.length !== 16) fail('KDNA_GRANT_FORMAT_INVALID', 'grant wrap salt must be 16 bytes');
330
- kek = hkdfSha256(shared, salt, Buffer.from(`${DEVICE_KEK_PROFILE}\n${grantId}`, 'utf8'), 32);
333
+ kek = hkdfSha256(
334
+ shared,
335
+ salt,
336
+ Buffer.from(
337
+ `${DEVICE_KEK_PROFILE}\n${EXTERNAL_GRANT_CONTRACT_VERSION}\n${grantId}`,
338
+ 'utf8',
339
+ ),
340
+ 32,
341
+ );
331
342
  const grant = {
332
343
  profile: EXTERNAL_GRANT_PROFILE,
344
+ contract_version: EXTERNAL_GRANT_CONTRACT_VERSION,
333
345
  grant_id: grantId,
334
346
  issuer,
335
347
  signing_key_id: signingKeyId,
@@ -339,7 +351,7 @@ function createExternalKeyGrant(options = {}) {
339
351
  device_public_key: devicePublicKey,
340
352
  device_signing_public_key: deviceSigningPublicKey,
341
353
  asset: {
342
- asset_id: manifest.asset_id || manifest.name,
354
+ asset_id: manifest.asset_id,
343
355
  asset_uid: manifest.asset_uid,
344
356
  version: manifest.version,
345
357
  digest: assetDigest,
@@ -402,7 +414,7 @@ function authorizeExternalKeyGrant(options = {}) {
402
414
  grant,
403
415
  issuerPublicKeys,
404
416
  manifest,
405
- checksums,
417
+ expectedAssetDigest,
406
418
  envelope: envelopeValue,
407
419
  deviceAgreementKey,
408
420
  expectedAccountId,
@@ -433,10 +445,15 @@ function authorizeExternalKeyGrant(options = {}) {
433
445
  'KDNA_GRANT_DEVICE_MISMATCH',
434
446
  'device signing key',
435
447
  );
436
- expectEqual(grant.asset.asset_id, manifest.asset_id || manifest.name, 'KDNA_GRANT_ASSET_MISMATCH', 'asset ID');
448
+ expectEqual(grant.asset.asset_id, manifest.asset_id, 'KDNA_GRANT_ASSET_MISMATCH', 'asset ID');
437
449
  expectEqual(grant.asset.asset_uid, manifest.asset_uid, 'KDNA_GRANT_ASSET_MISMATCH', 'asset UID');
438
450
  expectEqual(grant.asset.version, manifest.version, 'KDNA_GRANT_ASSET_MISMATCH', 'asset version');
439
- expectEqual(grant.asset.digest, checksums?.asset_digest, 'KDNA_GRANT_DIGEST_MISMATCH', 'asset digest');
451
+ expectEqual(
452
+ grant.asset.digest,
453
+ expectedAssetDigest,
454
+ 'KDNA_GRANT_DIGEST_MISMATCH',
455
+ 'asset digest',
456
+ );
440
457
  expectEqual(grant.asset.entry_path, envelope.entry_path, 'KDNA_GRANT_ASSET_MISMATCH', 'entry path');
441
458
  expectEqual(grant.asset.key_ref, envelope.key_ref, 'KDNA_GRANT_ASSET_MISMATCH', 'key reference');
442
459
  expectEqual(grant.asset.issuer_key_id, envelope.issuer_key_id, 'KDNA_GRANT_ASSET_MISMATCH', 'issuer asset key');
@@ -454,7 +471,15 @@ function authorizeExternalKeyGrant(options = {}) {
454
471
  const ephemeralKey = publicKeyFromRaw('X25519', `x25519:${b64url(ephemeralRaw)}`);
455
472
  const shared = crypto.diffieHellman({ privateKey, publicKey: ephemeralKey });
456
473
  const salt = decodeB64url(grant.wrap.salt, 'grant wrap salt', 16);
457
- const kek = hkdfSha256(shared, salt, Buffer.from(`${DEVICE_KEK_PROFILE}\n${grant.grant_id}`, 'utf8'), 32);
474
+ const kek = hkdfSha256(
475
+ shared,
476
+ salt,
477
+ Buffer.from(
478
+ `${DEVICE_KEK_PROFILE}\n${EXTERNAL_GRANT_CONTRACT_VERSION}\n${grant.grant_id}`,
479
+ 'utf8',
480
+ ),
481
+ 32,
482
+ );
458
483
  let cek;
459
484
  try {
460
485
  cek = aesUnwrap(kek, decodeB64url(grant.wrap.wrapped_cek, 'wrapped CEK', 40));
@@ -543,6 +568,7 @@ module.exports = {
543
568
  EXTERNAL_GRANT_PROFILE,
544
569
  EXTERNAL_AAD_PROFILE,
545
570
  DEVICE_KEK_PROFILE,
571
+ EXTERNAL_GRANT_CONTRACT_VERSION,
546
572
  KDNAExternalGrantError,
547
573
  canonicalJson,
548
574
  grantSigningPayload,
package/src/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  * @aikdna/kdna-core — CJS entry point
3
3
  */
4
4
  const loader = require('./loader');
5
- const lint = require('./lint-pure');
6
5
  const validate = require('./validate-pure');
7
6
  const render = require('./render');
8
7
  const compose = require('./compose');
@@ -11,15 +10,15 @@ const cryptoProfile = require('./crypto-profile');
11
10
  const externalKeyGrant = require('./external-key-grant');
12
11
  const publicApi = require('./public-api');
13
12
  const workpackPure = require('./workpack-pure');
14
- const v1 = require('./v1');
13
+ const container = require('./container');
15
14
  const runtimeApi = require('./runtime-api');
16
- const capsuleV2 = require('./capsule-v2');
17
- const executionContractV1 = require('./execution-contract-v1');
15
+ const runtimeCapsule = require('./runtime-capsule');
16
+ const runtimeContract = require('./runtime-contract');
17
+ const loaderCompatibility = require('./loader-compatibility');
18
18
 
19
19
  module.exports = {
20
20
  ...publicApi,
21
21
  ...loader,
22
- ...lint,
23
22
  ...validate,
24
23
  ...render,
25
24
  ...compose,
@@ -27,8 +26,12 @@ module.exports = {
27
26
  ...cryptoProfile,
28
27
  ...externalKeyGrant,
29
28
  ...workpackPure,
30
- ...v1,
29
+ ...container,
31
30
  ...runtimeApi,
32
- ...capsuleV2,
33
- ...executionContractV1,
31
+ ...runtimeCapsule,
32
+ ...runtimeContract,
33
+ STRICT_LOADER_VERSION: loaderCompatibility.STRICT_LOADER_VERSION,
34
+ parseLoaderVersion: loaderCompatibility.parseLoaderVersion,
35
+ compareLoaderVersions: loaderCompatibility.compareLoaderVersions,
36
+ assessLoaderCompatibility: loaderCompatibility.assessLoaderCompatibility,
34
37
  };
package/src/index.mjs CHANGED
@@ -10,10 +10,9 @@ export {
10
10
  formatContext,
11
11
  } from './loader.js';
12
12
 
13
- export { lintDomain, validateManifest } from './lint-pure.js';
14
-
15
13
  export {
16
14
  MIMETYPE,
15
+ KDNA_LOADER_VERSION,
17
16
  REQUIRED_DIR_ENTRIES,
18
17
  isKdnaSourceDir,
19
18
  detectContainerFormat,
@@ -23,12 +22,17 @@ export {
23
22
  buildChecksums,
24
23
  pack,
25
24
  unpack,
26
- buildCapsule,
27
25
  FORBIDDEN_OUTPUT_TERMS,
28
26
  parseSemver,
29
27
  compareSemver,
30
28
  satisfies,
31
- } from './v1/index.js';
29
+ } from './container/index.js';
30
+
31
+ import loaderCompatibility from './loader-compatibility.js';
32
+ export const STRICT_LOADER_VERSION = loaderCompatibility.STRICT_LOADER_VERSION;
33
+ export const parseLoaderVersion = loaderCompatibility.parseLoaderVersion;
34
+ export const compareLoaderVersions = loaderCompatibility.compareLoaderVersions;
35
+ export const assessLoaderCompatibility = loaderCompatibility.assessLoaderCompatibility;
32
36
 
33
37
  import runtimeApi from './runtime-api.js';
34
38
  export const planLoad = runtimeApi.planLoad;
@@ -57,8 +61,8 @@ import cryptoProfile from './crypto-profile.js';
57
61
  import externalKeyGrant from './external-key-grant.js';
58
62
  import publicApi from './public-api.js';
59
63
  import workpackPure from './workpack-pure.js';
60
- import capsuleV2 from './capsule-v2.js';
61
- import executionContractV1 from './execution-contract-v1.js';
64
+ import runtimeCapsule from './runtime-capsule.js';
65
+ import runtimeContract from './runtime-contract.js';
62
66
 
63
67
  export const openKDNA = publicApi.openKDNA;
64
68
  export const openKDNASync = publicApi.openKDNASync;
@@ -80,58 +84,62 @@ export const matchDomain = publicApi.matchDomain;
80
84
  export const matchDomainSync = publicApi.matchDomainSync;
81
85
  export const composeKDNA = publicApi.composeKDNA;
82
86
 
83
- export const DIGEST_PROFILE = capsuleV2.DIGEST_PROFILE;
84
- export const CAPSULE_DIGEST_PROFILE = capsuleV2.CAPSULE_DIGEST_PROFILE;
85
- export const BASIS = capsuleV2.BASIS;
86
- export const computeAssetDigest = capsuleV2.computeAssetDigest;
87
- export const computeRuntimeEntrySetDigest = capsuleV2.computeRuntimeEntrySetDigest;
88
- export const computeDigestEvidence = capsuleV2.computeDigestEvidence;
89
- export const canonicalizeJcs = capsuleV2.canonicalizeJcs;
90
- export const computeCapsuleDeliveryDigest = capsuleV2.computeCapsuleDeliveryDigest;
91
- export const buildCapsuleV2 = capsuleV2.buildCapsuleV2;
92
- export const loadCapsuleV2 = capsuleV2.loadCapsuleV2;
93
- export const adaptCapsuleV2ToV1 = capsuleV2.adaptCapsuleV2ToV1;
87
+ export const DIGEST_PROFILE = runtimeCapsule.DIGEST_PROFILE;
88
+ export const DIGEST_PROFILE_VERSION = runtimeCapsule.DIGEST_PROFILE_VERSION;
89
+ export const CAPSULE_DIGEST_PROFILE = runtimeCapsule.CAPSULE_DIGEST_PROFILE;
90
+ export const CANONICALIZATION_PROFILE_VERSION = runtimeCapsule.CANONICALIZATION_PROFILE_VERSION;
91
+ export const RUNTIME_CAPSULE_CONTRACT_VERSION = runtimeCapsule.RUNTIME_CAPSULE_CONTRACT_VERSION;
92
+ export const BASIS = runtimeCapsule.BASIS;
93
+ export const computeAssetDigest = runtimeCapsule.computeAssetDigest;
94
+ export const computeRuntimeEntrySetDigest = runtimeCapsule.computeRuntimeEntrySetDigest;
95
+ export const computeDigestEvidence = runtimeCapsule.computeDigestEvidence;
96
+ export const canonicalizeJcs = runtimeCapsule.canonicalizeJcs;
97
+ export const computeCapsuleDeliveryDigest = runtimeCapsule.computeCapsuleDeliveryDigest;
98
+ export const buildRuntimeCapsule = runtimeCapsule.buildRuntimeCapsule;
99
+ export const loadRuntimeCapsule = runtimeCapsule.loadRuntimeCapsule;
94
100
 
95
- export const KDNAExecutionContractError = executionContractV1.KDNAExecutionContractError;
96
- export const PLAN_DIGEST_PROFILE = executionContractV1.PLAN_DIGEST_PROFILE;
97
- export const HOST_PROTOCOL = executionContractV1.HOST_PROTOCOL;
98
- export const DEFAULT_CORE_CAPSULE_VERSIONS = executionContractV1.DEFAULT_CORE_CAPSULE_VERSIONS;
99
- export const parseExecutionContractJsonV1 = executionContractV1.parseExecutionContractJsonV1;
100
- export const computeConsumptionPlanDigestV1 = executionContractV1.computeConsumptionPlanDigestV1;
101
- export const buildConsumptionPlanV1 = executionContractV1.buildConsumptionPlanV1;
102
- export const validateConsumptionPlanV1 = executionContractV1.validateConsumptionPlanV1;
103
- export const negotiateExecutionPairV1 = executionContractV1.negotiateExecutionPairV1;
104
- export const buildAgentHost2RequestV1 = executionContractV1.buildAgentHost2RequestV1;
105
- export const validateAgentHost2RequestV1 = executionContractV1.validateAgentHost2RequestV1;
106
- export const validateAgentHost2ReceiptV1 = executionContractV1.validateAgentHost2ReceiptV1;
107
- export const deriveBudgetEvidenceV1 = executionContractV1.deriveBudgetEvidenceV1;
108
- export const buildPreHostBudgetBlockedTraceV1 =
109
- executionContractV1.buildPreHostBudgetBlockedTraceV1;
110
- export const validatePreHostBudgetBlockedTraceV1 =
111
- executionContractV1.validatePreHostBudgetBlockedTraceV1;
112
- export const buildJudgmentTraceV1 = executionContractV1.buildJudgmentTraceV1;
113
- export const validateJudgmentTraceV1 = executionContractV1.validateJudgmentTraceV1;
101
+ export const KDNARuntimeContractError = runtimeContract.KDNARuntimeContractError;
102
+ export const PLAN_DIGEST_PROFILE = runtimeContract.PLAN_DIGEST_PROFILE;
103
+ export const PLAN_DIGEST_PROFILE_VERSION = runtimeContract.PLAN_DIGEST_PROFILE_VERSION;
104
+ export const HOST_PROTOCOL = runtimeContract.HOST_PROTOCOL;
105
+ export const HOST_PROTOCOL_VERSION = runtimeContract.HOST_PROTOCOL_VERSION;
106
+ export const DEFAULT_CORE_CAPSULE_CONTRACT_VERSIONS =
107
+ runtimeContract.DEFAULT_CORE_CAPSULE_CONTRACT_VERSIONS;
108
+ export const parseRuntimeContractJson = runtimeContract.parseRuntimeContractJson;
109
+ export const computeConsumptionPlanDigest = runtimeContract.computeConsumptionPlanDigest;
110
+ export const buildConsumptionPlan = runtimeContract.buildConsumptionPlan;
111
+ export const validateConsumptionPlan = runtimeContract.validateConsumptionPlan;
112
+ export const negotiateRuntimePair = runtimeContract.negotiateRuntimePair;
113
+ export const buildAgentHostRequest = runtimeContract.buildAgentHostRequest;
114
+ export const validateAgentHostRequest = runtimeContract.validateAgentHostRequest;
115
+ export const validateAgentHostReceipt = runtimeContract.validateAgentHostReceipt;
116
+ export const deriveBudgetEvidence = runtimeContract.deriveBudgetEvidence;
117
+ export const buildPreHostBudgetBlockedTrace =
118
+ runtimeContract.buildPreHostBudgetBlockedTrace;
119
+ export const validatePreHostBudgetBlockedTrace =
120
+ runtimeContract.validatePreHostBudgetBlockedTrace;
121
+ export const buildJudgmentTrace = runtimeContract.buildJudgmentTrace;
122
+ export const validateJudgmentTrace = runtimeContract.validateJudgmentTrace;
114
123
 
115
124
  export const STANDARD_ENTRIES = assetReader.STANDARD_ENTRIES;
116
125
  export const createKdnaAssetReader = assetReader.createKdnaAssetReader;
117
126
 
118
127
  export const {
119
128
  LICENSED_ENTRY_PROFILE,
120
- LICENSED_EXPERIMENTAL_PROFILE,
121
129
  PASSWORD_PROTECTED_PROFILE,
122
130
  PASSWORD_PROTECTED_SCRYPT_PROFILE,
131
+ ENCRYPTION_PROFILE_VERSION,
123
132
  ALG,
124
133
  RFC_KDF,
125
134
  RFC_KEY_WRAPPING,
126
- LEGACY_KDF,
127
135
  PASSWORD_KDF,
128
136
  SCRYPT_KDF,
129
137
  deriveWrappingKey,
130
138
  generateCEK,
131
139
  wrapCEK,
132
140
  unwrapCEK,
133
- encryptLicensedEntryV1,
134
- decryptLicensedEntryV1,
141
+ encryptLicensedEntry,
142
+ decryptLicensedEntry,
135
143
  derivePasswordKey,
136
144
  generateRecoveryCode,
137
145
  decodeRecoveryCode,
@@ -143,11 +151,6 @@ export const {
143
151
  encryptProtectedEntryScrypt,
144
152
  decryptProtectedEntryScrypt,
145
153
  createPasswordDecryptEntryScrypt,
146
- deriveLicensedEntryKey,
147
- encryptLicensedEntryLegacy,
148
- decryptLicensedEntryLegacy,
149
- encryptLicensedEntry,
150
- decryptLicensedEntry,
151
154
  createLicensedDecryptEntry,
152
155
  hkdfSha256,
153
156
  aesWrap,
@@ -159,6 +162,7 @@ export const {
159
162
  EXTERNAL_GRANT_PROFILE,
160
163
  EXTERNAL_AAD_PROFILE,
161
164
  DEVICE_KEK_PROFILE,
165
+ EXTERNAL_GRANT_CONTRACT_VERSION,
162
166
  KDNAExternalGrantError,
163
167
  canonicalJson,
164
168
  grantSigningPayload,