icepick/crates/miniquorum/src/main.rs

118 lines
4.3 KiB
Rust

use clap::Parser;
use miniquorum::{Payload, PayloadVerification};
use sequoia_openpgp::Fingerprint;
use std::{fs::File, path::PathBuf};
#[derive(clap::Parser)]
/// An Icepick-specific subset of the Quorum decision-making system.
enum MiniQuorum {
/// Verify signatures on an Icepick Payload file.
VerifySignatures {
/// The file containing OpenPGP Certificates used for verifying signatures.
keyring_file: PathBuf,
/// The file provided as input.
///
/// If no file is passed, standard input is used.
input_file: Option<PathBuf>,
/// An OpenPGP Fingerprint to use in place of on-smartcard certificate detection.
///
/// This functionality is only recommended if verifying a payload without the physical
/// presence of any signer, and builds a web of trust from the signer fingerprint provided.
#[arg(long)]
fingerprint: Option<Fingerprint>,
/// The file to write the resulting payload to, if verification is successful.
#[arg(long)]
output_file: Option<PathBuf>,
},
/// Add a signature to an Icepick Payload file.
AddSignature {
/// The file to use as input.
///
/// If no file is provided, standard input is used. If a file is provided and no output
/// file is provided, it will be used in-place as the output file with the additional
/// signature added.
input_file: Option<PathBuf>,
/// The file to use as output.
///
/// If no file is provided, but an input file is provided, the input file is used. If no
/// input file is provided, standard output is used.
#[arg(long)]
output_file: Option<PathBuf>,
},
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
match MiniQuorum::parse() {
MiniQuorum::VerifySignatures {
keyring_file,
input_file,
fingerprint,
output_file,
} => {
assert_ne!(
input_file, output_file,
"output is verified data; not overwriting signed input data"
);
let (payload, certs) = match input_file {
Some(input_file) => Payload::load(&input_file, &keyring_file)?,
None => {
let stdin = std::io::stdin();
let keyring_file = File::open(&keyring_file)?;
Payload::from_readers(stdin, keyring_file)?
}
};
let policy = PayloadVerification::new().with_threshold(certs.len().try_into()?);
payload.verify_signatures(&certs, &policy, fingerprint)?;
if let Some(output_file) = output_file {
let file = File::create(output_file)?;
serde_json::to_writer_pretty(file, &payload)?;
} else {
let stdout = std::io::stdout();
serde_json::to_writer_pretty(stdout, &payload)?;
}
}
MiniQuorum::AddSignature {
input_file,
output_file,
} => {
let mut payload: Payload = match &input_file {
Some(input_file) => {
let input_file = File::open(input_file)?;
serde_json::from_reader(input_file)?
}
None => {
let stdin = std::io::stdin();
serde_json::from_reader(stdin)?
}
};
payload.add_signature()?;
if let Some(output_file) = output_file {
// write to output
let file = File::create(output_file)?;
serde_json::to_writer_pretty(file, &payload)?;
} else if let Some(input_file) = input_file {
// write to tempfile, move to input_file
let output_file = input_file.with_extension("tmp");
let mut file = File::create_new(&output_file)?;
serde_json::to_writer_pretty(&mut file, &payload)?;
drop(file);
std::fs::copy(&output_file, input_file)?;
std::fs::remove_file(output_file)?;
} else {
// write to standard output?
println!("{}", serde_json::to_string_pretty(&payload)?);
}
}
}
Ok(())
}