70 lines
3.4 KiB
Rust
70 lines
3.4 KiB
Rust
//! ISOLATED FIXTURE ONLY: native production crypto with a compile-time TEST anchor.
|
|
//! Not an Archipelago candidate executable; never installed on a real node.
|
|
use anyhow::{bail, ensure, Context, Result};
|
|
use ed25519_dalek::SigningKey;
|
|
use serde_json::{json, Value};
|
|
use std::{fs, path::Path, process::Command, thread, time::Duration};
|
|
mod trust {
|
|
pub mod anchor;
|
|
pub mod canonical;
|
|
pub mod did;
|
|
pub mod signed_doc;
|
|
}
|
|
|
|
fn guest_only() -> Result<()> {
|
|
ensure!(fs::read_to_string("/sys/class/dmi/id/sys_vendor")?.contains("QEMU"), "private QEMU fixture only");
|
|
ensure!(Path::new("/opt/os-ota-crypto-fixture.json").is_file(), "fixture appointment absent");
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
guest_only()?;
|
|
let args: Vec<String> = std::env::args().collect();
|
|
if args.len() == 4 && args[1..3] == ["ceremony", "verify"] {
|
|
let doc: Value = serde_json::from_slice(&fs::read(&args[3])?)?;
|
|
match trust::signed_doc::verify_detached(&doc)? {
|
|
trust::signed_doc::SignatureStatus::Verified { signer_did, anchored: true } => {
|
|
println!("{}", json!({"fixture_only":true,"anchored":true,"signed_by":signer_did}));
|
|
return Ok(());
|
|
}
|
|
_ => bail!("unsigned or unanchored fixture document refused"),
|
|
}
|
|
}
|
|
if args.len() == 5 && args[1] == "fixture-sign" {
|
|
let key_path = Path::new(&args[2]);
|
|
ensure!(key_path.parent() == Some(Path::new("/root/os-ota-crypto-fixture")), "only disposable fixture key directory");
|
|
let bytes: [u8;32] = fs::read(key_path)?.try_into().map_err(|_| anyhow::anyhow!("exact 32-byte disposable seed required"))?;
|
|
let key = SigningKey::from_bytes(&bytes);
|
|
let mut doc: Value = serde_json::from_slice(&fs::read(&args[3])?)?;
|
|
let (signature, signed_by) = trust::signed_doc::sign_detached(&key, &doc)?;
|
|
let object = doc.as_object_mut().context("object required")?;
|
|
object.insert("signature".into(), json!(signature));
|
|
object.insert("signed_by".into(), json!(signed_by));
|
|
fs::write(&args[4], serde_json::to_vec_pretty(&doc)?)?;
|
|
println!("EPHEMERAL_FIXTURE_SIGNATURE_NOT_RELEASE_CEREMONY");
|
|
return Ok(());
|
|
}
|
|
if args.len() == 2 && args[1] == "--version" {
|
|
println!("ISOLATED_OS_OTA_CRYPTO_FIXTURE_NOT_CANDIDATE v1");
|
|
return Ok(());
|
|
}
|
|
ensure!(args.len() == 1, "unsupported fixture operation");
|
|
// Inert native requester, so PID1's actual executable matches the appointed
|
|
// verifier/prerequisite. Every mutation goes through the shipped D09 client.
|
|
let control = Path::new("/run/d09-fixture-client");
|
|
loop {
|
|
for entry in fs::read_dir(control)? {
|
|
let path = entry?.path();
|
|
let name = path.file_name().unwrap().to_string_lossy();
|
|
if !name.starts_with("request-") || !name.ends_with(".json") { continue; }
|
|
let output = control.join(name.replacen("request-", "result-", 1));
|
|
if output.exists() { continue; }
|
|
let request: Vec<String> = serde_json::from_slice(&fs::read(&path)?)?;
|
|
let result = Command::new("/usr/libexec/archipelago-d09ctl").args(request).output()?;
|
|
fs::write(output, serde_json::to_vec(&json!({"exit":result.status.code().unwrap_or(128),
|
|
"stdout":String::from_utf8_lossy(&result.stdout),"stderr":String::from_utf8_lossy(&result.stderr)}))?)?;
|
|
}
|
|
thread::sleep(Duration::from_millis(100));
|
|
}
|
|
}
|