@aikdna/kdna-core 0.36.0 → 0.37.1-rc.browser.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/README.md +12 -2
- package/package.json +11 -1
- package/schema/manifest.schema.json +2 -1
- package/src/public-contract/browser-native/generated-contract.json +3 -3
- package/src/public-contract/browser-native/sections/contract.json +2 -2
- package/src/public-contract/browser-protected/request.js +77 -0
- package/src/public-contract/browser-protected/state.js +200 -0
- package/src/public-contract/browserwhole06/contract.json +2 -2
- package/src/public-contract/creation06/contract.json +2 -2
- package/src/public-contract/creation06/generation-manifest.json +9 -9
- package/src/public-contract/execution06/contract.json +2 -2
- package/src/public-contract/execution06/generation-manifest.json +9 -9
- package/src/public-contract/generated-contract.json +3 -3
- package/src/public-contract/issuer-contract.generated.json +6 -6
- package/src/public-contract/issuer-validators.generated.js +7 -7
- package/src/public-contract/key-grant-issuer-node.d.ts +1 -1
- package/src/public-contract/package-set-contract.generated.json +5 -5
- package/src/public-contract/package-set-node.d.ts +1 -1
- package/src/public-contract/package-set-validators.generated.js +9 -9
- package/src/public-contract/packageset06/contract.json +2 -2
- package/src/public-contract/packageset06/generation-manifest.json +9 -9
- package/src/public-contract/protected-browser-contract/contract.json +11824 -0
- package/src/public-contract/protected-browser-contract/descriptor.json +5 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedAdmissionResult.json +443 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedAuthorityContext.json +939 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedBinding.json +156 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedCallResult.json +10489 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedDeliveryToken.json +10 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedExpansionHandle.json +320 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedHostContext.json +677 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedHostObservation.json +208 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedReadAuthority.json +10 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedReadResult.json +10795 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedRequest.json +539 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedRequestAdmission.json +1016 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedSignaturePolicy.json +35 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedSnapshotData.json +8451 -0
- package/src/public-contract/protected-browser-contract/schemas/BrowserProtectedTransportCommitResult.json +304 -0
- package/src/public-contract/protected-browser-contract/schemas/TrustedBrowserProtectedHost.json +10 -0
- package/src/public-contract/protected-browser-contract/types.d.ts +389 -0
- package/src/public-contract/protected-browser-contract/validators.cjs +223756 -0
- package/src/public-contract/protected-browser.d.ts +17 -0
- package/src/public-contract/protected-browser.js +4 -0
- package/src/public-contract/protected-browser.mjs +2 -0
- package/src/public-contract/protected-sections-browser.d.ts +11 -0
- package/src/public-contract/protected-sections-browser.js +22 -0
- package/src/public-contract/protected-sections-browser.mjs +2 -0
- package/src/public-contract/protected-source-contract.generated.json +8 -8
- package/src/public-contract/protected-source-node.d.ts +1 -1
- package/src/public-contract/protection-admission-browser.js +132 -0
- package/src/public-contract/protection-contract.generated.json +14 -13
- package/src/public-contract/protection-crypto.js +7 -17
- package/src/public-contract/protection-envelope-codec.js +76 -13
- package/src/public-contract/protection-node.d.ts +2 -2
- package/src/public-contract/protection-validators.generated.js +5 -5
- package/src/public-contract/retained/contract.json +2 -2
- package/src/public-contract/retained/generation-manifest.json +9 -9
- package/src/public-contract/retained-browser06/contract.json +2 -2
- package/src/public-contract/sectionbytes/contract.json +2 -2
- package/src/public-contract/sectionbytes/generation-manifest.json +9 -9
- package/src/public-contract/sections/contract.json +2 -2
- package/src/public-contract/sections/generation-manifest.json +9 -9
- package/src/public-contract/source-validators.generated.js +6 -6
- package/src/public-contract/source06/contract.json +2 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated protected-browser definitions; schema validity is not authority.
|
|
2
|
+
import type { CanonicalReadSnapshot, CoreAdmissionCatalogOnly, CoreAdmissionRejected } from './types.js';
|
|
3
|
+
import type { ProtectionDiagnostic, ProtectionRuntimeDescriptor, ProtectedSignaturePolicy, TrustedProtectionProvider } from './protection-node.js';
|
|
4
|
+
export type ConsumerUnlockSelection = { readonly "slotIndex": number; readonly "slot": string; readonly "kdf_profile": "scrypt-sha256" | "argon2id"; };
|
|
5
|
+
export type ConsumerUnlockObservation = { readonly "kind": "consumer_unlock_observation"; readonly "proof": "observation_not_authority"; readonly "checked_at_ms": number; readonly "selection": ConsumerUnlockSelection; };
|
|
6
|
+
export type ProtectedBrowserInput = { readonly "bytes": Uint8Array; readonly "plaintextPayload": Uint8Array; readonly "observation": ConsumerUnlockObservation; readonly "signaturePolicy"?: ProtectedSignaturePolicy; };
|
|
7
|
+
export type ProtectedBrowserDisclosure = { readonly "slot_selection": ConsumerUnlockSelection; readonly "observation_not_authority": true; readonly "provenance": "host_supplied_triple"; };
|
|
8
|
+
export type ProtectedBrowserAccepted = { readonly "status": "accepted"; readonly "snapshot": CanonicalReadSnapshot; readonly "disclosure": ProtectedBrowserDisclosure; };
|
|
9
|
+
export type ProtectedBrowserCatalogOnly = { readonly "status": "catalog_only"; readonly "catalog": CoreAdmissionCatalogOnly; readonly "disclosure": ProtectedBrowserDisclosure; };
|
|
10
|
+
export type ProtectedBrowserCoreRejected = { readonly "status": "core_rejected"; readonly "stage": "container" | "manifest" | "payload" | "interpretation"; readonly "core": CoreAdmissionRejected; };
|
|
11
|
+
export type ProtectedBrowserFailed = { readonly "status": "protection_failed"; readonly "diagnostic": ProtectedBrowserDiagnostic; };
|
|
12
|
+
// The observation-phase codes are the reviewed family extension settled under
|
|
13
|
+
// D-CBPA-IMPL-3 (2026-10-06); the shared node codes keep their stages.
|
|
14
|
+
export type ProtectedBrowserDiagnostic = (ProtectionDiagnostic | { readonly "code": "PROTECTION_OBSERVATION_INVALID"; readonly "stage": "observation"; } | { readonly "code": "PROTECTION_OBSERVATION_BINDING_INVALID"; readonly "stage": "binding"; } | { readonly "code": "PROTECTION_NOT_DECLARED"; readonly "stage": "declaration"; });
|
|
15
|
+
export type ProtectedBrowserAdmissionResult = (ProtectedBrowserAccepted | ProtectedBrowserCatalogOnly | ProtectedBrowserCoreRejected | ProtectedBrowserFailed);
|
|
16
|
+
export declare function getProtectionContract(): ProtectionRuntimeDescriptor;
|
|
17
|
+
export declare function admitProtectedBrowser(input: ProtectedBrowserInput, provider: TrustedProtectionProvider): Promise<ProtectedBrowserAdmissionResult>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CanonicalReadSnapshot } from './types.js';
|
|
2
|
+
import type { BrowserProtectedRequestAdmission, AdmittedBrowserProtectedRequest, BrowserProtectedRequest, BrowserProtectedAuthorityContext, BrowserProtectedReadAuthority, BrowserProtectedAdmissionResult, BrowserProtectedSnapshot, BrowserProtectedSnapshotData, BrowserProtectedOperation } from './protected-browser-contract/types.js';
|
|
3
|
+
export type * from './protected-browser-contract/types.js';
|
|
4
|
+
export declare function admitProtectedPayloadRequestJson(requestJson: string): BrowserProtectedRequestAdmission;
|
|
5
|
+
export declare function inspectProtectedPayloadRequest(request: AdmittedBrowserProtectedRequest): Readonly<BrowserProtectedRequest> | null;
|
|
6
|
+
export declare function createProtectedPayloadReadAuthorityJson(callback: (context: Readonly<BrowserProtectedAuthorityContext>) => boolean | Promise<boolean>, signaturePolicyJson?: string): BrowserProtectedReadAuthority;
|
|
7
|
+
// Consumes one genuine admitProtectedBrowser origin. No new decryption or physical read.
|
|
8
|
+
export declare function admitProtectedSectionBrowser(snapshot: CanonicalReadSnapshot, request: AdmittedBrowserProtectedRequest, authority: BrowserProtectedReadAuthority): Promise<BrowserProtectedAdmissionResult>;
|
|
9
|
+
export declare function inspectProtectedPayloadSnapshot(snapshot: BrowserProtectedSnapshot): Readonly<BrowserProtectedSnapshotData> | null;
|
|
10
|
+
export declare function bindProtectedPayloadRequest(operation: BrowserProtectedOperation, requestJson: string): BrowserProtectedRequestAdmission;
|
|
11
|
+
export declare function disposeProtectedSectionOperation(operation: BrowserProtectedOperation): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const S = require('./browser-protected/state.js'), R = require('./browser-protected/request.js');
|
|
3
|
+
function bindProtectedPayloadRequest(operation, requestJson) {
|
|
4
|
+
const record = S.operation(operation);
|
|
5
|
+
if (!record) return R.failure(null);
|
|
6
|
+
const admitted = R.admit(requestJson);
|
|
7
|
+
if (admitted.channel !== 'admitted_request') return admitted;
|
|
8
|
+
try {
|
|
9
|
+
const token = S.normalized(S.request(admitted.request), record.view);
|
|
10
|
+
S.pairRequest(token, operation);
|
|
11
|
+
return Object.freeze({ channel: 'admitted_request', request: token });
|
|
12
|
+
} catch { return R.failure(S.inspectRequest(admitted.request)); }
|
|
13
|
+
}
|
|
14
|
+
module.exports = {
|
|
15
|
+
admitProtectedPayloadRequestJson: R.admit,
|
|
16
|
+
inspectProtectedPayloadRequest: S.inspectRequest,
|
|
17
|
+
createProtectedPayloadReadAuthorityJson: S.createAuthority,
|
|
18
|
+
admitProtectedSectionBrowser: S.admit,
|
|
19
|
+
inspectProtectedPayloadSnapshot: S.inspectSnapshot,
|
|
20
|
+
bindProtectedPayloadRequest,
|
|
21
|
+
disposeProtectedSectionOperation: S.dispose
|
|
22
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import api from './protected-sections-browser.js';
|
|
2
|
+
export const { admitProtectedPayloadRequestJson, inspectProtectedPayloadRequest, createProtectedPayloadReadAuthorityJson, admitProtectedSectionBrowser, inspectProtectedPayloadSnapshot, bindProtectedPayloadRequest, disposeProtectedSectionOperation } = api;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"api_typescript": "export type ProtectedSourceContractIdentity = { readonly id: 'kdna.protected-source/1'; readonly version: '1.0.0'; readonly definition_digest: import('./types.js').Digest };\nexport type ProtectedSourceRuntimeDescriptor = { readonly contract: ProtectedSourceContractIdentity; readonly implementation: { readonly package: '@aikdna/kdna-core'; readonly version: string }; readonly profiles: ReadonlyArray<'kdna.envelope.aead/0.1.0' | 'kdna.envelope.external-grant/0.1.0'>; readonly callables: ReadonlyArray<'getProtectedSourceContract' | 'createTrustedProtectedSourceHost' | 'withProtectedSourceNode' | 'commitProtectedSourceTransport' | 'previewProtectedSourceRevision' | 'produceProtectedSourceRevision'>; readonly limits: { readonly container_bytes: 26214400; readonly entries: 128; readonly entry_bytes: 8388608; readonly total_uncompressed_bytes: 12582912; readonly delivered_plaintext_bytes: 8388608; readonly documented_raw_byte_bound: 20971520; readonly timeout_ms_max: 60000 }; readonly proof_limits: ReadonlyArray<string> };\nexport type SourceHostObservationContext = { readonly context_id: string | null; readonly epoch: string | null; readonly asset_digest: string; readonly scope: 'complete_source'; readonly purpose: string; readonly requested_at_ms: number };\nexport type ProtectedSourceMember = { readonly name: string; readonly type: 'file'; readonly mode: number; readonly bytes: Uint8Array };\nexport type ProtectedSourceInventoryEntry = { readonly name: string; readonly type: 'file'; readonly mode: number; readonly size: number; readonly sha256: string };\nexport type ProtectedSourceBundle = { readonly identity: { readonly A: string; readonly C: string; readonly E: string }; readonly manifest: unknown; readonly payload: unknown; readonly original_members: ReadonlyArray<ProtectedSourceMember>; readonly original_inventory: ReadonlyArray<ProtectedSourceInventoryEntry>; readonly plaintext_payload: { readonly entry: 'payload.kdnab'; readonly bytes: Uint8Array; readonly sha256: string }; readonly prior_snapshot: unknown; readonly host_observation: unknown; readonly proof_limits: ReadonlyArray<string> };\nexport type ProtectedSourceRevisionContext = { readonly [brand: symbol]: true };\nexport type ProtectedSourcePreparedDelivery = { readonly [brand: symbol]: true };\nexport type ProtectedSourceHostCallbacks = { readonly observe: (context: SourceHostObservationContext) => SourceHostObservation | Promise<SourceHostObservation>; readonly deliver: (bundle: ProtectedSourceBundle, prepared: ProtectedSourcePreparedDelivery, context: ProtectedSourceRevisionContext) => boolean | Promise<boolean> };\nexport type TrustedProtectedSourceHost = { readonly [brand: symbol]: true };\nexport type ProtectedSourceRevisionOptions = { readonly admission: import('./protection-node.js').ProtectedAdmissionOptions; readonly expected_A: string; readonly timeout_ms: number };\nexport type TrustedProtectedSourceTransport = { readonly observeScope: () => SourceHostObservation | Promise<SourceHostObservation>; readonly commit: (bundle: ProtectedSourceBundle | null) => boolean };\nexport type ProtectedSourceCommitResult = { readonly status: 'source_committed'; readonly disclosure: import('./protection-node.js').ProtectionDisclosure; readonly body: null; readonly body_bytes: 0 } | SourceFailed;\nexport type ProtectedSourcePolicy = ({ readonly kind: 'password'; readonly asset_uid: string; readonly entitlement: { readonly profile: 'password'; readonly offline?: boolean; readonly revocable?: boolean }; readonly slots: ReadonlyArray<{ readonly slot: string; readonly kdf_profile: 'scrypt-sha256' | 'argon2id' }> } | { readonly kind: 'external-grant'; readonly asset_uid: string; readonly entitlement: { readonly profile: 'account' | 'org'; readonly offline?: boolean; readonly revocable?: boolean }; readonly key_ref: string; readonly issuer_key_id: string }) & { readonly checksums: boolean; readonly signature: 'none' | 'ed25519' };\nexport type ProtectedSourceEdits = { readonly manifest?: unknown; readonly payload?: unknown };\nexport type ProtectedSourceProviderRequest = { readonly source_A: string; readonly edits_digest: string; readonly policy_digest: string; readonly kind: 'password' | 'external-grant'; readonly signature: 'none' | 'ed25519' };\nexport type ProtectedSourceOutputSecrets = { readonly passwords?: ReadonlyArray<{ readonly slot: string; readonly password: Uint8Array }>; readonly signingSeed?: Uint8Array; readonly issuerRootKey?: Uint8Array };\nexport type SourceRevisionEvidence = { readonly contract: ProtectedSourceContractIdentity; readonly implementation: { readonly package: '@aikdna/kdna-core'; readonly version: string }; readonly source_A: string; readonly output: { readonly A: string; readonly C: string; readonly E: string }; readonly profile: { readonly id: string; readonly version: string }; readonly entry: 'payload.kdnab'; readonly member_actions: { readonly [name: string]: 'added' | 'replaced' | 'removed' }; readonly integrity: unknown; readonly payload_source: 'explicit_edit' | 'original_decrypted_cbor'; readonly proof: 'producer_observation_not_consumer_admission'; readonly output_delivery: 'not_published' | 'trusted_host'; readonly output_A: string | null };\nexport type ProtectedSourceRevisionResult = { readonly status: 'revision_produced'; readonly bytes: Uint8Array; readonly evidence: SourceRevisionEvidence; readonly body_bytes: number } | { readonly status: 'revision_rejected'; readonly diagnostic: SourceDiagnostic; readonly body: null; readonly body_bytes: 0 };\nexport declare function getProtectedSourceContract(): ProtectedSourceRuntimeDescriptor;\nexport declare function createTrustedProtectedSourceHost(callbacks: ProtectedSourceHostCallbacks): TrustedProtectedSourceHost;\nexport declare function withProtectedSourceNode(input: string | Uint8Array, options: ProtectedSourceRevisionOptions, protectionProvider: import('./protection-node.js').TrustedProtectionProvider, sourceHost: TrustedProtectedSourceHost): Promise<ProtectedSourceObservation>;\nexport declare function commitProtectedSourceTransport(prepared: ProtectedSourcePreparedDelivery, transport: TrustedProtectedSourceTransport): Promise<ProtectedSourceCommitResult>;\nexport declare function previewProtectedSourceRevision(context: ProtectedSourceRevisionContext, edits: ProtectedSourceEdits, policy: ProtectedSourcePolicy): Promise<ProtectedSourceRevisionPreviewResult>;\nexport declare function produceProtectedSourceRevision(context: ProtectedSourceRevisionContext, edits: ProtectedSourceEdits, policy: ProtectedSourcePolicy, outputSecretProvider: (request: ProtectedSourceProviderRequest) => ProtectedSourceOutputSecrets | Promise<ProtectedSourceOutputSecrets>): Promise<ProtectedSourceRevisionResult>;\n",
|
|
3
|
-
"definition_digest": "sha256:
|
|
3
|
+
"definition_digest": "sha256:6a0c62e9908100fcd03b9f9b735bb0d928fa340fbe57fb894f501968aac188a8",
|
|
4
4
|
"id": "kdna.protected-source/1",
|
|
5
5
|
"limits": {
|
|
6
6
|
"container_bytes": 26214400,
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
],
|
|
30
30
|
"schema_binding": {
|
|
31
31
|
"package_versions": {
|
|
32
|
-
"@aikdna/kdna-core": "0.
|
|
33
|
-
"@aikdna/kdna-read": "0.11.
|
|
32
|
+
"@aikdna/kdna-core": "0.37.1-rc.browser.1",
|
|
33
|
+
"@aikdna/kdna-read": "0.11.2-rc.browser.1"
|
|
34
34
|
},
|
|
35
35
|
"version_tuple": {
|
|
36
36
|
"container": "0.5.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"trace": "kdna.judgment-trace/0.3.1"
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
|
-
"schema_id": "urn:kdna:schema:protected-source:1.0.0:binding:r2:
|
|
49
|
-
"schema_path": "specs/protected-source-r2-binding-
|
|
48
|
+
"schema_id": "urn:kdna:schema:protected-source:1.0.0:binding:r2:8",
|
|
49
|
+
"schema_path": "specs/protected-source-r2-binding-8.schema.json",
|
|
50
50
|
"types": {
|
|
51
51
|
"AccessMode": {
|
|
52
52
|
"enum": [
|
|
@@ -9260,7 +9260,7 @@
|
|
|
9260
9260
|
"type": "string"
|
|
9261
9261
|
},
|
|
9262
9262
|
"version": {
|
|
9263
|
-
"const": "0.
|
|
9263
|
+
"const": "0.37.1-rc.browser.1",
|
|
9264
9264
|
"type": "string"
|
|
9265
9265
|
}
|
|
9266
9266
|
},
|
|
@@ -10632,7 +10632,7 @@
|
|
|
10632
10632
|
"type": "string"
|
|
10633
10633
|
},
|
|
10634
10634
|
"version": {
|
|
10635
|
-
"const": "0.
|
|
10635
|
+
"const": "0.37.1-rc.browser.1",
|
|
10636
10636
|
"type": "string"
|
|
10637
10637
|
}
|
|
10638
10638
|
},
|
|
@@ -13783,7 +13783,7 @@
|
|
|
13783
13783
|
"type": "string"
|
|
13784
13784
|
},
|
|
13785
13785
|
"version": {
|
|
13786
|
-
"const": "0.
|
|
13786
|
+
"const": "0.37.1-rc.browser.1",
|
|
13787
13787
|
"type": "string"
|
|
13788
13788
|
}
|
|
13789
13789
|
},
|
|
@@ -10,7 +10,7 @@ export type ProtectedSourceCatalog = { readonly "status": "catalog_only"; readon
|
|
|
10
10
|
export type SourceCoreRejected = { readonly "status": "core_rejected"; readonly "stage": "admission"; readonly "core": CoreAdmissionRejected; readonly "disclosure": ProtectionDisclosure; readonly "body": null; readonly "body_bytes": 0; };
|
|
11
11
|
export type ProtectedSourceObservation = (ProtectedSourceDelivered | ProtectedSourceCatalog | SourceCoreRejected | SourceFailed);
|
|
12
12
|
export type SourceHostObservation = { readonly "context_id": Identifier; readonly "epoch": Identifier; readonly "asset_digest": Digest; readonly "permission": "allowed" | "denied"; readonly "scope": "complete_source"; readonly "current_ms": UInt; readonly "expires_at_ms": UInt; readonly "revoked": boolean; };
|
|
13
|
-
export type SourcePreviewValidation = { readonly "contract": ProtectionContractIdentity; readonly "implementation": { readonly "name": "@aikdna/kdna-core"; readonly "version": "0.
|
|
13
|
+
export type SourcePreviewValidation = { readonly "contract": ProtectionContractIdentity; readonly "implementation": { readonly "name": "@aikdna/kdna-core"; readonly "version": "0.37.1-rc.browser.1"; }; readonly "source_A": Digest; readonly "original_payload_digest": Digest; readonly "edits_digest": Digest; readonly "policy_digest": Digest; readonly "semantic_status": "valid"; readonly "proof": "semantic_validation_not_ciphertext_admission"; };
|
|
14
14
|
export type SourcePreviewValid = { readonly "status": "preview_valid"; readonly "validation": SourcePreviewValidation; readonly "body": null; readonly "body_bytes": 0; };
|
|
15
15
|
export type SourcePreviewRejected = { readonly "status": "preview_rejected"; readonly "diagnostic": SourceDiagnostic; readonly "body": null; readonly "body_bytes": 0; };
|
|
16
16
|
export type ProtectedSourceRevisionPreviewResult = (SourcePreviewValid | SourcePreviewRejected);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Browser protected admission core (case A: an independent browser entry).
|
|
3
|
+
//
|
|
4
|
+
// The host supplies the container bytes, the payload plaintext bytes and the
|
|
5
|
+
// unlock observation; nothing here decrypts, and the observation is never
|
|
6
|
+
// authority (its provenance limit is named in the disclosure). The five-step
|
|
7
|
+
// admission mirrors the retained node entry's structure and codes; the kdsig
|
|
8
|
+
// face follows the browser family route (globalThis.crypto subtle, same
|
|
9
|
+
// kdsig.ed25519:0.1.0 framing as the node helper), and the checksums document
|
|
10
|
+
// is verified against a verbatim local port of protection-integrity.makeChecksums
|
|
11
|
+
// (that module is node-bound by its kdsig helper import).
|
|
12
|
+
const { parseJson, canonicalJson, freeze, uint, identifier } = require('./strict-input.js');
|
|
13
|
+
const { parseContainer } = require('./container.js');
|
|
14
|
+
const { inflate } = require('./browser-native/portable-inflate.js');
|
|
15
|
+
const { utf8, fromHex } = require('./browser-native/bytes.js');
|
|
16
|
+
const { digest, contentTreePreimage, runtimeEntryPreimage, runtimeEntryNames } = require('./digests.js');
|
|
17
|
+
const { validate } = require('./validate.js');
|
|
18
|
+
const { rejected } = require('./admit.js');
|
|
19
|
+
const { decodeValidatedPayload, assertContentBindings, finishAdmission } = require('./semantic-admission.js');
|
|
20
|
+
const { fail, failure, isFailure, closed, provider, assetProtectionDeclaration } = require('./protection-declaration.js');
|
|
21
|
+
const { decodeEnvelope, validateEnvelopeShape } = require('./protection-envelope-codec.js');
|
|
22
|
+
const { signatureContentPreimage } = require('./browser-native/signature-domain.js');
|
|
23
|
+
const { ChecksumsDocument1: checksumsShape } = require('./protection-validators.generated.js');
|
|
24
|
+
function checkDeclaration(manifest) {
|
|
25
|
+
const profile = assetProtectionDeclaration(manifest);
|
|
26
|
+
if (!profile) fail('NOT_DECLARED', 'declaration');
|
|
27
|
+
if (profile.id !== 'kdna.envelope.aead') fail('PROFILE_UNSUPPORTED', 'declaration');
|
|
28
|
+
if (manifest.entitlement.offline === false || manifest.entitlement.revocable === true) fail('DECLARATION_CONFLICT', 'authorization');
|
|
29
|
+
return profile;
|
|
30
|
+
}
|
|
31
|
+
function inputs(value, inputProvider) {
|
|
32
|
+
closed(value, ['bytes', 'plaintextPayload', 'observation'], ['signaturePolicy']);
|
|
33
|
+
const containerBytes = value.bytes, suppliedPlaintext = value.plaintextPayload;
|
|
34
|
+
if (!(containerBytes instanceof Uint8Array) || !(suppliedPlaintext instanceof Uint8Array)) fail('INPUT_INVALID', 'input');
|
|
35
|
+
const policy = value.signaturePolicy === undefined ? { requireSignature: false, expectedPublicKeyHex: null } : closed(value.signaturePolicy, ['requireSignature', 'expectedPublicKeyHex']);
|
|
36
|
+
if (typeof policy.requireSignature !== 'boolean' || (policy.expectedPublicKeyHex !== null && (typeof policy.expectedPublicKeyHex !== 'string' || !/^[0-9a-f]{64}$/.test(policy.expectedPublicKeyHex)))) fail('INPUT_INVALID', 'input');
|
|
37
|
+
const o = closed(value.observation, ['kind', 'proof', 'checked_at_ms', 'selection']);
|
|
38
|
+
if (o.kind !== 'consumer_unlock_observation' || o.proof !== 'observation_not_authority' || !uint(o.checked_at_ms)) fail('OBSERVATION_INVALID', 'observation');
|
|
39
|
+
const s = closed(o.selection, ['slotIndex', 'slot', 'kdf_profile']);
|
|
40
|
+
if (!uint(s.slotIndex) || !identifier(s.slot) || !['scrypt-sha256', 'argon2id'].includes(s.kdf_profile)) fail('OBSERVATION_INVALID', 'observation');
|
|
41
|
+
const trustedProvider = provider(inputProvider, 'password');
|
|
42
|
+
// Finish every caller-owned metadata access before allocating the plaintext
|
|
43
|
+
// copy. The caller may still throw while a Proxy supplies a data property.
|
|
44
|
+
const copied = { observation: { kind: o.kind, proof: o.proof, checked_at_ms: o.checked_at_ms, selection: { slotIndex: s.slotIndex, slot: s.slot, kdf_profile: s.kdf_profile } }, signaturePolicy: { ...policy }, trustedProvider };
|
|
45
|
+
const observed = copied.observation, selected = observed.selection, signaturePolicy = copied.signaturePolicy;
|
|
46
|
+
if (observed.kind !== 'consumer_unlock_observation' || observed.proof !== 'observation_not_authority' || !uint(observed.checked_at_ms) || !uint(selected.slotIndex) || !identifier(selected.slot) || !['scrypt-sha256', 'argon2id'].includes(selected.kdf_profile)) fail('OBSERVATION_INVALID', 'observation');
|
|
47
|
+
if (typeof signaturePolicy.requireSignature !== 'boolean' || (signaturePolicy.expectedPublicKeyHex !== null && (typeof signaturePolicy.expectedPublicKeyHex !== 'string' || !/^[0-9a-f]{64}$/.test(signaturePolicy.expectedPublicKeyHex)))) fail('INPUT_INVALID', 'input');
|
|
48
|
+
return { ...copied, bytes: new Uint8Array(containerBytes), plaintext: new Uint8Array(suppliedPlaintext) };
|
|
49
|
+
}
|
|
50
|
+
function assertObservationBinding(observation, envelope) {
|
|
51
|
+
const slots = envelope.key_slots;
|
|
52
|
+
if (observation.selection.slotIndex >= slots.length) fail('OBSERVATION_BINDING_INVALID', 'binding');
|
|
53
|
+
const slot = slots[observation.selection.slotIndex];
|
|
54
|
+
if (slot.slot !== observation.selection.slot || slot.kdf_profile !== observation.selection.kdf_profile) fail('OBSERVATION_BINDING_INVALID', 'binding');
|
|
55
|
+
}
|
|
56
|
+
// Verbatim port of protection-integrity.makeChecksums (node-bound there).
|
|
57
|
+
function makeChecksums(entries, manifest, E) {
|
|
58
|
+
const names = runtimeEntryNames(entries, manifest);
|
|
59
|
+
return { profile: 'kdna.checksums.document/1', profile_version: '1.0.0', algorithm: 'sha256', digest_profile: 'kdna.digest-basis.runtime-entry-set', digest_profile_version: '0.2.0', covered_entries: names, entries: names.map(name => ({ name, bytes: entries[name].length, digest: digest(entries[name]) })), entry_set_digest: E };
|
|
60
|
+
}
|
|
61
|
+
function parseKdsigBundle(raw) {
|
|
62
|
+
// The same bundle domain the node helper and the browser section verifier accept.
|
|
63
|
+
if (raw.length < 3 || raw.length > 1048576) fail('SIGNATURE_INVALID', 'integrity');
|
|
64
|
+
if (raw[0] === 0xef && raw[1] === 0xbb && raw[2] === 0xbf) fail('SIGNATURE_INVALID', 'integrity');
|
|
65
|
+
let value;
|
|
66
|
+
try { value = parseJson(raw); } catch { fail('SIGNATURE_INVALID', 'integrity'); }
|
|
67
|
+
const fields = ['algorithm', 'content_digest', 'profile', 'profile_version', 'public_key', 'signature'];
|
|
68
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).length !== fields.length || !fields.every(k => Object.hasOwn(value, k) && typeof value[k] === 'string')) fail('SIGNATURE_INVALID', 'integrity');
|
|
69
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value.content_digest) || !/^[0-9a-f]{64}$/.test(value.public_key) || !/^[0-9a-f]{128}$/.test(value.signature)) fail('SIGNATURE_INVALID', 'integrity');
|
|
70
|
+
if (value.profile !== 'kdsig.ed25519' || value.profile_version !== '0.1.0' || value.algorithm !== 'ed25519') fail('PROFILE_UNSUPPORTED', 'integrity');
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
async function verifyIntegrityBrowser(entries, manifest, E, policy) {
|
|
74
|
+
const integrity = { checksums: 'absent', signature: 'absent', signature_content_digest: null, public_key: null };
|
|
75
|
+
if (Object.hasOwn(entries, 'checksums.json')) {
|
|
76
|
+
const raw = entries['checksums.json']; let value;
|
|
77
|
+
try { if (raw.length > 1048576) throw Error(); value = parseJson(raw); } catch { fail('CHECKSUMS_INVALID', 'integrity'); }
|
|
78
|
+
if (value?.profile !== 'kdna.checksums.document/1' || value?.profile_version !== '1.0.0') fail('PROFILE_UNSUPPORTED', 'integrity');
|
|
79
|
+
if (!checksumsShape(value) || canonicalJson(value) !== canonicalJson(makeChecksums(entries, manifest, E))) fail('CHECKSUMS_INVALID', 'integrity');
|
|
80
|
+
integrity.checksums = 'verified_document_1';
|
|
81
|
+
}
|
|
82
|
+
if (Object.hasOwn(entries, 'signature.kdsig')) {
|
|
83
|
+
const bundle = parseKdsigBundle(entries['signature.kdsig']);
|
|
84
|
+
if (policy.expectedPublicKeyHex !== null && bundle.public_key !== policy.expectedPublicKeyHex) fail('SIGNATURE_PIN_MISMATCH', 'integrity');
|
|
85
|
+
const signatureDigest = digest(signatureContentPreimage(entries));
|
|
86
|
+
if (bundle.content_digest !== signatureDigest) fail('SIGNATURE_INVALID', 'integrity');
|
|
87
|
+
const subtle = globalThis.crypto?.subtle;
|
|
88
|
+
if (!subtle || typeof subtle.importKey !== 'function' || typeof subtle.verify !== 'function') throw Object.assign(new Error('WebCrypto Ed25519 unavailable'), { reason: 'READ_CORE_CAPABILITY_UNAVAILABLE' });
|
|
89
|
+
try {
|
|
90
|
+
const key = await subtle.importKey('raw', fromHex(bundle.public_key), 'Ed25519', false, ['verify']);
|
|
91
|
+
const ok = await subtle.verify('Ed25519', key, fromHex(bundle.signature), utf8('kdsig.ed25519:0.1.0:' + signatureDigest));
|
|
92
|
+
if (ok !== true) fail('SIGNATURE_INVALID', 'integrity');
|
|
93
|
+
} catch (error) { if (isFailure(error)) throw error; throw Object.assign(new Error('WebCrypto Ed25519 failed'), { reason: 'READ_CORE_CAPABILITY_UNAVAILABLE' }); }
|
|
94
|
+
integrity.signature = policy.expectedPublicKeyHex === null ? 'verified_self_key' : 'verified_pinned_key';
|
|
95
|
+
integrity.signature_content_digest = signatureDigest;
|
|
96
|
+
integrity.public_key = bundle.public_key;
|
|
97
|
+
} else if (policy.requireSignature || policy.expectedPublicKeyHex !== null) fail('SIGNATURE_INVALID', 'integrity');
|
|
98
|
+
return integrity;
|
|
99
|
+
}
|
|
100
|
+
async function admitProtectedBrowser(input, inputProvider) {
|
|
101
|
+
let copied, plaintext, stage = 'input';
|
|
102
|
+
try {
|
|
103
|
+
copied = inputs(input, inputProvider); plaintext = copied.plaintext; const bytes = copied.bytes;
|
|
104
|
+
stage = 'container'; const entries = parseContainer(bytes, inflate);
|
|
105
|
+
stage = 'manifest'; const manifest = validate('Manifest', parseJson(entries['kdna.json']));
|
|
106
|
+
const profile = checkDeclaration(manifest);
|
|
107
|
+
const digests = { A: digest(bytes), C: digest(contentTreePreimage(entries)), E: digest(runtimeEntryPreimage(entries, manifest)) };
|
|
108
|
+
assertContentBindings(manifest, digests.C);
|
|
109
|
+
const integrity = await verifyIntegrityBrowser(entries, manifest, digests.E, copied.signaturePolicy);
|
|
110
|
+
const envelope = validateEnvelopeShape(decodeEnvelope(entries['payload.kdnab']), profile);
|
|
111
|
+
assertObservationBinding(copied.observation, envelope);
|
|
112
|
+
stage = 'payload'; const payload = decodeValidatedPayload(manifest, plaintext);
|
|
113
|
+
stage = 'interpretation'; const result = finishAdmission(manifest, payload, entries, digests, integrity.checksums === 'verified_document_1' ? digests.E : null);
|
|
114
|
+
const disclosure = freeze({ slot_selection: { ...copied.observation.selection }, observation_not_authority: true, provenance: 'host_supplied_triple' });
|
|
115
|
+
if (result.status === 'accepted') {
|
|
116
|
+
const view = require('./brand.js').inspectSnapshot(result.snapshot);
|
|
117
|
+
require('./browser-protected/state.js').register(result.snapshot, {
|
|
118
|
+
provider: copied.trustedProvider, integrity, disclosure,
|
|
119
|
+
observation: { snapshot_id: view.snapshot_id, tuple: view.tuple, asset: view.asset,
|
|
120
|
+
...digests, ir_digest: view.ir_digest, integrity, unlock_observation: copied.observation,
|
|
121
|
+
disclosure, physical_reads: 'none', new_decryption: false }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return freeze(result.status === 'accepted' ? { status: 'accepted', snapshot: result.snapshot, disclosure } : { status: 'catalog_only', catalog: result, disclosure });
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (isFailure(error) || stage === 'input') return failure(error);
|
|
127
|
+
return freeze({ status: 'core_rejected', stage, core: rejected(error?.code === 'MODULE_NOT_FOUND' ? 'READ_CORE_CAPABILITY_UNAVAILABLE' : error?.reason ?? 'READ_CORE_INVALID', error.component_failure ?? null, error.diagnostic ?? null) });
|
|
128
|
+
} finally {
|
|
129
|
+
plaintext?.fill(0);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
module.exports = { admitProtectedBrowser };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"api_typescript": "export type ProtectedSignaturePolicy = { readonly requireSignature: boolean; readonly expectedPublicKeyHex: string | null };\nexport type ProtectionGrantExpectations = { readonly issuer: string; readonly signing_key_id: string; readonly issuer_public_key: string; readonly account_id: string; readonly entitlement_id: string; readonly entitlement_profile: 'account' | 'org'; readonly device_id: string; readonly device_agreement_public_key: string; readonly device_signing_public_key: string };\nexport type ProtectedCredential = { readonly kind: 'none' } | { readonly kind: 'password'; readonly password: Uint8Array; readonly slotIndex?: number } | { readonly kind: 'external-grant'; readonly grantBytes: Uint8Array; readonly deviceAgreementPrivateKeyPkcs8: Uint8Array; readonly mode: 'online' | 'offline'; readonly expected: ProtectionGrantExpectations };\nexport type ProtectedAdmissionOptions = { readonly credential: ProtectedCredential; readonly signaturePolicy: ProtectedSignaturePolicy };\nexport type ProtectionStateScope = { readonly issuer: string; readonly account_id: string; readonly entitlement_id: string };\nexport type ProtectionHighWater = { readonly scope: ProtectionStateScope; readonly highest_status_version: number; readonly status: 'active' | 'revoked' | 'expired'; readonly last_trusted_time_ms: number; readonly revision: number };\nexport type ProtectionStateAdvance = { readonly scope: ProtectionStateScope; readonly device_id: string; readonly verified: { readonly grant_digest: string; readonly status_version: number; readonly status: 'active' | 'revoked' | 'expired'; readonly observed_at_ms: number } };\nexport type ProtectionRefreshContext = ProtectionStateScope & { readonly device_id: string; readonly A: string; readonly grant_id: string; readonly status_version: number; readonly purpose: 'authorization' | 'projection' | 'host_handoff' | 'transport_commit' };\nexport type TrustedProtectionProvider = { readonly kind: 'local'; readonly clock: () => number } | { readonly kind: 'external'; readonly clock: () => number; readonly capabilities: { readonly offline: boolean; readonly status_refresh: boolean; readonly revocation: boolean }; readonly readAdvance: (input: ProtectionStateAdvance) => Promise<ProtectionHighWater>; readonly refresh?: (context: ProtectionRefreshContext) => Promise<Uint8Array> };\nexport type ProtectionPhase = 'projection' | 'host_observation' | 'host_handoff' | 'read_return' | 'transport_commit' | 'execution';\nexport type ProtectionCurrent = { readonly status: 'current'; readonly receipt: ProtectionReceipt };\nexport type ProtectionObserved = ProtectionCurrent & { readonly checkpoint: ProtectionCheckpoint };\nexport type ProtectionSource = { readonly kind: 'snapshot'; readonly snapshot: CanonicalReadSnapshot } | { readonly kind: 'catalog'; readonly catalog: CoreAdmissionCatalogOnly };\nexport type ProtectionBindingResult = { readonly status: 'bound'; readonly binding: ProtectionBinding } | ProtectedAdmissionFailed;\nexport type ProtectedExportOptions = ({ readonly kind: 'integrity' } | { readonly kind: 'password'; readonly asset_uid: string; readonly entitlement: { readonly profile: 'password'; readonly offline?: boolean; readonly revocable?: boolean }; readonly slots: ReadonlyArray<{ readonly slot: string; readonly kdf_profile: 'scrypt-sha256' | 'argon2id' }> } | { readonly kind: 'external-grant'; readonly asset_uid: string; readonly entitlement: { readonly profile: 'account' | 'org'; readonly offline?: boolean; readonly revocable?: boolean }; readonly key_ref: string; readonly issuer_key_id: string }) & { readonly checksums: boolean; readonly signature: 'none' | 'ed25519' };\nexport type ProtectedProducerSecrets = { readonly passwords: ReadonlyArray<{ readonly slot: string; readonly password: Uint8Array }>; readonly signingSeed?: Uint8Array; readonly issuerRootKey?: never } | { readonly issuerRootKey: Uint8Array; readonly signingSeed?: Uint8Array; readonly passwords?: never } | { readonly signingSeed?: Uint8Array; readonly issuerRootKey?: never; readonly passwords?: never };\nexport type ProtectedExportResult = { readonly status: 'produced'; readonly bytes: Uint8Array; readonly evidence: ProtectedExportEvidence } | ProtectedCoreRejected | ProtectedAdmissionFailed;\nexport declare function getProtectionContract(): ProtectionRuntimeDescriptor;\nexport declare function admitProtectedNode(input: string | Uint8Array, options: ProtectedAdmissionOptions, provider: TrustedProtectionProvider): Promise<ProtectedAdmissionResult>;\nexport declare function bindProtectionOperation(operation: ProtectionOperation): ProtectionBindingResult;\nexport declare function disposeProtectionOperation(operation: ProtectionOperation): void;\nexport declare function protectSourceBytes(input: Uint8Array, options: ProtectedExportOptions, secrets: ProtectedProducerSecrets): Promise<ProtectedExportResult>;\n",
|
|
3
|
+
"browser_api_typescript": "import type { CanonicalReadSnapshot, CoreAdmissionCatalogOnly, CoreAdmissionRejected } from './types.js';\nimport type { ProtectionDiagnostic, ProtectionRuntimeDescriptor, ProtectedSignaturePolicy, TrustedProtectionProvider } from './protection-node.js';\nexport type ConsumerUnlockSelection = { readonly \"slotIndex\": number; readonly \"slot\": string; readonly \"kdf_profile\": \"scrypt-sha256\" | \"argon2id\"; };\nexport type ConsumerUnlockObservation = { readonly \"kind\": \"consumer_unlock_observation\"; readonly \"proof\": \"observation_not_authority\"; readonly \"checked_at_ms\": number; readonly \"selection\": ConsumerUnlockSelection; };\nexport type ProtectedBrowserInput = { readonly \"bytes\": Uint8Array; readonly \"plaintextPayload\": Uint8Array; readonly \"observation\": ConsumerUnlockObservation; readonly \"signaturePolicy\"?: ProtectedSignaturePolicy; };\nexport type ProtectedBrowserDisclosure = { readonly \"slot_selection\": ConsumerUnlockSelection; readonly \"observation_not_authority\": true; readonly \"provenance\": \"host_supplied_triple\"; };\nexport type ProtectedBrowserAccepted = { readonly \"status\": \"accepted\"; readonly \"snapshot\": CanonicalReadSnapshot; readonly \"disclosure\": ProtectedBrowserDisclosure; };\nexport type ProtectedBrowserCatalogOnly = { readonly \"status\": \"catalog_only\"; readonly \"catalog\": CoreAdmissionCatalogOnly; readonly \"disclosure\": ProtectedBrowserDisclosure; };\nexport type ProtectedBrowserCoreRejected = { readonly \"status\": \"core_rejected\"; readonly \"stage\": \"container\" | \"manifest\" | \"payload\" | \"interpretation\"; readonly \"core\": CoreAdmissionRejected; };\nexport type ProtectedBrowserFailed = { readonly \"status\": \"protection_failed\"; readonly \"diagnostic\": ProtectedBrowserDiagnostic; };\n// The observation-phase codes are the reviewed family extension settled under\n// D-CBPA-IMPL-3 (2026-10-06); the shared node codes keep their stages.\nexport type ProtectedBrowserDiagnostic = (ProtectionDiagnostic | { readonly \"code\": \"PROTECTION_OBSERVATION_INVALID\"; readonly \"stage\": \"observation\"; } | { readonly \"code\": \"PROTECTION_OBSERVATION_BINDING_INVALID\"; readonly \"stage\": \"binding\"; } | { readonly \"code\": \"PROTECTION_NOT_DECLARED\"; readonly \"stage\": \"declaration\"; });\nexport type ProtectedBrowserAdmissionResult = (ProtectedBrowserAccepted | ProtectedBrowserCatalogOnly | ProtectedBrowserCoreRejected | ProtectedBrowserFailed);\nexport declare function getProtectionContract(): ProtectionRuntimeDescriptor;\nexport declare function admitProtectedBrowser(input: ProtectedBrowserInput, provider: TrustedProtectionProvider): Promise<ProtectedBrowserAdmissionResult>;",
|
|
3
4
|
"capability_shapes": {
|
|
4
5
|
"ProtectionBinding": "{ readonly source: () => ProtectionSource | ProtectedAdmissionFailed; readonly observe: (phase: ProtectionPhase) => Promise<ProtectionObserved | ProtectionCurrentFailure>; readonly assertCurrent: (checkpoint: ProtectionCheckpoint) => ProtectionCurrent | ProtectionCurrentFailure }"
|
|
5
6
|
},
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
"schema_path": "specs/checksums-document-1.schema.json",
|
|
14
15
|
"version": "1.0.0"
|
|
15
16
|
},
|
|
16
|
-
"definition_digest": "sha256:
|
|
17
|
+
"definition_digest": "sha256:7257adf964aadbce546b1403a059852b72f5df269503d6b58445d1f549e2252d",
|
|
17
18
|
"delivery_history": {
|
|
18
19
|
"acknowledgement": "Only literal synchronous true confirms the configured trusted transport handoff. False/throw/thenable/invalid return leaves outcome_unknown. Never await or execute an untrusted then method to improve the result. Callback itself may already have sent data.",
|
|
19
20
|
"disposal": "Disposal closes authority and clears secrets best effort but retains the redacted per-token delivery history while its token/owning Read remains reachable, so a later failure cannot erase past attempts.",
|
|
@@ -177,8 +178,8 @@
|
|
|
177
178
|
"packages/kdna-core/src/public-contract/protection-declaration.js:1-75",
|
|
178
179
|
"packages/kdna-core/src/public-contract/protection-admission.js:1-94",
|
|
179
180
|
"packages/kdna-core/src/public-contract/protection-producer.js:1-75",
|
|
180
|
-
"packages/kdna-core/src/public-contract/protection-crypto.js:1-
|
|
181
|
-
"packages/kdna-core/src/public-contract/protection-envelope-codec.js:1-
|
|
181
|
+
"packages/kdna-core/src/public-contract/protection-crypto.js:1-82",
|
|
182
|
+
"packages/kdna-core/src/public-contract/protection-envelope-codec.js:1-138"
|
|
182
183
|
],
|
|
183
184
|
"proof": [
|
|
184
185
|
{
|
|
@@ -707,8 +708,8 @@
|
|
|
707
708
|
"packages/kdna-core/src/public-contract/protection-declaration.js:1-75",
|
|
708
709
|
"packages/kdna-core/src/public-contract/protection-admission.js:1-94",
|
|
709
710
|
"packages/kdna-core/src/public-contract/protection-producer.js:1-75",
|
|
710
|
-
"packages/kdna-core/src/public-contract/protection-crypto.js:1-
|
|
711
|
-
"packages/kdna-core/src/public-contract/protection-envelope-codec.js:1-
|
|
711
|
+
"packages/kdna-core/src/public-contract/protection-crypto.js:1-82",
|
|
712
|
+
"packages/kdna-core/src/public-contract/protection-envelope-codec.js:1-138"
|
|
712
713
|
]
|
|
713
714
|
},
|
|
714
715
|
"PROTECTION-RESULTS": {
|
|
@@ -801,8 +802,8 @@
|
|
|
801
802
|
"runtime_not_implemented": [],
|
|
802
803
|
"schema_binding": {
|
|
803
804
|
"package_versions": {
|
|
804
|
-
"@aikdna/kdna-core": "0.
|
|
805
|
-
"@aikdna/kdna-read": "0.11.
|
|
805
|
+
"@aikdna/kdna-core": "0.37.1-rc.browser.1",
|
|
806
|
+
"@aikdna/kdna-read": "0.11.2-rc.browser.1"
|
|
806
807
|
},
|
|
807
808
|
"version_tuple": {
|
|
808
809
|
"container": "0.5.0",
|
|
@@ -817,12 +818,12 @@
|
|
|
817
818
|
"trace": "kdna.judgment-trace/0.3.1"
|
|
818
819
|
}
|
|
819
820
|
},
|
|
820
|
-
"schema_id": "urn:kdna:schema:protection-admission:1.0.0:binding:r2:
|
|
821
|
-
"schema_path": "specs/protection-admission-r2-binding-
|
|
821
|
+
"schema_id": "urn:kdna:schema:protection-admission:1.0.0:binding:r2:8",
|
|
822
|
+
"schema_path": "specs/protection-admission-r2-binding-8.schema.json",
|
|
822
823
|
"specification": {
|
|
823
|
-
"bytes":
|
|
824
|
+
"bytes": 38180,
|
|
824
825
|
"path": "specs/protection-admission.md",
|
|
825
|
-
"sha256": "
|
|
826
|
+
"sha256": "35180f6583ac97302096a8e2ff786f1c7a9a0c10caabc3a4fa627affd1640bef"
|
|
826
827
|
},
|
|
827
828
|
"types": {
|
|
828
829
|
"ChecksumsDocument1": {
|
|
@@ -1059,7 +1060,7 @@
|
|
|
1059
1060
|
"type": "string"
|
|
1060
1061
|
},
|
|
1061
1062
|
"version": {
|
|
1062
|
-
"const": "0.
|
|
1063
|
+
"const": "0.37.1-rc.browser.1",
|
|
1063
1064
|
"type": "string"
|
|
1064
1065
|
}
|
|
1065
1066
|
},
|
|
@@ -2294,7 +2295,7 @@
|
|
|
2294
2295
|
"type": "string"
|
|
2295
2296
|
},
|
|
2296
2297
|
"version": {
|
|
2297
|
-
"const": "0.
|
|
2298
|
+
"const": "0.37.1-rc.browser.1",
|
|
2298
2299
|
"type": "string"
|
|
2299
2300
|
}
|
|
2300
2301
|
},
|
|
@@ -4,28 +4,18 @@ const { fail } = require('./protection-declaration.js');
|
|
|
4
4
|
const { aesWrap, aesUnwrap } = require('../crypto-profile.js');
|
|
5
5
|
const { externalEnvelopeAad, deriveExternalAssetCek } = require('../external-key-grant.js');
|
|
6
6
|
const { digest } = require('./digests.js');
|
|
7
|
-
const {
|
|
7
|
+
const { decode64Bytes, validateEnvelopeShape, passwordAadBytes } = require('./protection-envelope-codec.js');
|
|
8
|
+
// The pure shapes live in the codec module (browser-reusable); these wrappers keep the
|
|
9
|
+
// retained result identities (Buffer outputs, same verdicts, same codes) so every existing
|
|
10
|
+
// caller in this package stays unchanged.
|
|
8
11
|
function decode64(value, length = null, url = false) {
|
|
9
|
-
|
|
10
|
-
if (typeof value !== 'string') fail('ENVELOPE_INVALID','envelope');
|
|
11
|
-
const decoded = Buffer.from(value,encoding);
|
|
12
|
-
if (decoded.toString(encoding) !== value || (length !== null && decoded.length !== length)) fail('ENVELOPE_INVALID','envelope');
|
|
13
|
-
return decoded;
|
|
12
|
+
return Buffer.from(decode64Bytes(value, length, url));
|
|
14
13
|
}
|
|
15
14
|
function validateEnvelope(value, profile) {
|
|
16
|
-
|
|
17
|
-
if (profile.id === 'kdna.envelope.aead') {
|
|
18
|
-
if (!passwordShape(value) || value.kdf_profile !== value.key_slots[0].kdf_profile) fail('ENVELOPE_INVALID','envelope');
|
|
19
|
-
for (const slot of value.key_slots) { decode64(slot.kdf_params.salt,16); decode64(slot.wrapped_key,40); }
|
|
20
|
-
decode64(value.iv,12); decode64(value.tag,16); decode64(value.ciphertext);
|
|
21
|
-
} else {
|
|
22
|
-
if (!externalShape(value) || value.entry_path !== 'payload.kdnab') fail('ENVELOPE_INVALID','envelope');
|
|
23
|
-
decode64(value.iv,12,true); decode64(value.tag,16,true); decode64(value.ciphertext,null,true);
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
15
|
+
return validateEnvelopeShape(value, profile);
|
|
26
16
|
}
|
|
27
17
|
function passwordAad(manifest) {
|
|
28
|
-
return Buffer.from(
|
|
18
|
+
return Buffer.from(passwordAadBytes(manifest));
|
|
29
19
|
}
|
|
30
20
|
function deriveKek(password, slot) {
|
|
31
21
|
const salt = decode64(slot.kdf_params.salt,16);
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const fail = (code, stage) => require('./protection-declaration.js').fail(code, stage);
|
|
3
|
+
const { utf8, equal, concat } = require('./browser-native/bytes.js');
|
|
3
4
|
const LIMIT = 8 * 1024 * 1024;
|
|
4
5
|
function invalid() { fail('ENVELOPE_INVALID','envelope'); }
|
|
5
6
|
function head(major, n) {
|
|
6
7
|
if (!Number.isSafeInteger(n) || n < 0) invalid();
|
|
7
|
-
if (n < 24) return
|
|
8
|
+
if (n < 24) return Uint8Array.of((major << 5) | n);
|
|
8
9
|
const width = n <= 255 ? 1 : n <= 65535 ? 2 : n <= 4294967295 ? 4 : 8;
|
|
9
|
-
const out =
|
|
10
|
-
if (width === 8)
|
|
10
|
+
const out = new Uint8Array(width + 1), view = new DataView(out.buffer); out[0] = (major << 5) | ({1:24,2:25,4:26,8:27})[width];
|
|
11
|
+
if (width === 8) view.setBigUint64(1,BigInt(n),false);
|
|
12
|
+
else if (width === 4) view.setUint32(1,n,false);
|
|
13
|
+
else if (width === 2) view.setUint16(1,n,false);
|
|
14
|
+
else out[1]=n;
|
|
11
15
|
return out;
|
|
12
16
|
}
|
|
13
|
-
function compare(a,b) { return a.length
|
|
17
|
+
function compare(a,b) { if(a.length!==b.length)return a.length-b.length;for(let i=0;i<a.length;i++)if(a[i]!==b[i])return a[i]-b[i];return 0; }
|
|
14
18
|
function encodeEnvelope(value) {
|
|
15
19
|
let count = 0;
|
|
16
20
|
function encode(v, depth) {
|
|
17
21
|
if (++count > 100000 || depth > 16) invalid();
|
|
18
22
|
if (typeof v === 'string') {
|
|
19
23
|
if (!v.isWellFormed()) invalid();
|
|
20
|
-
const bytes =
|
|
21
|
-
return
|
|
24
|
+
const bytes = utf8(v); if (bytes.length > LIMIT) invalid();
|
|
25
|
+
return concat([head(3,bytes.length),bytes]);
|
|
22
26
|
}
|
|
23
27
|
if (Number.isSafeInteger(v) && v >= 0) return head(0,v);
|
|
24
|
-
if (Array.isArray(v)) return
|
|
28
|
+
if (Array.isArray(v)) return concat([head(4,v.length), ...v.map(x=>encode(x,depth+1))]);
|
|
25
29
|
if (v && typeof v === 'object' && [Object.prototype,null].includes(Object.getPrototypeOf(v))) {
|
|
26
30
|
const members = Object.keys(v).map(k => [encode(k,depth+1), encode(v[k],depth+1)]).sort((a,b)=>compare(a[0],b[0]));
|
|
27
|
-
return
|
|
31
|
+
return concat([head(5,members.length), ...members.flat()]);
|
|
28
32
|
}
|
|
29
33
|
invalid();
|
|
30
34
|
}
|
|
@@ -34,7 +38,7 @@ function encodeEnvelope(value) {
|
|
|
34
38
|
// It never decodes Payload: authenticated plaintext goes to the sole Payload codec.
|
|
35
39
|
function decodeEnvelope(input) {
|
|
36
40
|
if (!(input instanceof Uint8Array) || !input.length || input.length > LIMIT) invalid();
|
|
37
|
-
const bytes =
|
|
41
|
+
const bytes = new Uint8Array(input), view = new DataView(bytes.buffer); let offset = 0, count = 0;
|
|
38
42
|
const decoder = new TextDecoder('utf-8',{fatal:true});
|
|
39
43
|
function read(depth) {
|
|
40
44
|
if (++count > 100000 || depth > 16 || offset >= bytes.length) invalid();
|
|
@@ -43,7 +47,7 @@ function decodeEnvelope(input) {
|
|
|
43
47
|
let n = add;
|
|
44
48
|
if (add >= 24) {
|
|
45
49
|
const width = 2 ** (add - 24); if (offset + width > bytes.length) invalid();
|
|
46
|
-
n = width === 8 ? Number(
|
|
50
|
+
n = width === 8 ? Number(view.getBigUint64(offset,false)) : width === 4 ? view.getUint32(offset,false) : width === 2 ? view.getUint16(offset,false) : bytes[offset]; offset += width;
|
|
47
51
|
if (!Number.isSafeInteger(n) || n < (width === 1 ? 24 : 2 ** (8 * width / 2))) invalid();
|
|
48
52
|
}
|
|
49
53
|
if (major === 0) return n;
|
|
@@ -63,7 +67,7 @@ function decodeEnvelope(input) {
|
|
|
63
67
|
}
|
|
64
68
|
return value;
|
|
65
69
|
}
|
|
66
|
-
const value = read(0); if (offset !== bytes.length || !encodeEnvelope(value)
|
|
70
|
+
const value = read(0); if (offset !== bytes.length || !equal(encodeEnvelope(value),bytes)) invalid(); return value;
|
|
67
71
|
}
|
|
68
72
|
// The frozen RFC18 ciphertext pattern is strict canonical padded base64. Native
|
|
69
73
|
// RegExp's repeated four-character group can exhaust the VM stack on an otherwise
|
|
@@ -71,8 +75,67 @@ function decodeEnvelope(input) {
|
|
|
71
75
|
// (including padding bits) in bounded linear memory; all other patterns stay native.
|
|
72
76
|
const CIPHERTEXT_PATTERN = '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/][AQgw]==|[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=)?$';
|
|
73
77
|
function envelopeRegExp(pattern, flags) {
|
|
74
|
-
if(pattern===CIPHERTEXT_PATTERN)return {test:value=>
|
|
78
|
+
if(pattern===CIPHERTEXT_PATTERN)return {test:value=>{try {decode64Bytes(value);return true;}catch{return false;}}};
|
|
75
79
|
return new RegExp(pattern,flags);
|
|
76
80
|
}
|
|
77
81
|
envelopeRegExp.code = "require('./protection-envelope-codec.js').envelopeRegExp";
|
|
78
|
-
|
|
82
|
+
// Pure base64/base64url codec for the frozen envelope domain: exactly the recognize-or-reject
|
|
83
|
+
// language of the retained Node buffer codec (strict canonical round-trip plus an optional
|
|
84
|
+
// length pin), with no Buffer/atob dependency so browser consumers can reuse it.
|
|
85
|
+
const B64_ALPHABETS = { std: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', url: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' };
|
|
86
|
+
const B64_MAPS = {};
|
|
87
|
+
function b64Map(url) {
|
|
88
|
+
const key = url ? 'url' : 'std';
|
|
89
|
+
if (!B64_MAPS[key]) { const map = new Int16Array(128).fill(-1); const alphabet = B64_ALPHABETS[key]; for (let i = 0; i < 64; i++) map[alphabet.charCodeAt(i)] = i; B64_MAPS[key] = map; }
|
|
90
|
+
return B64_MAPS[key];
|
|
91
|
+
}
|
|
92
|
+
function b64Encode(bytes, url) {
|
|
93
|
+
const alphabet = B64_ALPHABETS[url ? 'url' : 'std']; let out = '';
|
|
94
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
95
|
+
const b0 = bytes[i], b1 = bytes[i + 1], b2 = bytes[i + 2];
|
|
96
|
+
out += alphabet[b0 >> 2] + alphabet[((b0 & 3) << 4) | (b1 === undefined ? 0 : b1 >> 4)];
|
|
97
|
+
if (b1 === undefined) { if (!url) out += '=='; break; }
|
|
98
|
+
out += alphabet[((b1 & 15) << 2) | (b2 === undefined ? 0 : b2 >> 6)];
|
|
99
|
+
if (b2 === undefined) { if (!url) out += '='; break; }
|
|
100
|
+
out += alphabet[b2 & 63];
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function decode64Bytes(value, length = null, url = false) {
|
|
105
|
+
if (typeof value !== 'string') invalid();
|
|
106
|
+
const map = b64Map(url), n = value.length;
|
|
107
|
+
if (n % 4 === 1) invalid();
|
|
108
|
+
const out = new Uint8Array(((n + 3) >> 2) * 3);
|
|
109
|
+
let o = 0, acc = 0, bits = 0, padded = false;
|
|
110
|
+
for (let i = 0; i < n; i++) {
|
|
111
|
+
const code = value.charCodeAt(i);
|
|
112
|
+
if (code === 61) { padded = true; continue; }
|
|
113
|
+
if (padded) invalid();
|
|
114
|
+
const v = code < 128 ? map[code] : -1;
|
|
115
|
+
if (v < 0) invalid();
|
|
116
|
+
acc = ((acc << 6) | v) & 0xFFFFFF; bits += 6;
|
|
117
|
+
if (bits >= 8) { bits -= 8; out[o++] = (acc >> bits) & 0xff; }
|
|
118
|
+
}
|
|
119
|
+
const bytes = out.subarray(0, o);
|
|
120
|
+
if (b64Encode(bytes, url) !== value) invalid();
|
|
121
|
+
if (length !== null && bytes.length !== length) invalid();
|
|
122
|
+
return bytes;
|
|
123
|
+
}
|
|
124
|
+
function validateEnvelopeShape(value, profile) {
|
|
125
|
+
if (value?.profile !== profile.id || (value.profile_version ?? value.contract_version) !== profile.version) fail('PROFILE_UNSUPPORTED','envelope');
|
|
126
|
+
// Lazy: the generated validators require this module for the frozen ciphertext recognizer.
|
|
127
|
+
const { PasswordEnvelope: passwordShape, ExternalEnvelope: externalShape } = require('./protection-validators.generated.js');
|
|
128
|
+
if (profile.id === 'kdna.envelope.aead') {
|
|
129
|
+
if (!passwordShape(value) || value.kdf_profile !== value.key_slots[0].kdf_profile) fail('ENVELOPE_INVALID','envelope');
|
|
130
|
+
for (const slot of value.key_slots) { decode64Bytes(slot.kdf_params.salt,16); decode64Bytes(slot.wrapped_key,40); }
|
|
131
|
+
decode64Bytes(value.iv,12); decode64Bytes(value.tag,16); decode64Bytes(value.ciphertext);
|
|
132
|
+
} else {
|
|
133
|
+
if (!externalShape(value) || value.entry_path !== 'payload.kdnab') fail('ENVELOPE_INVALID','envelope');
|
|
134
|
+
decode64Bytes(value.iv,12,true); decode64Bytes(value.tag,16,true); decode64Bytes(value.ciphertext,null,true);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function passwordAadBytes(manifest) {
|
|
139
|
+
return new TextEncoder().encode(['kdna.envelope.aead','0.1.0',manifest.asset_uid,manifest.asset_id,manifest.version,'payload.kdnab',manifest.access,manifest.entitlement.profile].join('\n'));
|
|
140
|
+
}
|
|
141
|
+
module.exports = { encodeEnvelope, decodeEnvelope, envelopeRegExp, decode64Bytes, validateEnvelopeShape, passwordAadBytes };
|
|
@@ -39,8 +39,8 @@ declare const ProtectionCheckpointBrand: unique symbol;
|
|
|
39
39
|
export type ProtectionCheckpoint = { readonly [ProtectionCheckpointBrand]: true };
|
|
40
40
|
declare const ProtectedHostReadProviderBrand: unique symbol;
|
|
41
41
|
export type ProtectedHostReadProvider = { readonly [ProtectedHostReadProviderBrand]: true };
|
|
42
|
-
export type ProtectionRuntimeDescriptor = { readonly "contract": ProtectionContractIdentity; readonly "implementation": { readonly "package": "@aikdna/kdna-core"; readonly "version": "0.
|
|
43
|
-
export type ProtectedExportEvidence = { readonly "contract": ProtectionContractIdentity; readonly "implementation": { readonly "package": "@aikdna/kdna-core"; readonly "version": "0.
|
|
42
|
+
export type ProtectionRuntimeDescriptor = { readonly "contract": ProtectionContractIdentity; readonly "implementation": { readonly "package": "@aikdna/kdna-core"; readonly "version": "0.37.1-rc.browser.1"; }; readonly "profiles": ReadonlyArray<"kdna.envelope.aead/0.1.0" | "kdna.envelope.external-grant/0.1.0" | "kdna.checksums.document/1@1.0.0" | "kdsig.ed25519/0.1.0">; readonly "kdfs": ReadonlyArray<"scrypt-sha256" | "argon2id">; readonly "capabilities": ReadonlyArray<"protected_admission" | "protected_operation" | "protected_production">; };
|
|
43
|
+
export type ProtectedExportEvidence = { readonly "contract": ProtectionContractIdentity; readonly "implementation": { readonly "package": "@aikdna/kdna-core"; readonly "version": "0.37.1-rc.browser.1"; }; readonly "source_A": Digest; readonly "output": { readonly "A": Digest; readonly "C": Digest; readonly "E": Digest; }; readonly "profile": (ProtectionProfile | null); readonly "entry": "payload.kdnab"; readonly "integrity": ProtectionIntegrity; readonly "proof": "producer_observation_not_consumer_admission"; };
|
|
44
44
|
export type ProtectedRequestVersionRejected = { readonly "channel": "read_envelope"; readonly "envelope": ReadEnvelopeRejected; readonly "admission_rejection": null; readonly "control": null; readonly "transport_failure": null; };
|
|
45
45
|
export type ProtectionCurrentFailure = { readonly "status": "protection_failed"; readonly "diagnostic": ProtectionDiagnostic; readonly "checked_at_ms": (UInt | null); };
|
|
46
46
|
export type ProtectedSignaturePolicy = { readonly requireSignature: boolean; readonly expectedPublicKeyHex: string | null };
|