From 701f5ca4e930f9b4f92b438234814debbbae0fef Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 15 Jan 2024 21:44:48 -0500 Subject: [PATCH] all crates: add documentation --- README.md | 2 +- crates/daemon/keyforkd-client/src/lib.rs | 26 ++++++++ crates/daemon/keyforkd-models/src/lib.rs | 10 ++- crates/daemon/keyforkd/src/error.rs | 2 + crates/daemon/keyforkd/src/lib.rs | 30 +++++++++ crates/daemon/keyforkd/src/main.rs | 2 + crates/daemon/keyforkd/src/middleware.rs | 7 ++ crates/daemon/keyforkd/src/server.rs | 12 ++++ crates/daemon/keyforkd/src/service.rs | 9 +++ crates/derive/keyfork-derive-key/src/main.rs | 7 ++ .../derive/keyfork-derive-openpgp/Cargo.toml | 3 + .../derive/keyfork-derive-openpgp/src/lib.rs | 19 ++++++ .../derive/keyfork-derive-openpgp/src/main.rs | 2 + .../keyfork-derive-path-data/src/lib.rs | 5 ++ .../src/extended_key/mod.rs | 2 + .../src/extended_key/private_key.rs | 5 ++ .../derive/keyfork-derive-util/src/index.rs | 2 + crates/derive/keyfork-derive-util/src/lib.rs | 6 ++ crates/derive/keyfork-derive-util/src/path.rs | 5 ++ .../derive/keyfork-derive-util/src/request.rs | 40 ++++++++++- crates/keyfork-shard/Cargo.toml | 1 + .../src/bin/keyfork-shard-combine-openpgp.rs | 2 + .../src/bin/keyfork-shard-decrypt-openpgp.rs | 2 + .../src/bin/keyfork-shard-remote.rs | 2 + .../src/bin/keyfork-shard-split-openpgp.rs | 2 + crates/keyfork-shard/src/lib.rs | 19 +++++- crates/keyfork-shard/src/openpgp.rs | 66 ++++++++++++++++++- crates/keyfork/src/main.rs | 3 + .../src/bin/keyfork-qrcode-scan.rs | 2 + crates/qrcode/keyfork-qrcode/src/lib.rs | 25 +++++++ crates/qrcode/keyfork-zbar-sys/build.rs | 2 + crates/qrcode/keyfork-zbar-sys/src/lib.rs | 1 + .../qrcode/keyfork-zbar/examples/v4l-scan.rs | 4 +- crates/qrcode/keyfork-zbar/src/image.rs | 3 + .../qrcode/keyfork-zbar/src/image_scanner.rs | 19 +++++- crates/qrcode/keyfork-zbar/src/symbol.rs | 8 +++ .../examples/event-read-char-line.rs | 2 + .../util/keyfork-crossterm/examples/is_tty.rs | 4 +- .../util/keyfork-crossterm/examples/stderr.rs | 1 + crates/util/keyfork-crossterm/src/lib.rs | 1 + crates/util/keyfork-crossterm/src/macros.rs | 2 +- crates/util/keyfork-entropy/src/lib.rs | 7 ++ crates/util/keyfork-frame/src/asyncext.rs | 6 +- crates/util/keyfork-frame/src/lib.rs | 15 +++++ crates/util/keyfork-mnemonic-util/src/lib.rs | 7 ++ .../src/bin/keyfork-entropy.rs | 2 + .../src/bin/keyfork-mnemonic-from-seed.rs | 2 + .../bin => examples}/test-basic-prompt.rs | 2 + crates/util/keyfork-prompt/src/lib.rs | 46 +++++++++++++ crates/util/keyfork-prompt/src/terminal.rs | 11 ++++ crates/util/keyfork-prompt/src/validators.rs | 31 +++++++++ .../util/keyfork-slip10-test-data/src/lib.rs | 24 ++++++- crates/util/smex/src/lib.rs | 12 ++++ 53 files changed, 518 insertions(+), 14 deletions(-) rename crates/util/keyfork-prompt/{src/bin => examples}/test-basic-prompt.rs (99%) diff --git a/README.md b/README.md index b758dc6..28201ce 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Note: The following features are proposed, and may not yet be implemented. * Unpredictable * Generate a BIP39 phrase from OS or physicalized entropy * Provide and use BIP39 passphrase from user supplied entropy - * Read up on [https://milksad.info](milksad) to understand why this matters! + * Read up on [milksad](https://milksad.info) to understand why this matters! * Deterministic * Given the same seed, repeated derivation requests will be reproducible * Any secret data can be derived again at any point in the future diff --git a/crates/daemon/keyforkd-client/src/lib.rs b/crates/daemon/keyforkd-client/src/lib.rs index 77765fc..2362895 100644 --- a/crates/daemon/keyforkd-client/src/lib.rs +++ b/crates/daemon/keyforkd-client/src/lib.rs @@ -1,3 +1,5 @@ +//! A client for Keyforkd. + use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf}; use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError}; @@ -6,32 +8,46 @@ use keyforkd_models::{Request, Response, Error as KeyforkdError}; #[cfg(test)] mod tests; +/// An error occurred while interacting with Keyforkd. #[derive(Debug, thiserror::Error)] pub enum Error { + /// The environment variables used for determining a Keyforkd socket path were not set. #[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")] EnvVarsNotFound, + /// The Keyforkd client was unable to connect to the soocket. #[error("Socket was unable to connect to {1}: {0} (make sure keyforkd is running)")] Connect(std::io::Error, PathBuf), + /// Data could not be written to, or read from, the socket. #[error("Could not write to or from the socket: {0}")] Io(#[from] std::io::Error), + /// Attempting to serialize or deserialize a type to or from bincode encountered an error. #[error("Could not perform bincode transformation: {0}")] Bincode(#[from] Box), + /// A frame could not be encoded from the given data. #[error("Could not perform frame transformation: {0}")] FrameEnc(#[from] EncodeError), + /// A frame could not be decoded from the given data. #[error("Could not perform frame transformation: {0}")] FrameDec(#[from] DecodeError), + /// An error encountered in Keyforkd. #[error("Error in Keyforkd: {0}")] Keyforkd(#[from] KeyforkdError) } +#[allow(missing_docs)] pub type Result = std::result::Result; +/// Create a [`UnixStream`] from the common Keyforkd socket paths. +/// +/// # Errors +/// An error may be returned if the required environment variables were not set or if the socket +/// could not be connected to. pub fn get_socket() -> Result { let socket_vars = std::env::vars() .filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str())) @@ -71,11 +87,21 @@ impl Client { } /// Create a new client using well-known socket locations. + /// + /// # Errors + /// An error may be returned if the required environment variables were not set or if the + /// socket could not be connected to. pub fn discover_socket() -> Result { get_socket().map(|socket| Self { socket }) } /// Serialize and send a [`Request`] to the server, awaiting a [`Result`]. + /// + /// # Errors + /// An error may be returned if: + /// * Reading or writing from or to the socket encountered an error. + /// * Bincode could not serialize the request or deserialize the response. + /// * An error occurred in Keyforkd. pub fn request(&mut self, req: &Request) -> Result { try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?; let resp = try_decode_from(&mut self.socket)?; diff --git a/crates/daemon/keyforkd-models/src/lib.rs b/crates/daemon/keyforkd-models/src/lib.rs index ee5e10e..4414415 100644 --- a/crates/daemon/keyforkd-models/src/lib.rs +++ b/crates/daemon/keyforkd-models/src/lib.rs @@ -28,23 +28,30 @@ impl From<(DerivationRequest, String)> for Request { } } +/// Any error that could occur while deriving a key with Keyforkd. #[derive(thiserror::Error, Clone, Debug, Serialize, Deserialize)] pub enum DerivationError { + /// The TTY used for pinentry or passphrase entry was invalid. #[error("The provided TTY was not valid")] InvalidTTY, + /// No TTY was required for pinentry, but was not provided. #[error("A TTY was required by the pinentry program but was not provided")] NoTTY, - #[error("Invalid derivation length: Expected 2, actual: {0}")] + /// The derivation length was invalid, must be at least 2 indexes long. + #[error("Invalid derivation length: Expected at least 2, actual: {0}")] InvalidDerivationLength(usize), + /// An error occurred while deriving data. #[error("Derivation error: {0}")] Derivation(String), } +/// An error that could occur while interacting with Keyforkd. #[derive(thiserror::Error, Clone, Debug, Serialize, Deserialize)] pub enum Error { + /// An error occurred while processing a derivation request. #[error(transparent)] Derivation(#[from] DerivationError), } @@ -64,6 +71,7 @@ pub enum Response { Derivation(DerivationResponse), } +/// Attempting to convert from a [`DerivationResponse`] to a [`Response`] #[derive(thiserror::Error, Debug)] #[error("Unable to downcast to {0}")] pub struct Downcast(&'static str); diff --git a/crates/daemon/keyforkd/src/error.rs b/crates/daemon/keyforkd/src/error.rs index 229c3d0..6d9e818 100644 --- a/crates/daemon/keyforkd/src/error.rs +++ b/crates/daemon/keyforkd/src/error.rs @@ -1,7 +1,9 @@ use thiserror::Error; +/// An error occurred while starting the Keyfork server. #[derive(Debug, Clone, Error)] pub enum Keyforkd { + /// The required environment variables were not set and a socket could not be mounted. #[error("Neither KEYFORKD_SOCKET_PATH nor XDG_RUNTIME_DIR were set, nowhere to mount socket")] NoSocketPath, } diff --git a/crates/daemon/keyforkd/src/lib.rs b/crates/daemon/keyforkd/src/lib.rs index 20d9a62..e17d5ce 100644 --- a/crates/daemon/keyforkd/src/lib.rs +++ b/crates/daemon/keyforkd/src/lib.rs @@ -1,3 +1,18 @@ +//! ## The Keyfork server. +//! +//! The server uses a [`keyfork_frame`]'d [`bincode`]'d request+response format and can be +//! interacted with by using the `keyforkd_client` crate. +//! +//! All requests made to Keyfork are required to list at least two derivation paths. This helps +//! prevent cases where the master seed or the general protocol seed are leaked by a client. An +//! example is BIP-0044, where the path used is `m/44'/0'` for Bitcoin, and often `m/44'/60'` is +//! used for Ethereum. To prevent an Ethereum wallet from deriving the Bitcoin coin seed, and to +//! prevent leaking the master seed in general, all requests must contain at least two paths. +//! +//! Additionally, this ensures that keys are not reused across separate purposes. Because keys are +//! required to have at least two indexes, they are drawn to the pattern of using the first index +//! as the key's purpose, such as `m/ pgp'` for OpenPGP. + use std::{ collections::HashMap, path::{Path, PathBuf}, @@ -17,7 +32,10 @@ use tracing_subscriber::{ registry, }; +/// Errors occurring while starting Keyforkd. pub mod error; + +/// Middleware used by Keyforkd. pub mod middleware; pub mod server; pub mod service; @@ -25,6 +43,7 @@ pub use error::Keyforkd as KeyforkdError; pub use server::UnixServer; pub use service::Keyforkd; +/// Set up a Tracing subscriber, defaulting to debug mode. #[cfg(feature = "tracing")] pub fn setup_registry() { let envfilter = EnvFilter::builder() @@ -37,6 +56,11 @@ pub fn setup_registry() { .init(); } +/// Start and run a server on a given socket path. +/// +/// # Errors +/// The function may return an error if a socket can't be bound, if the service can't be created, +/// or if the server encounters an unrecoverable error while running. pub async fn start_and_run_server_on( mnemonic: Mnemonic, socket_path: &Path, @@ -66,6 +90,12 @@ pub async fn start_and_run_server_on( Ok(()) } +/// Start and run the server using a discovered socket location. +/// +/// # Errors +/// The function may return an error if the socket location could not be guessed, if a socket can't +/// be bound, if the service can't be created, or if the server encounters an unrecoverable error +/// while running. pub async fn start_and_run_server(mnemonic: Mnemonic) -> Result<(), Box> { let runtime_vars = std::env::vars() .filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str())) diff --git a/crates/daemon/keyforkd/src/main.rs b/crates/daemon/keyforkd/src/main.rs index dfe8853..f8a4524 100644 --- a/crates/daemon/keyforkd/src/main.rs +++ b/crates/daemon/keyforkd/src/main.rs @@ -1,3 +1,5 @@ +//! + use keyfork_mnemonic_util::Mnemonic; use tokio::io::{self, AsyncBufReadExt, BufReader}; diff --git a/crates/daemon/keyforkd/src/middleware.rs b/crates/daemon/keyforkd/src/middleware.rs index a8d6cbc..42c5029 100644 --- a/crates/daemon/keyforkd/src/middleware.rs +++ b/crates/daemon/keyforkd/src/middleware.rs @@ -5,12 +5,14 @@ use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; use tower::{Layer, Service}; +/// Layer a [`BincodeService`] upon another Service. pub struct BincodeLayer<'a, Request> { phantom: PhantomData<&'a ()>, phantom_request: PhantomData<&'a Request>, } impl<'a, Request> BincodeLayer<'a, Request> { + /// Create a new [`BincodeLayer`]. pub fn new() -> Self { Self { phantom: PhantomData, @@ -36,20 +38,25 @@ impl<'a, S: 'a, Request> Layer for BincodeLayer<'a, Request> { } } +/// Transform a Bincode-serialized type to a Rust type. #[derive(Clone)] pub struct BincodeService { service: S, phantom_request: PhantomData, } +/// An error encountered either while transforming data or against the interior Service. #[derive(Debug, Error)] pub enum BincodeServiceError { + /// An error occurred while polling the internal service. #[error("Error while polling: {0}")] Poll(String), + /// An error occurred while calling the internal service. #[error("Error while calling: {0}")] Call(String), + /// An error occurred while converting to or from bincode. #[error("Error while converting: {0}")] Convert(String), } diff --git a/crates/daemon/keyforkd/src/server.rs b/crates/daemon/keyforkd/src/server.rs index 9811672..42e6e5f 100644 --- a/crates/daemon/keyforkd/src/server.rs +++ b/crates/daemon/keyforkd/src/server.rs @@ -1,3 +1,5 @@ +//! A UNIX socket server to run a Tower Service. + use keyfork_frame::asyncext::{try_decode_from, try_encode_to}; use std::{ io::Error, @@ -9,12 +11,17 @@ use tower::{Service, ServiceExt}; #[cfg(feature = "tracing")] use tracing::debug; +/// A UNIX Socket Server. #[allow(clippy::module_name_repetitions)] pub struct UnixServer { listener: UnixListener, } impl UnixServer { + /// Bind a socket to the given `address` and create a [`UnixServer`]. This function also creates a ctrl_c handler to automatically clean up the socket file. + /// + /// # Errors + /// This function may return an error if the socket can't be bound. pub fn bind(address: impl AsRef) -> Result { let mut path = PathBuf::new(); path.extend(address.as_ref().components()); @@ -40,6 +47,11 @@ impl UnixServer { }) } + /// Given a Service, accept clients and use their input to call the Service. + /// + /// # Errors + /// The method may return an error if the server becomes unable to accept new connections. + /// Errors while the server is running are logged using the `tracing` crate. pub async fn run(&mut self, app: S) -> Result<(), Box> where S: Service + Clone + Send + 'static, diff --git a/crates/daemon/keyforkd/src/service.rs b/crates/daemon/keyforkd/src/service.rs index 2ff466b..31b5485 100644 --- a/crates/daemon/keyforkd/src/service.rs +++ b/crates/daemon/keyforkd/src/service.rs @@ -1,3 +1,9 @@ +//! ## The Keyfork Service. +//! +//! The Keyfork service performs the following operations: +//! +//! * Derivation of data from a preconfigured seed, with a derivation path of at least two indexes. + #![allow(clippy::implicit_clone)] use std::{future::Future, pin::Pin, sync::Arc, task::Poll}; @@ -11,12 +17,15 @@ use tracing::info; // NOTE: All values implemented in Keyforkd must implement Clone with low overhead, either by // using an Arc or by having a small signature. This is because Service takes &mut self. // + +/// The Keyfork Service. #[derive(Clone, Debug)] pub struct Keyforkd { seed: Arc>, } impl Keyforkd { + /// Create a new instance of Keyfork from a given seed. pub fn new(seed: Vec) -> Self { Self { seed: Arc::new(seed), diff --git a/crates/derive/keyfork-derive-key/src/main.rs b/crates/derive/keyfork-derive-key/src/main.rs index b6429c7..e3ed5e2 100644 --- a/crates/derive/keyfork-derive-key/src/main.rs +++ b/crates/derive/keyfork-derive-key/src/main.rs @@ -1,3 +1,5 @@ +//! + use std::{env, process::ExitCode, str::FromStr}; use keyfork_derive_util::{ @@ -6,18 +8,23 @@ use keyfork_derive_util::{ }; use keyforkd_client::Client; +/// Any error that can occur while deriving a key. #[derive(Debug, thiserror::Error)] pub enum Error { + /// The given algorithm could not be parsed. #[error("Could not parse the given algorithm {0:?}: {1}")] AlgoFormat(String, DerivationError), + /// The given path could not be parsed. #[error("Could not parse the given path: {0}")] PathFormat(#[from] keyfork_derive_util::path::Error), + /// The request to derive data failed. #[error("Unable to perform key derivation request: {0}")] KeyforkdClient(#[from] keyforkd_client::Error), } +#[allow(missing_docs)] pub type Result = std::result::Result; fn validate(algo: &str, path: &str) -> Result<(DerivationAlgorithm, DerivationPath)> { diff --git a/crates/derive/keyfork-derive-openpgp/Cargo.toml b/crates/derive/keyfork-derive-openpgp/Cargo.toml index b67737d..1080104 100644 --- a/crates/derive/keyfork-derive-openpgp/Cargo.toml +++ b/crates/derive/keyfork-derive-openpgp/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" license = "AGPL-3.0-only" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +bin = ["sequoia-openpgp/crypto-nettle"] [dependencies] keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", default-features = false, features = ["ed25519"] } diff --git a/crates/derive/keyfork-derive-openpgp/src/lib.rs b/crates/derive/keyfork-derive-openpgp/src/lib.rs index c794451..0fa727f 100644 --- a/crates/derive/keyfork-derive-openpgp/src/lib.rs +++ b/crates/derive/keyfork-derive-openpgp/src/lib.rs @@ -1,3 +1,5 @@ +//! Creation of OpenPGP certificates from BIP-0032 derived data. + use std::time::{Duration, SystemTime, SystemTimeError}; use derive_util::{ @@ -17,35 +19,52 @@ use sequoia_openpgp::{ }; pub use sequoia_openpgp as openpgp; +/// An error occurred while creating an OpenPGP key. #[derive(Debug, thiserror::Error)] pub enum Error { + /// An error occurred with the internal OpenPGP library. #[error("{0}")] Anyhow(#[from] anyhow::Error), + /// The key was configured with both encryption and non-encryption key flags. Keys can either + /// support Ed25519 signatures or Curve25519 ECDH. #[error("Key configured with both encryption and non-encryption key flags: {0:?}")] InvalidKeyFlags(KeyFlags), + /// The derivation response contained incorrect data. #[error("Incorrect derived data: {0}")] IncorrectDerivedData(#[from] TryFromDerivationResponseError), + /// A derivation index could not be created from the given index. #[error("Could not create derivation index: {0}")] Index(#[from] keyfork_derive_util::index::Error), + /// A derivation operation could not be performed against the private key. #[error("Could not perform operation against private key: {0}")] PrivateKey(#[from] keyfork_derive_util::extended_key::private_key::Error), + /// The operation involving system time was invalid. This means the system clock moved a + /// significant amount of time during the operation. #[error("Invalid system time: {0}")] SystemTime(#[from] SystemTimeError), + /// The first certificate in an OpenPGP keychain must have the Certify capability. #[error("First key in certificate must have certify capability")] NotCert, + /// The given index was out of bounds. #[error("Index out of bounds: {0}")] IndexOutOfBounds(#[from] std::num::TryFromIntError), } +#[allow(missing_docs)] pub type Result = std::result::Result; +/// Create an OpenPGP Cert with derived keys from the given derivation response, keys, and User +/// ID. +/// +/// # Errors +/// The function may error for any condition mentioned in [`Error`]. pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: &UserID) -> Result { let primary_key_flags = match keys.get(0) { Some(kf) if kf.for_certification() => kf, diff --git a/crates/derive/keyfork-derive-openpgp/src/main.rs b/crates/derive/keyfork-derive-openpgp/src/main.rs index e821836..3aec137 100644 --- a/crates/derive/keyfork-derive-openpgp/src/main.rs +++ b/crates/derive/keyfork-derive-openpgp/src/main.rs @@ -1,3 +1,5 @@ +//! + use std::{env, process::ExitCode, str::FromStr}; use keyfork_derive_util::{ diff --git a/crates/derive/keyfork-derive-path-data/src/lib.rs b/crates/derive/keyfork-derive-path-data/src/lib.rs index b16d7d2..24cbe7c 100644 --- a/crates/derive/keyfork-derive-path-data/src/lib.rs +++ b/crates/derive/keyfork-derive-path-data/src/lib.rs @@ -1,10 +1,15 @@ +//! ## Path data guesswork for BIP-0032 derivation paths. + #![allow(clippy::unreadable_literal)] use keyfork_derive_util::{DerivationIndex, DerivationPath}; +/// The default derivation path for OpenPGP. pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true); +/// A derivation target. pub enum Target { + /// An OpenPGP key, whose account is the given index. OpenPGP(DerivationIndex), } diff --git a/crates/derive/keyfork-derive-util/src/extended_key/mod.rs b/crates/derive/keyfork-derive-util/src/extended_key/mod.rs index a37c90a..8080032 100644 --- a/crates/derive/keyfork-derive-util/src/extended_key/mod.rs +++ b/crates/derive/keyfork-derive-util/src/extended_key/mod.rs @@ -1,2 +1,4 @@ +/// pub mod private_key; +/// pub mod public_key; diff --git a/crates/derive/keyfork-derive-util/src/extended_key/private_key.rs b/crates/derive/keyfork-derive-util/src/extended_key/private_key.rs index ef94c8a..6e74a94 100644 --- a/crates/derive/keyfork-derive-util/src/extended_key/private_key.rs +++ b/crates/derive/keyfork-derive-util/src/extended_key/private_key.rs @@ -100,6 +100,10 @@ where ) } + /// Create an [`ExtendedPrivateKey`] from a given `seed`, `depth`, and `chain_code`. + /// + /// # Errors + /// The function may error if a private key can't be created from the seed. pub fn new_from_parts(seed: &[u8], depth: u8, chain_code: [u8; 32]) -> Result { Ok(Self { private_key: K::from_bytes(seed.try_into()?), @@ -113,6 +117,7 @@ where &self.private_key } + /// Create an [`ExtendedPublicKey`] for the current [`PrivateKey`]. pub fn extended_public_key(&self) -> ExtendedPublicKey { ExtendedPublicKey::new(self.public_key(), self.chain_code) } diff --git a/crates/derive/keyfork-derive-util/src/index.rs b/crates/derive/keyfork-derive-util/src/index.rs index 75a75b6..3c0da37 100644 --- a/crates/derive/keyfork-derive-util/src/index.rs +++ b/crates/derive/keyfork-derive-util/src/index.rs @@ -44,6 +44,8 @@ impl DerivationIndex { } */ + /// Return the internal derivation index. Note that if the derivation index is hardened, the + /// highest bit will be set, and the value can't be used to create a new derivation index. pub fn inner(&self) -> u32 { self.0 } diff --git a/crates/derive/keyfork-derive-util/src/lib.rs b/crates/derive/keyfork-derive-util/src/lib.rs index cbbfce4..402c918 100644 --- a/crates/derive/keyfork-derive-util/src/lib.rs +++ b/crates/derive/keyfork-derive-util/src/lib.rs @@ -2,11 +2,17 @@ //! BIP-0032 derivation utilities. +/// pub mod extended_key; +/// pub mod index; +/// pub mod path; +/// pub mod private_key; +/// pub mod public_key; +/// pub mod request; #[cfg(test)] diff --git a/crates/derive/keyfork-derive-util/src/path.rs b/crates/derive/keyfork-derive-util/src/path.rs index eb2469c..c32bfdb 100644 --- a/crates/derive/keyfork-derive-util/src/path.rs +++ b/crates/derive/keyfork-derive-util/src/path.rs @@ -35,18 +35,23 @@ impl DerivationPath { self.path.iter() } + /// The amount of segments in the DerivationPath. For consistency, a [`usize`] is returned, but + /// BIP-0032 dictates that the depth should be no larger than `255`, [`u8::MAX`]. pub fn len(&self) -> usize { self.path.len() } + /// Returns true if there are no path segments. pub fn is_empty(&self) -> bool { self.path.is_empty() } + /// Append an index to the path. pub fn push(&mut self, index: DerivationIndex) { self.path.push(index); } + /// Append an index to the path, returning self to allow chaining method calls. pub fn chain_push(mut self, index: DerivationIndex) -> Self { self.path.push(index); self diff --git a/crates/derive/keyfork-derive-util/src/request.rs b/crates/derive/keyfork-derive-util/src/request.rs index a96f793..7336c7a 100644 --- a/crates/derive/keyfork-derive-util/src/request.rs +++ b/crates/derive/keyfork-derive-util/src/request.rs @@ -7,27 +7,41 @@ use crate::{ use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError}; use serde::{Deserialize, Serialize}; +/// An error encountered while deriving a key. #[derive(Debug, thiserror::Error)] pub enum DerivationError { - #[error("algorithm not supported")] + /// The algorithm requested was not supported. This may occur when a feature adding support for + /// an algorithm has not been enabled. + #[error("Algorithm not supported")] Algorithm, + /// A seed was unable to be created from the mnemonic. #[error("Unable to create seed from mnemonic: {0}")] Mnemonic(#[from] MnemonicGenerationError), + /// Generating an [`ExtendedPrivateKey`] resulted in an error. #[error("{0}")] ExtendedPrivateKey(#[from] XPrvError), } +#[allow(missing_docs)] pub type Result = std::result::Result; +/// The algorithm to derive a key for. The choice of algorithm will result in a different resulting +/// derivation. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum DerivationAlgorithm { + #[allow(missing_docs)] Ed25519, + #[allow(missing_docs)] Secp256k1, } impl DerivationAlgorithm { + /// Given a mnemonic seed and a derivation path, derive an [`ExtendedPrivateKey`]. + /// + /// # Errors + /// The method may error if the derivation fails or if the algorithm is not supported. pub fn derive(&self, seed: Vec, path: &DerivationPath) -> Result { match self { #[cfg(feature = "ed25519")] @@ -66,6 +80,7 @@ impl std::str::FromStr for DerivationAlgorithm { } } +/// A derivation request. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DerivationRequest { algorithm: DerivationAlgorithm, @@ -73,6 +88,7 @@ pub struct DerivationRequest { } impl DerivationRequest { + /// Create a new derivation request. pub fn new(algorithm: DerivationAlgorithm, path: &DerivationPath) -> Self { Self { algorithm, @@ -80,29 +96,47 @@ impl DerivationRequest { } } + /// Return the path of the derivation request. pub fn path(&self) -> &DerivationPath { &self.path } + /// Derive an [`ExtendedPrivateKey`] using the seed from the given mnemonic. + /// + /// # Errors + /// The method may error if the derivation fails or if the algorithm is not supported. pub fn derive_with_mnemonic(&self, mnemonic: &Mnemonic) -> Result { // TODO: passphrase support and/or store passphrase within mnemonic self.derive_with_master_seed(mnemonic.seed(None)?) } + /// Derive an [`ExtendedPrivateKey`] using the given seed. + /// + /// # Errors + /// The method may error if the derivation fails or if the algorithm is not supported. pub fn derive_with_master_seed(&self, seed: Vec) -> Result { self.algorithm.derive(seed, &self.path) } } +/// A response to a [`DerivationRequest`] #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DerivationResponse { + /// The algorithm used to derive the data. pub algorithm: DerivationAlgorithm, + + /// The derived private key. pub data: Vec, + + /// The chain code, used for further derivation. pub chain_code: [u8; 32], + + /// The depth, used for further derivation. pub depth: u8, } impl DerivationResponse { + /// Create a [`DerivationResponse`] with the given values. pub fn with_algo_and_xprv( algorithm: DerivationAlgorithm, xprv: &ExtendedPrivateKey, @@ -116,11 +150,15 @@ impl DerivationResponse { } } +/// An error when creating a [`DerivationResponse`]. #[derive(Debug, thiserror::Error)] pub enum TryFromDerivationResponseError { + /// The algorithm used to derive the data does not match the algorithm of the + /// [`ExtendedPrivateKey`] being created. #[error("incorrect algorithm provided")] Algorithm, + /// An error occurred while creating an [`ExtendedPrivateKey`] from the given response. #[error("{0}")] ExtendedPrivateKey(#[from] XPrvError), } diff --git a/crates/keyfork-shard/Cargo.toml b/crates/keyfork-shard/Cargo.toml index b7fda2c..f1ae88b 100644 --- a/crates/keyfork-shard/Cargo.toml +++ b/crates/keyfork-shard/Cargo.toml @@ -11,6 +11,7 @@ default = ["openpgp", "openpgp-card", "qrcode"] openpgp = ["sequoia-openpgp", "anyhow"] openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "dep:openpgp-card"] qrcode = ["keyfork-qrcode"] +bin = ["sequoia-openpgp/crypto-nettle", "keyfork-qrcode/decode-backend-rqrr"] [dependencies] keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", default-features = false, features = ["mnemonic"] } diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs b/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs index 060f843..6554ca5 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs @@ -1,3 +1,5 @@ +//! + use std::{ env, fs::File, diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs b/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs index 7290023..748b353 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs @@ -1,3 +1,5 @@ +//! + use std::{ env, fs::File, diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-remote.rs b/crates/keyfork-shard/src/bin/keyfork-shard-remote.rs index 437da9f..f187176 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-remote.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-remote.rs @@ -1,3 +1,5 @@ +//! + use std::{ env, process::ExitCode, diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs b/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs index cdf2107..2f12e58 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs @@ -1,3 +1,5 @@ +//! + use std::{env, path::PathBuf, process::ExitCode, str::FromStr}; use keyfork_shard::openpgp::{discover_certs, openpgp::Cert, split}; diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index 78bf936..91f99cf 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -1,3 +1,7 @@ +//! ## Keyfork Shard +//! +//! Utilities for securing secrets using Shamir's Secret Sharing. + use std::io::{stdin, stdout, Write}; use aes_gcm::{ @@ -17,15 +21,20 @@ use x25519_dalek::{EphemeralSecret, PublicKey}; #[cfg(feature = "openpgp")] pub mod openpgp; +/// Errors encountered while creating or combining shares using Shamir's Secret Sharing. #[derive(thiserror::Error, Debug)] pub enum SharksError { + /// A Shamir Share could not be created. #[error("Error creating share: {0}")] Share(String), + /// The Shamir shares could not be combined. #[error("Error combining shares: {0}")] CombineShare(String), } +/// The mnemonic or QR code used to transport an encrypted shard did not store the correct amount +/// of data. #[derive(thiserror::Error, Debug)] #[error("Mnemonic or QR code did not store enough data")] pub struct InvalidData; @@ -37,8 +46,16 @@ pub struct InvalidData; pub(crate) const HUNK_VERSION: u8 = 1; pub(crate) const HUNK_OFFSET: usize = 2; -/// # Panics +/// Establish ECDH transport for remote operators, receive transport-encrypted shares, decrypt the +/// shares, and combine them. /// +/// # Errors +/// The function may error if: +/// * Prompting for transport-encrypted shards fails. +/// * Decrypting shards fails. +/// * Combining shards fails. +/// +/// # Panics /// The function may panic if it is given payloads generated using a version of Keyfork that is /// incompatible with the currently running version. pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box> { diff --git a/crates/keyfork-shard/src/openpgp.rs b/crates/keyfork-shard/src/openpgp.rs index 4cc1375..3b90833 100644 --- a/crates/keyfork-shard/src/openpgp.rs +++ b/crates/keyfork-shard/src/openpgp.rs @@ -1,3 +1,5 @@ +//! OpenPGP Shard functionality. + use std::{ collections::{HashMap, VecDeque}, io::{stdin, stdout, Read, Write}, @@ -59,65 +61,86 @@ use super::{InvalidData, SharksError, HUNK_VERSION}; // 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding const ENC_LEN: u8 = 4 * 16; +/// Errors encountered while performing operations using OpenPGP. #[derive(Debug, thiserror::Error)] pub enum Error { + /// Errors encountered while creating or combining shares. #[error("{0}")] Sharks(#[from] SharksError), + /// Unable to decrypt a share. #[error("Error decrypting share: {0}")] SymDecryptShare(#[from] AesError), + /// The generated AES key is of an invalid length. #[error("Invalid length of AES key: {0}")] AesLength(#[from] InvalidLength), + /// The HKDF function was given an input of an invalid length. #[error("Invalid KDF length: {0}")] HkdfLength(#[from] HkdfInvalidLength), + /// The secret did not match the previously-known secret fingerprint. #[error("Derived secret hash {0} != expected {1}")] InvalidSecret(Fingerprint, Fingerprint), + /// An error occurred while performing an OpenPGP operation. #[error("OpenPGP error: {0}")] Sequoia(#[source] anyhow::Error), + /// An IO error occurred while performing an OpenPGP operation. #[error("OpenPGP IO error: {0}")] SequoiaIo(#[source] std::io::Error), + /// An error occurred while using a keyring. #[error("Keyring error: {0}")] Keyring(#[from] keyring::Error), + /// An error occurred while using a smartcard. #[error("Smartcard error: {0}")] Smartcard(#[from] smartcard::Error), + /// An error occurred while displaying a prompt. #[error("Prompt error: {0}")] Prompt(#[from] PromptError), + /// An error occurred while generating a mnemonic. #[error("Mnemonic generation error: {0}")] MnemonicGeneration(#[from] MnemonicGenerationError), + /// An error occurred while parsing a mnemonic. #[error("Mnemonic parse error: {0}")] MnemonicFromStr(#[from] MnemonicFromStrError), + /// An error occurred while converting mnemonic data. #[error("{0}")] InvalidMnemonicData(#[from] InvalidData), + /// An IO error occurred. #[error("IO error: {0}")] Io(#[source] std::io::Error), + /// An error occurred while parsing a derivation path. #[error("Derivation path: {0}")] DerivationPath(#[from] keyfork_derive_openpgp::derive_util::path::Error), + /// An error occurred while requesting derivation. #[error("Derivation request: {0}")] DerivationRequest(#[from] keyfork_derive_openpgp::derive_util::request::DerivationError), + /// An error occurred while decoding hex. #[error("Unable to decode hex: {0}")] HexDecode(#[from] smex::DecodeError), + /// An error occurred while creating an OpenPGP cert. #[error("Keyfork OpenPGP: {0}")] KeyforkOpenPGP(#[from] keyfork_derive_openpgp::Error), } +#[allow(missing_docs)] pub type Result = std::result::Result; +/// An OpenPGP encrypted message and public-key-encrypted-secret-key packets. #[derive(Debug, Clone)] pub struct EncryptedMessage { pkesks: Vec, @@ -125,6 +148,7 @@ pub struct EncryptedMessage { } impl EncryptedMessage { + /// Create a new EncryptedMessage from known parts. pub fn new(pkesks: &mut Vec, seip: SEIP) -> Self { Self { pkesks: std::mem::take(pkesks), @@ -132,6 +156,14 @@ impl EncryptedMessage { } } + /// Decrypt the message with a Sequoia policy and decryptor. + /// + /// This method creates a container containing the packets and passes the serialized container + /// to a DecryptorBuilder, which is used to decrypt the message. + /// + /// # Errors + /// The method may return an error if it is unable to rebuild the message to decrypt or if it + /// is unable to decrypt the message. pub fn decrypt_with(&self, policy: &'_ dyn Policy, decryptor: H) -> Result> where H: VerificationHelper + DecryptionHelper, @@ -168,6 +200,12 @@ impl EncryptedMessage { } } +/// Read all OpenPGP certificates in a path and return a [`Vec`] of them. Certificates are read +/// from a file, or from files one level deep in a directory. +/// +/// # Errors +/// The function may return an error if it is unable to read the directory or if Sequoia is unable +/// to load certificates from the file. pub fn discover_certs(path: impl AsRef) -> Result> { let path = path.as_ref(); @@ -191,8 +229,13 @@ pub fn discover_certs(path: impl AsRef) -> Result> { } } -/// # Panics +/// Parse messages from a type implementing [`Read`] and store them as [`EncryptedMessage`]. /// +/// # Errors +/// The function may return an error if the reader has run out of data or if the data is not +/// properly formatted OpenPGP messages. +/// +/// # Panics /// When given packets that are not a list of PKESK packets and SEIP packets, the function panics. /// The `split` utility should never give packets that are not in this format. pub fn parse_messages(reader: impl Read + Send + Sync) -> Result> { @@ -401,6 +444,15 @@ fn decrypt_one( unreachable!("smartcard manager should always decrypt") } +/// Decrypt a single shard, encrypt to a remote operator, and present the transport shard as a QR +/// code and mnemonic to be sent to the remote operator. +/// +/// # Errors +/// +/// The function may error if an error occurs while displaying a prompt or while decrypting the +/// shard. An error will not be returned if the camera has a hardware error while scanning a QR +/// code; instead, a mnemonic prompt will be used. +/// /// # Panics /// /// The function may panic if a share is decrypted but has a length larger than 256 bits. This is @@ -522,6 +574,11 @@ pub fn decrypt( Ok(()) } +/// Combine mulitple shards into a secret. +/// +/// # Errors +/// The function may return an error if an error occurs while decrypting shards, parsing shards, or +/// combining the shards into a secret. pub fn combine( certs: Vec, metadata: &EncryptedMessage, @@ -600,6 +657,13 @@ pub fn combine( Ok(()) } +/// Split a secret into an OpenPGP formatted Shard file. +/// +/// # Errors +/// +/// The function may return an error if the shards can't be encrypted to the provided OpenPGP +/// certs or if an error happens while writing the Shard file. +/// /// # Panics /// /// The function may panic if the metadata can't properly store the certificates used to generate diff --git a/crates/keyfork/src/main.rs b/crates/keyfork/src/main.rs index 1a718ea..23b369b 100644 --- a/crates/keyfork/src/main.rs +++ b/crates/keyfork/src/main.rs @@ -1,4 +1,7 @@ +#![doc = include_str!("../../../README.md")] + #![allow(clippy::module_name_repetitions)] + use std::process::ExitCode; use clap::Parser; diff --git a/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs b/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs index 89f3846..8621550 100644 --- a/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs +++ b/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs @@ -1,3 +1,5 @@ +//! + use std::time::Duration; use keyfork_qrcode::scan_camera; diff --git a/crates/qrcode/keyfork-qrcode/src/lib.rs b/crates/qrcode/keyfork-qrcode/src/lib.rs index e045d83..704d47e 100644 --- a/crates/qrcode/keyfork-qrcode/src/lib.rs +++ b/crates/qrcode/keyfork-qrcode/src/lib.rs @@ -1,3 +1,5 @@ +//! Encoding and decoding QR codes. + use image::io::Reader as ImageReader; use std::{ io::{Cursor, Write}, @@ -10,40 +12,61 @@ use v4l::{ Device, }; +/// A QR code could not be generated. #[derive(thiserror::Error, Debug)] pub enum QRGenerationError { + /// The resulting QR coode could not be read from the generator program. #[error("{0}")] Io(#[from] std::io::Error), + /// The generator program produced invalid data. #[error("Could not decode output of qrencode (this is a bug!): {0}")] StringParse(#[from] std::string::FromUtf8Error), } +/// An error occurred while scanning for a QR code. #[derive(thiserror::Error, Debug)] pub enum QRCodeScanError { + /// The camera could not load the requested format. #[error("Camera could not use {expected} format, instead used {actual}")] CameraGaveBadFormat { + /// The expected format, in FourCC format. expected: String, + + /// The actual format, in FourCC format. actual: String, }, + /// Interfacing with the camera resulted in an error. #[error("Unable to interface with camera: {0}")] CameraIO(#[from] std::io::Error), + /// Decoding an image from the camera resulted in an error. #[error("Could not decode image: {0}")] ImageDecode(#[from] image::ImageError), } +/// The level of error correction when generating a QR code. #[derive(Default)] pub enum ErrorCorrection { + /// 7% of the QR code can be recovered. #[default] Lowest, + + /// 15% of the QR code can be recovered. Medium, + + /// 25% of the QR code can be recovered. Quartile, + + /// 30% of the QR code can be recovered. Highest, } /// Generate a terminal-printable QR code for a given string. Uses the `qrencode` CLI utility. +/// +/// # Errors +/// The function may return an error if interacting with the QR code generation program fails. pub fn qrencode( text: &str, error_correction: impl Into>, @@ -73,6 +96,7 @@ pub fn qrencode( Ok(result) } +/// Continuously scan the `index`-th camera for a QR code. #[cfg(feature = "decode-backend-rqrr")] pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; @@ -100,6 +124,7 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result, QR Ok(None) } +/// Continuously scan the `index`-th camera for a QR code. #[cfg(feature = "decode-backend-zbar")] pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; diff --git a/crates/qrcode/keyfork-zbar-sys/build.rs b/crates/qrcode/keyfork-zbar-sys/build.rs index 003d22e..9bbea87 100644 --- a/crates/qrcode/keyfork-zbar-sys/build.rs +++ b/crates/qrcode/keyfork-zbar-sys/build.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, clippy::missing_errors_doc)] + use std::{env::VarError, path::Path}; use pkg_config::Config; diff --git a/crates/qrcode/keyfork-zbar-sys/src/lib.rs b/crates/qrcode/keyfork-zbar-sys/src/lib.rs index 8c9010c..8314f30 100644 --- a/crates/qrcode/keyfork-zbar-sys/src/lib.rs +++ b/crates/qrcode/keyfork-zbar-sys/src/lib.rs @@ -1,3 +1,4 @@ #![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] +#![allow(missing_docs)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/crates/qrcode/keyfork-zbar/examples/v4l-scan.rs b/crates/qrcode/keyfork-zbar/examples/v4l-scan.rs index c92db3b..d3e5813 100644 --- a/crates/qrcode/keyfork-zbar/examples/v4l-scan.rs +++ b/crates/qrcode/keyfork-zbar/examples/v4l-scan.rs @@ -1,3 +1,5 @@ +//! Scan for a barcode or QR code from the default camera. + use std::{ io::Cursor, time::{Duration, SystemTime}, @@ -31,7 +33,7 @@ fn main() -> Result<(), Box> { .decode()?, ); - for symbol in scanner.scan_image(&image) { + if let Some(symbol) = scanner.scan_image(&image).get(0) { println!("{}", String::from_utf8_lossy(symbol.data())); return Ok(()); } diff --git a/crates/qrcode/keyfork-zbar/src/image.rs b/crates/qrcode/keyfork-zbar/src/image.rs index 2afaa4f..e312735 100644 --- a/crates/qrcode/keyfork-zbar/src/image.rs +++ b/crates/qrcode/keyfork-zbar/src/image.rs @@ -1,5 +1,8 @@ +//! Conversions for the internal Image type used by zbar. + use super::sys; +/// The internal image type used by zbar. pub struct Image { pub(crate) inner: *mut sys::zbar_image_s, /// Set to store the data of inner, as it will otherwise be freed when the data is dropped. diff --git a/crates/qrcode/keyfork-zbar/src/image_scanner.rs b/crates/qrcode/keyfork-zbar/src/image_scanner.rs index dc98d01..8504cad 100644 --- a/crates/qrcode/keyfork-zbar/src/image_scanner.rs +++ b/crates/qrcode/keyfork-zbar/src/image_scanner.rs @@ -1,20 +1,29 @@ +//! ## Image scanning + use super::{ image::Image, symbol::{Symbol, SymbolType}, sys, Config, }; +/// Errors encountered while creating or using an [`ImageScanner`]. #[derive(thiserror::Error, Debug)] pub enum ImageScannerError { + /// The provided configuration resulted in an error. #[error("Unable to set Image Scanner configuration")] UnableToSetConfig, } +/// An [`ImageScanner`]. +/// +/// Link: [`sys::zbar_image_scanner_t`] pub struct ImageScanner { inner: *mut sys::zbar_image_scanner_t, } impl ImageScanner { + /// create a new ImageScanner. + /// /// Link: [`sys::zbar_image_scanner_create`] pub fn new() -> Self { Self { @@ -22,7 +31,12 @@ impl ImageScanner { } } + /// Set a configuration option for the ImageScanner. + /// /// Link: [`sys::zbar_image_scanner_set_config`] + /// + /// # Errors + /// The function may error if the provided configuration was invalid. pub fn set_config( &mut self, symbol: SymbolType, @@ -39,10 +53,9 @@ impl ImageScanner { Ok(()) } - /// Link: [`sys::zbar_scan_image`] + /// Scan an [`Image`] for QR codes. /// - /// TODO: move `image` to newtype, offering conversions - /// to and from image::Image + /// Link: [`sys::zbar_scan_image`] /// /// TODO: return an iterator over scanned values pub fn scan_image( diff --git a/crates/qrcode/keyfork-zbar/src/symbol.rs b/crates/qrcode/keyfork-zbar/src/symbol.rs index b94f404..f52500b 100644 --- a/crates/qrcode/keyfork-zbar/src/symbol.rs +++ b/crates/qrcode/keyfork-zbar/src/symbol.rs @@ -1,8 +1,13 @@ +//! + use super::sys; +/// The type of symbol (i.e. what type of barcode or QR code). pub use sys::zbar_symbol_type_e as SymbolType; // TODO: config, modifiers + +/// A Symbol detected by zbar. #[derive(Debug)] pub struct Symbol { _type: SymbolType, @@ -17,14 +22,17 @@ impl Symbol { } } + /// The type of symbol pub fn _type(&self) -> SymbolType { self._type } + /// The internal data of the image. pub fn data(&self) -> &[u8] { self.data.as_slice() } + /// Consume self, returning the internal data. pub fn into_data(self) -> Vec { self.data } diff --git a/crates/util/keyfork-crossterm/examples/event-read-char-line.rs b/crates/util/keyfork-crossterm/examples/event-read-char-line.rs index c5ca567..50b1786 100644 --- a/crates/util/keyfork-crossterm/examples/event-read-char-line.rs +++ b/crates/util/keyfork-crossterm/examples/event-read-char-line.rs @@ -7,6 +7,7 @@ use std::io; use keyfork_crossterm::event::{self, Event, KeyCode, KeyEvent}; +/// Read a character from input. pub fn read_char() -> io::Result { loop { if let Event::Key(KeyEvent { @@ -19,6 +20,7 @@ pub fn read_char() -> io::Result { } } +/// Read a line from input. pub fn read_line() -> io::Result { let mut line = String::new(); while let Event::Key(KeyEvent { code, .. }) = event::read()? { diff --git a/crates/util/keyfork-crossterm/examples/is_tty.rs b/crates/util/keyfork-crossterm/examples/is_tty.rs index 18eea90..c5aa87d 100644 --- a/crates/util/keyfork-crossterm/examples/is_tty.rs +++ b/crates/util/keyfork-crossterm/examples/is_tty.rs @@ -1,3 +1,5 @@ +//! + use keyfork_crossterm::{ execute, terminal::{size, SetSize}, @@ -5,7 +7,7 @@ use keyfork_crossterm::{ }; use std::io::{stdin, stdout}; -pub fn main() { +fn main() { println!("size: {:?}", size().unwrap()); execute!(stdout(), SetSize(10, 10)).unwrap(); println!("resized: {:?}", size().unwrap()); diff --git a/crates/util/keyfork-crossterm/examples/stderr.rs b/crates/util/keyfork-crossterm/examples/stderr.rs index b0183a1..a94be63 100644 --- a/crates/util/keyfork-crossterm/examples/stderr.rs +++ b/crates/util/keyfork-crossterm/examples/stderr.rs @@ -72,6 +72,7 @@ where Ok(user_char) } +/// Read a character from input. pub fn read_char() -> io::Result { loop { if let Event::Key(KeyEvent { diff --git a/crates/util/keyfork-crossterm/src/lib.rs b/crates/util/keyfork-crossterm/src/lib.rs index 52f6d00..8af994d 100644 --- a/crates/util/keyfork-crossterm/src/lib.rs +++ b/crates/util/keyfork-crossterm/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs, clippy::missing_errors_doc, clippy::missing_panics_doc)] #![deny(unused_imports, unused_must_use)] //! # Cross-platform Terminal Manipulation Library diff --git a/crates/util/keyfork-crossterm/src/macros.rs b/crates/util/keyfork-crossterm/src/macros.rs index ff9d7d7..6ceb414 100644 --- a/crates/util/keyfork-crossterm/src/macros.rs +++ b/crates/util/keyfork-crossterm/src/macros.rs @@ -149,7 +149,7 @@ mod tests { // Helper for execute tests to confirm flush #[derive(Default, Debug, Clone)] - pub(self) struct FakeWrite { + struct FakeWrite { buffer: String, flushed: bool, } diff --git a/crates/util/keyfork-entropy/src/lib.rs b/crates/util/keyfork-entropy/src/lib.rs index 49461af..9ac68a1 100644 --- a/crates/util/keyfork-entropy/src/lib.rs +++ b/crates/util/keyfork-entropy/src/lib.rs @@ -1,3 +1,5 @@ +//! Utilities for reading entropy from secure sources. + use std::{fs::{read_dir, read_to_string, File}, io::Read}; static WARNING_LINKS: [&str; 1] = @@ -45,6 +47,7 @@ fn ensure_offline() { } } +/// Ensure the system is safe. pub fn ensure_safe() { if !std::env::vars() .any(|(name, _)| name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED") @@ -54,6 +57,10 @@ pub fn ensure_safe() { } } +/// Read system entropy of a given size. +/// +/// # Errors +/// An error may be returned if an error occurred while reading from the random source. pub fn generate_entropy_of_size(byte_count: usize) -> Result, std::io::Error> { ensure_safe(); let mut vec = vec![0u8; byte_count]; diff --git a/crates/util/keyfork-frame/src/asyncext.rs b/crates/util/keyfork-frame/src/asyncext.rs index 365b0cf..f0893a7 100644 --- a/crates/util/keyfork-frame/src/asyncext.rs +++ b/crates/util/keyfork-frame/src/asyncext.rs @@ -1,3 +1,6 @@ +//! Functions for decoding from and encoding to types that implement [`AsyncRead`] and +//! [`AsyncWrite`]. + use std::marker::Unpin; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; @@ -10,6 +13,7 @@ use super::{hash, verify_checksum, DecodeError, EncodeError}; /// * The given `data` does not contain enough data to parse a length, /// * The given `data` does not contain the given length's worth of data, /// * The given `data` has a checksum that does not match what we build locally. +/// * The source for the data returned an error. pub async fn try_decode_from( readable: &mut (impl AsyncRead + Unpin), ) -> Result, DecodeError> { @@ -31,7 +35,7 @@ pub async fn try_decode_from( /// # Errors /// An error may be returned if: /// * The given `data` is more than [`u32::MAX`] bytes. This is a constraint on a protocol level. -/// * The resulting data was unable to be written to the given `writable`. +/// * The resulting data was unable to be written. pub async fn try_encode_to( data: &[u8], writable: &mut (impl AsyncWrite + Unpin), diff --git a/crates/util/keyfork-frame/src/lib.rs b/crates/util/keyfork-frame/src/lib.rs index 649cb31..4e39a45 100644 --- a/crates/util/keyfork-frame/src/lib.rs +++ b/crates/util/keyfork-frame/src/lib.rs @@ -19,6 +19,7 @@ pub mod asyncext; use sha2::{Digest, Sha256}; +/// An error encountered while decoding a frame. #[derive(Debug, thiserror::Error)] pub enum DecodeError { /// There were not enough bytes to determine the length of the data slice. @@ -42,6 +43,7 @@ pub enum DecodeError { Io(#[from] std::io::Error), } +/// An error encountered while encoding a frame. #[derive(Debug, thiserror::Error)] pub enum EncodeError { /// The given input was larger than could be encoded by this protocol. @@ -70,6 +72,11 @@ pub fn try_encode(data: &[u8]) -> Result, EncodeError> { Ok(output) } +/// Encode data to a type implementing [`Write`]. +/// +/// # Errors +/// An error may be returned if the givenu `data` is more than [`u32::MAX`] bytes, or if the writer +/// is unable to write data. pub fn try_encode_to(data: &[u8], writable: &mut impl Write) -> Result<(), EncodeError> { let hash = hash(data); let len = hash.len() + data.len(); @@ -104,6 +111,14 @@ pub fn try_decode(data: &[u8]) -> Result, DecodeError> { try_decode_from(&mut &data[..]) } +/// Read and decode a framed message into a `Vec`. +/// +/// # Errors +/// An error may be returned if: +/// * The given `data` does not contain enough data to parse a length, +/// * The given `data` does not contain the given length's worth of data, +/// * The given `data` has a checksum that does not match what we build locally. +/// * The source for the data returned an error. pub fn try_decode_from(readable: &mut impl Read) -> Result, DecodeError> { let mut bytes = 0u32.to_be_bytes(); readable.read_exact(&mut bytes)?; diff --git a/crates/util/keyfork-mnemonic-util/src/lib.rs b/crates/util/keyfork-mnemonic-util/src/lib.rs index e7b807f..d6732ed 100644 --- a/crates/util/keyfork-mnemonic-util/src/lib.rs +++ b/crates/util/keyfork-mnemonic-util/src/lib.rs @@ -1,3 +1,5 @@ +//! Zero-dependency Mnemonic encoding and decoding. + use std::{error::Error, fmt::Display, str::FromStr, sync::Arc}; use hmac::Hmac; @@ -258,18 +260,22 @@ impl Mnemonic { } } + /// The internal representation of the decoded data. pub fn as_bytes(&self) -> &[u8] { &self.entropy } + /// Drop self, returning the decoded data. pub fn to_bytes(self) -> Vec { self.entropy } + /// Clone the existing entropy. pub fn entropy(&self) -> Vec { self.entropy.clone() } + /// Create a BIP-0032 seed from the provided data and an optional passphrase. pub fn seed<'a>( &self, passphrase: impl Into>, @@ -284,6 +290,7 @@ impl Mnemonic { Ok(seed.to_vec()) } + /// Encode the mnemonic into a list of wordlist indexes. pub fn words(self) -> (Vec, Arc) { let bit_count = self.entropy.len() * 8; let mut bits = vec![false; bit_count + bit_count / 32]; diff --git a/crates/util/keyfork-plumbing/src/bin/keyfork-entropy.rs b/crates/util/keyfork-plumbing/src/bin/keyfork-entropy.rs index f1b1a46..b2f97b3 100644 --- a/crates/util/keyfork-plumbing/src/bin/keyfork-entropy.rs +++ b/crates/util/keyfork-plumbing/src/bin/keyfork-entropy.rs @@ -1,3 +1,5 @@ +//! + fn main() -> Result<(), Box> { let bit_size: usize = std::env::args() .nth(1) diff --git a/crates/util/keyfork-plumbing/src/bin/keyfork-mnemonic-from-seed.rs b/crates/util/keyfork-plumbing/src/bin/keyfork-mnemonic-from-seed.rs index a65a6a1..863a395 100644 --- a/crates/util/keyfork-plumbing/src/bin/keyfork-mnemonic-from-seed.rs +++ b/crates/util/keyfork-plumbing/src/bin/keyfork-mnemonic-from-seed.rs @@ -1,3 +1,5 @@ +//! + use keyfork_mnemonic_util::Mnemonic; fn main() -> Result<(), Box> { diff --git a/crates/util/keyfork-prompt/src/bin/test-basic-prompt.rs b/crates/util/keyfork-prompt/examples/test-basic-prompt.rs similarity index 99% rename from crates/util/keyfork-prompt/src/bin/test-basic-prompt.rs rename to crates/util/keyfork-prompt/examples/test-basic-prompt.rs index 8bf1f3b..fe4ad86 100644 --- a/crates/util/keyfork-prompt/src/bin/test-basic-prompt.rs +++ b/crates/util/keyfork-prompt/examples/test-basic-prompt.rs @@ -1,3 +1,5 @@ +//! + use std::io::{stdin, stdout}; use keyfork_prompt::{ diff --git a/crates/util/keyfork-prompt/src/lib.rs b/crates/util/keyfork-prompt/src/lib.rs index c14a4ae..72e8d9c 100644 --- a/crates/util/keyfork-prompt/src/lib.rs +++ b/crates/util/keyfork-prompt/src/lib.rs @@ -1,36 +1,66 @@ +//! Prompt display and interaction management. + use std::borrow::Borrow; #[cfg(feature = "mnemonic")] use keyfork_mnemonic_util::Wordlist; +/// pub mod terminal; pub mod validators; pub use terminal::{Terminal, DefaultTerminal, default_terminal}; +/// An error occurred while displaying a prompt. #[derive(thiserror::Error, Debug)] pub enum Error { + /// The given handler is not a TTY and can't be used to display prompts. #[error("The given handler is not a TTY")] NotATTY, + /// Validating user input failed. #[error("Validation of the input failed after {0} retries (last error: {1})")] Validation(u8, String), + /// An error occurred while interacting with a terminal. #[error("IO Error: {0}")] IO(#[from] std::io::Error), } +#[allow(missing_docs)] pub type Result = std::result::Result; +/// A message displayed by [`PromptHandler::prompt_message`]. pub enum Message { + /// A textual message, wrapping at space boundaries when reaching the end of the terminal. Text(String), + /// A data message, with no word wrapping, and automatic hiding of the message when a terminal + /// is too small. Data(String), } +/// A trait to allow displaying prompts and accepting input. pub trait PromptHandler { + /// Prompt the user for input. + /// + /// # Errors + /// The method may return an error if the message was not able to be displayed or if the input + /// could not be read. fn prompt_input(&mut self, prompt: &str) -> Result; + /// Prompt the user for input based on a wordlist. + /// + /// # Errors + /// The method may return an error if the message was not able to be displayed or if the input + /// could not be read. + #[cfg(feature = "mnemonic")] fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result; + /// Prompt the user for input based on a wordlist, while validating the wordlist using a + /// provided parser function, returning the type from the parser. + /// + /// # Errors + /// The method may return an error if the message was not able to be displayed, if the input + /// could not be read, or if the parser returned an error. #[cfg(feature = "mnemonic")] fn prompt_validated_wordlist( &mut self, @@ -43,8 +73,19 @@ pub trait PromptHandler { F: Fn(String) -> Result, E: std::error::Error; + /// Prompt the user for a passphrase, which is hidden while typing. + /// + /// # Errors + /// The method may return an error if the message was not able to be displayed or if the input + /// could not be read. fn prompt_passphrase(&mut self, prompt: &str) -> Result; + /// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase + /// using a provided parser function, returning the type from the parser. + /// + /// # Errors + /// The method may return an error if the message was not able to be displayed, if the input + /// could not be read, or if the parser returned an error. fn prompt_validated_passphrase( &mut self, prompt: &str, @@ -55,5 +96,10 @@ pub trait PromptHandler { F: Fn(String) -> Result, E: std::error::Error; + /// Prompt the user with a [`Message`]. + /// + /// # Errors + /// The method may return an error if the message was not able to be displayed or if an error + /// occurred while waiting for the user to dismiss the message. fn prompt_message(&mut self, prompt: impl Borrow) -> Result<()>; } diff --git a/crates/util/keyfork-prompt/src/terminal.rs b/crates/util/keyfork-prompt/src/terminal.rs index 6c49b60..ae1d2c6 100644 --- a/crates/util/keyfork-prompt/src/terminal.rs +++ b/crates/util/keyfork-prompt/src/terminal.rs @@ -14,6 +14,7 @@ use keyfork_crossterm::{ use crate::{PromptHandler, Message, Wordlist, Error}; +#[allow(missing_docs)] pub type Result = std::result::Result; struct TerminalGuard<'a, R, W> @@ -124,6 +125,7 @@ where } } +/// A handler for a terminal. pub struct Terminal { read: BufReader, write: W, @@ -135,6 +137,10 @@ where R: Read + Sized, W: Write + AsRawFd + Sized, { + /// Create a new [`Terminal`] from values implementing [`Read`] and [`Write`]. + /// + /// # Errors + /// The function may error if the write handle is not a terminal. pub fn new(read_handle: R, write_handle: W) -> Result { if !write_handle.is_tty() { return Err(Error::NotATTY); @@ -490,8 +496,13 @@ impl PromptHandler for Terminal where R: Read + Sized, W: Write + As } } +/// A default terminal, using [`Stdin`] and [`Stderr`]. pub type DefaultTerminal = Terminal; +/// Create a [`Terminal`] using the default [`Stdin`] and [`Stderr`] handles. +/// +/// # Errors +/// The function may error if [`Stderr`] is not a terminal. pub fn default_terminal() -> Result { Terminal::new(stdin(), stderr()) } diff --git a/crates/util/keyfork-prompt/src/validators.rs b/crates/util/keyfork-prompt/src/validators.rs index 8434cd9..b7b5afe 100644 --- a/crates/util/keyfork-prompt/src/validators.rs +++ b/crates/util/keyfork-prompt/src/validators.rs @@ -1,21 +1,32 @@ +//! Validator and parser types. + #![allow(clippy::type_complexity)] use std::ops::RangeInclusive; +/// A trait to create validator functions. pub trait Validator { + /// The output of the validator function. type Output; + + /// The error type returned from the validator function. type Error; + /// Create a validator function from the given parameters. fn to_fn(&self) -> Box Result>; } +/// A PIN could not be validated from the given input. #[derive(thiserror::Error, Debug)] pub enum PinError { + /// The provided PIN was too short. #[error("PIN too short: {0} < {1}")] TooShort(usize, usize), + /// The provided PIN was too long. #[error("PIN too long: {0} > {1}")] TooLong(usize, usize), + /// The PIN contained invalid characters. #[error("PIN contained invalid characters (found {0} at position {1})")] InvalidCharacters(char, usize), } @@ -23,8 +34,13 @@ pub enum PinError { /// Validate that a PIN is of a certain length and matches a range of characters. #[derive(Default, Clone)] pub struct PinValidator { + /// The minimum length of provided PINs. pub min_length: Option, + + /// The maximum length of provided PINs. pub max_length: Option, + + /// The characters allowed by the PIN parser. pub range: Option>, } @@ -57,24 +73,33 @@ impl Validator for PinValidator { #[cfg(feature = "mnemonic")] pub mod mnemonic { + //! Validators for mnemonics. + use std::{ops::Range, str::FromStr}; use super::Validator; use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError}; + /// A mnemonic could not be validated from the given input. #[derive(thiserror::Error, Debug)] pub enum MnemonicValidationError { + /// The provided mnemonic had an unexpected word length. #[error("Invalid word length: {0} does not match {1:?}")] InvalidLength(usize, WordLength), + /// A mnemonic could not be parsed from the given mnemonic. #[error("{0}")] MnemonicFromStrError(#[from] MnemonicFromStrError), } + /// The mnemonic had an unexpected word length. #[derive(Clone, Debug)] pub enum WordLength { + /// The bounds of a mnemonic. Range(Range), + + /// The exact count of words. Count(usize), } @@ -90,6 +115,7 @@ pub mod mnemonic { /// Validate a mnemonic of a range of word lengths or a specific length. #[derive(Default, Clone)] pub struct MnemonicValidator { + /// The allowed word length of provided mnemonics. pub word_length: Option, } @@ -116,11 +142,14 @@ pub mod mnemonic { } } + /// A mnemonic in the set of mnemonics could not be validated from the given inputs. #[derive(thiserror::Error, Debug)] pub enum MnemonicSetValidationError { + /// The provided mnemonic did not have the correct amount of words. #[error("Invalid word length in set {0}: {1} != expected {2}")] InvalidSetLength(usize, usize, usize), + /// A mnemonic could not be parsed from the provided mnemonics. #[error("Error parsing mnemonic set {0}: {1}")] MnemonicFromStrError(usize, MnemonicFromStrError), } @@ -128,6 +157,8 @@ pub mod mnemonic { /// Validate a set of mnemonics of a specific word length. #[derive(Clone)] pub struct MnemonicSetValidator { + /// The exact word lengths of all mnemonics. Unlike [`MnemonicValidator`], ranges of words + /// are not allowed. pub word_lengths: [usize; N], } diff --git a/crates/util/keyfork-slip10-test-data/src/lib.rs b/crates/util/keyfork-slip10-test-data/src/lib.rs index 3d5047e..6ba51ac 100644 --- a/crates/util/keyfork-slip10-test-data/src/lib.rs +++ b/crates/util/keyfork-slip10-test-data/src/lib.rs @@ -1,20 +1,37 @@ -// Source: https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vectors +//! SLIP-0010 test data for use by derivation tests. +//! Source: https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vectors + use std::collections::HashMap; +/// Decoded hex, as a [`Vec`] pub type DecodedHex = Vec; +/// A test and its results. #[derive(Clone, Debug)] pub struct Test { + /// The derivation path for the test. pub chain: &'static str, + + /// The expected fingerprint. pub fingerprint: DecodedHex, + + /// The expected chain code. pub chain_code: DecodedHex, + + /// The expected private key. pub private_key: DecodedHex, + + /// The expected public key. pub public_key: DecodedHex, } +/// A set of tests for a given seed. #[derive(Clone, Debug)] pub struct TestData { + /// The seed to run the tests on. pub seed: DecodedHex, + + /// The tests to run against the seed. pub tests: Vec, } @@ -24,7 +41,10 @@ const SECP256K1_512: &str = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b const ED25519_512: &str = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2\ 9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; -// Note: This should never error. +/// Return the SLIP-0010 test data. +/// +/// # Errors +/// This function should not error. If it errors, it is due to malformed hex in the test data. #[allow(clippy::too_many_lines)] pub fn test_data() -> Result>, Box> { // Format: diff --git a/crates/util/smex/src/lib.rs b/crates/util/smex/src/lib.rs index b36d8e6..88a6912 100644 --- a/crates/util/smex/src/lib.rs +++ b/crates/util/smex/src/lib.rs @@ -1,8 +1,14 @@ +//! Zero-dependency hex encoding and decoding. + use std::fmt::Write; +/// The type could not be decoded. #[derive(Debug)] pub enum DecodeError { + /// An invalid character was encountered. InvalidCharacter(u8), + + /// The amount of characters was invalid. Hex strings must be in pairs of two. InvalidCharacterCount(usize), } @@ -21,6 +27,7 @@ impl std::fmt::Display for DecodeError { impl std::error::Error for DecodeError {} +/// Encode a given input as a hex string. pub fn encode(input: &[u8]) -> String { let mut s = String::new(); for byte in input { @@ -38,6 +45,11 @@ fn val(c: u8) -> Result { } } +/// Attempt to decode a string as hex. +/// +/// # Errors +/// The function may error if a non-hex character is encountered or if the character count is not +/// evenly divisible by two. pub fn decode(input: &str) -> Result, DecodeError> { let len = input.len(); if len % 2 != 0 {