diff --git a/.gitignore b/.gitignore index eb5a316..54fc25f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target +testing_data diff --git a/keyfork-derive-key/src/main.rs b/keyfork-derive-key/src/main.rs index ecdab9b..d6456b5 100644 --- a/keyfork-derive-key/src/main.rs +++ b/keyfork-derive-key/src/main.rs @@ -1,4 +1,4 @@ -use std::{env, str::FromStr}; +use std::{env, process::ExitCode, str::FromStr}; use keyfork_derive_util::{ request::{DerivationAlgorithm, DerivationError, DerivationRequest}, @@ -27,7 +27,7 @@ fn validate(algo: &str, path: &str) -> Result<(DerivationAlgorithm, DerivationPa Ok((algo, path)) } -fn main() -> Result<(), Box> { +fn run() -> Result<(), Box> { let mut args = env::args(); let program_name = args.next().expect("program name"); let args = args.collect::>(); @@ -42,3 +42,12 @@ fn main() -> Result<(), Box> { println!("{}", smex::encode(&response.data)); Ok(()) } + +fn main() -> ExitCode { + if let Err(e) = run() { + eprintln!("Error: {e}"); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } +} diff --git a/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs b/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs index 98963c1..8e20e1c 100644 --- a/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs +++ b/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs @@ -1,5 +1,4 @@ use std::{ - collections::VecDeque, env, io::{stdin, stdout}, path::PathBuf, @@ -7,30 +6,7 @@ use std::{ str::FromStr, }; -use keyfork_shard::{combine, discover_certs, openpgp::Cert, EncryptedMessage}; -use openpgp::{ - packet::Packet, - parse::Parse, - PacketPile, -}; -use sequoia_openpgp as openpgp; - -#[derive(Clone, Debug)] -enum Error { - Usage(String), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::Usage(program_name) => { - write!(f, "Usage: {program_name} threshold key_discovery") - } - } - } -} - -impl std::error::Error for Error {} +use keyfork_shard::{combine, discover_certs, parse_messages, openpgp::Cert}; type Result> = std::result::Result; @@ -53,25 +29,10 @@ fn run() -> Result<()> { let args = args.collect::>(); let (threshold, cert_list) = match args.as_slice() { [threshold, key_discovery] => validate(threshold, key_discovery)?, - _ => return Err(Error::Usage(program_name).into()), + _ => panic!("Usage: {program_name} threshold key_discovery"), }; - let stdin = stdin(); - - let mut pkesks = Vec::new(); - let mut encrypted_messages = VecDeque::new(); - - for packet in PacketPile::from_reader(stdin)?.into_children() { - match packet { - Packet::PKESK(p) => pkesks.push(p), - Packet::SEIP(s) => { - encrypted_messages.push_back(EncryptedMessage::with_swap(&mut pkesks, s)); - } - s => { - panic!("Invalid variant found: {}", s.tag()); - } - } - } + let mut encrypted_messages = parse_messages(stdin())?; let encrypted_metadata = encrypted_messages .pop_front() diff --git a/keyfork-shard/src/lib.rs b/keyfork-shard/src/lib.rs index 64f24f9..76c0b4c 100644 --- a/keyfork-shard/src/lib.rs +++ b/keyfork-shard/src/lib.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, io::{Read, Write}, path::Path, str::FromStr, @@ -12,7 +12,7 @@ use keyfork_derive_openpgp::derive_util::{ use openpgp::{ armor::{Kind, Writer}, cert::{Cert, CertParser, ValidCert}, - packet::{Tag, UserID, PKESK, SEIP}, + packet::{Packet, Tag, UserID, PKESK, SEIP}, parse::{stream::DecryptorBuilder, Parse}, policy::{NullPolicy, Policy, StandardPolicy}, serialize::{ @@ -20,7 +20,7 @@ use openpgp::{ Marshal, }, types::KeyFlags, - KeyID, + KeyID, PacketPile }; pub use sequoia_openpgp as openpgp; use sharks::{Share, Sharks}; @@ -75,24 +75,6 @@ impl EncryptedMessage { message.write_all(&packet)?; message.finalize()?; - /* - // TODO: only serialize the message and use provided PKESK vec - let mut pkesks = vec![]; - let ppr = PacketParser::from_reader(&packets[..])?; - while let PacketParserResult::Some(mut pp) = ppr { - match pp.packet { - openpgp::Packet::PKESK(pkesk) => pkesks.push(pkesk), - openpgp::Packet::SEIP(_) => { - keyring.decrypt(&pkesks, &[], None, |algo, sk| { - // pushes packet to stack - pp.decrypt(algo, sk).is_ok() - }); - }, - p => panic!("Unexpected packet type: {}", p.tag()), - } - } - */ - let mut decryptor = DecryptorBuilder::from_bytes(&packets)?.with_policy(policy, None, keyring)?; @@ -124,6 +106,25 @@ pub fn discover_certs(path: impl AsRef) -> Result> { } } +pub fn parse_messages(reader: impl Read + Send + Sync) -> Result >{ + let mut pkesks = Vec::new(); + let mut encrypted_messages = VecDeque::new(); + + for packet in PacketPile::from_reader(reader)?.into_children() { + match packet { + Packet::PKESK(p) => pkesks.push(p), + Packet::SEIP(s) => { + encrypted_messages.push_back(EncryptedMessage::with_swap(&mut pkesks, s)); + } + s => { + panic!("Invalid variant found: {}", s.tag()); + } + } + } + + Ok(encrypted_messages) +} + fn get_encryption_keys<'a>( cert: &'a ValidCert, ) -> openpgp::cert::prelude::ValidKeyAmalgamationIter< diff --git a/keyfork-user-guide/src/SUMMARY.md b/keyfork-user-guide/src/SUMMARY.md index 3579e0e..7a544b4 100644 --- a/keyfork-user-guide/src/SUMMARY.md +++ b/keyfork-user-guide/src/SUMMARY.md @@ -1,6 +1,8 @@ # Summary +# User Guide + - [Installing Keyfork](./INSTALL.md) - [Common Usage](./usage.md) - [Binaries](./bin/index.md) @@ -11,4 +13,8 @@ - [keyfork-mnemonic-from-seed](./bin/keyfork-plumbing/mnemonic-from-seed.md) - [keyfork-derive-key](./bin/keyfork-derive-key.md) - [keyfork-derive-openpgp](./bin/keyfork-derive-openpgp.md) -- [Development Guide](./dev-guide/index.md) + +# Developers Guide + +- [Writing Binaries](./dev-guide/index.md) +- [Auditing](./dev-guide/auditing.md) diff --git a/keyfork-user-guide/src/bin/keyfork-derive-key.md b/keyfork-user-guide/src/bin/keyfork-derive-key.md index c5be8af..b6a6a62 100644 --- a/keyfork-user-guide/src/bin/keyfork-derive-key.md +++ b/keyfork-user-guide/src/bin/keyfork-derive-key.md @@ -18,3 +18,5 @@ the shell silently ignoring the single quotes in the derivation path. Hex-encoded private key. Note that this is not the _extended_ private key, and can't be used to derive further data. + +[`keyforkd`]: ./bin/keyforkd.md diff --git a/keyfork-user-guide/src/bin/keyfork-derive-openpgp.md b/keyfork-user-guide/src/bin/keyfork-derive-openpgp.md index 7bbfaaa..c9bd7fa 100644 --- a/keyfork-user-guide/src/bin/keyfork-derive-openpgp.md +++ b/keyfork-user-guide/src/bin/keyfork-derive-openpgp.md @@ -28,3 +28,5 @@ the shell silently ignoring the single quotes in the derivation path. ## Output OpenPGP ASCII armored key, signed to be valid for 24 hours. + +[`keyforkd`]: ./bin/keyforkd.md diff --git a/keyfork-user-guide/src/bin/keyfork/mnemonic/index.md b/keyfork-user-guide/src/bin/keyfork/mnemonic/index.md index 58fac96..6f0794a 100644 --- a/keyfork-user-guide/src/bin/keyfork/mnemonic/index.md +++ b/keyfork-user-guide/src/bin/keyfork/mnemonic/index.md @@ -12,11 +12,16 @@ requires a system be both offline and running an up-to-date kernel before generating a mnemonic. However, the command may be run with the variable `INSECURE_HARDWARE_ALLOWED=1` to override system validation. -## Usage +## Arguments -``` -% keyfork mnemonic generate -ceiling talent smooth jealous dust render hello resource ripple crucial pepper tribe noble gate shield glad slide document pulse negative spider this fancy seven -% keyfork mnemonic generate --size 128 -sustain glory control silk gym argue jaguar citizen remember doctor depth senior -``` +`keyfork mnemonic generate --source=system --size=256` + +* `--source`: The source from where a seed is created. Can be: + * `system` + + Choosing any option besides `system` will prompt the user for input: + + * `playing` + * `tarot` + * `dice` +* `--size`: The size in bits for the memonic, either `128` or `256`. diff --git a/keyfork-user-guide/src/dev-guide/auditing.md b/keyfork-user-guide/src/dev-guide/auditing.md new file mode 100644 index 0000000..196aa0c --- /dev/null +++ b/keyfork-user-guide/src/dev-guide/auditing.md @@ -0,0 +1,22 @@ +# Auditing Dependencies + +Dependencies must be reviewed before being added to the repository, and must +not be added for pure convenience. There are few exceptions, such as `clap` and +`thiserror`, which provide derivation macros that are used heavily throughout +`keyfork` and the codebase as a whole. Any dependency added must be reviewed at +least on a surface level to ensure no malicious actions are performed with the +data the library will be responsible for handling. For example, any use of +`std::process` in a crate providing cryptographic functions should be heavily +scrutinized, and any crate that loads arbitrary code or performs networking +requests should have an incredibly important reason for doing so. + +Dependencies should be restricted such that the least amount of dead code is +enabled. For instance, a crate such as `keyfork_derive_openpgp` can only make +use of the `ed25519` algorithm, so it exports its own `derive_util` that only +includes the crates required for that library. This can then be used by +programs such as `keyfork-shard`'s OpenPGP mode or `keyfork provision openpgp` +to ensure only the required dependencies are enabled. This reduces the burden +of auditors, but it does mean we can't use projects such as [`hakari`] to +optimize full-project builds. + +[`hakari`]: https://docs.rs/cargo-hakari/latest/cargo_hakari/index.html diff --git a/keyfork-user-guide/src/dev-guide/index.md b/keyfork-user-guide/src/dev-guide/index.md index c7a2028..cfc5451 100644 --- a/keyfork-user-guide/src/dev-guide/index.md +++ b/keyfork-user-guide/src/dev-guide/index.md @@ -1,4 +1,4 @@ -# Development Guide +# Writing Binaries ### Binaries - Porcelain and Plumbing @@ -13,25 +13,80 @@ Usually, only cryptographic functionality (such as `sequoia-openpgp` or `dalek-ed25519`) or hardware integration libraries (such as `openpgp-card`) are included. -### Auditing Dependencies +### Writing Binaries -Dependencies must be reviewed before being added to the repository, and must -not be added for pure convenience. There are few exceptions, such as `clap` and -`thiserror`, which provide derivation macros that are used heavily throughout -`keyfork` and the codebase as a whole. Any dependency added must be reviewed at -least on a surface level to ensure no malicious actions are performed with the -data the library will be responsible for handling. For example, any use of -`std::process` in a crate providing cryptographic functions should be heavily -scrutinized, and any crate that loads arbitrary code or performs networking -requests should have an incredibly important reason for doing so. +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. -Dependencies should be restricted such that the least amount of dead code is -enabled. For instance, a crate such as `keyfork_derive_openpgp` can only make -use of the `ed25519` algorithm, so it exports its own `derive_util` that only -includes the crates required for that library. This can then be used by -programs such as `keyfork-shard`'s OpenPGP mode or `keyfork provision openpgp` -to ensure only the required dependencies are enabled. This reduces the burden -of auditors, but it does mean we can't use projects such as [`hakari`] to -optimize full-project builds. +```rust +use std::{ + collections::VecDeque, + env, + io::{stdin, stdout}, + path::PathBuf, + process::ExitCode, + str::FromStr, +}; -[`hakari`]: https://docs.rs/cargo-hakari/latest/cargo_hakari/index.html +use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert}; +use sequoia_openpgp as openpgp; + +type Result> = std::result::Result; + +fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec)> { + 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::>(); + 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.