@aikdna/kdna-core 0.15.11 → 0.16.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 +60 -0
- package/README.md +36 -21
- package/package.json +2 -7
- package/schema/kdna-envelope-external-grant-v1.schema.json +20 -0
- package/schema/kdna-key-grant-v1.schema.json +54 -0
- package/schema/load-plan.schema.json +1 -1
- package/schema/manifest.schema.json +3 -3
- package/src/asset-reader.js +7 -11
- package/src/container-dispatcher.js +24 -102
- package/src/crypto-profile.js +6 -3
- package/src/external-key-grant.js +560 -0
- package/src/index.js +4 -0
- package/src/index.mjs +39 -0
- package/src/lint-pure.js +8 -13
- package/src/runtime-api.js +91 -0
- package/src/types.d.ts +174 -19
- package/src/v1/index.js +245 -95
- package/src/v1/index.mjs +7 -8
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const v1 = require('./v1');
|
|
6
|
+
|
|
7
|
+
function invalidDirectoryPlan(inputPath) {
|
|
8
|
+
const resolved = path.resolve(inputPath);
|
|
9
|
+
return {
|
|
10
|
+
kdna_version: null,
|
|
11
|
+
asset: { asset_id: null, asset_uid: null, title: null, version: null, judgment_version: null },
|
|
12
|
+
access: null,
|
|
13
|
+
access_alias: null,
|
|
14
|
+
entitlement_profile: null,
|
|
15
|
+
state: 'invalid',
|
|
16
|
+
required_action: 'block',
|
|
17
|
+
can_load_now: false,
|
|
18
|
+
projection_policy: 'none',
|
|
19
|
+
input_fingerprint: null,
|
|
20
|
+
checks: {
|
|
21
|
+
format_valid: false,
|
|
22
|
+
schema_valid: false,
|
|
23
|
+
payload_valid: false,
|
|
24
|
+
checksums_valid: false,
|
|
25
|
+
load_contract_valid: false,
|
|
26
|
+
overall_valid: false,
|
|
27
|
+
},
|
|
28
|
+
issues: [{
|
|
29
|
+
code: 'KDNA_ASSET_FILE_REQUIRED',
|
|
30
|
+
severity: 'blocking',
|
|
31
|
+
message: 'Runtime loading requires a packaged .kdna asset file. Source directories are authoring inputs only.',
|
|
32
|
+
}],
|
|
33
|
+
source: { kind: 'dir', path: resolved },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isDirectory(inputPath) {
|
|
38
|
+
if (typeof inputPath !== 'string') return false;
|
|
39
|
+
try {
|
|
40
|
+
return fs.statSync(path.resolve(inputPath)).isDirectory();
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function guardResolver(options = {}) {
|
|
47
|
+
if (typeof options.resolveAsset !== 'function') return options;
|
|
48
|
+
const resolveAsset = options.resolveAsset;
|
|
49
|
+
return {
|
|
50
|
+
...options,
|
|
51
|
+
resolveAsset(name) {
|
|
52
|
+
const resolved = resolveAsset(name);
|
|
53
|
+
if (resolved?.path && isDirectory(resolved.path)) {
|
|
54
|
+
const error = new Error(
|
|
55
|
+
`Resolved asset "${name}" is a source directory. Runtime dependencies must be packaged .kdna files.`,
|
|
56
|
+
);
|
|
57
|
+
error.code = 'KDNA_ASSET_FILE_REQUIRED';
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function planLoad(inputPath, options = {}) {
|
|
66
|
+
if (isDirectory(inputPath)) return invalidDirectoryPlan(inputPath);
|
|
67
|
+
return v1.planLoad(inputPath, guardResolver(options));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function loadAuthorized(inputPath, options = {}) {
|
|
71
|
+
const plan = planLoad(inputPath, options);
|
|
72
|
+
if (plan.can_load_now !== true) {
|
|
73
|
+
const issueCodes = Array.isArray(plan.issues)
|
|
74
|
+
? plan.issues.map((issue) => issue.code).filter(Boolean)
|
|
75
|
+
: [];
|
|
76
|
+
const error = new Error(
|
|
77
|
+
`LoadPlan denied loading: state=${plan.state || 'invalid'} required_action=${plan.required_action || 'block'}`,
|
|
78
|
+
);
|
|
79
|
+
error.code = issueCodes[0] || 'KDNA_LOAD_NOT_AUTHORIZED';
|
|
80
|
+
error.plan = plan;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
return v1.loadAuthorized(inputPath, guardResolver(options));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
planLoad,
|
|
88
|
+
loadAuthorized,
|
|
89
|
+
load: loadAuthorized,
|
|
90
|
+
loadAsset: loadAuthorized,
|
|
91
|
+
};
|
package/src/types.d.ts
CHANGED
|
@@ -203,9 +203,7 @@ export interface KDNAFileDataMap {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
export interface KDNAManifest {
|
|
206
|
-
|
|
207
|
-
format_version: '2.0';
|
|
208
|
-
spec_version: string;
|
|
206
|
+
kdna_version: '1.0';
|
|
209
207
|
name: string;
|
|
210
208
|
version: string;
|
|
211
209
|
judgment_version: string;
|
|
@@ -416,6 +414,165 @@ export function createLicensedDecryptEntry(options: {
|
|
|
416
414
|
machineFingerprint: string;
|
|
417
415
|
}): NonNullable<KdnaDecryptOptions['decryptEntry']>;
|
|
418
416
|
|
|
417
|
+
export const EXTERNAL_ENVELOPE_PROFILE: 'kdna-envelope-external-grant-v1';
|
|
418
|
+
export const EXTERNAL_GRANT_PROFILE: 'kdna-key-grant-v1';
|
|
419
|
+
|
|
420
|
+
export class KDNAExternalGrantError extends Error {
|
|
421
|
+
code: string;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export interface KDNAExternalGrantEnvelope {
|
|
425
|
+
profile: 'kdna-envelope-external-grant-v1';
|
|
426
|
+
alg: 'A256GCM';
|
|
427
|
+
cek_derivation: 'HKDF-SHA256';
|
|
428
|
+
key_ref: string;
|
|
429
|
+
issuer_key_id: string;
|
|
430
|
+
entry_path: string;
|
|
431
|
+
plaintext_digest: string;
|
|
432
|
+
iv: string;
|
|
433
|
+
tag: string;
|
|
434
|
+
ciphertext: string;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export interface KDNAExternalKeyGrant {
|
|
438
|
+
profile: 'kdna-key-grant-v1';
|
|
439
|
+
grant_id: string;
|
|
440
|
+
issuer: string;
|
|
441
|
+
signing_key_id: string;
|
|
442
|
+
entitlement_id: string;
|
|
443
|
+
account_id: string;
|
|
444
|
+
device_id: string;
|
|
445
|
+
device_public_key: string;
|
|
446
|
+
device_signing_public_key: string;
|
|
447
|
+
asset: {
|
|
448
|
+
asset_id: string;
|
|
449
|
+
asset_uid: string;
|
|
450
|
+
version: string;
|
|
451
|
+
digest: string;
|
|
452
|
+
entry_path: string;
|
|
453
|
+
ciphertext_digest: string;
|
|
454
|
+
key_ref: string;
|
|
455
|
+
issuer_key_id: string;
|
|
456
|
+
};
|
|
457
|
+
issued_at: string;
|
|
458
|
+
refresh_after: string;
|
|
459
|
+
offline_grace_until: string;
|
|
460
|
+
expires_at: string;
|
|
461
|
+
status: 'active' | 'revoked' | 'expired';
|
|
462
|
+
status_version: number;
|
|
463
|
+
wrap: {
|
|
464
|
+
alg: 'X25519-HKDF-SHA256+A256KW';
|
|
465
|
+
ephemeral_public_key: string;
|
|
466
|
+
salt: string;
|
|
467
|
+
wrapped_cek: string;
|
|
468
|
+
};
|
|
469
|
+
signature: string;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export interface KDNADeviceKeyPairs {
|
|
473
|
+
agreement: { public_key: string; private_key: string };
|
|
474
|
+
signing: { public_key: string; private_key: string };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Runtime-branded by authorizeExternalKeyGrant; a structurally similar object is not accepted. */
|
|
478
|
+
export interface KDNAVerifiedExternalEntitlement {
|
|
479
|
+
readonly status: 'active' | 'offline_grace';
|
|
480
|
+
readonly profile: 'kdna-key-grant-v1';
|
|
481
|
+
readonly grant_id: string;
|
|
482
|
+
readonly entitlement_id: string;
|
|
483
|
+
readonly account_id: string;
|
|
484
|
+
readonly device_id: string;
|
|
485
|
+
readonly refresh_after: string;
|
|
486
|
+
readonly offline_grace_until: string;
|
|
487
|
+
readonly expires_at: string;
|
|
488
|
+
readonly asset: {
|
|
489
|
+
readonly asset_id: string;
|
|
490
|
+
readonly asset_uid: string;
|
|
491
|
+
readonly version: string;
|
|
492
|
+
readonly digest: string;
|
|
493
|
+
readonly entry_path: string;
|
|
494
|
+
readonly ciphertext_digest: string;
|
|
495
|
+
readonly key_ref: string;
|
|
496
|
+
readonly issuer_key_id: string;
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export interface KDNAExternalAuthorization {
|
|
501
|
+
entitlement: KDNAVerifiedExternalEntitlement;
|
|
502
|
+
decryptEntry: NonNullable<KdnaDecryptOptions['decryptEntry']>;
|
|
503
|
+
dispose(): void;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function canonicalJson(value: unknown): string;
|
|
507
|
+
export function grantSigningPayload(grant: Omit<KDNAExternalKeyGrant, 'signature'> | KDNAExternalKeyGrant): Uint8Array;
|
|
508
|
+
export function validateExternalEnvelope(value: unknown): KDNAExternalGrantEnvelope;
|
|
509
|
+
export function validateExternalKeyGrant(value: unknown): KDNAExternalKeyGrant;
|
|
510
|
+
export function externalEnvelopeAad(options: {
|
|
511
|
+
manifest: KDNAManifest;
|
|
512
|
+
entryName: string;
|
|
513
|
+
plaintextDigest: string;
|
|
514
|
+
keyRef: string;
|
|
515
|
+
issuerKeyId: string;
|
|
516
|
+
}): Uint8Array;
|
|
517
|
+
export function deriveExternalAssetCek(options: {
|
|
518
|
+
issuerRootKey: string | Uint8Array;
|
|
519
|
+
manifest: KDNAManifest;
|
|
520
|
+
entryName: string;
|
|
521
|
+
plaintextDigest: string;
|
|
522
|
+
keyRef: string;
|
|
523
|
+
issuerKeyId: string;
|
|
524
|
+
}): Uint8Array;
|
|
525
|
+
export function encodeExternalEnvelope(value: KDNAExternalGrantEnvelope): Uint8Array;
|
|
526
|
+
export function decodeExternalEnvelope(value: KDNAExternalGrantEnvelope | Uint8Array): KDNAExternalGrantEnvelope;
|
|
527
|
+
export function encryptExternalGrantEntry(plaintext: string | Uint8Array, options: {
|
|
528
|
+
manifest: KDNAManifest;
|
|
529
|
+
entryName?: string;
|
|
530
|
+
issuerRootKey: string | Uint8Array;
|
|
531
|
+
keyRef: string;
|
|
532
|
+
issuerKeyId: string;
|
|
533
|
+
iv?: Uint8Array;
|
|
534
|
+
}): KDNAExternalGrantEnvelope;
|
|
535
|
+
export function generateDeviceKeyPairs(): KDNADeviceKeyPairs;
|
|
536
|
+
export function createExternalKeyGrant(options: {
|
|
537
|
+
issuerRootKey: string | Uint8Array;
|
|
538
|
+
issuerSigningPrivateKey: unknown;
|
|
539
|
+
signingKeyId: string;
|
|
540
|
+
issuer: string;
|
|
541
|
+
entitlementId: string;
|
|
542
|
+
accountId: string;
|
|
543
|
+
deviceId: string;
|
|
544
|
+
devicePublicKey: string;
|
|
545
|
+
deviceSigningPublicKey: string;
|
|
546
|
+
manifest: KDNAManifest;
|
|
547
|
+
envelope: KDNAExternalGrantEnvelope | Uint8Array;
|
|
548
|
+
assetDigest: string;
|
|
549
|
+
status?: 'active' | 'revoked' | 'expired';
|
|
550
|
+
statusVersion?: number;
|
|
551
|
+
issuedAt?: Date | string;
|
|
552
|
+
refreshAfter?: Date | string;
|
|
553
|
+
offlineGraceUntil?: Date | string;
|
|
554
|
+
expiresAt?: Date | string;
|
|
555
|
+
grantId?: string;
|
|
556
|
+
ephemeralKeyPair?: unknown;
|
|
557
|
+
wrapSalt?: Uint8Array;
|
|
558
|
+
}): KDNAExternalKeyGrant;
|
|
559
|
+
export function authorizeExternalKeyGrant(options: {
|
|
560
|
+
grant: KDNAExternalKeyGrant;
|
|
561
|
+
issuerPublicKeys: Map<string, unknown> | Record<string, unknown>;
|
|
562
|
+
manifest: KDNAManifest;
|
|
563
|
+
checksums: KDNAChecksums;
|
|
564
|
+
envelope: KDNAExternalGrantEnvelope | Uint8Array;
|
|
565
|
+
deviceAgreementKey: KDNADeviceKeyPairs['agreement'] | unknown;
|
|
566
|
+
expectedAccountId: string;
|
|
567
|
+
expectedDeviceId: string;
|
|
568
|
+
expectedDeviceSigningPublicKey: string;
|
|
569
|
+
minimumStatusVersion?: number;
|
|
570
|
+
now?: Date | string;
|
|
571
|
+
networkAvailable?: boolean;
|
|
572
|
+
allowOffline?: boolean;
|
|
573
|
+
}): KDNAExternalAuthorization;
|
|
574
|
+
export function isVerifiedExternalEntitlement(value: unknown): value is KDNAVerifiedExternalEntitlement;
|
|
575
|
+
|
|
419
576
|
// Stable public API — preferred entry points for third-party integrations.
|
|
420
577
|
export type KDNAAssetInput = string | Uint8Array | KdnaAsset;
|
|
421
578
|
|
|
@@ -484,36 +641,33 @@ export function matchDomain(input: string, candidates: Array<KDNAAssetInput | KD
|
|
|
484
641
|
export function matchDomainSync(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): KDNAMatchResult[];
|
|
485
642
|
export function composeKDNA(inputs: KDNAAssetInput[], options?: { input?: string; profile?: 'compact' | 'scenario' | 'full' | string; separator?: string } & KdnaDecryptOptions): Promise<KDNAComposeResult>;
|
|
486
643
|
|
|
487
|
-
// KDNA Core
|
|
644
|
+
// KDNA Core — authoring source and packaged runtime API
|
|
488
645
|
export const MIMETYPE: string;
|
|
489
|
-
export const
|
|
490
|
-
export const MIMETYPE_V2: string;
|
|
491
|
-
export const V1_REQUIRED_DIR_ENTRIES: string[];
|
|
646
|
+
export const REQUIRED_DIR_ENTRIES: string[];
|
|
492
647
|
|
|
493
|
-
export interface
|
|
648
|
+
export interface KDNAChecksumEntry {
|
|
494
649
|
algorithm: 'sha256';
|
|
495
650
|
value: string;
|
|
496
651
|
}
|
|
497
652
|
|
|
498
|
-
export interface
|
|
653
|
+
export interface KDNAChecksums {
|
|
499
654
|
algorithm: 'sha256';
|
|
500
655
|
manifest_digest: string;
|
|
501
656
|
payload_digest: string;
|
|
502
657
|
asset_digest: string;
|
|
503
|
-
entries: Record<string,
|
|
658
|
+
entries: Record<string, KDNAChecksumEntry>;
|
|
504
659
|
}
|
|
505
660
|
|
|
506
|
-
export function
|
|
507
|
-
export function
|
|
508
|
-
export function detectContainerFormat(inputPath: string): 'v1' | 'v2' | null;
|
|
661
|
+
export function isKdnaSourceDir(inputPath: string): boolean;
|
|
662
|
+
export function detectContainerFormat(inputPath: string): 'kdna' | null;
|
|
509
663
|
export function inspect(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
510
664
|
export function validate(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
511
|
-
export interface
|
|
665
|
+
export interface KDNALoadPlanIssue {
|
|
512
666
|
code: string;
|
|
513
667
|
severity: 'info' | 'warning' | 'blocking' | string;
|
|
514
668
|
message: string;
|
|
515
669
|
}
|
|
516
|
-
export interface
|
|
670
|
+
export interface KDNALoadPlan {
|
|
517
671
|
kdna_version: string | null;
|
|
518
672
|
asset: {
|
|
519
673
|
asset_id: string | null;
|
|
@@ -552,18 +706,19 @@ export interface KDNAV1LoadPlan {
|
|
|
552
706
|
projection_policy: 'minimal' | 'remote' | 'none' | string;
|
|
553
707
|
input_fingerprint: string | null;
|
|
554
708
|
checks: Record<string, boolean>;
|
|
555
|
-
issues:
|
|
709
|
+
issues: KDNALoadPlanIssue[];
|
|
556
710
|
source: {
|
|
557
711
|
kind: 'dir' | 'file' | string | null;
|
|
558
712
|
path: string;
|
|
559
713
|
};
|
|
560
714
|
}
|
|
561
|
-
export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: Record<string, any> }):
|
|
562
|
-
export function
|
|
715
|
+
export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: KDNAVerifiedExternalEntitlement | Record<string, any> }): KDNALoadPlan;
|
|
716
|
+
export function buildChecksums(sourceDir: string): KDNAChecksums;
|
|
563
717
|
export function pack(sourceDir: string, outputPath: string): void;
|
|
564
718
|
export function unpack(inputPath: string, outputDir: string): void;
|
|
565
719
|
export function loadAuthorized(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
566
|
-
export function
|
|
720
|
+
export function load(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
721
|
+
export function loadAsset(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
567
722
|
export const FORBIDDEN_OUTPUT_TERMS: readonly string[];
|
|
568
723
|
|
|
569
724
|
// Lint
|