Compare commits
5 Commits
c6e274c4da
...
e7a776f59f
Author | SHA1 | Date |
---|---|---|
|
e7a776f59f | |
|
672cc6a699 | |
|
df552250ba | |
|
0cb96782ef | |
|
625e8e490b |
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
|
@ -26,6 +26,23 @@ members = [
|
|||
"crates/tests",
|
||||
]
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "deny", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
|
||||
# used often in tests
|
||||
wildcard_imports = { level = "allow"}
|
||||
|
||||
# annoying
|
||||
must_use_candidate = "allow"
|
||||
return_self_not_must_use = "allow"
|
||||
|
||||
# sometimes i like the logical flow of keeping things in an "else"
|
||||
redundant_else = "allow"
|
||||
|
||||
# i hate using `.unwrap_or_else(|| keyfork_bug::bug!())`
|
||||
expect_fun_call = "allow"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
# Keyfork dependencies
|
||||
|
@ -84,4 +101,3 @@ debug = true
|
|||
[profile.dev.package.keyfork-qrcode]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
doc-valid-idents = ["OpenPGP", ".."]
|
|
@ -4,6 +4,9 @@ version = "0.2.2"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
//!
|
||||
//! ### Request: Derive Key
|
||||
//!
|
||||
//! The client creates a derivation path of at least two indices and requests a derived XPrv
|
||||
//! The client creates a derivation path of at least two indices and requests a derived `XPrv`
|
||||
//! (Extended Private Key) from the server.
|
||||
//!
|
||||
//! ```rust
|
||||
|
@ -68,7 +68,7 @@
|
|||
//! ## 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.
|
||||
//! 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.
|
||||
//!
|
||||
|
@ -117,7 +117,7 @@
|
|||
//!
|
||||
//! ## Testing Infrastructure
|
||||
//!
|
||||
//! In tests, the `keyforkd::test_util` module and TestPrivateKeys can be used. These provide
|
||||
//! In tests, the `keyforkd::test_util` module and `TestPrivateKeys` can be used. These provide
|
||||
//! useful utilities for writing tests that interact with the Keyfork Server without needing to
|
||||
//! manually create the server for the purpose of the test. The `run_test` method can be used to
|
||||
//! run a test, which can handle both returning errors and correctly translating panics (though,
|
||||
|
@ -199,6 +199,10 @@ pub enum Error {
|
|||
#[error("Socket was unable to connect to {1}: {0} (make sure keyforkd is running)")]
|
||||
Connect(std::io::Error, PathBuf),
|
||||
|
||||
/// The path of the derived key was of an invalid length.
|
||||
#[error("Derived key path is of invalid length")]
|
||||
InvalidPathLength(#[from] std::num::TryFromIntError),
|
||||
|
||||
/// 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),
|
||||
|
@ -237,12 +241,9 @@ pub fn get_socket() -> Result<UnixStream, Error> {
|
|||
.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) => {
|
||||
if let Some(occupied) = socket_vars.get("KEYFORKD_SOCKET_PATH") {
|
||||
socket_path = PathBuf::from(occupied);
|
||||
}
|
||||
None => {
|
||||
} else {
|
||||
socket_path = PathBuf::from(
|
||||
socket_vars
|
||||
.get("XDG_RUNTIME_DIR")
|
||||
|
@ -250,7 +251,6 @@ pub fn get_socket() -> Result<UnixStream, Error> {
|
|||
);
|
||||
socket_path.extend(["keyforkd", "keyforkd.sock"]);
|
||||
}
|
||||
}
|
||||
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ pub struct Client {
|
|||
|
||||
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,
|
||||
/// provided in case a specific `UnixStream` has to be used; otherwise,
|
||||
/// [`Client::discover_socket`] should be preferred.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -344,7 +344,7 @@ impl Client {
|
|||
return Err(Error::InvalidResponse);
|
||||
}
|
||||
|
||||
let depth = path.len() as u8;
|
||||
let depth = u8::try_from(path.len())?;
|
||||
ExtendedPrivateKey::from_parts(&d.data, depth, d.chain_code)
|
||||
.map_err(|_| Error::InvalidKey)
|
||||
}
|
||||
|
|
|
@ -9,14 +9,13 @@ use std::{os::unix::net::UnixStream, str::FromStr};
|
|||
fn secp256k1_test_suite() {
|
||||
use k256::SecretKey;
|
||||
|
||||
let tests = test_data()
|
||||
.unwrap()
|
||||
.remove("secp256k1")
|
||||
.unwrap();
|
||||
let tests = test_data().unwrap().remove("secp256k1").unwrap();
|
||||
|
||||
for seed_test in tests {
|
||||
let seed = seed_test.seed;
|
||||
run_test(&seed, move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
|
||||
run_test(
|
||||
&seed,
|
||||
move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
|
||||
for test in seed_test.tests {
|
||||
let socket = UnixStream::connect(socket_path).unwrap();
|
||||
let mut client = Client::new(socket);
|
||||
|
@ -60,7 +59,8 @@ fn secp256k1_test_suite() {
|
|||
assert_eq!(&response.data, test.private_key.as_slice());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.2.0"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.4"
|
|||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -89,14 +89,10 @@ pub async fn start_and_run_server(mnemonic: Mnemonic) -> Result<(), Box<dyn std:
|
|||
let runtime_vars = std::env::vars()
|
||||
.filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str()))
|
||||
.collect::<HashMap<String, String>>();
|
||||
let mut runtime_path: PathBuf;
|
||||
#[allow(clippy::single_match_else)]
|
||||
match runtime_vars.get("KEYFORKD_SOCKET_PATH") {
|
||||
Some(occupied) => {
|
||||
runtime_path = PathBuf::from(occupied);
|
||||
}
|
||||
None => {
|
||||
runtime_path = PathBuf::from(
|
||||
let runtime_path = if let Some(occupied) = runtime_vars.get("KEYFORKD_SOCKET_PATH") {
|
||||
PathBuf::from(occupied)
|
||||
} else {
|
||||
let mut runtime_path = PathBuf::from(
|
||||
runtime_vars
|
||||
.get("XDG_RUNTIME_DIR")
|
||||
.ok_or(KeyforkdError::NoSocketPath)?,
|
||||
|
@ -108,8 +104,8 @@ pub async fn start_and_run_server(mnemonic: Mnemonic) -> Result<(), Box<dyn std:
|
|||
tokio::fs::create_dir(&runtime_path).await?;
|
||||
}
|
||||
runtime_path.push("keyforkd.sock");
|
||||
}
|
||||
}
|
||||
runtime_path
|
||||
};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
debug!(
|
||||
|
|
|
@ -162,6 +162,9 @@ mod tests {
|
|||
.call(content.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result, serialize(&Result::<Test, Infallible>::Ok(test)).unwrap());
|
||||
assert_eq!(
|
||||
result,
|
||||
serialize(&Result::<Test, Infallible>::Ok(test)).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ impl IsDisconnect for EncodeError {
|
|||
}
|
||||
|
||||
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.
|
||||
/// 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.
|
||||
|
|
|
@ -39,7 +39,10 @@ impl Keyforkd {
|
|||
/// Create a new instance of Keyfork from a given seed.
|
||||
pub fn new(seed: Vec<u8>) -> Self {
|
||||
if seed.len() < 16 {
|
||||
warn!("Entropy size is lower than 128 bits: {} bits.", seed.len() * 8);
|
||||
warn!(
|
||||
"Entropy size is lower than 128 bits: {} bits.",
|
||||
seed.len() * 8
|
||||
);
|
||||
}
|
||||
Self {
|
||||
seed: Arc::new(seed),
|
||||
|
@ -77,11 +80,11 @@ impl Service<Request> for Keyforkd {
|
|||
.iter()
|
||||
.take(2)
|
||||
.enumerate()
|
||||
.find(|(_, index)| {
|
||||
!index.is_hardened()
|
||||
})
|
||||
.find(|(_, index)| !index.is_hardened())
|
||||
{
|
||||
return Err(DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into())
|
||||
return Err(
|
||||
DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
|
@ -111,10 +114,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn properly_derives_secp256k1() {
|
||||
let tests = test_data()
|
||||
.unwrap()
|
||||
.remove("secp256k1")
|
||||
.unwrap();
|
||||
let tests = test_data().unwrap().remove("secp256k1").unwrap();
|
||||
|
||||
for per_seed in tests {
|
||||
let seed = &per_seed.seed;
|
||||
|
@ -174,7 +174,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "InvalidDerivationLength(0)")]
|
||||
#[tokio::test]
|
||||
async fn errors_on_no_path() {
|
||||
let tests = [(
|
||||
|
@ -200,7 +200,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "InvalidDerivationLength(1)")]
|
||||
#[tokio::test]
|
||||
async fn errors_on_short_path() {
|
||||
let tests = [(
|
||||
|
|
|
@ -89,8 +89,11 @@ where
|
|||
.expect(bug!("couldn't send server start signal"));
|
||||
let service = ServiceBuilder::new()
|
||||
.layer(middleware::BincodeLayer::new())
|
||||
.service(Keyforkd::new(seed.to_vec()));
|
||||
server.run(service).await.expect(bug!("Unable to start service"));
|
||||
.service(Keyforkd::new(seed.clone()));
|
||||
server
|
||||
.run(service)
|
||||
.await
|
||||
.expect(bug!("Unable to start service"));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.2"
|
|||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -46,7 +46,10 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut client = Client::discover_socket()?;
|
||||
let request = DerivationRequest::new(algo, &path);
|
||||
let response = client.request(&request.into())?;
|
||||
println!("{}", smex::encode(DerivationResponse::try_from(response)?.data));
|
||||
println!(
|
||||
"{}",
|
||||
smex::encode(DerivationResponse::try_from(response)?.data)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.5"
|
|||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
default = ["bin"]
|
||||
|
|
|
@ -74,7 +74,7 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
|
|||
///
|
||||
/// # Errors
|
||||
/// The function may error for any condition mentioned in [`Error`].
|
||||
pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
|
||||
pub fn derive(xprv: &XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
|
||||
let primary_key_flags = match keys.first() {
|
||||
Some(kf) if kf.for_certification() => kf,
|
||||
_ => return Err(Error::NotCert),
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use std::{env, process::ExitCode, str::FromStr};
|
||||
|
||||
use keyfork_derive_util::DerivationPath;
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyfork_derive_util::DerivationPath;
|
||||
use keyforkd_client::Client;
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
@ -82,7 +82,10 @@ fn validate(
|
|||
let index = paths::OPENPGP.inner().first().unwrap();
|
||||
|
||||
let path = DerivationPath::from_str(path)?;
|
||||
assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
|
||||
assert!(
|
||||
path.len() >= 2,
|
||||
"Expected path of at least m/{index}/account_id'"
|
||||
);
|
||||
|
||||
let given_index = path.iter().next().expect("checked .len() above");
|
||||
assert_eq!(
|
||||
|
@ -117,7 +120,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.map(|kt| kt.inner().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cert = keyfork_derive_openpgp::derive(derived_xprv, 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)?;
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.3"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
|
||||
|
@ -11,7 +11,7 @@ pub mod paths {
|
|||
use super::*;
|
||||
|
||||
/// The default derivation path for OpenPGP.
|
||||
pub static OPENPGP: Lazy<DerivationPath> = Lazy::new(|| {
|
||||
pub static OPENPGP: LazyLock<DerivationPath> = LazyLock::new(|| {
|
||||
DerivationPath::default().chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00pgp"),
|
||||
true,
|
||||
|
@ -19,7 +19,7 @@ pub mod paths {
|
|||
});
|
||||
|
||||
/// The derivation path for OpenPGP certificates used for sharding.
|
||||
pub static OPENPGP_SHARD: Lazy<DerivationPath> = Lazy::new(|| {
|
||||
pub static OPENPGP_SHARD: LazyLock<DerivationPath> = LazyLock::new(|| {
|
||||
DerivationPath::default()
|
||||
.chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00pgp"),
|
||||
|
@ -32,7 +32,7 @@ pub mod paths {
|
|||
});
|
||||
|
||||
/// The derivation path for OpenPGP certificates used for disaster recovery.
|
||||
pub static OPENPGP_DISASTER_RECOVERY: Lazy<DerivationPath> = Lazy::new(|| {
|
||||
pub static OPENPGP_DISASTER_RECOVERY: LazyLock<DerivationPath> = LazyLock::new(|| {
|
||||
DerivationPath::default()
|
||||
.chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00pgp"),
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.2.2"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -52,7 +52,7 @@ pub struct VariableLengthSeed<'a> {
|
|||
}
|
||||
|
||||
impl<'a> VariableLengthSeed<'a> {
|
||||
/// Create a new VariableLengthSeed.
|
||||
/// Create a new `VariableLengthSeed`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
|
@ -167,6 +167,7 @@ where
|
|||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
|
||||
/// ```
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(seed: impl as_private_key::AsPrivateKey) -> Result<Self> {
|
||||
Self::new_internal(seed.as_private_key())
|
||||
}
|
||||
|
@ -189,7 +190,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
assert!(has_any_nonzero, bug!("hmac function returned all-zero master key"));
|
||||
assert!(
|
||||
has_any_nonzero,
|
||||
bug!("hmac function returned all-zero master key")
|
||||
);
|
||||
|
||||
Self::from_parts(
|
||||
private_key
|
||||
|
@ -223,13 +227,11 @@ where
|
|||
/// ```
|
||||
pub fn from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Result<Self> {
|
||||
match K::from_bytes(key) {
|
||||
Ok(key) => {
|
||||
Ok(Self {
|
||||
Ok(key) => Ok(Self {
|
||||
private_key: key,
|
||||
depth,
|
||||
chain_code,
|
||||
})
|
||||
}
|
||||
}),
|
||||
Err(_) => Err(Error::InvalidKey),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ mod tests {
|
|||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "IndexTooLarge")]
|
||||
fn fails_on_high_index() {
|
||||
DerivationIndex::new(0x8000_0001, false).unwrap();
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "IndexTooLarge")]
|
||||
fn from_str_fails_on_high_index() {
|
||||
DerivationIndex::from_str(&0x8000_0001u32.to_string()).unwrap();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::module_name_repetitions, clippy::must_use_candidate)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod extended_key;
|
||||
|
@ -17,7 +17,10 @@ pub mod public_key;
|
|||
mod tests;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::extended_key::{private_key::{ExtendedPrivateKey, Error as XPrvError, VariableLengthSeed}, public_key::{ExtendedPublicKey, Error as XPubError}};
|
||||
pub use crate::extended_key::{
|
||||
private_key::{Error as XPrvError, ExtendedPrivateKey, VariableLengthSeed},
|
||||
public_key::{Error as XPubError, ExtendedPublicKey},
|
||||
};
|
||||
|
||||
pub use crate::{
|
||||
index::{DerivationIndex, Error as IndexError},
|
||||
|
|
|
@ -11,7 +11,7 @@ pub enum Error {
|
|||
|
||||
/// The path could not be parsed due to a bad prefix. Paths must be in the format:
|
||||
///
|
||||
/// m [/ index [']]+
|
||||
/// `m [/ index [']]+`
|
||||
///
|
||||
/// The prefix for the path must be `m`, and all indices must be integers between 0 and
|
||||
/// 2^31.
|
||||
|
@ -35,8 +35,8 @@ 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`].
|
||||
/// 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()
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ mod tests {
|
|||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "UnknownPathPrefix")]
|
||||
fn requires_master_path() {
|
||||
DerivationPath::from_str("1234/5678'").unwrap();
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ impl PublicKey for k256::PublicKey {
|
|||
|
||||
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self, Self::Err> {
|
||||
use k256::elliptic_curve::ScalarPrimitive;
|
||||
use k256::{Secp256k1, Scalar};
|
||||
use k256::{Scalar, Secp256k1};
|
||||
|
||||
// Construct a scalar from bytes
|
||||
let scalar = ScalarPrimitive::<Secp256k1>::from_bytes(&other.into());
|
||||
|
@ -156,7 +156,7 @@ pub struct TestPublicKey {
|
|||
}
|
||||
|
||||
impl TestPublicKey {
|
||||
/// Create a new TestPublicKey from the given bytes.
|
||||
/// Create a new [`TestPublicKey`] from the given bytes.
|
||||
#[allow(dead_code)]
|
||||
pub fn from_bytes(b: &[u8]) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -13,10 +13,7 @@ use keyfork_slip10_test_data::{test_data, Test};
|
|||
fn secp256k1() {
|
||||
use k256::SecretKey;
|
||||
|
||||
let tests = test_data()
|
||||
.unwrap()
|
||||
.remove("secp256k1")
|
||||
.unwrap();
|
||||
let tests = test_data().unwrap().remove("secp256k1").unwrap();
|
||||
|
||||
for per_seed in tests {
|
||||
let seed = &per_seed.seed;
|
||||
|
@ -105,7 +102,7 @@ fn ed25519() {
|
|||
|
||||
#[cfg(feature = "ed25519")]
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "HardenedDerivationRequired")]
|
||||
fn panics_with_unhardened_derivation() {
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
|
@ -117,7 +114,7 @@ fn panics_with_unhardened_derivation() {
|
|||
|
||||
#[cfg(feature = "ed25519")]
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "Depth")]
|
||||
fn panics_at_depth() {
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.3.4"
|
|||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -35,7 +35,11 @@ fn run() -> Result<()> {
|
|||
let openpgp = OpenPGP;
|
||||
let prompt_handler = default_handler()?;
|
||||
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(
|
||||
key_discovery.as_deref(),
|
||||
messages_file,
|
||||
prompt_handler,
|
||||
)?;
|
||||
print!("{}", smex::encode(bytes));
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
|||
};
|
||||
|
||||
use keyfork_prompt::default_handler;
|
||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
||||
|
@ -35,7 +35,11 @@ fn run() -> Result<()> {
|
|||
let openpgp = OpenPGP;
|
||||
let prompt_handler = default_handler()?;
|
||||
|
||||
openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
||||
openpgp.decrypt_one_shard_for_transport(
|
||||
key_discovery.as_deref(),
|
||||
messages_file,
|
||||
prompt_handler,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
//! Combine OpenPGP shards using remote transport and output the hex-encoded secret.
|
||||
|
||||
use std::{
|
||||
env,
|
||||
process::ExitCode,
|
||||
};
|
||||
use std::{env, process::ExitCode};
|
||||
|
||||
use keyfork_shard::remote_decrypt;
|
||||
|
||||
|
@ -16,7 +13,7 @@ fn run() -> Result<()> {
|
|||
match args.as_slice() {
|
||||
[] => (),
|
||||
_ => panic!("Usage: {program_name}"),
|
||||
};
|
||||
}
|
||||
|
||||
let mut bytes = vec![];
|
||||
remote_decrypt(&mut bytes)?;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
|
||||
|
||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Error {
|
||||
|
@ -52,7 +52,13 @@ fn run() -> Result<()> {
|
|||
|
||||
let openpgp = OpenPGP;
|
||||
|
||||
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
|
||||
openpgp.shard_and_encrypt(
|
||||
threshold,
|
||||
max,
|
||||
&input,
|
||||
key_discovery.as_path(),
|
||||
std::io::stdout(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![allow(clippy::expect_fun_call)]
|
||||
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
|
@ -92,9 +91,10 @@ pub trait KeyDiscovery<F: Format + ?Sized> {
|
|||
/// # Errors
|
||||
/// The method may return an error if private keys could not be loaded from the given
|
||||
/// discovery mechanism. Keys may exist off-system (such as with smartcards), in which case the
|
||||
/// PrivateKeyData type of the asssociated format should be either `()` (if the keys may never
|
||||
/// exist on-system) or an empty container (such as an empty Vec); in either case, this method
|
||||
/// _must not_ return an error if keys are accessible but can't be transferred into memory.
|
||||
/// `PrivateKeyData` type of the asssociated format should be either `()` (if the keys may
|
||||
/// never exist on-system) or an empty container (such as an empty Vec); in either case, this
|
||||
/// method _must not_ return an error if keys are accessible but can't be transferred into
|
||||
/// memory.
|
||||
fn discover_private_keys(&self) -> Result<F::PrivateKeyData, F::Error>;
|
||||
}
|
||||
|
||||
|
@ -121,8 +121,8 @@ pub trait Format {
|
|||
/// Format a header containing necessary metadata. Such metadata contains a version byte, a
|
||||
/// threshold byte, a public version of the [`Format::SigningKey`], and the public keys used to
|
||||
/// encrypt shards. The public keys must be kept _in order_ to the encrypted shards. Keyfork
|
||||
/// will use the same key_data for both, ensuring an iteration of this method will match with
|
||||
/// iterations in methods called later.
|
||||
/// will use the same `key_data` for both, ensuring an iteration of this method will match
|
||||
/// with iterations in methods called later.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if encryption to any of the public keys fails.
|
||||
|
@ -263,6 +263,7 @@ pub trait Format {
|
|||
/// The method may return an error if a share can't be decrypted. The method will not return an
|
||||
/// error if the camera is inaccessible or if a hardware error is encountered while scanning a
|
||||
/// QR code; instead, a mnemonic prompt will be used.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn decrypt_one_shard_for_transport(
|
||||
&self,
|
||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||
|
@ -326,14 +327,14 @@ pub trait Format {
|
|||
if choice == RetryScanMnemonic::Continue {
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if QR code scanning failed or was unavailable, read from a set of mnemonics
|
||||
let their_pubkey = match pubkey_data {
|
||||
Some(pubkey) => pubkey,
|
||||
None => {
|
||||
let their_pubkey = if let Some(pubkey) = pubkey_data {
|
||||
pubkey
|
||||
} else {
|
||||
let validator = MnemonicValidator {
|
||||
word_length: Some(WordLength::Count(24)),
|
||||
};
|
||||
|
@ -347,20 +348,6 @@ pub trait Format {
|
|||
.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| InvalidData)?
|
||||
/*
|
||||
prompt
|
||||
.lock()
|
||||
.expect(bug!(POISONED_MUTEX))
|
||||
.prompt_validated_wordlist::<English, _>(
|
||||
QRCODE_COULDNT_READ,
|
||||
3,
|
||||
validator.to_fn(),
|
||||
)?
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| InvalidData)?
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
// create our shared key
|
||||
|
@ -401,7 +388,6 @@ pub trait Format {
|
|||
// NOTE: Previous versions of Keyfork Shard would modify the padding bytes to avoid
|
||||
// duplicate mnemonic words. This version does not include that, and instead uses a
|
||||
// repeated length byte.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let mut plaintext_bytes = [u8::try_from(payload.len()).expect(bug!(
|
||||
"previously asserted length must be < {PLAINTEXT_LENGTH}",
|
||||
PLAINTEXT_LENGTH = PLAINTEXT_LENGTH
|
||||
|
@ -481,11 +467,11 @@ pub trait Format {
|
|||
"must have less than u8::MAX public keys"
|
||||
);
|
||||
assert_eq!(
|
||||
max,
|
||||
public_keys.len() as u8,
|
||||
max as usize,
|
||||
public_keys.len(),
|
||||
"max must be equal to amount of public keys"
|
||||
);
|
||||
let max = public_keys.len() as u8;
|
||||
let max = u8::try_from(public_keys.len()).expect(bug!("invalid max: {max}", max = max));
|
||||
assert!(max >= threshold, "threshold must not exceed max keys");
|
||||
|
||||
let header = self.format_encrypted_header(&signing_key, &public_keys, threshold)?;
|
||||
|
@ -547,6 +533,7 @@ static QRCODE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
|
|||
/// # Panics
|
||||
/// The function may panic if it is given payloads generated using a version of Keyfork that is
|
||||
/// incompatible with the currently running version.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut pm = keyfork_prompt::default_handler()?;
|
||||
|
||||
|
@ -627,13 +614,13 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
if choice == RetryScanMnemonic::Continue {
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (pubkey, payload) = match (pubkey_data, payload_data) {
|
||||
(Some(pubkey), Some(payload)) => (pubkey, payload),
|
||||
_ => {
|
||||
let (pubkey, payload) = if let Some((pubkey, payload)) = pubkey_data.zip(payload_data) {
|
||||
(pubkey, payload)
|
||||
} else {
|
||||
let validator = MnemonicSetValidator {
|
||||
word_lengths: [24, 39],
|
||||
};
|
||||
|
@ -650,7 +637,6 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
.map_err(|_| InvalidData)?;
|
||||
let payload = payload_mnemonic.to_bytes();
|
||||
(pubkey, payload)
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
|
@ -677,17 +663,14 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
let payload = shared_key.decrypt(nonce, payload.as_slice())?;
|
||||
assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version");
|
||||
|
||||
match &mut iter_count {
|
||||
Some(n) => {
|
||||
if let Some(n) = &mut iter_count {
|
||||
// Must be > 0 to start loop, can't go lower
|
||||
*n -= 1;
|
||||
}
|
||||
None => {
|
||||
} else {
|
||||
// NOTE: Should always be >= 1, < 256 due to Shamir constraints
|
||||
threshold = payload[1];
|
||||
let _ = iter_count.insert(threshold - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let payload_len = payload.last().expect(bug!("payload should not be empty"));
|
||||
shares.push(payload[HUNK_OFFSET..usize::from(*payload_len)].to_vec());
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
//! OpenPGP Shard functionality.
|
||||
|
||||
#![allow(clippy::expect_fun_call)]
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
sync::Mutex,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use blahaj::Share;
|
||||
use keyfork_bug::bug;
|
||||
use keyfork_derive_openpgp::{
|
||||
derive_util::{DerivationPath, VariableLengthSeed},
|
||||
|
@ -25,7 +24,7 @@ use openpgp::{
|
|||
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
|
||||
Parse,
|
||||
},
|
||||
policy::{NullPolicy, StandardPolicy, Policy},
|
||||
policy::{NullPolicy, Policy, StandardPolicy},
|
||||
serialize::{
|
||||
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
|
||||
Marshal,
|
||||
|
@ -34,7 +33,6 @@ use openpgp::{
|
|||
KeyID, PacketPile,
|
||||
};
|
||||
pub use sequoia_openpgp as openpgp;
|
||||
use blahaj::Share;
|
||||
|
||||
mod keyring;
|
||||
use keyring::Keyring;
|
||||
|
@ -94,7 +92,7 @@ pub struct EncryptedMessage {
|
|||
}
|
||||
|
||||
impl EncryptedMessage {
|
||||
/// Create a new EncryptedMessage from known parts.
|
||||
/// Create a new [`EncryptedMessage`] from known parts.
|
||||
pub fn new(pkesks: &mut Vec<PKESK>, seip: SEIP) -> Self {
|
||||
Self {
|
||||
pkesks: std::mem::take(pkesks),
|
||||
|
@ -160,7 +158,7 @@ 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.
|
||||
/// 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
|
||||
|
@ -238,7 +236,7 @@ impl OpenPGP {
|
|||
let policy = StandardPolicy::new();
|
||||
let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?;
|
||||
if get_encryption_keys(&valid_cert).next().is_none() {
|
||||
return Err(Error::NoValidKeys(valid_cert.keyid()))
|
||||
return Err(Error::NoValidKeys(valid_cert.keyid()));
|
||||
}
|
||||
}
|
||||
Ok(certs.into_values().collect())
|
||||
|
@ -265,7 +263,7 @@ impl Format for OpenPGP {
|
|||
.derive_path(&path)
|
||||
.expect(bug!("valid derivation"));
|
||||
keyfork_derive_openpgp::derive(
|
||||
xprv,
|
||||
&xprv,
|
||||
&[KeyFlags::empty().set_certification().set_signing()],
|
||||
&userid,
|
||||
)
|
||||
|
@ -450,8 +448,8 @@ impl Format for OpenPGP {
|
|||
// We don't want to invalidate someone's keys just because the old sig expired.
|
||||
let policy = NullPolicy::new();
|
||||
|
||||
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?;
|
||||
let mut manager = SmartcardManager::new(prompt.clone())?;
|
||||
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone());
|
||||
let mut manager = SmartcardManager::new(prompt.clone());
|
||||
|
||||
let mut encrypted_messages = encrypted_data.iter();
|
||||
|
||||
|
@ -482,9 +480,9 @@ impl Format for OpenPGP {
|
|||
|
||||
let left_from_threshold = threshold as usize - decrypted_messages.len();
|
||||
if left_from_threshold > 0 {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let new_messages = decrypt_with_manager(
|
||||
left_from_threshold as u8,
|
||||
u8::try_from(left_from_threshold)
|
||||
.expect(bug!("threshold too large: {}", left_from_threshold)),
|
||||
&mut messages,
|
||||
&certs,
|
||||
&policy,
|
||||
|
@ -509,8 +507,8 @@ impl Format for OpenPGP {
|
|||
) -> std::result::Result<(Share, u8), Self::Error> {
|
||||
let policy = NullPolicy::new();
|
||||
|
||||
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?;
|
||||
let mut manager = SmartcardManager::new(prompt.clone())?;
|
||||
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone());
|
||||
let mut manager = SmartcardManager::new(prompt.clone());
|
||||
|
||||
let mut encrypted_messages = encrypted_data.iter();
|
||||
|
||||
|
@ -557,8 +555,8 @@ impl Format for OpenPGP {
|
|||
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
) -> std::result::Result<(u8, Vec<Self::PublicKey>), Self::Error> {
|
||||
let policy = NullPolicy::new();
|
||||
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?;
|
||||
let mut manager = SmartcardManager::new(prompt.clone())?;
|
||||
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone());
|
||||
let mut manager = SmartcardManager::new(prompt.clone());
|
||||
let mut encrypted_messages = encrypted_data.iter();
|
||||
|
||||
let metadata = encrypted_messages
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::expect_fun_call)]
|
||||
|
||||
use std::{rc::Rc, sync::Mutex};
|
||||
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
|
@ -25,8 +23,6 @@ pub enum Error {
|
|||
Prompt(#[from] PromptError),
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
pub struct Keyring {
|
||||
full_certs: Vec<Cert>,
|
||||
root: Option<Cert>,
|
||||
|
@ -34,12 +30,12 @@ pub struct Keyring {
|
|||
}
|
||||
|
||||
impl Keyring {
|
||||
pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self {
|
||||
Self {
|
||||
full_certs: certs.as_ref().to_vec(),
|
||||
root: Default::default(),
|
||||
root: Option::default(),
|
||||
pm: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::expect_fun_call)]
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
|
@ -81,13 +79,13 @@ pub struct SmartcardManager {
|
|||
}
|
||||
|
||||
impl SmartcardManager {
|
||||
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self {
|
||||
Self {
|
||||
current_card: None,
|
||||
root: None,
|
||||
pm: p,
|
||||
pin_cache: Default::default(),
|
||||
})
|
||||
pin_cache: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the root cert, returning the old cert
|
||||
|
@ -177,10 +175,9 @@ impl SmartcardManager {
|
|||
|
||||
impl VerificationHelper for &mut SmartcardManager {
|
||||
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
||||
#[allow(clippy::flat_map_option)]
|
||||
Ok(ids
|
||||
.iter()
|
||||
.flat_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh))
|
||||
.filter_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh))
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
@ -281,8 +278,7 @@ impl DecryptionHelper for &mut SmartcardManager {
|
|||
let temp_pin = prompt_validated_passphrase(&mut **prompt, &message, 3, &pin_validator)?;
|
||||
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||
match verification_status {
|
||||
#[allow(clippy::ignored_unit_patterns)]
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
self.pin_cache.insert(fp.clone(), temp_pin.clone());
|
||||
pin.replace(temp_pin);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.3.3"
|
|||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"completion",
|
||||
|
|
|
@ -43,7 +43,7 @@ impl FromStr for Options {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.is_empty() {
|
||||
return Ok(Default::default())
|
||||
return Ok(Self::default());
|
||||
}
|
||||
let values = s
|
||||
.split(',')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{Keyfork, create};
|
||||
use super::{create, Keyfork};
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use std::{fmt::Display, io::Write, path::PathBuf};
|
||||
|
||||
|
@ -26,9 +26,9 @@ pub trait Deriver {
|
|||
|
||||
fn derivation_path(&self) -> DerivationPath;
|
||||
|
||||
fn derive_with_xprv(&self, writer: OptWrite, xprv: XPrv<Self::Prv>) -> Result<()>;
|
||||
fn derive_with_xprv(&self, writer: OptWrite, xprv: &XPrv<Self::Prv>) -> Result<()>;
|
||||
|
||||
fn derive_public_with_xprv(&self, writer: OptWrite, xprv: XPrv<Self::Prv>) -> Result<()>;
|
||||
fn derive_public_with_xprv(&self, writer: OptWrite, xprv: &XPrv<Self::Prv>) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
|
@ -138,12 +138,12 @@ impl std::str::FromStr for Slug {
|
|||
|
||||
impl Display for Slug {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
#[allow(clippy::redundant_at_rest_pattern)]
|
||||
match (self.0.inner() & (0b1 << 31)).to_be_bytes().as_slice() {
|
||||
[0, 0, 0, 0] => Ok(()),
|
||||
[0, 0, 0, bytes @ ..] | [0, 0, bytes @ ..] | [0, bytes @ ..] | [bytes @ ..] => f
|
||||
.write_str(
|
||||
std::str::from_utf8(&bytes[..]).expect("slug constructed from non-utf8"),
|
||||
),
|
||||
[0, 0, 0, bytes @ ..] | [0, 0, bytes @ ..] | [0, bytes @ ..] | [bytes @ ..] => {
|
||||
f.write_str(std::str::from_utf8(bytes).expect("slug constructed from non-utf8"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,9 +170,9 @@ impl DeriveSubcommands {
|
|||
let xprv = Client::discover_socket()?
|
||||
.request_xprv::<<OpenPGP as Deriver>::Prv>(&path.chain_push(account))?;
|
||||
if is_public {
|
||||
opgp.derive_public_with_xprv(writer, xprv)
|
||||
opgp.derive_public_with_xprv(writer, &xprv)
|
||||
} else {
|
||||
opgp.derive_with_xprv(writer, xprv)
|
||||
opgp.derive_with_xprv(writer, &xprv)
|
||||
}
|
||||
}
|
||||
DeriveSubcommands::Key(key) => {
|
||||
|
@ -180,9 +180,9 @@ impl DeriveSubcommands {
|
|||
let xprv = Client::discover_socket()?
|
||||
.request_xprv::<<Key as Deriver>::Prv>(&path.chain_push(account))?;
|
||||
if is_public {
|
||||
key.derive_public_with_xprv(writer, xprv)
|
||||
key.derive_public_with_xprv(writer, &xprv)
|
||||
} else {
|
||||
key.derive_with_xprv(writer, xprv)
|
||||
key.derive_with_xprv(writer, &xprv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ impl DeriveSubcommands {
|
|||
}
|
||||
|
||||
impl OpenPGP {
|
||||
fn cert_from_xprv(&self, xprv: keyfork_derive_openpgp::XPrv) -> Result<Cert> {
|
||||
fn cert_from_xprv(&self, xprv: &keyfork_derive_openpgp::XPrv) -> Result<Cert> {
|
||||
let subkeys = vec![
|
||||
KeyFlags::empty().set_certification(),
|
||||
KeyFlags::empty().set_signing(),
|
||||
|
@ -213,15 +213,12 @@ impl Deriver for OpenPGP {
|
|||
self.derivation_path.derivation_path()
|
||||
}
|
||||
|
||||
fn derive_with_xprv(&self, writer: OptWrite, xprv: XPrv<Self::Prv>) -> Result<()> {
|
||||
fn derive_with_xprv(&self, writer: OptWrite, xprv: &XPrv<Self::Prv>) -> Result<()> {
|
||||
let cert = self.cert_from_xprv(xprv)?;
|
||||
let writer = match writer {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
let writer = if let Some(writer) = writer { writer } else {
|
||||
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
|
||||
let file = create(&path)?;
|
||||
Box::new(file)
|
||||
}
|
||||
};
|
||||
let mut writer = Writer::new(writer, Kind::SecretKey)?;
|
||||
for packet in cert.as_tsk().into_packets() {
|
||||
|
@ -231,15 +228,12 @@ impl Deriver for OpenPGP {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn derive_public_with_xprv(&self, writer: OptWrite, xprv: XPrv<Self::Prv>) -> Result<()> {
|
||||
fn derive_public_with_xprv(&self, writer: OptWrite, xprv: &XPrv<Self::Prv>) -> Result<()> {
|
||||
let cert = self.cert_from_xprv(xprv)?;
|
||||
let writer = match writer {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
let writer = if let Some(writer) = writer { writer } else {
|
||||
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
|
||||
let file = create(&path)?;
|
||||
Box::new(file)
|
||||
}
|
||||
};
|
||||
let mut writer = Writer::new(writer, Kind::PublicKey)?;
|
||||
for packet in cert.into_packets2() {
|
||||
|
@ -259,7 +253,7 @@ impl Deriver for Key {
|
|||
DerivationPath::default().chain_push(self.slug.0.clone())
|
||||
}
|
||||
|
||||
fn derive_with_xprv(&self, writer: OptWrite, xprv: XPrv<Self::Prv>) -> Result<()> {
|
||||
fn derive_with_xprv(&self, writer: OptWrite, xprv: &XPrv<Self::Prv>) -> Result<()> {
|
||||
let (formatted, ext) = match self.format {
|
||||
KeyFormat::Hex => (smex::encode(xprv.private_key().to_bytes()), "hex"),
|
||||
KeyFormat::Base64 => {
|
||||
|
@ -278,7 +272,7 @@ impl Deriver for Key {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn derive_public_with_xprv(&self, writer: OptWrite, xprv: XPrv<Self::Prv>) -> Result<()> {
|
||||
fn derive_public_with_xprv(&self, writer: OptWrite, xprv: &XPrv<Self::Prv>) -> Result<()> {
|
||||
let (formatted, ext) = match self.format {
|
||||
KeyFormat::Hex => (smex::encode(xprv.public_key().to_bytes()), "hex"),
|
||||
KeyFormat::Base64 => {
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use super::{
|
||||
create,
|
||||
derive::{self, Deriver},
|
||||
provision,
|
||||
Keyfork,
|
||||
provision, Keyfork,
|
||||
};
|
||||
use crate::{clap_ext::*, config, openpgp_card::factory_reset_current_card};
|
||||
use card_backend_pcsc::PcscBackend;
|
||||
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{IsTerminal, Write},
|
||||
|
@ -20,7 +19,7 @@ use keyfork_derive_openpgp::{
|
|||
openpgp::{
|
||||
self,
|
||||
armor::{Kind, Writer},
|
||||
packet::{UserID, signature::SignatureBuilder},
|
||||
packet::{signature::SignatureBuilder, UserID},
|
||||
policy::StandardPolicy,
|
||||
serialize::{
|
||||
stream::{Encryptor2, LiteralWriter, Message, Recipient},
|
||||
|
@ -165,7 +164,7 @@ pub enum Error {
|
|||
MissingOption(&'static str),
|
||||
}
|
||||
|
||||
fn context_stub<'a>(path: &'a Path) -> impl Fn(std::io::Error) -> Error + 'a {
|
||||
fn context_stub(path: &Path) -> impl Fn(std::io::Error) -> Error + use<'_> {
|
||||
|e| Error::IOContext(e, path.to_path_buf())
|
||||
}
|
||||
|
||||
|
@ -228,13 +227,13 @@ pub enum MnemonicSubcommands {
|
|||
///
|
||||
/// The following additional arguments are available:
|
||||
///
|
||||
/// * threshold, m: the minimum amount of shares required to reconstitute the shard. By
|
||||
/// * `threshold`, m: the minimum amount of shares required to reconstitute the shard. By
|
||||
/// default, this is the amount of certificates provided.
|
||||
///
|
||||
/// * max, n: the maximum amount of shares. When provided, this is used to ensure the
|
||||
/// * `max`, n: the maximum amount of shares. When provided, this is used to ensure the
|
||||
/// certificate count is correct. This is required when using `threshold` or `m`.
|
||||
///
|
||||
/// * output: the file to write the generated Shardfile to. By default, assuming the
|
||||
/// * `output`: the file to write the generated Shardfile to. By default, assuming the
|
||||
/// certificate input is `input.asc`, the generated Shardfile would be written to
|
||||
/// `input.shard.asc`.
|
||||
#[arg(long)]
|
||||
|
@ -257,13 +256,13 @@ pub enum MnemonicSubcommands {
|
|||
///
|
||||
/// The following additional arguments are required:
|
||||
///
|
||||
/// * threshold, m: the minimum amount of shares required to reconstitute the shard.
|
||||
/// * `threshold`, m: the minimum amount of shares required to reconstitute the shard.
|
||||
///
|
||||
/// * max, n: the maximum amount of shares.
|
||||
/// * `max`, n: the maximum amount of shares.
|
||||
///
|
||||
/// * cards_per_shard: the amount of OpenPGP smartcards to provision per shardholder.
|
||||
/// * `cards_per_shard`: the amount of OpenPGP smartcards to provision per shardholder.
|
||||
///
|
||||
/// * cert_output: the file to write all generated OpenPGP certificates to; if not
|
||||
/// * `cert_output`: the file to write all generated OpenPGP certificates to; if not
|
||||
/// provided, files will be automatically generated for each certificate.
|
||||
#[arg(long)]
|
||||
shard_to_self: Option<ValueWithOptions<PathBuf>>,
|
||||
|
@ -303,24 +302,23 @@ fn determine_valid_output_path<T: AsRef<Path>>(
|
|||
mid_ext: &str,
|
||||
optional_path: Option<T>,
|
||||
) -> PathBuf {
|
||||
match optional_path {
|
||||
Some(p) => p.as_ref().to_path_buf(),
|
||||
None => {
|
||||
if let Some(p) = optional_path {
|
||||
p.as_ref().to_path_buf()
|
||||
} else {
|
||||
let extension = match path.extension() {
|
||||
Some(ext) => format!("{mid_ext}.{ext}", ext = ext.to_string_lossy()),
|
||||
None => format!("{mid_ext}.asc"),
|
||||
};
|
||||
path.with_extension(extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_extension_armored(path: &Path) -> bool {
|
||||
match path.extension().and_then(|s| s.to_str()) {
|
||||
Some("pgp") | Some("gpg") => false,
|
||||
Some("pgp" | "gpg") => false,
|
||||
Some("asc") => true,
|
||||
_ => {
|
||||
eprintln!("unable to determine whether to armor file: {path:?}");
|
||||
eprintln!("unable to determine whether to armor file: {path}", path = path.display());
|
||||
eprintln!("use .gpg, .pgp, or .asc extension, or `armor=true`");
|
||||
eprintln!("defaulting to armored");
|
||||
true
|
||||
|
@ -395,8 +393,11 @@ fn do_encrypt_to_self(
|
|||
.clone()
|
||||
.chain_push(account);
|
||||
|
||||
let cert =
|
||||
keyfork_derive_openpgp::derive(xprv.derive_path(&derivation_path)?, &subkeys, &userid)?;
|
||||
let cert = keyfork_derive_openpgp::derive(
|
||||
&xprv.derive_path(&derivation_path)?,
|
||||
&subkeys,
|
||||
&userid,
|
||||
)?;
|
||||
|
||||
certs.push(cert);
|
||||
}
|
||||
|
@ -456,15 +457,12 @@ fn do_shard(
|
|||
// if only one is set: true
|
||||
|
||||
if threshold.is_some() ^ max.is_some() {
|
||||
return Err(MissingThresholdOrMax)?;
|
||||
return Err(MissingThresholdOrMax.into());
|
||||
}
|
||||
|
||||
let (threshold, max) = match threshold.zip(max) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
let (threshold, max) = if let Some(t) = threshold.zip(max) { t } else {
|
||||
let len = u8::try_from(certs.len())?;
|
||||
(len, len)
|
||||
}
|
||||
};
|
||||
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
|
@ -544,12 +542,14 @@ fn derive_key(seed: [u8; 64], index: u8) -> Result<openpgp::Cert, Box<dyn std::e
|
|||
];
|
||||
|
||||
let subkey = DerivationIndex::new(u32::from(index), true)?;
|
||||
let path = keyfork_derive_path_data::paths::OPENPGP_SHARD.clone().chain_push(subkey);
|
||||
let path = keyfork_derive_path_data::paths::OPENPGP_SHARD
|
||||
.clone()
|
||||
.chain_push(subkey);
|
||||
let xprv = XPrv::new(seed)
|
||||
.expect("could not construct master key from seed")
|
||||
.derive_path(&path)?;
|
||||
let userid = UserID::from(format!("Keyfork Shard {index}"));
|
||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
|
||||
let cert = keyfork_derive_openpgp::derive(&xprv, &subkeys, &userid)?;
|
||||
Ok(cert)
|
||||
}
|
||||
|
||||
|
@ -611,7 +611,6 @@ fn do_shard_to_self(
|
|||
.parse()?;
|
||||
let cards_per_shard = options
|
||||
.get("cards_per_shard")
|
||||
.as_deref()
|
||||
.map(|cps| u8::from_str(cps))
|
||||
.transpose()?;
|
||||
|
||||
|
@ -626,8 +625,8 @@ fn do_shard_to_self(
|
|||
for i in 0..cards_per_shard.unwrap_or(1) {
|
||||
pm.prompt_message(keyfork_prompt::Message::Text(format!(
|
||||
"Please remove all keys and insert key #{} for user #{}",
|
||||
(i as u16) + 1,
|
||||
(index as u16) + 1,
|
||||
(u16::from(i)) + 1,
|
||||
(u16::from(index)) + 1,
|
||||
)))?;
|
||||
let card_backend = loop {
|
||||
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
||||
|
@ -671,7 +670,7 @@ fn do_shard_to_self(
|
|||
let output = File::create(path)?;
|
||||
opgp.shard_and_encrypt(
|
||||
threshold,
|
||||
certs.len() as u8,
|
||||
u8::try_from(certs.len()).expect("provided more than u8::MAX certs"),
|
||||
mnemonic.as_bytes(),
|
||||
&certs[..],
|
||||
output,
|
||||
|
@ -742,10 +741,10 @@ fn do_provision(
|
|||
metadata @ None => {
|
||||
*metadata = Some(config.clone());
|
||||
}
|
||||
};
|
||||
}
|
||||
provision
|
||||
.provisioner_name
|
||||
.provision_with_mnemonic(mnemonic, provisioner)?;
|
||||
.provision_with_mnemonic(mnemonic, &provisioner)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -772,11 +771,11 @@ fn do_derive(
|
|||
use keyfork_derive_openpgp::XPrv;
|
||||
let root_xprv = XPrv::new(mnemonic.generate_seed(None))?;
|
||||
let account = DerivationIndex::new(*account_id, true)?;
|
||||
let derived = root_xprv.derive_path(&opgp.derivation_path().chain_push(account))?;
|
||||
let derived_key = root_xprv.derive_path(&opgp.derivation_path().chain_push(account))?;
|
||||
if *public {
|
||||
opgp.derive_public_with_xprv(writer, derived)?;
|
||||
opgp.derive_public_with_xprv(writer, &derived_key)?;
|
||||
} else {
|
||||
opgp.derive_with_xprv(writer, derived)?;
|
||||
opgp.derive_with_xprv(writer, &derived_key)?;
|
||||
}
|
||||
}
|
||||
derive::Derive {
|
||||
|
@ -790,11 +789,11 @@ fn do_derive(
|
|||
use keyfork_derive_openpgp::XPrv;
|
||||
let root_xprv = XPrv::new(mnemonic.generate_seed(None))?;
|
||||
let account = DerivationIndex::new(*account_id, true)?;
|
||||
let derived = root_xprv.derive_path(&key.derivation_path().chain_push(account))?;
|
||||
let derived_key = root_xprv.derive_path(&key.derivation_path().chain_push(account))?;
|
||||
if *public {
|
||||
key.derive_public_with_xprv(writer, derived)?;
|
||||
key.derive_public_with_xprv(writer, &derived_key)?;
|
||||
} else {
|
||||
key.derive_with_xprv(writer, derived)?;
|
||||
key.derive_with_xprv(writer, &derived_key)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -802,6 +801,7 @@ fn do_derive(
|
|||
}
|
||||
|
||||
impl MnemonicSubcommands {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn handle(
|
||||
&self,
|
||||
_m: &Mnemonic,
|
||||
|
@ -831,13 +831,13 @@ impl MnemonicSubcommands {
|
|||
// * Sharding to existing, usable keys
|
||||
// * Sharding to newly provisioned keys
|
||||
let mut will_print_mnemonic =
|
||||
encrypt_to.is_none() || encrypt_to.as_ref().is_some_and(|e| e.is_empty());
|
||||
encrypt_to.is_none() || encrypt_to.as_ref().is_some_and(Vec::is_empty);
|
||||
will_print_mnemonic = will_print_mnemonic
|
||||
&& (encrypt_to_self.as_ref().is_none() || provision.as_ref().is_none());
|
||||
will_print_mnemonic = will_print_mnemonic && shard_to.is_none()
|
||||
|| shard_to.as_ref().is_some_and(|s| s.is_empty());
|
||||
|| shard_to.as_ref().is_some_and(Vec::is_empty);
|
||||
will_print_mnemonic = will_print_mnemonic && shard.is_none()
|
||||
|| shard.as_ref().is_some_and(|s| s.is_empty());
|
||||
|| shard.as_ref().is_some_and(Vec::is_empty);
|
||||
will_print_mnemonic = will_print_mnemonic && shard_to_self.is_none();
|
||||
|
||||
let mnemonic = source.handle(size)?;
|
||||
|
@ -859,7 +859,7 @@ impl MnemonicSubcommands {
|
|||
}
|
||||
|
||||
if let Some(encrypt_to_self) = encrypt_to_self {
|
||||
let mut accounts: std::collections::HashSet<u32> = Default::default();
|
||||
let mut accounts: HashSet<u32> = HashSet::default();
|
||||
if let Some(provision::Provision {
|
||||
provisioner_name: provision::Provisioner::OpenPGPCard(_),
|
||||
account_id,
|
||||
|
@ -884,7 +884,7 @@ impl MnemonicSubcommands {
|
|||
!indices.is_empty(),
|
||||
"neither derived nor provisioned accounts were found"
|
||||
);
|
||||
do_encrypt_to_self(&mnemonic, &encrypt_to_self, &indices)?;
|
||||
do_encrypt_to_self(&mnemonic, encrypt_to_self, &indices)?;
|
||||
}
|
||||
|
||||
if let Some(shard_to_self) = shard_to_self {
|
||||
|
@ -938,7 +938,7 @@ impl MnemonicSubcommands {
|
|||
}
|
||||
|
||||
if will_print_mnemonic {
|
||||
println!("{}", mnemonic);
|
||||
println!("{mnemonic}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ mod recover;
|
|||
mod shard;
|
||||
|
||||
pub fn create(path: &std::path::Path) -> std::io::Result<std::fs::File> {
|
||||
eprintln!("Writing derived key to: {path}", path=path.display());
|
||||
eprintln!("Writing derived key to: {path}", path = path.display());
|
||||
std::fs::File::create(path)
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ pub struct Keyfork {
|
|||
pub command: KeyforkCommands,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
pub enum KeyforkCommands {
|
||||
/// Derive keys of various formats. These commands require that the Keyfork server is running,
|
||||
|
@ -95,12 +96,7 @@ impl KeyforkCommands {
|
|||
KeyforkCommands::Completion { shell } => {
|
||||
let mut command = Keyfork::command();
|
||||
let command_name = command.get_name().to_string();
|
||||
clap_complete::generate(
|
||||
*shell,
|
||||
&mut command,
|
||||
command_name,
|
||||
&mut std::io::stdout(),
|
||||
);
|
||||
clap_complete::generate(*shell, &mut command, command_name, &mut std::io::stdout());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -38,7 +38,7 @@ impl Provisioner {
|
|||
|
||||
pub fn provision(
|
||||
&self,
|
||||
provisioner: config::Provisioner,
|
||||
provisioner: &config::Provisioner,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
Provisioner::OpenPGPCard(o) => {
|
||||
|
@ -49,7 +49,7 @@ impl Provisioner {
|
|||
.chain_push(account_index);
|
||||
let mut client = keyforkd_client::Client::discover_socket()?;
|
||||
let xprv: XPrv = client.request_xprv(&path)?;
|
||||
o.provision(xprv, provisioner)
|
||||
o.provision(&xprv, provisioner)
|
||||
}
|
||||
Provisioner::Shard(s) => {
|
||||
type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey;
|
||||
|
@ -59,7 +59,7 @@ impl Provisioner {
|
|||
.chain_push(account_index);
|
||||
let mut client = keyforkd_client::Client::discover_socket()?;
|
||||
let xprv: XPrv = client.request_xprv(&path)?;
|
||||
s.provision(xprv, provisioner)
|
||||
s.provision(&xprv, provisioner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ impl Provisioner {
|
|||
pub fn provision_with_mnemonic(
|
||||
&self,
|
||||
mnemonic: &keyfork_mnemonic::Mnemonic,
|
||||
provisioner: config::Provisioner,
|
||||
provisioner: &config::Provisioner,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
Provisioner::OpenPGPCard(o) => {
|
||||
|
@ -77,7 +77,7 @@ impl Provisioner {
|
|||
let path = <openpgp::OpenPGPCard as ProvisionExec>::derivation_prefix()
|
||||
.chain_push(account_index);
|
||||
let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?;
|
||||
o.provision(xprv, provisioner)
|
||||
o.provision(&xprv, provisioner)
|
||||
}
|
||||
Provisioner::Shard(s) => {
|
||||
type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey;
|
||||
|
@ -86,7 +86,7 @@ impl Provisioner {
|
|||
let path = <openpgp::Shard as ProvisionExec>::derivation_prefix()
|
||||
.chain_push(account_index);
|
||||
let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?;
|
||||
s.provision(xprv, provisioner)
|
||||
s.provision(&xprv, provisioner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,10 @@ impl Provisioner {
|
|||
|
||||
impl ValueEnum for Provisioner {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[Self::OpenPGPCard(openpgp::OpenPGPCard), Self::Shard(openpgp::Shard)]
|
||||
&[
|
||||
Self::OpenPGPCard(openpgp::OpenPGPCard),
|
||||
Self::Shard(openpgp::Shard),
|
||||
]
|
||||
}
|
||||
|
||||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||
|
@ -129,8 +132,8 @@ trait ProvisionExec {
|
|||
/// Derive a key and deploy it to a target.
|
||||
fn provision(
|
||||
&self,
|
||||
xprv: keyfork_derive_util::ExtendedPrivateKey<Self::PrivateKey>,
|
||||
p: config::Provisioner,
|
||||
xprv: &keyfork_derive_util::ExtendedPrivateKey<Self::PrivateKey>,
|
||||
p: &config::Provisioner,
|
||||
) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
|
@ -186,7 +189,7 @@ impl TryFrom<Provision> for config::Provisioner {
|
|||
Ok(Self {
|
||||
account: value.account_id,
|
||||
identifier: value.identifier.ok_or(MissingField("identifier"))?,
|
||||
metadata: Default::default(),
|
||||
metadata: Option::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -207,9 +210,9 @@ impl Provision {
|
|||
}
|
||||
}
|
||||
None => {
|
||||
let provisioner_with_identifier = match self.identifier {
|
||||
Some(_) => self.clone(),
|
||||
None => {
|
||||
let provisioner_with_identifier = if self.identifier.is_some() {
|
||||
self.clone()
|
||||
} else {
|
||||
let identifiers = self.provisioner_name.discover()?;
|
||||
let [id] = &identifiers[..] else {
|
||||
panic!("invalid amount of identifiers; pass --identifier");
|
||||
|
@ -218,10 +221,9 @@ impl Provision {
|
|||
identifier: Some(id.0.clone()),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
};
|
||||
let config = config::Provisioner::try_from(provisioner_with_identifier)?;
|
||||
self.provisioner_name.provision(config)?;
|
||||
self.provisioner_name.provision(&config)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -22,7 +22,9 @@ use std::path::PathBuf;
|
|||
#[error("Provisioner was unable to find a matching smartcard")]
|
||||
struct NoMatchingSmartcard;
|
||||
|
||||
fn discover_cards() -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
|
||||
pub type CardList = Vec<(String, Option<String>)>;
|
||||
|
||||
fn discover_cards() -> Result<CardList, Box<dyn std::error::Error>> {
|
||||
let mut idents = vec![];
|
||||
for backend in PcscBackend::cards(None)? {
|
||||
let backend = backend?;
|
||||
|
@ -37,8 +39,8 @@ fn discover_cards() -> Result<Vec<(String, Option<String>)>, Box<dyn std::error:
|
|||
}
|
||||
|
||||
fn provision_card(
|
||||
provisioner: config::Provisioner,
|
||||
xprv: XPrv,
|
||||
provisioner: &config::Provisioner,
|
||||
xprv: &XPrv,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut pm = default_handler()?;
|
||||
|
||||
|
@ -57,23 +59,24 @@ fn provision_card(
|
|||
Some(userid) => UserID::from(userid.as_str()),
|
||||
None => UserID::from("Keyfork-Provisioned Key"),
|
||||
};
|
||||
let cert = keyfork_derive_openpgp::derive(xprv.clone(), &subkeys, &userid)?;
|
||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
|
||||
|
||||
if !provisioner
|
||||
.metadata
|
||||
.as_ref()
|
||||
.is_some_and(|m| m.contains_key("_skip_cert_output"))
|
||||
{
|
||||
let cert_output = match provisioner.metadata.as_ref().and_then(|m| m.get("output")) {
|
||||
Some(cert_output) => PathBuf::from(cert_output),
|
||||
None => {
|
||||
let cert_output = if let Some(cert_output) =
|
||||
provisioner.metadata.as_ref().and_then(|m| m.get("output"))
|
||||
{
|
||||
PathBuf::from(cert_output)
|
||||
} else {
|
||||
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
|
||||
eprintln!(
|
||||
"Writing OpenPGP certificate to: {path}",
|
||||
path = path.display()
|
||||
);
|
||||
path
|
||||
}
|
||||
};
|
||||
|
||||
let cert_output_file = std::fs::File::create(cert_output)?;
|
||||
|
@ -100,7 +103,7 @@ fn provision_card(
|
|||
}
|
||||
|
||||
if !has_provisioned {
|
||||
return Err(NoMatchingSmartcard)?;
|
||||
return Err(NoMatchingSmartcard.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -122,8 +125,8 @@ impl ProvisionExec for OpenPGPCard {
|
|||
|
||||
fn provision(
|
||||
&self,
|
||||
xprv: XPrv,
|
||||
provisioner: config::Provisioner,
|
||||
xprv: &XPrv,
|
||||
provisioner: &config::Provisioner,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
provision_card(provisioner, xprv)
|
||||
}
|
||||
|
@ -145,8 +148,8 @@ impl ProvisionExec for Shard {
|
|||
|
||||
fn provision(
|
||||
&self,
|
||||
xprv: XPrv,
|
||||
provisioner: config::Provisioner,
|
||||
xprv: &XPrv,
|
||||
provisioner: &config::Provisioner,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
provision_card(provisioner, xprv)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use super::Keyfork;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
use nix::{
|
||||
sys::wait::waitpid,
|
||||
unistd::{fork, ForkResult},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_prompt::{
|
||||
|
@ -86,7 +86,7 @@ pub struct Recover {
|
|||
command: RecoverSubcommands,
|
||||
|
||||
/// Daemonize the server once started, restoring control back to the shell.
|
||||
#[arg(long, global=true)]
|
||||
#[arg(long, global = true)]
|
||||
daemon: bool,
|
||||
}
|
||||
|
||||
|
@ -102,12 +102,12 @@ impl Recover {
|
|||
// wait for the child to die, so we don't exit prematurely
|
||||
waitpid(Some(child), None)?;
|
||||
return Ok(());
|
||||
},
|
||||
}
|
||||
ForkResult::Child => {
|
||||
if let ForkResult::Parent { .. } = unsafe { fork() }? {
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
|
|
|
@ -109,14 +109,15 @@ impl ShardExec for OpenPGP {
|
|||
output: &mut impl Write,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use keyfork_derive_openpgp::openpgp::{
|
||||
armor::{Kind, Writer},
|
||||
serialize::Marshal,
|
||||
armor::{Writer, Kind},
|
||||
};
|
||||
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
let prompt = default_handler()?;
|
||||
|
||||
let (threshold, certs) = openpgp.decrypt_metadata_from_file(key_discovery, input, prompt)?;
|
||||
let (threshold, certs) =
|
||||
openpgp.decrypt_metadata_from_file(key_discovery, input, prompt)?;
|
||||
let mut writer = Writer::new(output_pubkeys, Kind::PublicKey)?;
|
||||
for cert in certs {
|
||||
cert.serialize(&mut writer)?;
|
||||
|
@ -187,7 +188,7 @@ pub enum ShardSubcommands {
|
|||
|
||||
/// The path to discover private keys from.
|
||||
key_discovery: Option<PathBuf>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
impl ShardSubcommands {
|
||||
|
@ -256,7 +257,11 @@ impl ShardSubcommands {
|
|||
None => panic!("{COULD_NOT_DETERMINE_FORMAT}"),
|
||||
}
|
||||
}
|
||||
ShardSubcommands::Metadata { shardfile, output_pubkeys, key_discovery } => {
|
||||
ShardSubcommands::Metadata {
|
||||
shardfile,
|
||||
output_pubkeys,
|
||||
key_discovery,
|
||||
} => {
|
||||
let shard_content = std::fs::read_to_string(shardfile)?;
|
||||
if shard_content.contains("BEGIN PGP MESSAGE") {
|
||||
let _ = format.insert(Format::OpenPGP(OpenPGP));
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
@ -8,9 +7,9 @@ use clap::Parser;
|
|||
|
||||
use keyfork_bin::{Bin, ClosureBin};
|
||||
|
||||
pub mod clap_ext;
|
||||
mod cli;
|
||||
mod config;
|
||||
pub mod clap_ext;
|
||||
mod openpgp_card;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
|
|
|
@ -34,11 +34,10 @@ pub fn get_new_pins(
|
|||
3,
|
||||
&user_pin_validator,
|
||||
)?;
|
||||
if user_pin != validated_user_pin {
|
||||
pm.prompt_message(Message::Text("User PINs did not match. Retrying.".into()))?;
|
||||
} else {
|
||||
if user_pin == validated_user_pin {
|
||||
break user_pin;
|
||||
}
|
||||
pm.prompt_message(Message::Text("User PINs did not match. Retrying.".into()))?;
|
||||
};
|
||||
|
||||
let admin_pin = loop {
|
||||
|
@ -54,11 +53,10 @@ pub fn get_new_pins(
|
|||
3,
|
||||
&admin_pin_validator,
|
||||
)?;
|
||||
if admin_pin != validated_admin_pin {
|
||||
pm.prompt_message(Message::Text("Admin PINs did not match. Retrying.".into()))?;
|
||||
} else {
|
||||
if admin_pin == validated_admin_pin {
|
||||
break admin_pin;
|
||||
}
|
||||
pm.prompt_message(Message::Text("Admin PINs did not match. Retrying.".into()))?;
|
||||
};
|
||||
|
||||
Ok((user_pin, admin_pin))
|
||||
|
|
|
@ -5,6 +5,9 @@ repository = "https://git.distrust.co/public/keyfork"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//! Encoding and decoding QR codes.
|
||||
|
||||
#![allow(clippy::expect_fun_call)]
|
||||
|
||||
use keyfork_bug as bug;
|
||||
|
||||
use bug::POISONED_MUTEX;
|
||||
|
@ -39,10 +37,10 @@ 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.
|
||||
/// The expected format, in `FourCC` format.
|
||||
expected: String,
|
||||
|
||||
/// The actual format, in FourCC format.
|
||||
/// The actual format, in `FourCC` format.
|
||||
actual: String,
|
||||
},
|
||||
|
||||
|
@ -165,13 +163,12 @@ mod rqrr {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, clippy::cast_precision_loss)]
|
||||
fn dbg_elapsed(count: u64, instant: Instant) {
|
||||
let elapsed = instant.elapsed().as_secs();
|
||||
let framerate = count as f64 / elapsed as f64;
|
||||
eprintln!("framerate: {count}/{elapsed} = {framerate}");
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -5,10 +5,13 @@ repository = "https://git.distrust.co/public/keyfork"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = { version = "0.68", default-features = false, features = ["runtime"] }
|
||||
bindgen = { version = "0.70", default-features = false, features = ["runtime"] }
|
||||
pkg-config = "0.3"
|
||||
|
|
|
@ -45,7 +45,7 @@ fn main() -> Result<()> {
|
|||
if let Err(e) = generate_bindings_file() {
|
||||
eprintln!("Building zbar-sys failed: {e}");
|
||||
eprintln!("Ensure zbar headers, libclang, and pkg-config are installed");
|
||||
return Err(e)
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(clippy::unreadable_literal, clippy::pub_underscore_fields)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
|
|
@ -5,6 +5,9 @@ repository = "https://git.distrust.co/public/keyfork"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
|
||||
use keyfork_zbar::{image::Image, image_scanner::ImageScanner};
|
||||
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::ImageReader;
|
||||
use v4l::{
|
||||
buffer::Type,
|
||||
io::{traits::CaptureStream, userptr::Stream},
|
||||
|
|
|
@ -20,16 +20,13 @@ impl Image {
|
|||
|
||||
/// Link: [`sys::zbar_image_set_format`]
|
||||
///
|
||||
/// A FourCC code can be given in the format:
|
||||
/// A `FourCC` code can be given in the format:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// self.set_format(b"Y800")
|
||||
/// ```
|
||||
pub(crate) fn set_format(&mut self, fourcc: &[u8; 4]) {
|
||||
let fourcc: u64 = fourcc[0] as u64
|
||||
| ((fourcc[1] as u64) << 8)
|
||||
| ((fourcc[2] as u64) << 16)
|
||||
| ((fourcc[3] as u64) << 24);
|
||||
let fourcc = std::os::raw::c_ulong::from(u32::from_le_bytes(*fourcc));
|
||||
unsafe { sys::zbar_image_set_format(self.inner, fourcc) }
|
||||
}
|
||||
|
||||
|
@ -43,12 +40,7 @@ impl Image {
|
|||
/// Accepts raw data in the configured format. See: [`Image::set_format`]
|
||||
fn set_data(&mut self, data: Vec<u8>) {
|
||||
unsafe {
|
||||
sys::zbar_image_set_data(
|
||||
self.inner,
|
||||
data.as_ptr().cast(),
|
||||
data.len() as u64,
|
||||
None,
|
||||
)
|
||||
sys::zbar_image_set_data(self.inner, data.as_ptr().cast(), data.len() as u64, None);
|
||||
}
|
||||
// keep data in self to avoid use after free when data goes out of scope
|
||||
let _ = self.inner_data.insert(data);
|
||||
|
|
|
@ -22,7 +22,7 @@ pub struct ImageScanner {
|
|||
}
|
||||
|
||||
impl ImageScanner {
|
||||
/// create a new ImageScanner.
|
||||
/// Create a new `ImageScanner`.
|
||||
///
|
||||
/// Link: [`sys::zbar_image_scanner_create`]
|
||||
pub fn new() -> Self {
|
||||
|
@ -31,7 +31,7 @@ impl ImageScanner {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set a configuration option for the ImageScanner.
|
||||
/// Set a configuration option.
|
||||
///
|
||||
/// Link: [`sys::zbar_image_scanner_set_config`]
|
||||
///
|
||||
|
@ -58,10 +58,7 @@ impl ImageScanner {
|
|||
/// Link: [`sys::zbar_scan_image`]
|
||||
///
|
||||
/// TODO: return an iterator over scanned values
|
||||
pub fn scan_image(
|
||||
&mut self,
|
||||
image: &Image,
|
||||
) -> Vec<Symbol> {
|
||||
pub fn scan_image(&mut self, image: &Image) -> Vec<Symbol> {
|
||||
unsafe { sys::zbar_scan_image(self.inner, image.inner) };
|
||||
let mut result = vec![];
|
||||
let mut symbol = unsafe { sys::zbar_image_first_symbol(image.inner) };
|
||||
|
@ -70,7 +67,7 @@ impl ImageScanner {
|
|||
let symbol_data = unsafe { sys::zbar_symbol_get_data(symbol) };
|
||||
let symbol_data_len = unsafe { sys::zbar_symbol_get_data_length(symbol) };
|
||||
let symbol_slice = unsafe {
|
||||
std::slice::from_raw_parts(symbol_data as *const u8, symbol_data_len as usize)
|
||||
std::slice::from_raw_parts(symbol_data.cast::<u8>(), symbol_data_len as usize)
|
||||
};
|
||||
result.push(Symbol::new(symbol_type, symbol_slice));
|
||||
symbol = unsafe { sys::zbar_symbol_next(symbol) };
|
||||
|
|
|
@ -11,6 +11,6 @@ pub use sys::zbar_config_e as Config;
|
|||
pub use sys::zbar_modifier_e as Modifier;
|
||||
pub use sys::zbar_orientation_e as Orientation;
|
||||
|
||||
pub mod image_scanner;
|
||||
pub mod image;
|
||||
pub mod image_scanner;
|
||||
pub mod symbol;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! A Symbol represents some form of encoded data.
|
||||
|
||||
#![allow(clippy::used_underscore_binding)]
|
||||
|
||||
use super::sys;
|
||||
|
||||
/// The type of symbol (i.e. what type of barcode or QR code).
|
||||
|
|
|
@ -5,6 +5,9 @@ edition = "2021"
|
|||
publish = false
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
assert_cmd = "2.0.16"
|
||||
keyforkd = { workspace = true, features = ["default"] }
|
||||
|
|
|
@ -52,7 +52,5 @@ fn test() {
|
|||
key.alive().expect("is live after being generated");
|
||||
key.parts_into_secret().expect("has secret keys");
|
||||
}
|
||||
if !key_formats.is_empty() {
|
||||
panic!("remaining key formats: {key_formats:?}");
|
||||
}
|
||||
assert!(key_formats.is_empty(), "remaining key formats: {key_formats:?}");
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -45,6 +45,7 @@ use std::process::ExitCode;
|
|||
/// A result that may contain any error.
|
||||
pub type ProcessResult<T = ()> = Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn report_err(e: Box<dyn std::error::Error>) {
|
||||
eprintln!("Unable to run command: {e}");
|
||||
let mut source = e.source();
|
||||
|
@ -102,10 +103,13 @@ pub trait Bin {
|
|||
|
||||
/// A Bin that doesn't take any arguments.
|
||||
pub struct ClosureBin<F: Fn() -> ProcessResult> {
|
||||
closure: F
|
||||
closure: F,
|
||||
}
|
||||
|
||||
impl<F> ClosureBin<F> where F: Fn() -> ProcessResult {
|
||||
impl<F> ClosureBin<F>
|
||||
where
|
||||
F: Fn() -> ProcessResult,
|
||||
{
|
||||
/// Create a new Bin from a closure.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -120,13 +124,14 @@ impl<F> ClosureBin<F> where F: Fn() -> ProcessResult {
|
|||
/// bin.main();
|
||||
/// ```
|
||||
pub fn new(closure: F) -> Self {
|
||||
Self {
|
||||
closure
|
||||
}
|
||||
Self { closure }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Bin for ClosureBin<F> where F: Fn() -> ProcessResult {
|
||||
impl<F> Bin for ClosureBin<F>
|
||||
where
|
||||
F: Fn() -> ProcessResult,
|
||||
{
|
||||
type Args = ();
|
||||
|
||||
fn validate_args(&self, _args: impl Iterator<Item = String>) -> ProcessResult<Self::Args> {
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.1"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -14,6 +14,9 @@ edition = "2021"
|
|||
rust-version = "1.58.0"
|
||||
# categories = ["command-line-interface", "command-line-utilities"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# [lib]
|
||||
# name = "crossterm"
|
||||
# path = "src/lib.rs"
|
||||
|
|
|
@ -61,6 +61,7 @@ impl Filter for EventFilter {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InternalEventFilter;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(missing_docs, clippy::missing_errors_doc, clippy::missing_panics_doc)]
|
||||
#![deny(unused_imports, unused_must_use)]
|
||||
#![allow(clippy::pedantic, clippy::all, unexpected_cfgs)]
|
||||
|
||||
//! # Cross-platform Terminal Manipulation Library
|
||||
//!
|
||||
|
|
|
@ -140,7 +140,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_attributes_const() {
|
||||
const ATTRIBUTES: Attributes = Attributes::none().with(Attribute::Bold).with(Attribute::Italic).without(Attribute::Bold);
|
||||
const ATTRIBUTES: Attributes = Attributes::none()
|
||||
.with(Attribute::Bold)
|
||||
.with(Attribute::Italic)
|
||||
.without(Attribute::Bold);
|
||||
assert!(!ATTRIBUTES.has(Attribute::Bold));
|
||||
assert!(ATTRIBUTES.has(Attribute::Italic));
|
||||
}
|
||||
|
|
|
@ -108,7 +108,10 @@ pub struct FdTerminal {
|
|||
stored_termios: Option<libc::termios>,
|
||||
}
|
||||
|
||||
impl<T> From<T> for FdTerminal where T: os::fd::AsRawFd {
|
||||
impl<T> From<T> for FdTerminal
|
||||
where
|
||||
T: os::fd::AsRawFd,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
fd: value.as_raw_fd(),
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.2"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -41,7 +41,7 @@ fn ensure_offline() {
|
|||
.to_str()
|
||||
.expect(bug!("Unable to decode UTF-8 filepath"))
|
||||
.split('/')
|
||||
.last()
|
||||
.next_back()
|
||||
.expect(bug!("No data in file path"))
|
||||
== "lo"
|
||||
{
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -6,6 +6,9 @@ repository = "https://git.distrust.co/public/keyfork"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["bin"]
|
||||
bin = ["smex"]
|
||||
|
|
|
@ -8,7 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
input.read_line(&mut line)?;
|
||||
let decoded = smex::decode(line.trim())?;
|
||||
|
||||
let mnemonic = Mnemonic::from_raw_bytes(&decoded) ;
|
||||
let mnemonic = Mnemonic::from_raw_bytes(&decoded);
|
||||
|
||||
println!("{mnemonic}");
|
||||
|
||||
|
|
|
@ -114,7 +114,9 @@ impl Wordlist for English {
|
|||
let mut words = wordlist_file.lines().skip(1).map(|x| x.trim().to_string());
|
||||
English {
|
||||
words: std::array::from_fn(|_| {
|
||||
words.next().expect(bug!("wordlist {} should have 2048 words"))
|
||||
words
|
||||
.next()
|
||||
.expect(bug!("wordlist {} should have 2048 words"))
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
@ -283,7 +285,7 @@ where
|
|||
return Err(MnemonicGenerationError::InvalidByteLength(bit_count));
|
||||
}
|
||||
|
||||
Ok( Self::from_raw_bytes(bytes) )
|
||||
Ok(Self::from_raw_bytes(bytes))
|
||||
}
|
||||
|
||||
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data may be of a size
|
||||
|
@ -380,12 +382,12 @@ where
|
|||
|
||||
/// A clone of the internal representation of the decoded data.
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.data.to_vec()
|
||||
self.data.clone()
|
||||
}
|
||||
|
||||
/// A clone of the internal representation of the decoded data.
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.data.to_vec()
|
||||
self.data.clone()
|
||||
}
|
||||
|
||||
/// Conver the Mnemonic into the internal representation of the decoded data.
|
||||
|
@ -419,8 +421,8 @@ where
|
|||
/// Create a BIP-0032 seed from the provided data and an optional passphrase.
|
||||
///
|
||||
/// # Panics
|
||||
/// The function may panic if the HmacSha512 function returns an error. The only error the
|
||||
/// HmacSha512 function should return is an invalid length, which should not be possible.
|
||||
/// The function may panic if the `HmacSha512` function returns an error. The only error the
|
||||
/// `HmacSha512` function should return is an invalid length, which should not be possible.
|
||||
pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> [u8; 64] {
|
||||
let passphrase = passphrase.into();
|
||||
|
||||
|
@ -603,7 +605,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "bytes.len() <= 1024")]
|
||||
fn fails_over_8192_bits() {
|
||||
let entropy = &mut [0u8; 1024 + 4];
|
||||
let mut random = std::fs::File::open("/dev/urandom").unwrap();
|
||||
|
@ -612,7 +614,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
#[should_panic(expected = "bytes.len() % 4 == 0")]
|
||||
fn fails_over_invalid_size() {
|
||||
let entropy = &mut [0u8; 255];
|
||||
let mut random = std::fs::File::open("/dev/urandom").unwrap();
|
||||
|
|
|
@ -6,6 +6,9 @@ repository = "https://git.distrust.co/public/keyfork"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
|
|
@ -60,11 +60,7 @@ impl PromptHandler for Headless {
|
|||
|
||||
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
|
||||
match prompt {
|
||||
Message::Text(s) => {
|
||||
writeln!(&mut self.stderr, "{s}")?;
|
||||
self.stderr.flush()?;
|
||||
}
|
||||
Message::Data(s) => {
|
||||
Message::Text(s) | Message::Data(s) => {
|
||||
writeln!(&mut self.stderr, "{s}")?;
|
||||
self.stderr.flush()?;
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ pub fn prompt_validated_passphrase<V>(
|
|||
/// * `KEYFORK_PROMPT_TYPE=headless`: [`Headless`]
|
||||
///
|
||||
/// Otherwise, the following heuristics are followed:
|
||||
/// * [`std::io::IsTerminal::is_terminal`]: [`DefaultTerminal`]
|
||||
/// * [`std::io::IsTerminal::is_terminal`][]: [`DefaultTerminal`]
|
||||
/// * default: [`Headless`]
|
||||
///
|
||||
/// # Errors
|
||||
|
|
|
@ -106,7 +106,7 @@ where
|
|||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.read.consume(amt)
|
||||
self.read.consume(amt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,11 +148,14 @@ where
|
|||
.queue(cursor::MoveTo(0, 0))
|
||||
.expect(bug!("can't move to origin"));
|
||||
}
|
||||
self.write.flush().expect(bug!("can't execute terminal reset commands"));
|
||||
self.write
|
||||
.flush()
|
||||
.expect(bug!("can't execute terminal reset commands"));
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler for a terminal.
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct Terminal<R, W> {
|
||||
read: BufReader<R>,
|
||||
write: W,
|
||||
|
@ -257,8 +260,11 @@ where
|
|||
let printable_space = (cols as usize) - prefix_length;
|
||||
input.len() - (printable_space - 1)
|
||||
};
|
||||
|
||||
terminal
|
||||
.queue(cursor::MoveToColumn(prefix_length as u16))?
|
||||
.queue(cursor::MoveToColumn(
|
||||
u16::try_from(prefix_length).unwrap_or(u16::MAX),
|
||||
))?
|
||||
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
|
||||
.queue(Print(&input[printable_start..]))?
|
||||
.flush()?;
|
||||
|
@ -349,12 +355,14 @@ where
|
|||
KeyCode::Left | KeyCode::Up => {
|
||||
active_choice = active_choice.saturating_sub(1);
|
||||
}
|
||||
KeyCode::Right | KeyCode::Down => match choices.len().saturating_sub(active_choice) {
|
||||
KeyCode::Right | KeyCode::Down => {
|
||||
match choices.len().saturating_sub(active_choice) {
|
||||
0 | 1 => {}
|
||||
_ => {
|
||||
active_choice += 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
return Ok(active_choice);
|
||||
}
|
||||
|
@ -384,9 +392,7 @@ where
|
|||
}
|
||||
Err(Error::Validation(
|
||||
retries,
|
||||
last_error
|
||||
.map(|e| e.to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string()),
|
||||
last_error.map_or_else(|| "Unknown".to_string(), |e| e.to_string()),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -455,7 +461,7 @@ where
|
|||
KeyCode::Char(c) => {
|
||||
input.push(c);
|
||||
let entry_mode = std::env::var("KEYFORK_PROMPT_MNEMONIC_MODE");
|
||||
if entry_mode.is_ok_and(|mode| mode.to_ascii_lowercase() == "steel") {
|
||||
if entry_mode.is_ok_and(|mode| mode.eq_ignore_ascii_case("steel")) {
|
||||
let word = input.split_whitespace().next_back().map(ToOwned::to_owned);
|
||||
if let Some(steel_word) = word {
|
||||
if steel_word.len() >= 4 {
|
||||
|
@ -476,10 +482,9 @@ where
|
|||
|
||||
let usable_space = cols as usize - prefix_length - 1;
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
terminal
|
||||
.queue(cursor::MoveToColumn(
|
||||
std::cmp::min(u16::MAX as usize, prefix_length) as u16,
|
||||
u16::try_from(prefix_length).unwrap_or(u16::MAX),
|
||||
))?
|
||||
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
|
||||
.flush()?;
|
||||
|
@ -547,9 +552,7 @@ where
|
|||
}
|
||||
Err(Error::Validation(
|
||||
retries,
|
||||
last_error
|
||||
.map(|e| e.to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string()),
|
||||
last_error.map_or_else(|| "Unknown".to_string(), |e| e.to_string()),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -628,8 +631,7 @@ where
|
|||
for line in text.lines() {
|
||||
let mut written_chars = 0;
|
||||
for word in line.split_whitespace() {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let len = std::cmp::min(u16::MAX as usize, word.len()) as u16;
|
||||
let len = u16::try_from(word.len()).unwrap_or(u16::MAX);
|
||||
written_chars += len + 1;
|
||||
if written_chars > cols {
|
||||
terminal.queue(cursor::MoveToNextLine(1))?;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Validator and parser types.
|
||||
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// A trait to create validator functions.
|
||||
|
@ -87,22 +88,20 @@ impl Validator for SecurePinValidator {
|
|||
if !range.contains(&ch) {
|
||||
return Err(Box::new(PinError::InvalidCharacters(ch, index)));
|
||||
}
|
||||
if [-1, 1].contains(&(ch as i32 - last_char))
|
||||
&& !ignore_sequential_characters
|
||||
{
|
||||
if [-1, 1].contains(&(ch as i32 - last_char)) && !ignore_sequential_characters {
|
||||
score += 1;
|
||||
}
|
||||
last_char = ch as i32;
|
||||
}
|
||||
let mut chars = s.chars().collect::<Vec<_>>();
|
||||
chars.sort();
|
||||
chars.sort_unstable();
|
||||
chars.dedup();
|
||||
if !ignore_repeated_characters {
|
||||
// SAFETY: the amount of characters can't have _increased_ since deduping
|
||||
score += s.chars().count() - chars.len();
|
||||
}
|
||||
if score * 2 > s.chars().count() {
|
||||
return Err(Box::new(PinError::InsecurePIN))
|
||||
return Err(Box::new(PinError::InsecurePIN));
|
||||
}
|
||||
Ok(s)
|
||||
})
|
||||
|
@ -204,19 +203,16 @@ pub mod mnemonic {
|
|||
|
||||
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Mnemonic, Box<dyn std::error::Error>>> {
|
||||
let word_length = self.word_length.clone();
|
||||
Box::new(move |s: String| match word_length.as_ref() {
|
||||
Some(wl) => {
|
||||
Box::new(move |s: String| if let Some(wl) = word_length.as_ref() {
|
||||
let count = s.split_whitespace().count();
|
||||
if !wl.matches(count) {
|
||||
return Err(Box::new(Self::Error::InvalidLength(count, wl.clone())));
|
||||
}
|
||||
let m = Mnemonic::from_str(&s)?;
|
||||
Ok(m)
|
||||
}
|
||||
None => {
|
||||
} else {
|
||||
let m = Mnemonic::from_str(&s)?;
|
||||
Ok(m)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,5 +4,8 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
smex = { workspace = true }
|
||||
|
|
|
@ -4,6 +4,9 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -76,6 +76,7 @@ ignore = [
|
|||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
|
||||
{ id = "RUSTSEC-2023-0071", reason = "Not applicable, vulnerable path is not used" },
|
||||
{ id = "RUSTSEC-2025-0011", reason = "No alternative available" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
|
@ -96,7 +97,7 @@ allow = [
|
|||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"CC0-1.0",
|
||||
"Unicode-DFS-2016",
|
||||
# "Unicode-DFS-2016",
|
||||
"LGPL-2.0",
|
||||
"LGPL-3.0",
|
||||
"Unicode-3.0",
|
||||
|
@ -115,6 +116,11 @@ exceptions = [
|
|||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
{ allow = ["BSL-1.0"], name = "xxhash-rust", version = "*" },
|
||||
{ allow = ["Zlib"], name = "foldhash", version = "*" },
|
||||
{ allow = ["AGPL-3.0"], name = "keyforkd", version = "*" },
|
||||
{ allow = ["AGPL-3.0"], name = "keyfork-shard", version = "*" },
|
||||
{ allow = ["AGPL-3.0"], name = "keyfork-derive-openpgp", version = "*" },
|
||||
{ allow = ["AGPL-3.0"], name = "keyfork-derive-key", version = "*" },
|
||||
{ allow = ["AGPL-3.0"], name = "keyfork", version = "*" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
|
|
Loading…
Reference in New Issue