add canonical sigchain bundle format

This commit is contained in:
Eric Wendland 2026-07-19 03:16:14 +02:00
commit decff4b995
14 changed files with 1515 additions and 42 deletions

View file

@ -1048,7 +1048,7 @@ pub enum KeychainCommand {
#[arg(long)]
principal: Option<String>,
},
/// Verify a linked SSHSIGCHAIN JSONL file against an explicit root key
/// Verify SSHSIGCHAIN JSONL or a canonical bundle against an explicit root key
VerifySigchain {
#[arg(long = "in")]
input: PathBuf,
@ -1059,6 +1059,22 @@ pub enum KeychainCommand {
#[arg(long, default_value = geth_keychain::SSH_SIGCHAIN_NAMESPACE)]
namespace: String,
},
/// Convert an SSHSIGCHAIN JSONL interchange file to the canonical binary bundle
BundleCreate {
#[arg(long = "in")]
input: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value = geth_keychain::SSH_SIGCHAIN_NAMESPACE)]
namespace: String,
},
/// Extract records from a canonical SSHSIGCHAIN bundle as JSONL
BundleExtract {
#[arg(long = "in")]
input: PathBuf,
#[arg(long)]
out: PathBuf,
},
/// Explain why one keychain operation was accepted or rejected
Explain { op_id: String },
/// Explain the current trust state of one signer
@ -2646,6 +2662,21 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
root_key_path: root_key,
namespace: Some(namespace),
},
Command::Keychain {
command:
KeychainCommand::BundleCreate {
input,
out,
namespace,
},
} => ControlRequest::KeychainSigchainBundleCreate {
input,
out,
namespace,
},
Command::Keychain {
command: KeychainCommand::BundleExtract { input, out },
} => ControlRequest::KeychainSigchainBundleExtract { input, out },
Command::Keychain {
command: KeychainCommand::Explain { op_id },
} => ControlRequest::KeychainExplain { op_id },
@ -4117,6 +4148,11 @@ fn print_response(response: ControlResponse, output: OutputMode) -> Result<()> {
anchor_backend_threshold,
required_anchor_backends,
required_anchor_classes,
format,
disclosures,
claims,
receipts,
bundle_hash,
note,
} => {
println!("sigchain: {}", input.display());
@ -4131,6 +4167,34 @@ fn print_response(response: ControlResponse, output: OutputMode) -> Result<()> {
println!("anchor_backend_threshold: {anchor_backend_threshold}");
println!("required_anchor_backends: {required_anchor_backends}");
println!("required_anchor_classes: {required_anchor_classes}");
println!("format: {format}");
println!("disclosures: {disclosures}");
println!("claims: {claims}");
println!("receipts: {receipts}");
println!("bundle_hash: {}", bundle_hash.as_deref().unwrap_or("none"));
eprintln!("note: {note}");
}
ControlResponse::KeychainSigchainBundleWritten {
input,
out,
format,
records,
disclosures,
claims,
receipts,
bundle_hash,
static_http_path,
note,
} => {
println!("input: {}", input.display());
println!("out: {}", out.display());
println!("format: {format}");
println!("records: {records}");
println!("disclosures: {disclosures}");
println!("claims: {claims}");
println!("receipts: {receipts}");
println!("bundle_hash: {bundle_hash}");
println!("static_http_path: {static_http_path}");
eprintln!("note: {note}");
}
ControlResponse::KeychainExplained { subject, lines } => {
@ -5346,6 +5410,38 @@ mod tests {
command: KeychainCommand::VerifySigchain { .. }
}
));
assert!(matches!(
Cli::try_parse_from([
"geth",
"keychain",
"bundle-create",
"--in",
"chain.jsonl",
"--out",
"chain.sscb",
])
.expect("parse canonical bundle creation")
.command,
Command::Keychain {
command: KeychainCommand::BundleCreate { .. }
}
));
assert!(matches!(
Cli::try_parse_from([
"geth",
"keychain",
"bundle-extract",
"--in",
"chain.sscb",
"--out",
"chain.jsonl",
])
.expect("parse canonical bundle extraction")
.command,
Command::Keychain {
command: KeychainCommand::BundleExtract { .. }
}
));
for removed in [
"sigchain",
"publish-bundle",