Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T06:35:39.103Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

omega_identity_proof.rs

365 lines · 11.8 KB · rust
1use std::{io::Read as _, path::PathBuf, process::ExitCode};
2
3use anyhow::{Context as _, Result, bail};
4use clap::{Parser, Subcommand, ValueEnum};
5use omega_identity::{
6    AdmittedSigningRequest, IDENTITY_PROOF_KEYRING_ACCOUNT, IDENTITY_PROOF_KEYRING_SERVICE,
7    IDENTITY_PROOF_PROTOCOL, IdentityProofService, IdentityRef, ProofCrashBoundary,
8    ProofSafeScenario, ReceiptRef, RecoveryPassword, SigningPurpose, UnsignedEventTemplate,
9};
10use serde::Serialize;
11use serde_json::json;
12
13#[derive(Parser, Debug)]
14#[command(
15    name = "omega-identity-proof",
16    about = "Run explicit disposable Omega identity custody proofs"
17)]
18struct Args {
19    #[arg(long)]
20    root: PathBuf,
21
22    #[command(subcommand)]
23    command: Command,
24}
25
26#[derive(Subcommand, Debug)]
27enum Command {
28    Initialize {
29        #[arg(long)]
30        confirm_disposable: bool,
31    },
32    Create {
33        #[arg(long)]
34        receipt: String,
35        #[arg(long, value_enum)]
36        crash_after: Option<CrashBoundary>,
37    },
38    ResumeCreate,
39    Inspect,
40    ProcessStart {
41        #[arg(long, value_enum)]
42        crash_after: Option<CrashBoundary>,
43    },
44    Sign {
45        #[arg(long)]
46        identity_ref: String,
47        #[arg(long)]
48        request: String,
49    },
50    ProbeForged {
51        #[arg(long)]
52        request: String,
53    },
54    ProbeStale {
55        #[arg(long)]
56        stale_identity_ref: String,
57        #[arg(long)]
58        request: String,
59    },
60    SimulateSafe {
61        #[arg(long, value_enum)]
62        scenario: SafeScenario,
63    },
64    ProtectRecovery {
65        #[arg(long)]
66        identity_ref: String,
67        #[arg(long)]
68        password_fd: i32,
69    },
70    Recover {
71        #[arg(long)]
72        receipt: String,
73        #[arg(long)]
74        password_fd: i32,
75    },
76    ProbeWrongRecovery {
77        #[arg(long)]
78        password_fd: i32,
79    },
80    ProbeCorruptRecovery {
81        #[arg(long)]
82        password_fd: i32,
83    },
84    Reset {
85        #[arg(long)]
86        identity_ref: String,
87        #[arg(long)]
88        receipt: String,
89        #[arg(long, value_enum)]
90        crash_after: Option<CrashBoundary>,
91    },
92    ResumeReset {
93        #[arg(long, value_enum)]
94        crash_after: Option<CrashBoundary>,
95    },
96    Safety,
97}
98
99#[derive(Debug, Copy, Clone, ValueEnum)]
100enum CrashBoundary {
101    SecretWrite,
102    SecretReadBack,
103    ManifestCommit,
104    ResetMarker,
105    ResetCommit,
106    RelaunchAcknowledge,
107}
108
109#[derive(Debug, Copy, Clone, ValueEnum)]
110enum SafeScenario {
111    ConflictCustody,
112    LostCustody,
113    LockedCustody,
114    SymlinkRefusal,
115    WeakPermissionRefusal,
116    KeychainUnavailable,
117    CorruptKeychain,
118    MalformedEventRejection,
119    UnadmittedPurposeRejection,
120    ConflictingRecoverySelection,
121    LateCompletionFencing,
122    SignerCrashBeforeCompletion,
123}
124
125impl From<SafeScenario> for ProofSafeScenario {
126    fn from(value: SafeScenario) -> Self {
127        match value {
128            SafeScenario::ConflictCustody => Self::ConflictCustody,
129            SafeScenario::LostCustody => Self::LostCustody,
130            SafeScenario::LockedCustody => Self::LockedCustody,
131            SafeScenario::SymlinkRefusal => Self::SymlinkRefusal,
132            SafeScenario::WeakPermissionRefusal => Self::WeakPermissionRefusal,
133            SafeScenario::KeychainUnavailable => Self::KeychainUnavailable,
134            SafeScenario::CorruptKeychain => Self::CorruptKeychain,
135            SafeScenario::MalformedEventRejection => Self::MalformedEventRejection,
136            SafeScenario::UnadmittedPurposeRejection => Self::UnadmittedPurposeRejection,
137            SafeScenario::ConflictingRecoverySelection => Self::ConflictingRecoverySelection,
138            SafeScenario::LateCompletionFencing => Self::LateCompletionFencing,
139            SafeScenario::SignerCrashBeforeCompletion => Self::SignerCrashBeforeCompletion,
140        }
141    }
142}
143
144impl From<CrashBoundary> for ProofCrashBoundary {
145    fn from(value: CrashBoundary) -> Self {
146        match value {
147            CrashBoundary::SecretWrite => Self::AfterSecretWrite,
148            CrashBoundary::SecretReadBack => Self::AfterSecretReadBack,
149            CrashBoundary::ManifestCommit => Self::AfterManifestCommit,
150            CrashBoundary::ResetMarker => Self::AfterResetMarker,
151            CrashBoundary::ResetCommit => Self::AfterResetCommit,
152            CrashBoundary::RelaunchAcknowledge => Self::AfterRelaunchAcknowledge,
153        }
154    }
155}
156
157#[derive(Serialize)]
158struct Outcome<'a, T: Serialize> {
159    protocol: &'static str,
160    outcome: &'a str,
161    facts: T,
162}
163
164fn main() -> ExitCode {
165    match run() {
166        Ok(()) => ExitCode::SUCCESS,
167        Err(error) => {
168            let failure = Outcome {
169                protocol: IDENTITY_PROOF_PROTOCOL,
170                outcome: "rejected",
171                facts: json!({ "error": format!("{error:#}") }),
172            };
173            eprintln!(
174                "{}",
175                serde_json::to_string(&failure).unwrap_or_else(|_| {
176                    "{\"protocol\":\"openagents.omega.identity-proof.v1\",\"outcome\":\"rejected\"}".to_string()
177                })
178            );
179            ExitCode::FAILURE
180        }
181    }
182}
183
184fn run() -> Result<()> {
185    let args = Args::parse();
186    if let Command::Initialize { confirm_disposable } = &args.command {
187        if !confirm_disposable {
188            bail!("initialization requires --confirm-disposable");
189        }
190        IdentityProofService::initialize(&args.root).context("initialize disposable proof root")?;
191        return print_outcome(
192            "initialized",
193            json!({
194                "root": args.root,
195                "keyring_service": IDENTITY_PROOF_KEYRING_SERVICE,
196                "keyring_account": IDENTITY_PROOF_KEYRING_ACCOUNT,
197            }),
198        );
199    }
200
201    let crash_boundary = command_crash_boundary(&args.command);
202    let service = IdentityProofService::open(args.root, crash_boundary)
203        .context("open disposable proof root")?;
204    match args.command {
205        Command::Initialize { .. } => bail!("initialize command was not handled"),
206        Command::Create { receipt, .. } => {
207            let result = service.create(ReceiptRef::new(receipt)?)?;
208            print_outcome("create-complete", result)
209        }
210        Command::ResumeCreate => print_outcome("create-resumed", service.resume_create()?),
211        Command::Inspect => print_outcome("inspection", service.inspect()?),
212        Command::ProcessStart { .. } => {
213            print_outcome("process-start", service.inspect_for_process_start()?)
214        }
215        Command::Sign {
216            identity_ref,
217            request,
218        } => {
219            let signing_request = signing_request(IdentityRef::new(identity_ref)?, request)?;
220            print_outcome("signed", service.sign(&signing_request)?)
221        }
222        Command::ProbeForged { request } => {
223            let forged = IdentityRef::new("omega-nostr-forged-request")?;
224            rejected_signing_outcome(&service, forged, request, "forged-request-rejected")
225        }
226        Command::ProbeStale {
227            stale_identity_ref,
228            request,
229        } => rejected_signing_outcome(
230            &service,
231            IdentityRef::new(stale_identity_ref)?,
232            request,
233            "stale-request-rejected",
234        ),
235        Command::SimulateSafe { scenario } => print_outcome(
236            "safe-scenario-simulated",
237            service.simulate_safe_scenario(scenario.into()),
238        ),
239        Command::ProtectRecovery {
240            identity_ref,
241            password_fd,
242        } => print_outcome(
243            "recovery-protected",
244            service.protect_recovery(
245                &IdentityRef::new(identity_ref)?,
246                read_recovery_password(password_fd)?,
247            )?,
248        ),
249        Command::Recover {
250            receipt,
251            password_fd,
252        } => print_outcome(
253            "recovery-complete",
254            service.recover(
255                read_recovery_password(password_fd)?,
256                ReceiptRef::new(receipt)?,
257            )?,
258        ),
259        Command::ProbeWrongRecovery { password_fd } => print_outcome(
260            "wrong-recovery-password-rejected",
261            service.probe_wrong_recovery_password(read_recovery_password(password_fd)?)?,
262        ),
263        Command::ProbeCorruptRecovery { password_fd } => print_outcome(
264            "corrupt-recovery-artifact-rejected",
265            service.probe_corrupt_recovery_artifact(read_recovery_password(password_fd)?)?,
266        ),
267        Command::Reset {
268            identity_ref,
269            receipt,
270            ..
271        } => print_outcome(
272            "reset-marked",
273            service.reset(&IdentityRef::new(identity_ref)?, ReceiptRef::new(receipt)?)?,
274        ),
275        Command::ResumeReset { .. } => print_outcome("reset-resumed", service.resume_reset()?),
276        Command::Safety => print_outcome(
277            "safety-checked",
278            json!({
279                "keyring_service": IDENTITY_PROOF_KEYRING_SERVICE,
280                "keyring_account": IDENTITY_PROOF_KEYRING_ACCOUNT,
281                "production_locator_access": "rejected-by-construction",
282            }),
283        ),
284    }
285}
286
287fn read_recovery_password(file_descriptor: i32) -> Result<RecoveryPassword> {
288    if file_descriptor < 3 {
289        bail!("recovery password requires a protected file descriptor");
290    }
291    #[cfg(unix)]
292    {
293        use std::fs::File;
294        use std::os::fd::FromRawFd as _;
295        use zeroize::Zeroizing;
296
297        let mut file = unsafe { File::from_raw_fd(file_descriptor) };
298        let mut bytes = Zeroizing::new(Vec::with_capacity(1_025));
299        file.by_ref().take(1_025).read_to_end(&mut bytes)?;
300        if bytes.len() > 1_024 {
301            bail!("recovery password exceeds the proof-driver limit");
302        }
303        let mut password = Zeroizing::new(
304            String::from_utf8(std::mem::take(&mut *bytes))
305                .map_err(|_| anyhow::anyhow!("recovery password is not UTF-8"))?,
306        );
307        return RecoveryPassword::new(std::mem::take(&mut *password))
308            .map_err(|error| anyhow::anyhow!(error));
309    }
310    #[cfg(not(unix))]
311    {
312        let _ = file_descriptor;
313        bail!("protected password file descriptors are unsupported on this platform");
314    }
315}
316
317fn command_crash_boundary(command: &Command) -> Option<ProofCrashBoundary> {
318    match command {
319        Command::Create { crash_after, .. }
320        | Command::ProcessStart { crash_after }
321        | Command::Reset { crash_after, .. }
322        | Command::ResumeReset { crash_after } => crash_after.map(Into::into),
323        _ => None,
324    }
325}
326
327fn signing_request(identity_ref: IdentityRef, request: String) -> Result<AdmittedSigningRequest> {
328    Ok(AdmittedSigningRequest {
329        request_ref: ReceiptRef::new(request)?,
330        identity_ref,
331        purpose: SigningPurpose::NostrEvent,
332        event: UnsignedEventTemplate {
333            created_at: 1_700_000_000,
334            kind: 1,
335            tags: Vec::new(),
336            content: "Omega identity proof event".to_string(),
337        },
338    })
339}
340
341fn rejected_signing_outcome(
342    service: &IdentityProofService,
343    identity_ref: IdentityRef,
344    request: String,
345    outcome: &str,
346) -> Result<()> {
347    let request = signing_request(identity_ref, request)?;
348    match service.sign(&request) {
349        Ok(_) => bail!("forged or stale signing request was unexpectedly admitted"),
350        Err(error) => print_outcome(outcome, json!({ "typed_error": error.to_string() })),
351    }
352}
353
354fn print_outcome<T: Serialize>(outcome: &str, facts: T) -> Result<()> {
355    println!(
356        "{}",
357        serde_json::to_string_pretty(&Outcome {
358            protocol: IDENTITY_PROOF_PROTOCOL,
359            outcome,
360            facts,
361        })?
362    );
363    Ok(())
364}
365
Served at tenant.openagents/omega Member data and write actions are omitted.