@k9o/kibo 0.0.0-stage → 1.1.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/LICENSE +201 -0
- package/NOTICE +4 -0
- package/README.md +273 -2
- package/dist/index.d.ts +266 -0
- package/dist/index.js +259 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +161 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -4
- package/src/client-factory.ts +24 -0
- package/src/crypto-types.ts +117 -0
- package/src/crypto.ts +133 -0
- package/src/index.ts +33 -0
- package/src/types.ts +171 -0
- package/src/vault.ts +212 -0
- package/wasm/kibo_kms_rest_ffi.d.ts +54 -0
- package/wasm/kibo_kms_rest_ffi.js +887 -0
- package/wasm/kibo_kms_rest_ffi_bg.wasm +0 -0
- package/wasm/kibo_kms_rest_ffi_bg.wasm.d.ts +26 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@k9o/kibo` — Zero-Knowledge BYOK/HYOK vault SDK types.
|
|
3
|
+
*
|
|
4
|
+
* Surface: the ZK-DEK store/fetch. The wrapped
|
|
5
|
+
* envelope is produced client-side by the audited ZK core (the
|
|
6
|
+
* `kibo-kms-rest` binding); this SDK ships it **verbatim and opaque** and never
|
|
7
|
+
* reads its interior — the strict-ZK invariant + envelope framing live in the
|
|
8
|
+
* core, never here.
|
|
9
|
+
*/
|
|
10
|
+
/** Customer KMS/HSM provider — non-secret routing metadata (mirrors the ZK FFI provider selector). */
|
|
11
|
+
type KmsProvider = "aws-kms" | "gcp-kms" | "azure-kv" | "hashicorp-vault";
|
|
12
|
+
/**
|
|
13
|
+
* Input to store an opaque customer-wrapped DEK.
|
|
14
|
+
* `provider` + `keyVersionRef` are explicit non-secret fields the SDK supplies
|
|
15
|
+
* (vault-ops never parses the envelope to derive them).
|
|
16
|
+
*/
|
|
17
|
+
type StoreZkDekInput = {
|
|
18
|
+
/** base64 opaque `WrappedDek` envelope (AEAD-sealed under the customer KEK). */
|
|
19
|
+
envelope: string;
|
|
20
|
+
/** Which customer KMS/HSM holds the wrapping KEK. */
|
|
21
|
+
provider: KmsProvider;
|
|
22
|
+
/** Customer key reference (KMS ARN / GCP resource / AKV id / Vault path). */
|
|
23
|
+
keyVersionRef: string;
|
|
24
|
+
};
|
|
25
|
+
/** Store response — the opaque `zkdek_<base62>` handle + creation timestamp. */
|
|
26
|
+
type ZkDekHandle = {
|
|
27
|
+
/** `zkdek_<base62>` handle. */
|
|
28
|
+
id: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
};
|
|
31
|
+
/** Fetch response — the stored record: opaque envelope + non-secret metadata. */
|
|
32
|
+
type ZkDekRecord = {
|
|
33
|
+
id: string;
|
|
34
|
+
/** base64 opaque `WrappedDek` envelope, returned verbatim for the customer to unwrap. */
|
|
35
|
+
envelope: string;
|
|
36
|
+
provider: KmsProvider;
|
|
37
|
+
keyVersionRef: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
};
|
|
40
|
+
/** secret visibility — write ops (create/update) stay owner-only regardless of this tag. */
|
|
41
|
+
type SecretVisibility = {
|
|
42
|
+
kind: "private";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "shared";
|
|
45
|
+
principals?: string[];
|
|
46
|
+
roles?: string[];
|
|
47
|
+
};
|
|
48
|
+
/** Input to create a vault secret — `POST /@<projectSlug>/vault/secrets/<name>?region=<region>`. */
|
|
49
|
+
type CreateSecretInput = {
|
|
50
|
+
/** Hierarchical path the secret is addressed under, e.g. `byos/s3-prod` (also the `:name` URL segment). */
|
|
51
|
+
name: string;
|
|
52
|
+
/** The plaintext to seal, as a regular (non-base64) string — `vault.secrets.create` base64-encodes
|
|
53
|
+
* it for the wire itself (live-verified against staging: the route's OpenAPI description says
|
|
54
|
+
* "the plaintext to seal" but a raw non-base64 `value` is rejected as unparseable). */
|
|
55
|
+
value: string;
|
|
56
|
+
/** The authoritative region for this vault write (sent as `?region=`), REQUIRED — the vault-forward
|
|
57
|
+
* proxy fail-closes without it (live-verified against staging, not documented in the route's
|
|
58
|
+
* OpenAPI schema as of this writing). Distinct from any region carried inside `value`'s own
|
|
59
|
+
* envelope. */
|
|
60
|
+
region: string;
|
|
61
|
+
visibility?: SecretVisibility;
|
|
62
|
+
metadata?: Record<string, string>;
|
|
63
|
+
};
|
|
64
|
+
/** A secret WITHOUT its sealed ciphertext — the create/list/read metadata shape. */
|
|
65
|
+
type SecretSummary = {
|
|
66
|
+
/** `sec_<base62>`. */
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
ownerId: string;
|
|
70
|
+
visibility: SecretVisibility;
|
|
71
|
+
version: number;
|
|
72
|
+
/** The wrapping-key id at this version. */
|
|
73
|
+
kid: string;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
createdBy: string;
|
|
76
|
+
updatedAt: string;
|
|
77
|
+
updatedBy: string;
|
|
78
|
+
rotatedAt: string | null;
|
|
79
|
+
retiredAt: string | null;
|
|
80
|
+
metadata: Record<string, string>;
|
|
81
|
+
};
|
|
82
|
+
/** `client.kibo.vault.secrets.list()` input — none of these need `region` (live-verified: unlike every
|
|
83
|
+
* other secrets operation, list is NOT region-gated). */
|
|
84
|
+
type ListSecretsInput = {
|
|
85
|
+
/** Filter to a path prefix. */
|
|
86
|
+
prefix?: string;
|
|
87
|
+
cursor?: string;
|
|
88
|
+
limit?: number;
|
|
89
|
+
};
|
|
90
|
+
type ListSecretsResult = {
|
|
91
|
+
secrets: readonly SecretSummary[];
|
|
92
|
+
nextCursor: string | null;
|
|
93
|
+
};
|
|
94
|
+
/** releasing-node observed attestation tier (K4) — present on every reveal. */
|
|
95
|
+
type AttestationTier = {
|
|
96
|
+
/** `attestation:destination:region` trust-tier URN. */
|
|
97
|
+
urn: string;
|
|
98
|
+
/** Node provenance: `platform` (Kibo-operated) or `client` (Private Perimeter). */
|
|
99
|
+
operator: string;
|
|
100
|
+
/** Whether node attestation was verified against the vendor PKI on the URN's attestation segment. */
|
|
101
|
+
verified: boolean;
|
|
102
|
+
};
|
|
103
|
+
/** `revealChallenge()` result — a single-use challenge to present on the immediately-following `reveal()`. */
|
|
104
|
+
type RevealChallenge = {
|
|
105
|
+
challenge: string;
|
|
106
|
+
expiresAt: string;
|
|
107
|
+
};
|
|
108
|
+
/** `reveal()` result. `value` is the decoded plaintext (the SDK base64-decodes the wire value, the
|
|
109
|
+
* inverse of `create`'s encode) — never the raw base64. */
|
|
110
|
+
type RevealSecretResult = {
|
|
111
|
+
id: string;
|
|
112
|
+
name: string;
|
|
113
|
+
version: number;
|
|
114
|
+
value: string;
|
|
115
|
+
revealedAt: string;
|
|
116
|
+
attestationTier: AttestationTier;
|
|
117
|
+
};
|
|
118
|
+
/** `update()` input. */
|
|
119
|
+
type UpdateSecretInput = {
|
|
120
|
+
/** The authoritative region for this write (sent as `?region=`), REQUIRED — same live-verified gate
|
|
121
|
+
* as `create`. */
|
|
122
|
+
region: string;
|
|
123
|
+
/** New plaintext — bumps `version`. The SDK base64-encodes it for the wire. */
|
|
124
|
+
value: string;
|
|
125
|
+
metadata?: Record<string, string>;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* `update()` result — 🔴 live-verified NARROWER than the documented `SecretSummary`: the gateway
|
|
129
|
+
* returns only `{id, name, version}` on a real PUT, not the full summary the OpenAPI schema claims.
|
|
130
|
+
* Typed to what's actually observed rather than the aspirational doc shape; call `get()` afterward if
|
|
131
|
+
* the full summary is needed.
|
|
132
|
+
*/
|
|
133
|
+
type UpdateSecretResult = {
|
|
134
|
+
id: string;
|
|
135
|
+
name: string;
|
|
136
|
+
version: number;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* `rotate()` result — 🔴 live-verified shape, not documented anywhere in the OpenAPI schema (the route
|
|
140
|
+
* only declares `t.Union([SecretSummary, RevealChallengeResponse])`, which this matches neither).
|
|
141
|
+
* `ledgerSequenceNumber` is undocumented; kept as-observed.
|
|
142
|
+
*/
|
|
143
|
+
type RotateSecretResult = {
|
|
144
|
+
id: string;
|
|
145
|
+
name: string;
|
|
146
|
+
version: number;
|
|
147
|
+
rotatedAt: string;
|
|
148
|
+
ledgerSequenceNumber: number;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* `move()` result — 🔴 live-verified shape, also not the documented `SecretSummary`: no `version`, and
|
|
152
|
+
* an undocumented `event` field (`"SECRET_PATH_CHANGED"` observed; typed as `string` since only one
|
|
153
|
+
* sample was seen and a closed enum can't be asserted from one observation).
|
|
154
|
+
*/
|
|
155
|
+
type MoveSecretResult = {
|
|
156
|
+
id: string;
|
|
157
|
+
name: string;
|
|
158
|
+
event: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export type { AttestationTier, CreateSecretInput, KmsProvider, ListSecretsInput, ListSecretsResult, MoveSecretResult, RevealChallenge, RevealSecretResult, RotateSecretResult, SecretSummary, SecretVisibility, StoreZkDekInput, UpdateSecretInput, UpdateSecretResult, ZkDekHandle, ZkDekRecord };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k9o/kibo",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Kibo — Zero-Knowledge BYOK/HYOK vault SDK (client-side; composes into @k9o/core as client.kibo). ZK-DEK store/fetch (F.5) + encrypt/decrypt (thin wrappers over the audited bound core compiled to wasm).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"types": "./dist/types.d.ts",
|
|
16
|
+
"import": "./dist/types.js",
|
|
17
|
+
"default": "./dist/types.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"wasm",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"NOTICE",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/k9o-dev",
|
|
36
|
+
"directory": "packages/kibo"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@k9o/core": "1.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"bun-types": "^1.1.0",
|
|
43
|
+
"typescript": "^5.6.0"
|
|
44
|
+
},
|
|
45
|
+
"module": "./dist/index.js"
|
|
46
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createKiboClient(config)` — per-instance Kibo (ZK vault) SDK factory.
|
|
3
|
+
*
|
|
4
|
+
* Composes into `@k9o/core` as `client.kibo` (re-export, available when a
|
|
5
|
+
* project is bound — the same composition pattern as `@k9o/gate`). Reuses the
|
|
6
|
+
* core HTTP layer (`@k9o/core/http`) so the bearer/header/retry contract is
|
|
7
|
+
* identical to the rest of the SDK pool.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type HttpClientConfig } from "@k9o/core/http";
|
|
11
|
+
import { makeVaultNamespace } from "./vault.ts";
|
|
12
|
+
|
|
13
|
+
/** Shape of the per-instance client returned by `createKiboClient`. */
|
|
14
|
+
export type KiboClient = {
|
|
15
|
+
/** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (F.5). */
|
|
16
|
+
vault: ReturnType<typeof makeVaultNamespace>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Construct a Kibo client bound to the supplied (core) HTTP config. */
|
|
20
|
+
export function createKiboClient(config: HttpClientConfig): KiboClient {
|
|
21
|
+
return {
|
|
22
|
+
vault: makeVaultNamespace(config),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TS contract for the ZK encrypt/decrypt FFI surface — mirrors the Rust
|
|
3
|
+
* `kibo-kms-rest-ffi::types` (the bound core, piece C) field-for-field.
|
|
4
|
+
*
|
|
5
|
+
* These types cross the wasm boundary as plain JS objects (serde-wasm-bindgen),
|
|
6
|
+
* so the field names + enum wire-tokens must match the Rust serde exactly:
|
|
7
|
+
* - `KmsProvider` kebab-case (`aws-kms`, `gcp-kms`, `azure-kv`, `hashicorp-vault`)
|
|
8
|
+
* - all other enums snake_case (`byok`, `local_envelope`, `hardware_backed`, …)
|
|
9
|
+
* - bytes (`Vec<u8>`) `Uint8Array` at the boundary
|
|
10
|
+
*
|
|
11
|
+
* Strict-ZK invariant: the `secret` in `KiboCredential` is a customer-issued
|
|
12
|
+
* short-lived federated token — never a platform KEK; it is zeroized in the core
|
|
13
|
+
* after use and never logged or returned. The platform never sees plaintext.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// The customer KMS provider — the same routing selector as the F.5 store
|
|
17
|
+
// surface. Reuse the canonical definition (no duplication).
|
|
18
|
+
import type { KmsProvider } from "./types.ts";
|
|
19
|
+
export type { KmsProvider };
|
|
20
|
+
|
|
21
|
+
/** Custody posture for an encrypt call. `platform-managed` is structurally absent
|
|
22
|
+
* here — `kibo-kms-rest` is the KEK-free customer-KMS layer (ZK is never PM). */
|
|
23
|
+
export type KiboPosture = "byok" | "hyok";
|
|
24
|
+
|
|
25
|
+
/** Explicit strategy pin. Omit → `local_envelope` (the posture default). */
|
|
26
|
+
export type KiboStrategy = "local_envelope" | "customer_kms_oracle";
|
|
27
|
+
|
|
28
|
+
/** Oracle routing mode. Omit → `brokered` (default). */
|
|
29
|
+
export type KiboOracleMode = "brokered" | "direct";
|
|
30
|
+
|
|
31
|
+
/** Device attestation tier bound into the AAD for every operation. */
|
|
32
|
+
export type KiboAttestationTier = "software" | "hardware_backed" | "confidential";
|
|
33
|
+
|
|
34
|
+
/** The min-custody-posture floor ladder (3-mode; `platform_managed` = no floor).
|
|
35
|
+
* Ordering: `PLATFORM_MANAGED < BYOK < HYOK`. */
|
|
36
|
+
export type KiboMinPosture = "platform_managed" | "byok" | "hyok";
|
|
37
|
+
|
|
38
|
+
/** Acceptable oracle-mode set (the `allowed_oracle_modes` policy dial). */
|
|
39
|
+
export interface KiboAllowedOracleModes {
|
|
40
|
+
brokered: boolean;
|
|
41
|
+
direct: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Project-declared crypto-posture floor. Omit fields for the
|
|
45
|
+
* most-permissive default (`platform_managed` floor + both oracle modes). */
|
|
46
|
+
export interface KiboPosturePolicy {
|
|
47
|
+
min_custody_posture?: KiboMinPosture;
|
|
48
|
+
allowed_oracle_modes?: KiboAllowedOracleModes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Short-lived customer KMS credential — the edge device's federated token.
|
|
52
|
+
* `secret` is treated as secret material (zeroized in-core; never returned). */
|
|
53
|
+
export interface KiboCredential {
|
|
54
|
+
secret: string;
|
|
55
|
+
expires_at_unix_secs: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Key version reference identifying the customer KMS key. `external_ref` is
|
|
59
|
+
* provider-specific (AWS key ARN / GCP resource name / Azure key URL / Vault
|
|
60
|
+
* `<mount>/<key>:<ver>`). `version` is the monotone KEK-rotation counter. */
|
|
61
|
+
export interface KiboKeyRef {
|
|
62
|
+
version: number;
|
|
63
|
+
external_ref: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Provider-specific init params. Only the compiled-in provider's field is read. */
|
|
67
|
+
export interface KiboProviderConfig {
|
|
68
|
+
aws_region?: string;
|
|
69
|
+
vault_address?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A single encryption-context key/value pair. Bound into the AAD at encrypt and
|
|
73
|
+
* re-supplied byte-for-byte at decrypt. (Record shape — uniffi can't cross
|
|
74
|
+
* tuples; the wasm JSON is `[{"key":"…","value":"…"}]`.) `kibo:` is reserved. */
|
|
75
|
+
export interface KiboKvPair {
|
|
76
|
+
key: string;
|
|
77
|
+
value: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** All inputs to `encrypt` — mirrors Rust `EncryptInput`. */
|
|
81
|
+
export interface KiboEncryptInput {
|
|
82
|
+
provider: KmsProvider;
|
|
83
|
+
posture: KiboPosture;
|
|
84
|
+
strategy?: KiboStrategy;
|
|
85
|
+
oracle_mode?: KiboOracleMode;
|
|
86
|
+
kms_cred: KiboCredential;
|
|
87
|
+
key_ref: KiboKeyRef;
|
|
88
|
+
provider_config: KiboProviderConfig;
|
|
89
|
+
attestation_tier: KiboAttestationTier;
|
|
90
|
+
encryption_context: KiboKvPair[];
|
|
91
|
+
/** Plaintext bytes to encrypt. */
|
|
92
|
+
plaintext: Uint8Array;
|
|
93
|
+
posture_policy?: KiboPosturePolicy;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** All inputs to `decrypt` — mirrors Rust `DecryptInput`. `strategy`,
|
|
97
|
+
* `attestation_tier`, and `encryption_context` MUST be re-supplied (never read
|
|
98
|
+
* from the envelope — anti-envelope-substitution discipline). */
|
|
99
|
+
export interface KiboDecryptInput {
|
|
100
|
+
kms_cred: KiboCredential;
|
|
101
|
+
provider_config: KiboProviderConfig;
|
|
102
|
+
/** The canonical (`b"KIBO"`) envelope from a prior `encrypt`. */
|
|
103
|
+
envelope: Uint8Array;
|
|
104
|
+
strategy: KiboStrategy;
|
|
105
|
+
attestation_tier: KiboAttestationTier;
|
|
106
|
+
encryption_context: KiboKvPair[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Output of `encrypt` — the opaque ZKFE envelope (pass straight to `decrypt`). */
|
|
110
|
+
export interface KiboEncryptOutput {
|
|
111
|
+
envelope: Uint8Array;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Output of `decrypt` — the recovered plaintext. */
|
|
115
|
+
export interface KiboDecryptOutput {
|
|
116
|
+
plaintext: Uint8Array;
|
|
117
|
+
}
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZK encrypt/decrypt — the thin TS wrapper over the bound crypto core
|
|
3
|
+
* (`kibo-kms-rest-ffi`, piece C) compiled to wasm. ZERO crypto here: every
|
|
4
|
+
* wrap/unwrap, envelope framing, posture-floor enforcement, and KMS round-trip
|
|
5
|
+
* lives in the audited Rust core. This layer only marshals the typed input
|
|
6
|
+
* across the wasm boundary and maps the core's error string → `KError`.
|
|
7
|
+
*
|
|
8
|
+
* The core exposes `zk_encrypt`/`zk_decrypt` (async) taking/returning plain JS
|
|
9
|
+
* objects via serde-wasm-bindgen, and rejecting with the `ZkFfiError` display
|
|
10
|
+
* string (`"<K9O_CODE>: <message>"`, optionally with a posture-violation tail).
|
|
11
|
+
*
|
|
12
|
+
* Composes into `@k9o/core` as `client.kibo.encrypt`/`decrypt`; the F.5
|
|
13
|
+
* `vault.zkDeks.{store,fetch}` surface (envelope persistence) is separate +
|
|
14
|
+
* decoupled (the core never sees the store).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { KError } from "@k9o/core/errors";
|
|
18
|
+
import type {
|
|
19
|
+
KiboEncryptInput,
|
|
20
|
+
KiboEncryptOutput,
|
|
21
|
+
KiboDecryptInput,
|
|
22
|
+
KiboDecryptOutput,
|
|
23
|
+
} from "./crypto-types.ts";
|
|
24
|
+
|
|
25
|
+
/** The minimal surface the bound ZK-core wasm exposes. */
|
|
26
|
+
export interface ZkWasm {
|
|
27
|
+
zk_encrypt(input: unknown): Promise<unknown>;
|
|
28
|
+
zk_decrypt(input: unknown): Promise<unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let cachedWasm: ZkWasm | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Inject a pre-loaded core wasm module. Hosts that bundle the wasm themselves
|
|
35
|
+
* (browser/RN, or a test) call this; otherwise `encrypt`/`decrypt` lazy-load
|
|
36
|
+
* the vendored per-provider artifact.
|
|
37
|
+
*/
|
|
38
|
+
export function setZkWasm(mod: ZkWasm): void {
|
|
39
|
+
cachedWasm = mod;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Lazily load the bound core wasm. ONE all-provider artifact is generated from
|
|
44
|
+
* `kibo-kms-rest-ffi` (the core compiles every v2 KMS provider in; the
|
|
45
|
+
* `provider` input field selects at call time), vendored under `wasm/` by the
|
|
46
|
+
* publish pipeline and gitignored in-repo. The specifier is assembled at
|
|
47
|
+
* runtime so the bundler/typechecker must not hard-resolve it — the contract
|
|
48
|
+
* is the `ZkWasm` interface above.
|
|
49
|
+
*/
|
|
50
|
+
async function loadWasm(): Promise<ZkWasm> {
|
|
51
|
+
if (cachedWasm) return cachedWasm;
|
|
52
|
+
const pkg = ["..", "wasm", "kibo_kms_rest_ffi.js"].join("/");
|
|
53
|
+
try {
|
|
54
|
+
cachedWasm = (await import(pkg)) as unknown as ZkWasm;
|
|
55
|
+
} catch {
|
|
56
|
+
// A package published without its native core (no bundle at publish time). Say so; never surface a bare
|
|
57
|
+
// module-resolution error, and do not cache the failure.
|
|
58
|
+
throw new KError({
|
|
59
|
+
code: "KIBO_WASM_ERROR",
|
|
60
|
+
message: "the Kibo zero-knowledge core (wasm) is not bundled in this build of @k9o/kibo; encrypt and decrypt are unavailable",
|
|
61
|
+
context: {},
|
|
62
|
+
recommendation: "Install a release of @k9o/kibo that ships the core, or load it yourself.",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return cachedWasm;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Encrypt `input.plaintext` via the customer KMS, client-side. Returns the
|
|
70
|
+
* opaque ZKFE envelope — store it via `vault.zkDeks.store` (F.5) or keep it
|
|
71
|
+
* yourself; it is meaningless to the platform.
|
|
72
|
+
*/
|
|
73
|
+
export async function encrypt(input: KiboEncryptInput): Promise<KiboEncryptOutput> {
|
|
74
|
+
const wasm = await loadWasm();
|
|
75
|
+
let out: unknown;
|
|
76
|
+
try {
|
|
77
|
+
out = await wasm.zk_encrypt(input);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
throw mapZkError(e);
|
|
80
|
+
}
|
|
81
|
+
return { envelope: toBytes((out as { envelope?: unknown }).envelope) };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Decrypt a ZKFE `input.envelope`. `strategy`, `attestation_tier`, and
|
|
86
|
+
* `encryption_context` MUST match the values used at encrypt — they are AAD-bound
|
|
87
|
+
* and never recovered from the envelope (anti-substitution discipline).
|
|
88
|
+
*/
|
|
89
|
+
export async function decrypt(input: KiboDecryptInput): Promise<KiboDecryptOutput> {
|
|
90
|
+
const wasm = await loadWasm();
|
|
91
|
+
let out: unknown;
|
|
92
|
+
try {
|
|
93
|
+
out = await wasm.zk_decrypt(input);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
throw mapZkError(e);
|
|
96
|
+
}
|
|
97
|
+
return { plaintext: toBytes((out as { plaintext?: unknown }).plaintext) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Normalise wasm byte output (serde-wasm-bindgen emits `Vec<u8>` as a number
|
|
101
|
+
* array; some hosts return a `Uint8Array`) to `Uint8Array`. */
|
|
102
|
+
function toBytes(v: unknown): Uint8Array {
|
|
103
|
+
if (v instanceof Uint8Array) return v;
|
|
104
|
+
if (Array.isArray(v)) return Uint8Array.from(v as number[]);
|
|
105
|
+
throw new KError({
|
|
106
|
+
code: "KIBO_WASM_DECODE_ERROR",
|
|
107
|
+
message: "ZK core returned a non-byte value where bytes were expected",
|
|
108
|
+
context: {},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Map the core's rejected error → `KError`. The wasm path flattens `ZkFfiError`
|
|
114
|
+
* to its display string: `"<CODE>: <message>"` where `<CODE>` is a `K9O_*` wire
|
|
115
|
+
* code (or `UPSTREAM_KMS_ERROR` for the customer's own KMS failures), optionally
|
|
116
|
+
* followed by `"(dimension: …, requested: …, declared: …)"` for a posture-floor
|
|
117
|
+
* violation. Codes are passed through (never hardcoded), per the KError discipline.
|
|
118
|
+
*/
|
|
119
|
+
function mapZkError(e: unknown): KError {
|
|
120
|
+
if (e instanceof KError) return e;
|
|
121
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
122
|
+
const head = /^([A-Z][A-Z0-9_]*):\s*([\s\S]*)$/.exec(raw);
|
|
123
|
+
const code = head?.[1] ?? "KIBO_WASM_ERROR";
|
|
124
|
+
let message = head?.[2] ?? raw;
|
|
125
|
+
|
|
126
|
+
let context: Record<string, unknown> = {};
|
|
127
|
+
const pv = /\(dimension:\s*([^,]+),\s*requested:\s*([^,]+),\s*declared:\s*([^)]+)\)\s*$/.exec(message);
|
|
128
|
+
if (pv) {
|
|
129
|
+
context = { dimension: pv[1]!.trim(), requested: pv[2]!.trim(), declared: pv[3]!.trim() };
|
|
130
|
+
message = message.slice(0, pv.index).trim();
|
|
131
|
+
}
|
|
132
|
+
return new KError({ code, message, context });
|
|
133
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@k9o/kibo` — Zero-Knowledge BYOK/HYOK vault SDK (client-side).
|
|
3
|
+
*
|
|
4
|
+
* Composes into `@k9o/core` as `client.kibo`. Two surfaces:
|
|
5
|
+
* - F.5 ZK-DEK store/fetch — `vault.zkDeks.{store,fetch}`;
|
|
6
|
+
* decoupled, gateway-only opaque-blob persistence.
|
|
7
|
+
* - ZK `encrypt`/`decrypt` (/) — thin wrappers over the
|
|
8
|
+
* audited core (piece C, `kibo-kms-rest-ffi` compiled to wasm).
|
|
9
|
+
*
|
|
10
|
+
* Zero crypto in this package: every wrap/unwrap, envelope framing, and
|
|
11
|
+
* posture-floor enforcement lives in the bound core; the SDK only marshals
|
|
12
|
+
* typed input across the wasm boundary + ships/retrieves opaque blobs.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { createKiboClient, type KiboClient } from "./client-factory.ts";
|
|
16
|
+
export { makeVaultNamespace } from "./vault.ts";
|
|
17
|
+
export type {
|
|
18
|
+
KmsProvider, StoreZkDekInput, ZkDekHandle, ZkDekRecord,
|
|
19
|
+
SecretVisibility, CreateSecretInput, SecretSummary,
|
|
20
|
+
ListSecretsInput, ListSecretsResult, AttestationTier, RevealChallenge, RevealSecretResult,
|
|
21
|
+
UpdateSecretInput, UpdateSecretResult, RotateSecretResult, MoveSecretResult,
|
|
22
|
+
} from "./types.ts";
|
|
23
|
+
|
|
24
|
+
// ZK encrypt/decrypt (bound-core wasm). The ergonomic client-bound surface
|
|
25
|
+
// (gathering provider/posture/key-ref from config) composes per the
|
|
26
|
+
// method-shape contract; these are the FFI-mapped primitives it builds on.
|
|
27
|
+
export { encrypt, decrypt, setZkWasm, type ZkWasm } from "./crypto.ts";
|
|
28
|
+
export type {
|
|
29
|
+
KiboPosture, KiboStrategy, KiboOracleMode, KiboAttestationTier, KiboMinPosture,
|
|
30
|
+
KiboAllowedOracleModes, KiboPosturePolicy, KiboCredential, KiboKeyRef,
|
|
31
|
+
KiboProviderConfig, KiboKvPair, KiboEncryptInput, KiboEncryptOutput,
|
|
32
|
+
KiboDecryptInput, KiboDecryptOutput,
|
|
33
|
+
} from "./crypto-types.ts";
|