Compare commits

..

4 Commits

39 changed files with 447 additions and 236 deletions

View File

@ -10,6 +10,20 @@ define clone-repo
test `git -C $(1) rev-parse HEAD` = $(3) test `git -C $(1) rev-parse HEAD` = $(3)
endef endef
docs/book: docs/src/links.md $(shell find docs/src -type f -name '*.md')
mdbook build docs
mkdir -p docs/book/rustdoc
cargo doc --no-deps
cp -r ${CARGO_TARGET_DIR}/doc/* docs/book/rustdoc/
docs/src/links.md: docs/src/links.md.template
echo "<!-- DO NOT EDIT THIS FILE MANUALLY, edit links.md.template -->" > $@
envsubst < $< >> $@
.PHONY: touch
touch:
touch docs/src/links.md.template
.PHONY: review .PHONY: review
review: review:
$(eval BASE_REF_PARSED := $(shell git rev-parse $(BASE_REF))) $(eval BASE_REF_PARSED := $(shell git rev-parse $(BASE_REF)))

View File

@ -2,8 +2,13 @@
use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf}; use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf};
use keyfork_derive_util::{
request::{AsAlgorithm, DerivationRequest},
DerivationPath, ExtendedPrivateKey, PrivateKey,
};
use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError}; use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError};
use keyforkd_models::{Request, Response, Error as KeyforkdError}; use keyforkd_models::{Error as KeyforkdError, Request, Response};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -11,6 +16,10 @@ mod tests;
/// An error occurred while interacting with Keyforkd. /// An error occurred while interacting with Keyforkd.
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum 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. /// The environment variables used for determining a Keyforkd socket path were not set.
#[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")] #[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")]
EnvVarsNotFound, EnvVarsNotFound,
@ -37,7 +46,7 @@ pub enum Error {
/// An error encountered in Keyforkd. /// An error encountered in Keyforkd.
#[error("Error in Keyforkd: {0}")] #[error("Error in Keyforkd: {0}")]
Keyforkd(#[from] KeyforkdError) Keyforkd(#[from] KeyforkdError),
} }
#[allow(missing_docs)] #[allow(missing_docs)]
@ -95,6 +104,38 @@ impl Client {
get_socket().map(|socket| Self { socket }) 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.
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::new_from_parts(
&d.data,
depth,
d.chain_code,
))
}
_ => Err(Error::InvalidResponse),
}
}
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`]. /// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
/// ///
/// # Errors /// # Errors

View File

@ -2,13 +2,10 @@
use std::time::{Duration, SystemTime, SystemTimeError}; use std::time::{Duration, SystemTime, SystemTimeError};
use derive_util::{ use derive_util::{DerivationIndex, ExtendedPrivateKey, IndexError, PrivateKey};
request::{DerivationResponse, TryFromDerivationResponseError},
DerivationIndex, ExtendedPrivateKey, PrivateKey,
IndexError,
};
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
pub use keyfork_derive_util as derive_util; pub use keyfork_derive_util as derive_util;
pub use sequoia_openpgp as openpgp;
use sequoia_openpgp::{ use sequoia_openpgp::{
packet::{ packet::{
key::{Key4, PrimaryRole, SubordinateRole}, key::{Key4, PrimaryRole, SubordinateRole},
@ -18,7 +15,9 @@ use sequoia_openpgp::{
types::{KeyFlags, SignatureType}, types::{KeyFlags, SignatureType},
Cert, Packet, Cert, Packet,
}; };
pub use sequoia_openpgp as openpgp;
pub type XPrvKey = SigningKey;
pub type XPrv = ExtendedPrivateKey<SigningKey>;
/// An error occurred while creating an OpenPGP key. /// An error occurred while creating an OpenPGP key.
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -32,10 +31,6 @@ pub enum Error {
#[error("Key configured with both encryption and non-encryption key flags: {0:?}")] #[error("Key configured with both encryption and non-encryption key flags: {0:?}")]
InvalidKeyFlags(KeyFlags), 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. /// A derivation index could not be created from the given index.
#[error("Could not create derivation index: {0}")] #[error("Could not create derivation index: {0}")]
Index(#[from] IndexError), Index(#[from] IndexError),
@ -66,7 +61,7 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
/// ///
/// # Errors /// # Errors
/// The function may error for any condition mentioned in [`Error`]. /// The function may error for any condition mentioned in [`Error`].
pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> { pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
let primary_key_flags = match keys.get(0) { let primary_key_flags = match keys.get(0) {
Some(kf) if kf.for_certification() => kf, Some(kf) if kf.for_certification() => kf,
_ => return Err(Error::NotCert), _ => return Err(Error::NotCert),
@ -76,7 +71,6 @@ pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: &UserID) -> R
let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24); let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
// Create certificate with initial key and signature // Create certificate with initial key and signature
let xprv = ExtendedPrivateKey::<SigningKey>::try_from(data)?;
let derived_primary_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?; let derived_primary_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?;
let primary_key = Key::from(Key4::<_, PrimaryRole>::import_secret_ed25519( let primary_key = Key::from(Key4::<_, PrimaryRole>::import_secret_ed25519(
&PrivateKey::to_bytes(derived_primary_key.private_key()), &PrivateKey::to_bytes(derived_primary_key.private_key()),
@ -118,21 +112,14 @@ pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: &UserID) -> R
bytes[0] &= 0b1111_1000; bytes[0] &= 0b1111_1000;
bytes[31] &= !0b1000_0000; bytes[31] &= !0b1000_0000;
bytes[31] |= 0b0100_0000; bytes[31] |= 0b0100_0000;
Key::from( Key::from(Key4::<_, SubordinateRole>::import_secret_cv25519(
Key4::<_, SubordinateRole>::import_secret_cv25519( &bytes, None, None, epoch,
&bytes, )?)
None,
None,
epoch,
)?
)
} else { } else {
Key::from( Key::from(Key4::<_, SubordinateRole>::import_secret_ed25519(
Key4::<_, SubordinateRole>::import_secret_ed25519( &PrivateKey::to_bytes(derived_key.private_key()),
&PrivateKey::to_bytes(derived_key.private_key()), epoch,
epoch, )?)
)?
)
}; };
// As per OpenPGP spec, signing keys must backsig the primary key // As per OpenPGP spec, signing keys must backsig the primary key

View File

@ -2,12 +2,16 @@
use std::{env, process::ExitCode, str::FromStr}; use std::{env, process::ExitCode, str::FromStr};
use keyfork_derive_util::{ use keyfork_derive_util::{DerivationIndex, DerivationPath};
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
DerivationIndex, DerivationPath,
};
use keyforkd_client::Client; use keyforkd_client::Client;
use sequoia_openpgp::{packet::UserID, types::KeyFlags, armor::{Kind, Writer}, serialize::Marshal};
use ed25519_dalek::SigningKey;
use sequoia_openpgp::{
armor::{Kind, Writer},
packet::UserID,
serialize::Marshal,
types::KeyFlags,
};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
enum Error { enum Error {
@ -108,16 +112,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
_ => panic!("Usage: {program_name} path subkey_format default_userid"), _ => panic!("Usage: {program_name} path subkey_format default_userid"),
}; };
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); let derived_xprv = Client::discover_socket()?.request_xprv::<SigningKey>(&path)?;
let derived_data: DerivationResponse = Client::discover_socket()?
.request(&request.into())?
.try_into()?;
let subkeys = subkey_format let subkeys = subkey_format
.iter() .iter()
.map(|kt| kt.inner().clone()) .map(|kt| kt.inner().clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let cert = keyfork_derive_openpgp::derive(derived_data, subkeys.as_slice(), &default_userid)?; let cert = keyfork_derive_openpgp::derive(derived_xprv, subkeys.as_slice(), &default_userid)?;
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?; let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;

View File

@ -10,18 +10,10 @@ const KEY_SIZE: usize = 256;
/// Errors associated with creating or deriving Extended Private Keys. /// Errors associated with creating or deriving Extended Private Keys.
#[derive(Error, Clone, Debug)] #[derive(Error, Clone, Debug)]
pub enum Error { pub enum Error {
/// The seed has an unsuitable length; supported lengths are 16 bytes, 32 bytes, or 64 bytes.
#[error("Seed had an unsuitable length: {0}")]
BadSeedLength(usize),
/// The maximum depth for key derivation has been reached. The supported maximum depth is 255. /// The maximum depth for key derivation has been reached. The supported maximum depth is 255.
#[error("Reached maximum depth for key derivation")] #[error("Reached maximum depth for key derivation")]
Depth, Depth,
/// This should never happen. HMAC keys should be able to take any size input.
#[error("Invalid length for HMAC key while generating master key (report me!)")]
HmacInvalidLength(#[from] hmac::digest::InvalidLength),
/// An unknown error occurred while deriving a child key. /// An unknown error occurred while deriving a child key.
#[error("Unknown error while deriving child key")] #[error("Unknown error while deriving child key")]
Derivation, Derivation,
@ -45,11 +37,36 @@ type HmacSha512 = Hmac<Sha512>;
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct ExtendedPrivateKey<K: PrivateKey + Clone> { pub struct ExtendedPrivateKey<K: PrivateKey + Clone> {
/// The internal private key data. /// The internal private key data.
#[serde(with = "serde_with")]
private_key: K, private_key: K,
depth: u8, depth: u8,
chain_code: ChainCode, chain_code: ChainCode,
} }
mod serde_with {
use super::*;
pub(crate) fn serialize<S, K>(value: &K, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
K: PrivateKey + Clone,
{
serializer.serialize_bytes(&value.to_bytes())
}
pub(crate) fn deserialize<'de, D, K>(deserializer: D) -> Result<K, D::Error>
where
D: serde::Deserializer<'de>,
K: PrivateKey + Clone,
{
let variable_len_bytes = <&[u8]>::deserialize(deserializer)?;
let bytes: [u8; 32] = variable_len_bytes
.try_into()
.expect("unable to parse serialized private key; no support for static len");
Ok(K::from_bytes(&bytes))
}
}
impl<K: PrivateKey + Clone> std::fmt::Debug for ExtendedPrivateKey<K> { impl<K: PrivateKey + Clone> std::fmt::Debug for ExtendedPrivateKey<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExtendedPrivateKey") f.debug_struct("ExtendedPrivateKey")
@ -73,7 +90,7 @@ where
/// # Errors /// # Errors
/// An error may be returned if: /// An error may be returned if:
/// * The given seed had an incorrect length. /// * The given seed had an incorrect length.
/// * A `HmacSha512` can't be constructed - this should be impossible. /// * A `HmacSha512` can't be constructed.
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
@ -82,31 +99,26 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// # Ok(())
/// # }
/// ``` /// ```
pub fn new(seed: impl AsRef<[u8]>) -> Result<Self> { pub fn new(seed: impl AsRef<[u8]>) -> Self {
Self::new_internal(seed.as_ref()) Self::new_internal(seed.as_ref())
} }
fn new_internal(seed: &[u8]) -> Result<Self> { fn new_internal(seed: &[u8]) -> Self {
let len = seed.len(); let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())
if ![16, 32, 64].contains(&len) { .expect("HmacSha512 InvalidLength should be infallible")
return Err(Error::BadSeedLength(len));
}
let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())?
.chain_update(seed) .chain_update(seed)
.finalize() .finalize()
.into_bytes(); .into_bytes();
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8); let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
Self::new_from_parts( Self::new_from_parts(
private_key, private_key
.try_into()
.expect("KEY_SIZE / 8 did not give a 32 byte slice"),
0, 0,
// Checked: chain_code is always the same length, hash is static size // Checked: chain_code is always the same length, hash is static size
chain_code.try_into().expect("Invalid chain code length"), chain_code.try_into().expect("Invalid chain code length"),
@ -125,21 +137,18 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = // /// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
/// # Ok(())
/// # }
/// ``` /// ```
pub fn new_from_parts(seed: &[u8], depth: u8, chain_code: [u8; 32]) -> Result<Self> { pub fn new_from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Self {
Ok(Self { Self {
private_key: K::from_bytes(seed.try_into()?), private_key: K::from_bytes(key),
depth, depth,
chain_code, chain_code,
}) }
} }
/// Returns a reference to the [`PrivateKey`]. /// Returns a reference to the [`PrivateKey`].
@ -152,15 +161,12 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = // /// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
/// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key)); /// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key));
/// # Ok(())
/// # }
/// ``` /// ```
pub fn private_key(&self) -> &K { pub fn private_key(&self) -> &K {
&self.private_key &self.private_key
@ -185,7 +191,7 @@ where
/// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27, /// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27,
/// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251 /// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251
/// # ]; /// # ];
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let xpub = xprv.extended_public_key(); /// let xpub = xprv.extended_public_key();
/// assert_eq!(known_key, xpub.public_key().to_bytes()); /// assert_eq!(known_key, xpub.public_key().to_bytes());
/// # Ok(()) /// # Ok(())
@ -209,7 +215,7 @@ where
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let pubkey = xprv.public_key(); /// let pubkey = xprv.public_key();
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -227,15 +233,12 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = // /// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
/// assert_eq!(xprv.depth(), 4); /// assert_eq!(xprv.depth(), 4);
/// # Ok(())
/// # }
/// ``` /// ```
pub fn depth(&self) -> u8 { pub fn depth(&self) -> u8 {
self.depth self.depth
@ -250,15 +253,12 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = // /// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
/// assert_eq!(chain_code, &xprv.chain_code()); /// assert_eq!(chain_code, &xprv.chain_code());
/// # Ok(())
/// # }
/// ``` /// ```
pub fn chain_code(&self) -> [u8; 32] { pub fn chain_code(&self) -> [u8; 32] {
self.chain_code self.chain_code
@ -280,7 +280,7 @@ where
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let path = DerivationPath::default() /// let path = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?) /// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?) /// .chain_push(DerivationIndex::new(0, true)?)
@ -326,7 +326,7 @@ where
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let bip44_wallet = DerivationPath::default() /// let bip44_wallet = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?) /// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?) /// .chain_push(DerivationIndex::new(0, true)?)
@ -342,8 +342,8 @@ where
pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> { pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> {
let depth = self.depth.checked_add(1).ok_or(Error::Depth)?; let depth = self.depth.checked_add(1).ok_or(Error::Depth)?;
let mut hmac = let mut hmac = HmacSha512::new_from_slice(&self.chain_code)
HmacSha512::new_from_slice(&self.chain_code).map_err(Error::HmacInvalidLength)?; .expect("HmacSha512 InvalidLength should be infallible");
if index.is_hardened() { if index.is_hardened() {
hmac.update(&[0]); hmac.update(&[0]);
hmac.update(&self.private_key.to_bytes()); hmac.update(&self.private_key.to_bytes());

View File

@ -68,7 +68,7 @@ impl DerivationAlgorithm {
match self { match self {
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
Self::Ed25519 => { Self::Ed25519 => {
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed)?; let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
self.clone(), self.clone(),
@ -77,7 +77,7 @@ impl DerivationAlgorithm {
} }
#[cfg(feature = "secp256k1")] #[cfg(feature = "secp256k1")]
Self::Secp256k1 => { Self::Secp256k1 => {
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed)?; let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
self.clone(), self.clone(),
@ -85,7 +85,7 @@ impl DerivationAlgorithm {
)) ))
} }
Self::Internal => { Self::Internal => {
let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed)?; let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
self.clone(), self.clone(),
@ -110,6 +110,18 @@ impl std::str::FromStr for DerivationAlgorithm {
} }
} }
/// Acquire the associated [`DerivationAlgorithm`] for a [`PrivateKey`].
pub trait AsAlgorithm: PrivateKey {
/// Return the appropriate [`DerivationAlgorithm`].
fn as_algorithm() -> DerivationAlgorithm;
}
impl AsAlgorithm for TestPrivateKey {
fn as_algorithm() -> DerivationAlgorithm {
DerivationAlgorithm::Internal
}
}
/// A derivation request. /// A derivation request.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct DerivationRequest { pub struct DerivationRequest {
@ -234,7 +246,7 @@ pub struct DerivationResponse {
pub algorithm: DerivationAlgorithm, pub algorithm: DerivationAlgorithm,
/// The derived private key. /// The derived private key.
pub data: Vec<u8>, pub data: [u8; 32],
/// The chain code, used for further derivation. /// The chain code, used for further derivation.
pub chain_code: [u8; 32], pub chain_code: [u8; 32],
@ -251,7 +263,7 @@ impl DerivationResponse {
) -> Self { ) -> Self {
Self { Self {
algorithm, algorithm,
data: PrivateKey::to_bytes(xprv.private_key()).to_vec(), data: PrivateKey::to_bytes(xprv.private_key()),
chain_code: xprv.chain_code(), chain_code: xprv.chain_code(),
depth: xprv.depth(), depth: xprv.depth(),
} }
@ -272,47 +284,71 @@ pub enum TryFromDerivationResponseError {
} }
#[cfg(feature = "secp256k1")] #[cfg(feature = "secp256k1")]
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<k256::SecretKey> { mod secp256k1 {
type Error = TryFromDerivationResponseError; use super::*;
use k256::SecretKey;
fn try_from(value: &DerivationResponse) -> std::result::Result<Self, Self::Error> { impl AsAlgorithm for SecretKey {
match value.algorithm { fn as_algorithm() -> DerivationAlgorithm {
DerivationAlgorithm::Secp256k1 => { DerivationAlgorithm::Secp256k1
Self::new_from_parts(&value.data, value.depth, value.chain_code).map_err(From::from)
}
_ => Err(Self::Error::Algorithm),
} }
} }
}
#[cfg(feature = "secp256k1")] impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<SecretKey> {
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<k256::SecretKey> { type Error = TryFromDerivationResponseError;
type Error = TryFromDerivationResponseError;
fn try_from(value: DerivationResponse) -> std::result::Result<Self, Self::Error> { fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
ExtendedPrivateKey::<k256::SecretKey>::try_from(&value) match value.algorithm {
} DerivationAlgorithm::Secp256k1 => Ok(Self::new_from_parts(
} &value.data,
value.depth,
#[cfg(feature = "ed25519")] value.chain_code,
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<ed25519_dalek::SigningKey> { )),
type Error = TryFromDerivationResponseError; _ => Err(Self::Error::Algorithm),
fn try_from(value: &DerivationResponse) -> std::result::Result<Self, Self::Error> {
match value.algorithm {
DerivationAlgorithm::Ed25519 => {
Self::new_from_parts(&value.data, value.depth, value.chain_code).map_err(From::from)
} }
_ => Err(Self::Error::Algorithm), }
}
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<SecretKey> {
type Error = TryFromDerivationResponseError;
fn try_from(value: DerivationResponse) -> Result<Self, Self::Error> {
ExtendedPrivateKey::<SecretKey>::try_from(&value)
} }
} }
} }
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<ed25519_dalek::SigningKey> { mod ed25519 {
type Error = TryFromDerivationResponseError; use super::*;
use ed25519_dalek::SigningKey;
fn try_from(value: DerivationResponse) -> std::result::Result<Self, Self::Error> { impl AsAlgorithm for SigningKey {
ExtendedPrivateKey::<ed25519_dalek::SigningKey>::try_from(&value) fn as_algorithm() -> DerivationAlgorithm {
DerivationAlgorithm::Ed25519
}
}
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<SigningKey> {
type Error = TryFromDerivationResponseError;
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
match value.algorithm {
DerivationAlgorithm::Ed25519 => Ok(Self::new_from_parts(
&value.data,
value.depth,
value.chain_code,
)),
_ => Err(Self::Error::Algorithm),
}
}
}
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<SigningKey> {
type Error = TryFromDerivationResponseError;
fn try_from(value: DerivationResponse) -> Result<Self, Self::Error> {
ExtendedPrivateKey::<SigningKey>::try_from(&value)
}
} }
} }

View File

@ -30,7 +30,7 @@ fn secp256k1() {
} = test; } = test;
// Tests for ExtendedPrivateKey // Tests for ExtendedPrivateKey
let xkey = ExtendedPrivateKey::<SecretKey>::new(seed).unwrap(); let xkey = ExtendedPrivateKey::<SecretKey>::new(seed);
let derived_key = xkey.derive_path(&chain).unwrap(); let derived_key = xkey.derive_path(&chain).unwrap();
assert_eq!( assert_eq!(
derived_key.chain_code().as_slice(), derived_key.chain_code().as_slice(),
@ -51,7 +51,7 @@ fn secp256k1() {
// Tests for DerivationRequest // Tests for DerivationRequest
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain); let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
let response = request.derive_with_master_seed(seed.clone()).unwrap(); let response = request.derive_with_master_seed(seed.clone()).unwrap();
assert_eq!(&response.data, private_key, "test: {chain}"); assert_eq!(&response.data, private_key.as_slice(), "test: {chain}");
} }
} }
} }
@ -75,7 +75,7 @@ fn ed25519() {
} = test; } = test;
// Tests for ExtendedPrivateKey // Tests for ExtendedPrivateKey
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap(); let xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
let derived_key = xkey.derive_path(&chain).unwrap(); let derived_key = xkey.derive_path(&chain).unwrap();
assert_eq!( assert_eq!(
derived_key.chain_code().as_slice(), derived_key.chain_code().as_slice(),
@ -96,7 +96,7 @@ fn ed25519() {
// Tests for DerivationRequest // Tests for DerivationRequest
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain); let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain);
let response = request.derive_with_master_seed(seed.to_vec()).unwrap(); let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
assert_eq!(&response.data, private_key, "test: {chain}"); assert_eq!(&response.data, private_key.as_slice(), "test: {chain}");
} }
} }
} }
@ -108,7 +108,7 @@ fn panics_with_unhardened_derivation() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
let seed = hex!("000102030405060708090a0b0c0d0e0f"); let seed = hex!("000102030405060708090a0b0c0d0e0f");
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap(); let xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
xkey.derive_path(&DerivationPath::from_str("m/0").unwrap()) xkey.derive_path(&DerivationPath::from_str("m/0").unwrap())
.unwrap(); .unwrap();
} }
@ -120,7 +120,7 @@ fn panics_at_depth() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
let seed = hex!("000102030405060708090a0b0c0d0e0f"); let seed = hex!("000102030405060708090a0b0c0d0e0f");
let mut xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap(); let mut xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
for i in 0..=u32::from(u8::MAX) { for i in 0..=u32::from(u8::MAX) {
xkey = xkey xkey = xkey
.derive_child(&DerivationIndex::new(i, true).unwrap()) .derive_child(&DerivationIndex::new(i, true).unwrap())

View File

@ -13,9 +13,9 @@ use aes_gcm::{
Aes256Gcm, Error as AesError, KeyInit, Nonce, Aes256Gcm, Error as AesError, KeyInit, Nonce,
}; };
use hkdf::{Hkdf, InvalidLength as HkdfInvalidLength}; use hkdf::{Hkdf, InvalidLength as HkdfInvalidLength};
use keyfork_derive_openpgp::derive_util::{ use keyfork_derive_openpgp::{
request::{DerivationAlgorithm, DerivationRequest}, derive_util::{DerivationPath, PathError},
DerivationPath, PathError, XPrv,
}; };
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist}; use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist};
use keyfork_prompt::{ use keyfork_prompt::{
@ -123,6 +123,10 @@ pub enum Error {
#[error("IO error: {0}")] #[error("IO error: {0}")]
Io(#[source] std::io::Error), Io(#[source] std::io::Error),
/// An error occurred while deriving data.
#[error("Derivation: {0}")]
Derivation(#[from] keyfork_derive_openpgp::derive_util::extended_key::private_key::Error),
/// An error occurred while parsing a derivation path. /// An error occurred while parsing a derivation path.
#[error("Derivation path: {0}")] #[error("Derivation path: {0}")]
DerivationPath(#[from] PathError), DerivationPath(#[from] PathError),
@ -643,13 +647,10 @@ pub fn combine(
// TODO: extract as function // TODO: extract as function
let userid = UserID::from("keyfork-sss"); let userid = UserID::from("keyfork-sss");
let kdr = DerivationRequest::new( let path = DerivationPath::from_str("m/7366512'/0'")?;
DerivationAlgorithm::Ed25519, let xprv = XPrv::new(&secret).derive_path(&path)?;
&DerivationPath::from_str("m/7366512'/0'")?,
)
.derive_with_master_seed(secret.clone())?;
let derived_cert = keyfork_derive_openpgp::derive( let derived_cert = keyfork_derive_openpgp::derive(
kdr, xprv,
&[KeyFlags::empty().set_certification().set_signing()], &[KeyFlags::empty().set_certification().set_signing()],
&userid, &userid,
)?; )?;
@ -680,13 +681,10 @@ pub fn combine(
pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write) -> Result<()> { pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write) -> Result<()> {
// build cert to sign encrypted shares // build cert to sign encrypted shares
let userid = UserID::from("keyfork-sss"); let userid = UserID::from("keyfork-sss");
let kdr = DerivationRequest::new( let path = DerivationPath::from_str("m/7366512'/0'")?;
DerivationAlgorithm::Ed25519, let xprv = XPrv::new(&secret).derive_path(&path)?;
&DerivationPath::from_str("m/7366512'/0'")?,
)
.derive_with_master_seed(secret.to_vec())?;
let derived_cert = keyfork_derive_openpgp::derive( let derived_cert = keyfork_derive_openpgp::derive(
kdr, xprv,
&[KeyFlags::empty().set_certification().set_signing()], &[KeyFlags::empty().set_certification().set_signing()],
&userid, &userid,
)?; )?;

View File

@ -1,16 +1,16 @@
use super::Keyfork; use super::Keyfork;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use keyfork_derive_openpgp::openpgp::{ use keyfork_derive_openpgp::{
armor::{Kind, Writer}, openpgp::{
packet::UserID, armor::{Kind, Writer},
serialize::Marshal, packet::UserID,
types::KeyFlags, serialize::Marshal,
}; types::KeyFlags,
use keyfork_derive_util::{ },
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse}, XPrvKey,
DerivationIndex, DerivationPath,
}; };
use keyfork_derive_util::{DerivationIndex, DerivationPath};
use keyforkd_client::Client; use keyforkd_client::Client;
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>; type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -48,12 +48,9 @@ impl DeriveSubcommands {
.set_storage_encryption(), .set_storage_encryption(),
KeyFlags::empty().set_authentication(), KeyFlags::empty().set_authentication(),
]; ];
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
let derived_data: DerivationResponse = Client::discover_socket()?
.request(&request.into())?
.try_into()?;
let default_userid = UserID::from(user_id.as_str()); let default_userid = UserID::from(user_id.as_str());
let cert = keyfork_derive_openpgp::derive(derived_data, &subkeys, &default_userid)?; let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?; let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;

View File

@ -5,11 +5,11 @@ use std::{collections::HashSet, fs::File, io::IsTerminal, path::PathBuf};
use card_backend_pcsc::PcscBackend; use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, types::KeyType, Card}; use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
use keyfork_derive_openpgp::openpgp::{self, packet::UserID, types::KeyFlags, Cert}; use keyfork_derive_openpgp::{
use keyfork_derive_util::{ openpgp::{self, packet::UserID, types::KeyFlags, Cert},
request::{DerivationAlgorithm, DerivationRequest}, XPrv,
DerivationIndex, DerivationPath,
}; };
use keyfork_derive_util::{DerivationIndex, DerivationPath};
use keyfork_prompt::{ use keyfork_prompt::{
validators::{PinValidator, Validator}, validators::{PinValidator, Validator},
Message, PromptHandler, Terminal, Message, PromptHandler, Terminal,
@ -42,10 +42,9 @@ fn derive_key(seed: &[u8], index: u8) -> Result<Cert> {
.chain_push(chain) .chain_push(chain)
.chain_push(account) .chain_push(account)
.chain_push(subkey); .chain_push(subkey);
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); let xprv = XPrv::new(seed).derive_path(&path)?;
let response = request.derive_with_master_seed(seed.to_vec())?;
let userid = UserID::from(format!("Keyfork Shard {index}")); let userid = UserID::from(format!("Keyfork Shard {index}"));
let cert = keyfork_derive_openpgp::derive(response, &subkeys, &userid)?; let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
Ok(cert) Ok(cert)
} }

View File

@ -9,7 +9,7 @@ license = "MIT"
[features] [features]
default = [] default = []
bin = ["decode-backend-zbar"] bin = ["decode-backend-rqrr"]
decode-backend-rqrr = ["dep:rqrr"] decode-backend-rqrr = ["dep:rqrr"]
decode-backend-zbar = ["dep:keyfork-zbar"] decode-backend-zbar = ["dep:keyfork-zbar"]

View File

@ -22,9 +22,9 @@ impl Image {
/// ///
/// A FourCC code can be given in the format: /// A FourCC code can be given in the format:
/// ///
/// ```no_run /// ```rust,ignore
/// self.set_format(b"Y800") /// self.set_format(b"Y800")
/// ```` /// ```
pub(crate) fn set_format(&mut self, fourcc: &[u8; 4]) { pub(crate) fn set_format(&mut self, fourcc: &[u8; 4]) {
let fourcc: u64 = fourcc[0] as u64 let fourcc: u64 = fourcc[0] as u64
| ((fourcc[1] as u64) << 8) | ((fourcc[1] as u64) << 8)

View File

@ -1,3 +1,5 @@
{{#include links.md}}
## Dependencies ## Dependencies
Keyfork has different dependencies depending on the feature set used for Keyfork has different dependencies depending on the feature set used for
@ -66,5 +68,3 @@ cargo install --index https://git.distrust.co/public/_cargo-index keyfork-entrop
# Confirmed to work as of 2024-01-17. # Confirmed to work as of 2024-01-17.
cargo install --locked --path crates/util/keyfork-entropy --bin keyfork-entropy --features bin cargo install --locked --path crates/util/keyfork-entropy --bin keyfork-entropy --features bin
``` ```
[SBOM]: https://en.wikipedia.org/wiki/SBOM

View File

@ -1,8 +1,10 @@
<!-- vim:set et sts=0 sw=2 ts=2: --> <!-- vim:set et sts=0 sw=2 ts=2: -->
{{ #include links.md }}
# Summary # Summary
# User Guide # User Guide
- [Introduction to Keyfork](./introduction.md)
- [Installing Keyfork](./INSTALL.md) - [Installing Keyfork](./INSTALL.md)
- [Security Considerations](./security.md) - [Security Considerations](./security.md)
- [Shard Commands](./shard.md) - [Shard Commands](./shard.md)

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# keyfork-derive-key # keyfork-derive-key
Derive a key from a given derivation path. Derive a key from a given derivation path.
@ -18,5 +20,3 @@ 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 Hex-encoded private key. Note that this is not the _extended_ private key, and
can't be used to derive further data. can't be used to derive further data.
[`keyforkd`]: ./bin/keyforkd.md

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# keyfork-derive-openpgp # keyfork-derive-openpgp
Derive a key from a given derivation path. Derive a key from a given derivation path.
@ -28,5 +30,3 @@ the shell silently ignoring the single quotes in the derivation path.
## Output ## Output
OpenPGP ASCII armored key, signed to be valid for 24 hours. OpenPGP ASCII armored key, signed to be valid for 24 hours.
[`keyforkd`]: ./bin/keyforkd.md

View File

@ -1,3 +1,5 @@
{{#include ../../links.md}}
# keyfork-entropy # keyfork-entropy
Retrieve system entropy, output in hex format. The machine must be running a Retrieve system entropy, output in hex format. The machine must be running a

View File

@ -1,3 +1,5 @@
{{#include ../../links.md}}
# keyfork-mnemonic-from-seed # keyfork-mnemonic-from-seed
Generate a mnemonic from a seed passed by input. Generate a mnemonic from a seed passed by input.

View File

@ -1,3 +1,5 @@
{{#include ../../links.md}}
# keyfork-shard # keyfork-shard
<!-- Linked to: keyfork-user-guide/src/bin/keyfork/shard/index.md --> <!-- Linked to: keyfork-user-guide/src/bin/keyfork/shard/index.md -->
@ -7,13 +9,9 @@ data. All binaries use Shamir's Secret Sharing through the [`sharks`] crate.
## OpenPGP ## OpenPGP
Keyfork provides OpenPGP compatible [`split`][openpgp-split] and Keyfork provides OpenPGP compatible [`split`][kshard-opgp-split] and
[`combine`][openpgp-combine] versions of Shard binaries. These binaries use [`combine`][kshard-opgp-combine] versions of Shard binaries. These binaries use
Sequoia OpenPGP and while they require all the necessary certificates for the Sequoia OpenPGP and while they require all the necessary certificates for the
splitting stage, the certificates are included in the payload, and once Keyfork splitting stage, the certificates are included in the payload, and once Keyfork
supports decrypting using OpenPGP smartcards, certificates will not be required supports decrypting using OpenPGP smartcards, certificates will not be required
to decrypt the shares. to decrypt the shares.
[`sharks`]: https://docs.rs/sharks/latest/sharks/
[openpgp-split]: ./openpgp/split.md
[openpgp-combine]: ./openpgp/combine.md

View File

@ -1,6 +1,8 @@
{{#include ../../../links.md}}
# keyfork-shard-combine-openpgp # keyfork-shard-combine-openpgp
Combine `threshold` shares into a previously [`split`] secret. Combine shares into a previously [`split`][kshard-opgp-split] secret.
## Arguments ## Arguments
@ -31,5 +33,3 @@ keyfork-shard-combine-openpgp shard.pgp
# Decrypt using on-disk private keys # Decrypt using on-disk private keys
keyfork-shard-combine-openpgp key_discovery.pgp shard.pgp keyfork-shard-combine-openpgp key_discovery.pgp shard.pgp
``` ```
[`split`]: ./split.md

View File

@ -1,3 +1,5 @@
{{#include ../../../links.md}}
# keyfork-shard-split-openpgp # keyfork-shard-split-openpgp
<!-- Linked to: keyfork-user-guide/src/bin/keyfork-shard/index.md --> <!-- Linked to: keyfork-user-guide/src/bin/keyfork-shard/index.md -->

View File

@ -1,3 +1,5 @@
{{#include ../../../links.md}}
# `keyfork derive` # `keyfork derive`
Derive keys of various formats. Derive keys of various formats.

View File

@ -1,3 +1,5 @@
{{#include ../../links.md}}
# keyfork # keyfork
The primary interface for interacting with Keyfork utilities. The primary interface for interacting with Keyfork utilities.
@ -33,5 +35,3 @@ been recovered, the Keyfork server starts, and derivation requests can begin.
Utilities to automatically manage the setup of Keyfork. This includes Utilities to automatically manage the setup of Keyfork. This includes
generating a seed, splitting it into a Shard file, and provisioning smart cards generating a seed, splitting it into a Shard file, and provisioning smart cards
with the capability to decrypt the shards. with the capability to decrypt the shards.
[BIP-0044]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki

View File

@ -1,3 +1,5 @@
{{#include ../../../links.md}}
# `keyfork mnemonic` # `keyfork mnemonic`
Utilities for managing mnemonics. Utilities for managing mnemonics.

View File

@ -1,3 +1,5 @@
{{#include ../../../links.md}}
# `keyfork recover` # `keyfork recover`
Recover a seed to memory from a mnemonic, shard, or other format, then launch Recover a seed to memory from a mnemonic, shard, or other format, then launch
@ -38,5 +40,3 @@ shardholders.
For every shardholder, the recovery command will prompt 33 words to be sent to For every shardholder, the recovery command will prompt 33 words to be sent to
the shardholder, followed by an input prompt of 72 words to be received from the shardholder, followed by an input prompt of 72 words to be received from
the shardholder. the shardholder.
[`keyfork shard transport`]: ../shard/index.md#keyfork-shard-transport

View File

@ -1,3 +1,5 @@
{{#include ../../../links.md}}
# `keyfork shard` # `keyfork shard`
<!-- Linked to: keyfork-user-guide/src/bin/keyfork-shard/index.md --> <!-- Linked to: keyfork-user-guide/src/bin/keyfork-shard/index.md -->
@ -128,5 +130,3 @@ keyfork shard transport shard.pgp
# Transport using on-disk private keys # Transport using on-disk private keys
keyfork shard transport key_discovery.pgp shard.pgp keyfork shard transport key_discovery.pgp shard.pgp
``` ```
[`keyfork recover remote-shard`]: ../recover/index.md#keyfork-recover-remote-shard

View File

@ -1,3 +1,5 @@
{{#include ../../../links.md}}
# `keyfork wizard` # `keyfork wizard`
Set up Keyfork using a guided setup process. Set up Keyfork using a guided setup process.
@ -63,5 +65,3 @@ shardholder.
An OpenPGP-encrypted Shard file, if not previously configured to be written to An OpenPGP-encrypted Shard file, if not previously configured to be written to
a file using `--output`. a file using `--output`.
[BIP-0032]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# keyforkd # keyforkd
Keyforkd is the backend for deriving data using Keyfork. A mnemonic is loaded Keyforkd is the backend for deriving data using Keyfork. A mnemonic is loaded
@ -13,7 +15,7 @@ are not leaked. In the future, `keyforkd` could implement GUI or TTY approval
for users to approve the path requested by the client, such as `m/44'/0'` being for users to approve the path requested by the client, such as `m/44'/0'` being
"Bitcoin", or `m/7366512'` being "OpenPGP". "Bitcoin", or `m/7366512'` being "OpenPGP".
The protocol for the UNIX socket is a framed, [bincode] format. While it is The protocol for the UNIX socket is a framed, [`bincode`] format. While it is
custom to Keyfork, it is easy to implement. The crate `keyfork-frame` provides custom to Keyfork, it is easy to implement. The crate `keyfork-frame` provides
a sync (`Read`, `Write`) and Tokio-compatible async (`AsyncRead`, `AsyncWrite`) a sync (`Read`, `Write`) and Tokio-compatible async (`AsyncRead`, `AsyncWrite`)
pair of methods for encoding and decoding frames. pair of methods for encoding and decoding frames.
@ -27,6 +29,3 @@ For encoding the data, the process is reversed. A SHA-256 hash is created, and
the length of the hash and the data is encoded to big-endian and written to the the length of the hash and the data is encoded to big-endian and written to the
stream. Then, the hash is written to the stream. Lastly, the data itself is stream. Then, the hash is written to the stream. Lastly, the data itself is
written as-is to the stream. written as-is to the stream.
[bincode]: https://docs.rs/bincode/latest/bincode/
[BIP-0044]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki

View File

@ -1,3 +1,5 @@
{{#include links.md}}
# Configuration File # Configuration File
The Keyfork configuration file is used to store the integrity of the mnemonic The Keyfork configuration file is used to store the integrity of the mnemonic

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# Auditing Dependencies # Auditing Dependencies
Dependencies must be reviewed before being added to the repository, and must Dependencies must be reviewed before being added to the repository, and must
@ -35,7 +37,7 @@ These dependencies will show up often:
A command line interface for generating, deriving from, and managing secrets. A command line interface for generating, deriving from, and managing secrets.
* [`card-backend-pcsc`]: Interacting with smartcards using PCSC. Used as a card * [`card-backend-pcsc`]: Interacting with smartcards using PCSC. Used as a card
backend for `openpgp-card`. backend for [`openpgp-card`].
* [`clap`]: Command line argument parsing, helps building an intuitive command * [`clap`]: Command line argument parsing, helps building an intuitive command
line interface. line interface.
* [`clap_complete`]: Shell autocompletion file generator. Helps the user * [`clap_complete`]: Shell autocompletion file generator. Helps the user
@ -221,40 +223,6 @@ Test data for SLIP10/BIP-0032 derivation.
Zero-dependency hex encoding and decoding. Zero-dependency hex encoding and decoding.
[`aes-gcm`]: https://github.com/RustCrypto/AEADs/tree/master/aes-gcm
[`anyhow`]: https://github.com/dtolnay/anyhow
[`bincode`]: https://github.com/bincode-org/bincode
[`card-backend`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/card-backend
[`card-backend-pcsc`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/pcsc
[`clap`]: https://github.com/clap-rs/clap/
[`clap_complete`]: https://github.com/clap-rs/clap/tree/master/clap_complete
[`digest`]: https://github.com/RustCrypto/traits/tree/master/digest
[`ed25519-dalek`]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek
[`hakari`]: https://docs.rs/cargo-hakari/latest/cargo_hakari/index.html
[`hkdf`]: https://github.com/RustCrypto/KDFs/tree/master/hkdf
[`hmac`]: https://github.com/RustCrypto/MACs/tree/master/hmac
[`image`]: https://github.com/image-rs/image
[`k256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/k256
[`openpgp-card`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main
[`openpgp-card-sequoia`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/openpgp-card-sequoia
[`pbkdf2`]: https://github.com/RustCrypto/password-hashes/tree/master/pbkdf2
[`ripemd`]: https://github.com/RustCrypto/hashes/tree/master/ripemd
[`rqrr`]: https://github.com/WanzenBug/rqrr/
[`sequoia-openpgp`]: https://gitlab.com/sequoia-pgp/sequoia
[`serde`]: https://github.com/dtolnay/serde
[`sha2`]: https://github.com/RustCrypto/hashes/tree/master/sha2
[`thiserror`]: https://github.com/dtolnay/thiserror
[`tokio`]: https://github.com/tokio-rs/tokio
[`tower`]: https://github.com/tower-rs/tower
[`tracing`]: https://github.com/tokio-rs/tracing
[`tracing-error`]: https://github.com/tokio-rs/tracing/tree/master/tracing-error
[`tracing-subscriber`]: https://github.com/tokio-rs/tracing/tree/master/tracing-subscriber
[`v4l`]: https://github.com/raymanfx/libv4l-rs/
[`zbar`]: https://github.com/mchehab/zbar
[`bindgen`]: https://github.com/rust-lang/rust-bindgen
[`pkg-config`]: https://github.com/rust-lang/pkg-config-rs
[`keyfork-crossterm`]: #keyfork-crossterm [`keyfork-crossterm`]: #keyfork-crossterm
[`keyfork-derive-openpgp`]: #keyfork-derive-openpgp [`keyfork-derive-openpgp`]: #keyfork-derive-openpgp
[`keyfork-derive-path-data`]: #keyfork-derive-path-data [`keyfork-derive-path-data`]: #keyfork-derive-path-data

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# Entropy Guide # Entropy Guide
Keyfork provides a `keyfork-entropy` crate for generating entropy. The crate Keyfork provides a `keyfork-entropy` crate for generating entropy. The crate

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# Handling Data # Handling Data
In Rust, it is common to name things `as_*`, `to_*`, and `into_*`. These three In Rust, it is common to name things `as_*`, `to_*`, and `into_*`. These three

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# Writing Binaries # Writing Binaries
### Binaries - Porcelain and Plumbing ### Binaries - Porcelain and Plumbing

View File

@ -1,3 +1,5 @@
{{#include ../links.md}}
# Developing Provisioners # Developing Provisioners
**Note:** This document makes heavy use of references to OpenPGP and assumes **Note:** This document makes heavy use of references to OpenPGP and assumes
@ -75,6 +77,3 @@ device. The porcelain provisioner code should make a best-effort attempt to
derive unique keys for each use, such as OpenPGP capabilities or PIV slots. derive unique keys for each use, such as OpenPGP capabilities or PIV slots.
Additionally, when provisioning to a key, the configuration for that Additionally, when provisioning to a key, the configuration for that
provisioner should be stored to the configuration file. provisioner should be stored to the configuration file.
[application identifier]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.application_identifier
[cardholder name]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.cardholder_name

14
docs/src/introduction.md Normal file
View File

@ -0,0 +1,14 @@
{{#include links.md}}
# Introduction
Keyfork is a tool to help manage the creation and derivation of binary data
using [BIP-0039] mnemonics. A mnemonic is, in simple terms, a way of encoding a
large number between 128 and 256 bits, as a list of 12 to 24 words that can be
easily stored or memorized. Once a user has a mnemonic, Keyfork utilizes
[BIP-0032] to derive cryptographic keys, which can be utilized by a variety of
applications.
## Rust documentation
Documentation is [automatically built][keyfork-rustdoc].

71
docs/src/links.md Normal file
View File

@ -0,0 +1,71 @@
<!-- DO NOT EDIT THIS FILE MANUALLY, edit links.md.template -->
<!-- vim:set et sw=4 ts=4 tw=79 ft=markdown: -->
[comments]: <> (
Please keep all links contained in this file, so they can be reused if
necessary across multiple pages.
)
[comments]: <> (
External links
)
[application identifier]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.application_identifier
[cardholder name]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.cardholder_name
[BIP-0032]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[BIP-0039]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[BIP-0044]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
[SBOM]: https://en.wikipedia.org/wiki/SBOM
[Sequoia]: https://sequoia-pgp.org
[comments]: <> (
Crate source links
)
[`aes-gcm`]: https://github.com/RustCrypto/AEADs/tree/master/aes-gcm
[`anyhow`]: https://github.com/dtolnay/anyhow
[`bincode`]: https://github.com/bincode-org/bincode
[`card-backend`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/card-backend
[`card-backend-pcsc`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/pcsc
[`clap`]: https://github.com/clap-rs/clap/
[`clap_complete`]: https://github.com/clap-rs/clap/tree/master/clap_complete
[`digest`]: https://github.com/RustCrypto/traits/tree/master/digest
[`ed25519-dalek`]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek
[`hakari`]: https://docs.rs/cargo-hakari/latest/cargo_hakari/index.html
[`hkdf`]: https://github.com/RustCrypto/KDFs/tree/master/hkdf
[`hmac`]: https://github.com/RustCrypto/MACs/tree/master/hmac
[`image`]: https://github.com/image-rs/image
[`k256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/k256
[`openpgp-card`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main
[`openpgp-card-sequoia`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/openpgp-card-sequoia
[`pbkdf2`]: https://github.com/RustCrypto/password-hashes/tree/master/pbkdf2
[`ripemd`]: https://github.com/RustCrypto/hashes/tree/master/ripemd
[`rqrr`]: https://github.com/WanzenBug/rqrr/
[`sequoia-openpgp`]: https://gitlab.com/sequoia-pgp/sequoia
[`serde`]: https://github.com/dtolnay/serde
[`sha2`]: https://github.com/RustCrypto/hashes/tree/master/sha2
[`sharks`]: https://github.com/c0dearm/sharks
[`thiserror`]: https://github.com/dtolnay/thiserror
[`tokio`]: https://github.com/tokio-rs/tokio
[`tower`]: https://github.com/tower-rs/tower
[`tracing`]: https://github.com/tokio-rs/tracing
[`tracing-error`]: https://github.com/tokio-rs/tracing/tree/master/tracing-error
[`tracing-subscriber`]: https://github.com/tokio-rs/tracing/tree/master/tracing-subscriber
[`v4l`]: https://github.com/raymanfx/libv4l-rs/
[`zbar`]: https://github.com/mchehab/zbar
[`bindgen`]: https://github.com/rust-lang/rust-bindgen
[`pkg-config`]: https://github.com/rust-lang/pkg-config-rs
[comments]: <> (
Internal links, based on root path
)
[`keyforkd`]: /bin/keyforkd.md
[`keyfork shard transport`]: /bin/keyfork/shard/index.md#keyfork-shard-transport
[`keyfork recover remote-shard`]: /bin/keyfork/recover/index.md#keyfork-recover-remote-shard
[kshard-opgp-split]: /bin/keyfork-shard/openpgp/split.md
[kshard-opgp-combine]: /bin/keyfork-shard/openpgp/combine.md
[keyfork-rustdoc]: ./rustdoc/keyfork/index.html

View File

@ -0,0 +1,70 @@
<!-- vim:set et sw=4 ts=4 tw=79 ft=markdown: -->
[comments]: <> (
Please keep all links contained in this file, so they can be reused if
necessary across multiple pages.
)
[comments]: <> (
External links
)
[application identifier]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.application_identifier
[cardholder name]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.cardholder_name
[BIP-0032]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[BIP-0039]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[BIP-0044]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
[SBOM]: https://en.wikipedia.org/wiki/SBOM
[Sequoia]: https://sequoia-pgp.org
[comments]: <> (
Crate source links
)
[`aes-gcm`]: https://github.com/RustCrypto/AEADs/tree/master/aes-gcm
[`anyhow`]: https://github.com/dtolnay/anyhow
[`bincode`]: https://github.com/bincode-org/bincode
[`card-backend`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/card-backend
[`card-backend-pcsc`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/pcsc
[`clap`]: https://github.com/clap-rs/clap/
[`clap_complete`]: https://github.com/clap-rs/clap/tree/master/clap_complete
[`digest`]: https://github.com/RustCrypto/traits/tree/master/digest
[`ed25519-dalek`]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek
[`hakari`]: https://docs.rs/cargo-hakari/latest/cargo_hakari/index.html
[`hkdf`]: https://github.com/RustCrypto/KDFs/tree/master/hkdf
[`hmac`]: https://github.com/RustCrypto/MACs/tree/master/hmac
[`image`]: https://github.com/image-rs/image
[`k256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/k256
[`openpgp-card`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main
[`openpgp-card-sequoia`]: https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/openpgp-card-sequoia
[`pbkdf2`]: https://github.com/RustCrypto/password-hashes/tree/master/pbkdf2
[`ripemd`]: https://github.com/RustCrypto/hashes/tree/master/ripemd
[`rqrr`]: https://github.com/WanzenBug/rqrr/
[`sequoia-openpgp`]: https://gitlab.com/sequoia-pgp/sequoia
[`serde`]: https://github.com/dtolnay/serde
[`sha2`]: https://github.com/RustCrypto/hashes/tree/master/sha2
[`sharks`]: https://github.com/c0dearm/sharks
[`thiserror`]: https://github.com/dtolnay/thiserror
[`tokio`]: https://github.com/tokio-rs/tokio
[`tower`]: https://github.com/tower-rs/tower
[`tracing`]: https://github.com/tokio-rs/tracing
[`tracing-error`]: https://github.com/tokio-rs/tracing/tree/master/tracing-error
[`tracing-subscriber`]: https://github.com/tokio-rs/tracing/tree/master/tracing-subscriber
[`v4l`]: https://github.com/raymanfx/libv4l-rs/
[`zbar`]: https://github.com/mchehab/zbar
[`bindgen`]: https://github.com/rust-lang/rust-bindgen
[`pkg-config`]: https://github.com/rust-lang/pkg-config-rs
[comments]: <> (
Internal links, based on root path
)
[`keyforkd`]: ${ROOT_PATH}/bin/keyforkd.md
[`keyfork shard transport`]: ${ROOT_PATH}/bin/keyfork/shard/index.md#keyfork-shard-transport
[`keyfork recover remote-shard`]: ${ROOT_PATH}/bin/keyfork/recover/index.md#keyfork-recover-remote-shard
[kshard-opgp-split]: ${ROOT_PATH}/bin/keyfork-shard/openpgp/split.md
[kshard-opgp-combine]: ${ROOT_PATH}/bin/keyfork-shard/openpgp/combine.md
[keyfork-rustdoc]: ./rustdoc/keyfork/index.html

View File

@ -1,3 +1,5 @@
{{#include links.md}}
# Keyfork Shard Commands # Keyfork Shard Commands
Sharding a seed allows "M-of-N" recovery of the seed, which is useful for Sharding a seed allows "M-of-N" recovery of the seed, which is useful for
@ -77,5 +79,3 @@ The key, including the secret portions, can be retrieved by running the command
without the `sq` portion, but should not be run on a system with a persistent without the `sq` portion, but should not be run on a system with a persistent
filesystem, to avoid keeping the key on written memory for longer than filesystem, to avoid keeping the key on written memory for longer than
necessary. necessary.
[Sequoia]: https://sequoia-pgp.org

View File

@ -1,3 +1,5 @@
{{#include links.md}}
# Common Usage # Common Usage
Keyfork is a tool to help manage the creation and derivation of binary data Keyfork is a tool to help manage the creation and derivation of binary data
@ -74,6 +76,3 @@ the following command for an OpenPGP certificate with one of each subkey:
```sh ```sh
keyfork derive openpgp "John Doe <jdoe@example.com>" keyfork derive openpgp "John Doe <jdoe@example.com>"
``` ```
[BIP-0039]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[BIP-0032]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki