@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.
- package/CHANGELOG.md +128 -94
- package/README.md +109 -215
- package/package.json +7 -2
- package/schema/agent-host-capabilities.schema.json +62 -0
- package/schema/agent-host-receipt.schema.json +371 -0
- package/schema/agent-host-request.schema.json +223 -0
- package/schema/{bundle-profile-v1.schema.json → bundle-profile.schema.json} +9 -5
- package/schema/checksums.schema.json +8 -8
- package/schema/consumption-plan.schema.json +428 -0
- package/schema/digest-evidence.schema.json +115 -26
- package/schema/{kdna-envelope-external-grant-v1.schema.json → external-grant-envelope.schema.json} +17 -4
- package/schema/{kdna-key-grant-v1.schema.json → external-key-grant.schema.json} +39 -7
- package/schema/judgment-trace.schema.json +1251 -0
- package/schema/load-plan.schema.json +5 -3
- package/schema/manifest.schema.json +70 -10
- package/schema/{payload-profile-v1.schema.json → payload-profile.schema.json} +30 -6
- package/schema/{runtime-capsule-2.schema.json → runtime-capsule.schema.json} +7 -31
- package/src/asset-reader.js +8 -2
- package/src/{v1 → container}/index.js +385 -198
- package/src/container/index.mjs +20 -0
- package/src/container-dispatcher.js +6 -6
- package/src/crypto-profile.js +37 -107
- package/src/external-key-grant.js +41 -15
- package/src/index.js +11 -8
- package/src/index.mjs +49 -45
- package/src/loader-compatibility.js +59 -0
- package/src/loader.js +1 -1
- package/src/public-api.js +5 -5
- package/src/remote-runtime.d.ts +13 -0
- package/src/remote-runtime.js +27 -0
- package/src/remote-runtime.mjs +3 -0
- package/src/runtime-api.js +5 -5
- package/src/{capsule-v2.js → runtime-capsule.js} +96 -254
- package/src/{execution-contract-v1.js → runtime-contract.js} +184 -152
- package/src/types.d.ts +200 -277
- package/src/workpack-pure.js +1 -1
- package/schema/agent-host-2-receipt.schema.json +0 -206
- package/schema/agent-host-2-request.schema.json +0 -135
- package/schema/agent-host-capabilities-1.schema.json +0 -40
- package/schema/consumption-plan-1.schema.json +0 -280
- package/schema/judgment-trace-1.schema.json +0 -748
- package/schema/runtime-capsule-1.schema.json +0 -68
- package/src/lint-pure.js +0 -433
- 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
|
|
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:
|
|
198
|
-
payloadRaw:
|
|
199
|
-
checksums:
|
|
200
|
-
? (() => { try { return JSON.parse(
|
|
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(
|
|
205
|
+
entries: new Map(Object.entries(layout.map)),
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
|
package/src/crypto-profile.js
CHANGED
|
@@ -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
|
|
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
|
|
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 (
|
|
59
|
+
throw new Error('encrypted entry envelope must be CBOR (format 0.1.0)');
|
|
67
60
|
}
|
|
68
61
|
|
|
69
|
-
function encryptedEntryAad(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
432
|
+
// ── B2: scrypt-based password profile (0.1 write profile) ─────────
|
|
426
433
|
// Uses Node built-in crypto.scryptSync — zero additional dependencies.
|
|
427
|
-
//
|
|
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
|
|
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
|
|
552
|
+
const { licenseKey } = options;
|
|
615
553
|
return ({ entryName, ciphertext, manifest }) =>
|
|
616
|
-
decryptLicensedEntry(ciphertext, { entryName, manifest, licenseKey
|
|
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
|
-
|
|
638
|
-
|
|
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 (
|
|
586
|
+
// B2 scrypt password profile (0.1 write profile)
|
|
650
587
|
derivePasswordKeyScrypt,
|
|
651
588
|
encryptProtectedEntryScrypt,
|
|
652
589
|
decryptProtectedEntryScrypt,
|
|
653
590
|
createPasswordDecryptEntryScrypt,
|
|
654
591
|
|
|
655
|
-
//
|
|
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
|
|
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
|
|
17
|
-
const EXTERNAL_GRANT_PROFILE = 'kdna-key
|
|
18
|
-
const EXTERNAL_AAD_PROFILE = 'kdna-
|
|
19
|
-
const DEVICE_KEK_PROFILE = 'kdna-device-grant
|
|
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
|
|
25
|
+
const ajv = new JsonSchema2020({ allErrors: true, strict: true });
|
|
25
26
|
addFormats(ajv);
|
|
26
27
|
const validateEnvelopeSchema = ajv.compile(
|
|
27
|
-
require(path.join(schemaRoot, '
|
|
28
|
+
require(path.join(schemaRoot, 'external-grant-envelope.schema.json')),
|
|
28
29
|
);
|
|
29
30
|
const validateGrantSchema = ajv.compile(
|
|
30
|
-
require(path.join(schemaRoot, '
|
|
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 ||
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
13
|
+
const container = require('./container');
|
|
15
14
|
const runtimeApi = require('./runtime-api');
|
|
16
|
-
const
|
|
17
|
-
const
|
|
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
|
-
...
|
|
29
|
+
...container,
|
|
31
30
|
...runtimeApi,
|
|
32
|
-
...
|
|
33
|
-
...
|
|
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 './
|
|
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
|
|
61
|
-
import
|
|
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 =
|
|
84
|
-
export const
|
|
85
|
-
export const
|
|
86
|
-
export const
|
|
87
|
-
export const
|
|
88
|
-
export const
|
|
89
|
-
export const
|
|
90
|
-
export const
|
|
91
|
-
export const
|
|
92
|
-
export const
|
|
93
|
-
export const
|
|
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
|
|
96
|
-
export const PLAN_DIGEST_PROFILE =
|
|
97
|
-
export const
|
|
98
|
-
export const
|
|
99
|
-
export const
|
|
100
|
-
export const
|
|
101
|
-
|
|
102
|
-
export const
|
|
103
|
-
export const
|
|
104
|
-
export const
|
|
105
|
-
export const
|
|
106
|
-
export const
|
|
107
|
-
export const
|
|
108
|
-
export const
|
|
109
|
-
|
|
110
|
-
export const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
export const
|
|
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
|
-
|
|
134
|
-
|
|
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,
|