95 lines
3.0 KiB
Markdown
95 lines
3.0 KiB
Markdown
{{#include ../links.md}}
|
|
|
|
# Writing Binaries
|
|
|
|
### Binaries - Porcelain and Plumbing
|
|
|
|
Binaries are split into two categories, porcelain (such as `keyfork`) and
|
|
plumbing (just about everything else). Porcelain binaries include what can be
|
|
called "the kitchen sink". They offer support for everything - an intuitive
|
|
interface, automatic `keyforkd` management, interconnectivity between
|
|
derivation utilities and provisioning utilities, and the ability to read from
|
|
and write to a configuration file. Plumbing binaries, on the other hand, are
|
|
often very rough around the edges and pull in as few dependencies as possible.
|
|
Usually, only cryptographic functionality (such as `sequoia-openpgp` or
|
|
`dalek-ed25519`) or hardware integration libraries (such as `openpgp-card`) are
|
|
included.
|
|
|
|
### Writing Binaries
|
|
|
|
Crates can be either a library, or a library and binary, but should never be
|
|
just a binary. When creating a crate with a binary, the `main.rs` file should
|
|
be designed to validate arguments, load any necessary system resources, and
|
|
call a separate exposed function to do the heavy lifting. The following example
|
|
was taken from `keyfork-shard` to demonstrate how a program can validate
|
|
arguments, parse input, and stream an output.
|
|
|
|
```rust
|
|
use std::{
|
|
collections::VecDeque,
|
|
env,
|
|
io::{stdin, stdout},
|
|
path::PathBuf,
|
|
process::ExitCode,
|
|
str::FromStr,
|
|
};
|
|
|
|
use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert};
|
|
use sequoia_openpgp as openpgp;
|
|
|
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
|
|
|
fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
|
|
let threshold = u8::from_str(threshold)?;
|
|
let key_discovery = PathBuf::from(key_discovery);
|
|
|
|
// Verify path exists
|
|
std::fs::metadata(&key_discovery)?;
|
|
|
|
// Load certs from path
|
|
let certs = discover_certs(key_discovery)?;
|
|
|
|
Ok((threshold, certs))
|
|
}
|
|
|
|
fn run() -> Result<()> {
|
|
let mut args = env::args();
|
|
let program_name = args.next().expect("program name");
|
|
let args = args.collect::<Vec<_>>();
|
|
let (threshold, cert_list) = match args.as_slice() {
|
|
[threshold, key_discovery] => validate(threshold, key_discovery)?,
|
|
_ => panic!("Usage: {program_name} threshold key_discovery"),
|
|
};
|
|
|
|
let encrypted_messages = parse_messages(stdin())?;
|
|
|
|
let encrypted_metadata = encrypted_messages
|
|
.pop_front()
|
|
.expect("any pgp encrypted message");
|
|
|
|
combine(
|
|
threshold,
|
|
cert_list,
|
|
encrypted_metadata,
|
|
encrypted_messages.into(),
|
|
stdout(),
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
let result = run();
|
|
if let Err(e) = result {
|
|
eprintln!("Error: {e}");
|
|
return ExitCode::FAILURE;
|
|
}
|
|
ExitCode::SUCCESS
|
|
}
|
|
```
|
|
|
|
Designing binaries with this format makes it easier to load them to the Keyfork
|
|
porcelain binary, since the porcelain can call `combine()` with arguments that
|
|
it has parsed using its own configuration systems, using a `String` as a `mut
|
|
Write` as necessary.
|