118 lines
4.3 KiB
Rust
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(())
|
|
}
|