262 lines
10 KiB
Rust
262 lines
10 KiB
Rust
//! # The Keyforkd Client
|
|
//!
|
|
//! Keyfork allows securing the master key and highest-level derivation keys by having derivation
|
|
//! requests performed against a server, "Keyforkd" or the "Keyfork Server". The server is operated
|
|
//! on a UNIX socket with messages sent using the Keyfork Frame format.
|
|
//!
|
|
//! Programs using the Keyfork Client should ensure they are built against a compatible version of
|
|
//! the Keyfork Server. For versions prior to `1.0.0`, all versions within a "minor" version (i.e.,
|
|
//! `0.5.x`) will be compatible, but `0.5.x` will not be compatible with `0.6.x`. For versions
|
|
//! after `1.0.0`, all versions within a "major" version (i.e., `1.0.0`) will be compatible, but
|
|
//! `1.x.y` will not be compatible with `2.0.0`.
|
|
//!
|
|
//! Presently, the Keyfork server only supports the following requests:
|
|
//!
|
|
//! * Derive Key
|
|
//!
|
|
//! ## Extended Private Keys
|
|
//!
|
|
//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended
|
|
//! Private Key (often shortened to "XPrv") has been created, further derivations can be performed.
|
|
//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two
|
|
//! will be derived similarly between the server and the client.
|
|
//!
|
|
//! # Examples
|
|
//! ```rust
|
|
//! use std::str::FromStr;
|
|
//!
|
|
//! use keyforkd_client::Client;
|
|
//! use keyfork_derive_util::DerivationPath;
|
|
//! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
|
//! // use k256::SecretKey as PrivateKey;
|
|
//! // use ed25519_dalek::SigningKey as PrivateKey;
|
|
//!
|
|
//! # let seed = b"funky accordion noises";
|
|
//! # keyforkd::test_util::run_test(seed, |socket_path| {
|
|
//! # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
|
//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
|
|
//! let mut client = Client::discover_socket().unwrap();
|
|
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
|
|
//! # keyforkd::test_util::Infallible::Ok(())
|
|
//! # }).unwrap();
|
|
//! ```
|
|
//!
|
|
//! In tests, the Keyforkd test_util module and TestPrivateKeys can be used.
|
|
//!
|
|
//! ```rust
|
|
//! use std::str::FromStr;
|
|
//!
|
|
//! use keyforkd_client::Client;
|
|
//! use keyfork_derive_util::DerivationPath;
|
|
//! use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
|
//!
|
|
//! let seed = b"funky accordion noises";
|
|
//! keyforkd::test_util::run_test(seed, |socket_path| {
|
|
//! std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
|
//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
|
|
//! let mut client = Client::discover_socket().unwrap();
|
|
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
|
|
//! keyforkd::test_util::Infallible::Ok(())
|
|
//! }).unwrap();
|
|
//! ```
|
|
|
|
pub use std::os::unix::net::UnixStream;
|
|
use std::{collections::HashMap, path::PathBuf};
|
|
|
|
use keyfork_derive_util::{
|
|
request::{AsAlgorithm, DerivationRequest},
|
|
DerivationPath, ExtendedPrivateKey, PrivateKey,
|
|
};
|
|
|
|
use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError};
|
|
use keyforkd_models::{Error as KeyforkdError, Request, Response};
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
/// An error occurred while interacting with Keyforkd.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
/// The response from the server did not match the request.
|
|
#[error("The response from the server did not match the request")]
|
|
InvalidResponse,
|
|
|
|
/// 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<bincode::ErrorKind>),
|
|
|
|
/// 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<T, E = Error> = std::result::Result<T, E>;
|
|
|
|
/// 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<UnixStream, Error> {
|
|
let socket_vars = std::env::vars()
|
|
.filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str()))
|
|
.collect::<HashMap<String, String>>();
|
|
let mut socket_path: PathBuf;
|
|
#[allow(clippy::single_match_else)]
|
|
match socket_vars.get("KEYFORKD_SOCKET_PATH") {
|
|
Some(occupied) => {
|
|
socket_path = PathBuf::from(occupied);
|
|
}
|
|
None => {
|
|
socket_path = PathBuf::from(
|
|
socket_vars
|
|
.get("XDG_RUNTIME_DIR")
|
|
.ok_or(Error::EnvVarsNotFound)?,
|
|
);
|
|
socket_path.extend(["keyforkd", "keyforkd.sock"]);
|
|
}
|
|
}
|
|
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
|
|
}
|
|
|
|
/// A client to interact with Keyforkd.
|
|
///
|
|
/// Upon creation, a socket is opened, and is kept open for the duration of the object's lifetime.
|
|
/// Currently, Keyforkd does not support the reuse of sockets. Attempting to reuse the socket after
|
|
/// previously using it will likely result in an error.
|
|
#[derive(Debug)]
|
|
pub struct Client {
|
|
socket: UnixStream,
|
|
}
|
|
|
|
impl Client {
|
|
/// Create a new client from a given already-connected [`UnixStream`]. This function is
|
|
/// provided in case a specific UnixStream has to be used; otherwise,
|
|
/// [`Client::discover_socket`] should be preferred.
|
|
///
|
|
/// # Examples
|
|
/// ```rust
|
|
/// use keyforkd_client::{Client, get_socket};
|
|
///
|
|
/// # let seed = b"funky accordion noises";
|
|
/// # keyforkd::test_util::run_test(seed, |socket_path| {
|
|
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
|
/// let mut socket = get_socket().unwrap();
|
|
/// let mut client = Client::new(socket);
|
|
/// # keyforkd::test_util::Infallible::Ok(())
|
|
/// # }).unwrap();
|
|
/// ```
|
|
pub fn new(socket: UnixStream) -> Self {
|
|
Self { socket }
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// # Examples
|
|
/// ```rust
|
|
/// use keyforkd_client::Client;
|
|
///
|
|
/// # let seed = b"funky accordion noises";
|
|
/// # keyforkd::test_util::run_test(seed, |socket_path| {
|
|
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
|
/// let mut client = Client::discover_socket().unwrap();
|
|
/// # keyforkd::test_util::Infallible::Ok(())
|
|
/// # }).unwrap();
|
|
/// ```
|
|
pub fn discover_socket() -> Result<Self> {
|
|
get_socket().map(|socket| Self { socket })
|
|
}
|
|
|
|
/// Request an [`ExtendedPrivateKey`] for a given [`DerivationPath`].
|
|
///
|
|
/// # 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.
|
|
/// * Keyforkd returned invalid data.
|
|
///
|
|
/// # Examples
|
|
/// ```rust
|
|
/// use std::str::FromStr;
|
|
///
|
|
/// use keyforkd_client::Client;
|
|
/// use keyfork_derive_util::DerivationPath;
|
|
/// # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
|
/// // use k256::SecretKey as PrivateKey;
|
|
/// // use ed25519_dalek::SigningKey as PrivateKey;
|
|
///
|
|
/// # let seed = b"funky accordion noises";
|
|
/// # keyforkd::test_util::run_test(seed, |socket_path| {
|
|
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
|
/// let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
|
|
/// let mut client = Client::discover_socket().unwrap();
|
|
/// let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
|
|
/// # keyforkd::test_util::Infallible::Ok(())
|
|
/// # }).unwrap();
|
|
/// ```
|
|
pub fn request_xprv<K>(&mut self, path: &DerivationPath) -> Result<ExtendedPrivateKey<K>>
|
|
where
|
|
K: PrivateKey + Clone + AsAlgorithm,
|
|
{
|
|
let algo = K::as_algorithm();
|
|
let request = Request::Derivation(DerivationRequest::new(algo.clone(), path));
|
|
let response = self.request(&request)?;
|
|
match response {
|
|
Response::Derivation(d) => {
|
|
if d.algorithm != algo {
|
|
return Err(Error::InvalidResponse);
|
|
}
|
|
|
|
let depth = path.len() as u8;
|
|
Ok(ExtendedPrivateKey::from_parts(
|
|
&d.data,
|
|
depth,
|
|
d.chain_code,
|
|
))
|
|
}
|
|
_ => Err(Error::InvalidResponse),
|
|
}
|
|
}
|
|
|
|
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
|
|
///
|
|
/// # 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.
|
|
#[doc(hidden)]
|
|
pub fn request(&mut self, req: &Request) -> Result<Response> {
|
|
try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?;
|
|
let resp = try_decode_from(&mut self.socket)?;
|
|
let resp: Result<Response, KeyforkdError> = bincode::deserialize(&resp)?;
|
|
resp.map_err(From::from)
|
|
}
|
|
}
|