Skip to content

Commit 8133e28

Browse files
senamakelmedullabot
andcommitted
feat: add confidential native config cipher operations
Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent a3a657c commit 8133e28

25 files changed

Lines changed: 2773 additions & 27 deletions

‎.github/scripts/check-file-coverage.sh‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ cargo llvm-cov \
1414
--workspace \
1515
--all-targets \
1616
--all-features \
17-
--json \
18-
--output-path "$report"
17+
--no-report
18+
19+
.github/scripts/run-native-master-cipher-coverage.sh
20+
cargo llvm-cov report --json --output-path "$report"
1921

2022
covered_files="$(jq --arg source_root "$source_root" '
2123
[
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
# Match cargo llvm-cov's workspace test/report directory. show-env otherwise
4+
# defaults to target/ and its native profiles would never reach the report.
5+
native_coverage_directory="${CARGO_LLVM_COV_TARGET_DIR:-$(cargo metadata --locked --no-deps --format-version 1 | jq -r .target_directory)/llvm-cov-target}"
6+
eval "$(cargo llvm-cov show-env --sh)"
7+
export CARGO_LLVM_COV_TARGET_DIR="$native_coverage_directory"
8+
export CARGO_LLVM_COV_BUILD_DIR="$native_coverage_directory"
9+
export LLVM_PROFILE_FILE="$native_coverage_directory/tinysecurity-%p-%28m.profraw"
10+
cargo build --target-dir "$CARGO_LLVM_COV_BUILD_DIR" --locked -p tinysecurity-module --lib --example secret_cipher_native_contract --example device_native_contract --all-features
11+
case "${RUNNER_OS:-$(uname -s)}" in
12+
Linux) library=libtinysecurity_module.so; executable=secret_cipher_native_contract ;;
13+
macOS|Darwin) library=libtinysecurity_module.dylib; executable=secret_cipher_native_contract ;;
14+
Windows|MINGW*|MSYS*) library=tinysecurity_module.dll; executable=secret_cipher_native_contract.exe ;;
15+
*) echo 'unsupported native cipher coverage platform' >&2; exit 1 ;;
16+
esac
17+
device_executable=device_native_contract
18+
if [[ "${RUNNER_OS:-}" == Windows ]]; then device_executable+=.exe; fi
19+
build_directory="$CARGO_LLVM_COV_BUILD_DIR"
20+
if [[ "${RUNNER_OS:-}" == Windows ]]; then build_directory="$(cygpath -u "$build_directory")"; fi
21+
module="$build_directory/debug/$library"
22+
verifier="$build_directory/debug/examples/$executable"
23+
device_verifier="$build_directory/debug/examples/$device_executable"
24+
if [[ "${RUNNER_OS:-}" == Windows ]]; then
25+
# The native loader/audit fixture retains the established restricted namespace.
26+
module="$(cygpath -u "$TINYSECURITY_AUDIT_TEST_ROOT")/$library"
27+
cp "$build_directory/debug/$library" "$module"
28+
fi
29+
printf 'Native fixtures: repository working directory\n'
30+
"$verifier" "$module"
31+
"$device_verifier" "$module"
32+
external_cwd="$(mktemp -d)"
33+
trap 'rmdir "$external_cwd"' EXIT
34+
printf 'Native fixtures: external working directory\n'
35+
(cd -- "$external_cwd" && "$verifier" "$module" && "$device_verifier" "$module")

‎.github/workflows/ci.yml‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ jobs:
154154
if ($LASTEXITCODE -ne 0) { throw 'native approval verifier failed in workspace' }
155155
cargo run --locked --package tinysecurity-module --example judge_native_contract -- $verifiedModule
156156
if ($LASTEXITCODE -ne 0) { throw 'native judge verifier failed in workspace' }
157+
cargo run --locked --package tinysecurity-module --example secret_cipher_native_contract -- $verifiedModule
158+
if ($LASTEXITCODE -ne 0) { throw 'native master/cipher verifier failed in workspace' }
159+
$cipherVerifier = (Resolve-Path 'target/debug/examples/secret_cipher_native_contract.exe').Path
157160
cargo run --locked --package tinysecurity-module --example sandbox_native_contract -- $verifiedModule
158161
if ($LASTEXITCODE -ne 0) { throw 'native sandbox verifier failed in workspace' }
159162
$sandboxVerifier = (Resolve-Path 'target/debug/examples/sandbox_native_contract.exe').Path
@@ -162,6 +165,8 @@ jobs:
162165
$auditVerifier = (Resolve-Path 'target/debug/examples/audit_native_contract.exe').Path
163166
Push-Location $env:RUNNER_TEMP
164167
try {
168+
& $cipherVerifier $verifiedModule
169+
if ($LASTEXITCODE -ne 0) { throw 'native master/cipher verifier failed outside workspace' }
165170
& $verifier $verifiedModule
166171
if ($LASTEXITCODE -ne 0) { throw 'native verifier failed outside workspace' }
167172
& $sandboxVerifier $verifiedModule
@@ -202,6 +207,14 @@ jobs:
202207
cargo run -p tinysecurity-module --example approval_native_contract -- "$module"
203208
cargo run -p tinysecurity-module --example judge_native_contract -- "$module"
204209
cargo run -p tinysecurity-module --example sandbox_native_contract -- "$module"
210+
cargo run -p tinysecurity-module --example secret_cipher_native_contract -- "$module"
211+
# The new actual native master/cipher fixture also runs from an
212+
# independent data-only cwd; no build output is sent there.
213+
cipherModule="$(pwd)/$module"
214+
cipherVerifier="$(pwd)/target/debug/examples/secret_cipher_native_contract"
215+
externalCipherCwd="$(mktemp -d)"
216+
(cd -- "$externalCipherCwd" && "$cipherVerifier" "$cipherModule")
217+
rmdir "$externalCipherCwd"
205218
# Installed examples must also work outside the workspace root.
206219
module="$(pwd)/$module"
207220
(cd target && ./debug/examples/verify_module "$module")
@@ -274,7 +287,9 @@ jobs:
274287
}
275288
pathlib.Path('coverage-native-metadata.json').write_text(json.dumps(metadata, indent=2), encoding='utf-8')
276289
PYTHON
277-
cargo llvm-cov --locked --workspace --all-targets --all-features --json --output-path coverage-native.json
290+
cargo llvm-cov --locked --workspace --all-targets --all-features --no-report
291+
.github/scripts/run-native-master-cipher-coverage.sh
292+
cargo llvm-cov report --json --output-path coverage-native.json
278293
# Report exports the instrumented all-feature/all-target run. Selection
279294
# flags are invalid for cargo-llvm-cov's export-only report subcommand.
280295
cargo llvm-cov report --lcov --output-path coverage-native.info

‎MODULE.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,44 @@ close authority permanently. Device replay capacity and every scope epoch are
198198
checked before publication; bootstrap evidence survives accepted reinit. Native OS providers, physical files, passwords, encryption,
199199
plaintext delivery, routine native consumers, pairing and production host
200200
migration remain outside this subset.
201+
202+
## Confidential native config cipher (contract 1.11)
203+
204+
`EvaluateSecretCipher2` and `ExecuteSecretCipher2` are confidential routine
205+
operations over an actual `ExecuteSecret2` ConfigUserMaster acquisition. They
206+
never prompt for routine fields or create master authority from a receipt.
207+
`secret_cipher` SDK metadata fixes one existing registration, consumer identity,
208+
enabled setting, callback timeout and physical slot count. It cannot change on
209+
reinit. Older 1.10 initialization omits it and retains its original behavior.
210+
An older decoder does not know the additive `native_routine` audit kind; hosts
211+
using this capability must negotiate the new contract vocabulary.
212+
213+
Evaluate owns the real private crypto pending reservation, then commits required
214+
sanitized NativeRoutine intent through the existing AuditClient. Execute removes
215+
it once, requires unchanged metadata and confidential input, and checks actual
216+
execution, output-stage and publication audit acknowledgements. Audit arguments
217+
contain keyed operation/output correlations, Engine-derived durable scope/policy
218+
and fixed classifications. Caller-supplied audit event identities reserve stream
219+
order; they never construct a private routine acknowledgement or cipher grant.
220+
221+
Plaintext, disabled/empty passthrough and disabled migration replacements go only
222+
to the fixed confidential `SecretCipherConsumer2.Deliver` port. A real trusted
223+
receiver authenticates the pinned loaded module and stages zeroizing output in
224+
an exclusive bounded physical slot. It refuses overwrite and releases locally
225+
only after the matching successful owner response. A staged callback reply is
226+
not product plaintext publication. The owner retains its original reservation
227+
through final required audit and live validation; only true ciphertext appears
228+
in ordinary result fields. A failed/canceled/late/invalid stage leaves its sender
229+
slot permanently occupied across generation changes and reinit. Operation-ID
230+
or status echoes cannot clear uncertainty. A successful response permits the
231+
receiver to consume/wipe its exact slot before accepting another stage there.
232+
233+
The broker currently requires every confidential destination to be an admitted
234+
native module. Ordinary unattested host callbacks are denied, including master
235+
HumanOnly prompt and secret backend delivery. The tracked native fixture loads
236+
the real hashed TinySecurity cdylib and a separately admitted host-trusted native
237+
callback fixture. This proves module-to-admitted-native-consumer composition;
238+
it does not prove ordinary OpenHuman host callback admission. That TinyBus
239+
integration gap remains open. SDK serde/frame/partial-decode copies and process
240+
abort are outside the owned-buffer wiping guarantee. Host migration, passwords,
241+
pairing and app document storage remain separate unfinished capabilities.

‎crates/tinysecurity-bus/src/audit.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize};
77
pub enum AuditKind2 {
88
/// Accepted policy activation.
99
PolicyChanged,
10+
/// Fixed native routine operation using a live Human Once acquired master.
11+
NativeRoutine,
1012
/// Approval lifecycle.
1113
Approval,
1214
/// Optional judge verdict.

‎crates/tinysecurity-bus/src/callbacks.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ impl ApprovalCasRequest2 {
137137
}
138138
/// Fixed new callback identities; incompatible semantics never reuse legacy interfaces.
139139
pub mod callback_names2 {
140+
/// Fixed init-bound response-controlled confidential native output slots.
141+
pub const SECRET_CIPHER_CONSUMER: &str = "ai.tinyhumans.tinysecurity.SecretCipherConsumer2";
142+
/// Stage exact bound output privately until successful owner response.
143+
pub const DELIVER: &str = "Deliver";
140144
/// Confidential injected secret backend; only exact Load and Set.
141145
pub const SECRET_BACKEND: &str = "ai.tinyhumans.tinysecurity.SecretBackend2";
142146
/// Exact confidential target mutation.

‎crates/tinysecurity-bus/src/lib.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//!
33
//! Hosts link this crate; the policy engine and native module stay isolated.
44
//! Implements bootstrap checks plus immutable native filesystem scope contracts.
5+
mod secret_cipher;
6+
pub use secret_cipher::*;
57
mod secret;
68
pub use secret::*;
79
mod device;
@@ -36,7 +38,7 @@ pub use prompt::*;
3638
pub use redact::RedactorConfig;
3739
pub mod names;
3840
/// Current wire vocabulary version, independent of the artifact version.
39-
pub const CONTRACT_VERSION: (u32, u32) = (1, 10);
41+
pub const CONTRACT_VERSION: (u32, u32) = (1, 11);
4042
/// Whether a remote module's advertised version supplies this host's vocabulary.
4143
///
4244
/// `version` is the provider version received by the host, not a host requirement

‎crates/tinysecurity-bus/src/names.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ pub const INTERFACE: &str = "ai.tinyhumans.tinysecurity.Policy";
55
pub const OBJECT_PATH: &str = "/ai/tinyhumans/tinysecurity/Policy";
66
/// Method names; availability is reported by `PolicyInfo`.
77
pub mod methods {
8+
/// Admit a bounded routine operation against a real acquired config master.
9+
pub const EVALUATE_SECRET_CIPHER2: &str = "EvaluateSecretCipher2";
10+
/// Consume exact private pending cipher authority once.
11+
pub const EXECUTE_SECRET_CIPHER2: &str = "ExecuteSecretCipher2";
812
/// Begin `HumanOnly` confidential master review.
913
pub const EVALUATE_SECRET2: &str = "EvaluateSecret2";
1014
/// Resolve a master after exact Human Once and required audit.
@@ -100,6 +104,8 @@ pub mod methods {
100104
}
101105
/// Bootstrap implemented methods only.
102106
pub const METHODS: &[&str] = &[
107+
methods::EVALUATE_SECRET_CIPHER2,
108+
methods::EXECUTE_SECRET_CIPHER2,
103109
methods::EVALUATE_SECRET2,
104110
methods::EXECUTE_SECRET2,
105111
methods::EVALUATE_DEVICE2,

‎crates/tinysecurity-bus/src/policy.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ impl fmt::Debug for JudgeConfig {
287287
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
288288
#[serde(deny_unknown_fields)]
289289
pub struct ModuleConfig {
290+
/// Immutable fixed confidential native config cipher consumer.
291+
#[serde(default, skip_serializing_if = "Option::is_none")]
292+
pub secret_cipher: Option<crate::SecretCipherConfig2>,
290293
/// Init-only metadata for confidential native master resolution.
291294
#[serde(default, skip_serializing_if = "Option::is_none")]
292295
pub secret: Option<crate::SecretConfig2>,
@@ -322,6 +325,7 @@ impl Default for ModuleConfig {
322325
contract_version: crate::CONTRACT_VERSION,
323326
device: None,
324327
secret: None,
328+
secret_cipher: None,
325329
sandbox: None,
326330
policy: PolicyConfig::default(),
327331
judge: None,
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//! Fixed confidential native config cipher contract. No plaintext return member.
2+
use crate::AuditCorrelation2;
3+
use serde::{Deserialize, Serialize};
4+
/// Init-only consumer bound to one registered `ConfigUserMaster` authority.
5+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
6+
#[serde(deny_unknown_fields)]
7+
pub struct SecretCipherConfig2 {
8+
/// Exact existing native master registration.
9+
pub registration_id: String,
10+
/// Opaque fixed consumer identity; no caller-selected endpoint.
11+
pub consumer_id: String,
12+
/// Immutable encryption setting.
13+
pub enabled: bool,
14+
/// Physical receiver slots, each independently bounded and never overwritten.
15+
pub slots: u16,
16+
/// Fixed confidential callback timeout, 1..=30000 milliseconds.
17+
pub timeout_ms: u32,
18+
}
19+
/// Exact routine operation, never a master-resolution or approval-recovery method.
20+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
21+
#[serde(rename_all = "snake_case")]
22+
pub enum SecretCipherKind2 {
23+
/// Encrypt, including historical empty/disabled compatibility.
24+
Encrypt,
25+
/// Decrypt, including legacy and plaintext compatibility.
26+
Decrypt,
27+
/// Decrypt and optionally produce a legacy replacement.
28+
DecryptAndMigrate,
29+
}
30+
/// Confidential input; receivers must immediately move it into zeroizing ownership.
31+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
32+
#[serde(deny_unknown_fields)]
33+
pub struct SecretCipherRequest2 {
34+
/// Exact initialized registration.
35+
pub registration_id: String,
36+
/// Selected existing private native master alias.
37+
pub master_handle: String,
38+
/// Bounded original native call identity.
39+
pub call_id: String,
40+
/// Immutable operation.
41+
pub operation: SecretCipherKind2,
42+
/// UTF-8 confidential input, bounded by the native cipher owner.
43+
pub input: Vec<u8>,
44+
/// Shortening deadline; never renews the selected master.
45+
pub ttl_ms: u32,
46+
}
47+
/// Required exact intent audit and confidential routine input.
48+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
49+
#[serde(deny_unknown_fields)]
50+
pub struct EvaluateSecretCipherRequest2 {
51+
/// Exact original request.
52+
pub request: SecretCipherRequest2,
53+
/// Required intent audit.
54+
pub audit: AuditCorrelation2,
55+
}
56+
/// Opaque process-ephemeral pending identity; strings alone grant no authority.
57+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
58+
#[serde(deny_unknown_fields)]
59+
pub struct SecretCipherPending2 {
60+
/// Owner-minted once-only private pending lookup.
61+
pub pending_id: String,
62+
/// Original absolute deadline.
63+
pub expires_at_ms: u64,
64+
}
65+
/// Consume private pending with unchanged request and ordered actual audit phases.
66+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
67+
#[serde(deny_unknown_fields)]
68+
pub struct ExecuteSecretCipherRequest2 {
69+
/// Unchanged original confidential request.
70+
pub request: SecretCipherRequest2,
71+
/// Exact owner-minted pending identity.
72+
pub pending_id: String,
73+
/// Required pre-execution audit.
74+
pub audit: AuditCorrelation2,
75+
/// Required output-stage audit (also persisted for ciphertext).
76+
pub delivery_audit: AuditCorrelation2,
77+
/// Required final publication audit.
78+
pub completion_audit: AuditCorrelation2,
79+
}
80+
/// Exact private receiver slot identity. Echo alone is not completion proof.
81+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
82+
#[serde(deny_unknown_fields)]
83+
pub struct SecretCipherDeliveryIdentity2 {
84+
/// Fixed initialized consumer.
85+
pub consumer_id: String,
86+
/// Fixed initialized registration.
87+
pub registration_id: String,
88+
/// Original operation correlation, keyed by the owner.
89+
pub operation_id: String,
90+
/// Distinct keyed exact-output commitment.
91+
pub output_id: String,
92+
/// Current owner generation.
93+
pub generation: u64,
94+
/// Exact original operation.
95+
pub operation: SecretCipherKind2,
96+
/// Physical fixed receiver slot index.
97+
pub slot: u16,
98+
/// Original deadline; late staging never permits publication.
99+
pub expires_at_ms: u64,
100+
}
101+
/// Confidential staged output. Trusted receiver stores it only in a zeroizing slot.
102+
/// The receiver authorizes the actual pinned module sender and refuses overwrite.
103+
/// It releases output only after the matching successful owner response, wipes on
104+
/// denial/cancellation, and performs all work inside the callback future.
105+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
106+
#[serde(deny_unknown_fields)]
107+
pub struct SecretCipherDeliveryRequest2 {
108+
/// Exact captured destination/output binding.
109+
pub identity: SecretCipherDeliveryIdentity2,
110+
/// Confidential output; never ordinary bus result.
111+
pub plaintext: Vec<u8>,
112+
/// Disabled migration replacements also remain confidential.
113+
pub replacement: Option<Vec<u8>>,
114+
}
115+
/// Actual trusted native callback completion, not a caller-created receipt.
116+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
117+
#[serde(deny_unknown_fields)]
118+
pub struct SecretCipherDeliveryResponse2 {
119+
/// Exact completed stage identity.
120+
pub identity: SecretCipherDeliveryIdentity2,
121+
/// True only after work completed and the exclusive physical slot owns output.
122+
pub staged: bool,
123+
}
124+
/// Classified safe result; plaintext remains in the fixed trusted native slot.
125+
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
126+
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
127+
pub enum SecretCipherResponse2 {
128+
/// Only true nonempty enabled encryption returns ordinary ciphertext.
129+
Ciphertext {
130+
/// Authenticated enc2 envelope.
131+
ciphertext: String,
132+
},
133+
/// Successful owner response permits local consumption of this exact slot.
134+
Delivered {
135+
/// Exact identity of privately staged output.
136+
identity: SecretCipherDeliveryIdentity2,
137+
/// Whether legacy replacement was produced.
138+
migrated: bool,
139+
/// True encrypted replacement only; disabled replacement stays in slot.
140+
replacement_ciphertext: Option<String>,
141+
},
142+
}
143+
macro_rules! redacted {
144+
($($ty:ty),*) => {$ (impl std::fmt::Debug for $ty {
145+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146+
f.write_str(concat!(stringify!($ty), "([REDACTED])"))
147+
}
148+
})*};
149+
}
150+
redacted!(
151+
SecretCipherConfig2,
152+
SecretCipherRequest2,
153+
EvaluateSecretCipherRequest2,
154+
SecretCipherPending2,
155+
ExecuteSecretCipherRequest2,
156+
SecretCipherDeliveryIdentity2,
157+
SecretCipherDeliveryRequest2,
158+
SecretCipherDeliveryResponse2,
159+
SecretCipherResponse2
160+
);
161+
162+
#[cfg(test)]
163+
#[path = "secret_cipher_tests.rs"]
164+
mod tests;

0 commit comments

Comments
 (0)