Compare commits

...

5 Commits

84 changed files with 1116 additions and 926 deletions

976
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,23 @@ members = [
"crates/tests", "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] [workspace.dependencies]
# Keyfork dependencies # Keyfork dependencies
@ -84,4 +101,3 @@ debug = true
[profile.dev.package.keyfork-qrcode] [profile.dev.package.keyfork-qrcode]
opt-level = 3 opt-level = 3
debug = true debug = true

1
clippy.toml Normal file
View File

@ -0,0 +1 @@
doc-valid-idents = ["OpenPGP", ".."]

View File

@ -4,6 +4,9 @@ version = "0.2.2"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -26,7 +26,7 @@
//! //!
//! ### Request: Derive Key //! ### 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. //! (Extended Private Key) from the server.
//! //!
//! ```rust //! ```rust
@ -68,7 +68,7 @@
//! ## Extended Private Keys //! ## Extended Private Keys
//! //!
//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended //! 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 //! 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. //! will be derived similarly between the server and the client.
//! //!
@ -117,7 +117,7 @@
//! //!
//! ## Testing Infrastructure //! ## 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 //! 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 //! 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, //! 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)")] #[error("Socket was unable to connect to {1}: {0} (make sure keyforkd is running)")]
Connect(std::io::Error, PathBuf), 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. /// Data could not be written to, or read from, the socket.
#[error("Could not write to or from the socket: {0}")] #[error("Could not write to or from the socket: {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
@ -237,19 +241,15 @@ pub fn get_socket() -> Result<UnixStream, Error> {
.filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str())) .filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str()))
.collect::<HashMap<String, String>>(); .collect::<HashMap<String, String>>();
let mut socket_path: PathBuf; let mut socket_path: PathBuf;
#[allow(clippy::single_match_else)] if let Some(occupied) = socket_vars.get("KEYFORKD_SOCKET_PATH") {
match socket_vars.get("KEYFORKD_SOCKET_PATH") { socket_path = PathBuf::from(occupied);
Some(occupied) => { } else {
socket_path = PathBuf::from(occupied); socket_path = PathBuf::from(
} socket_vars
None => { .get("XDG_RUNTIME_DIR")
socket_path = PathBuf::from( .ok_or(Error::EnvVarsNotFound)?,
socket_vars );
.get("XDG_RUNTIME_DIR") socket_path.extend(["keyforkd", "keyforkd.sock"]);
.ok_or(Error::EnvVarsNotFound)?,
);
socket_path.extend(["keyforkd", "keyforkd.sock"]);
}
} }
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path)) UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
} }
@ -266,7 +266,7 @@ pub struct Client {
impl Client { impl Client {
/// Create a new client from a given already-connected [`UnixStream`]. This function is /// 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. /// [`Client::discover_socket`] should be preferred.
/// ///
/// # Examples /// # Examples
@ -344,7 +344,7 @@ impl Client {
return Err(Error::InvalidResponse); 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) ExtendedPrivateKey::from_parts(&d.data, depth, d.chain_code)
.map_err(|_| Error::InvalidKey) .map_err(|_| Error::InvalidKey)
} }

View File

@ -9,58 +9,58 @@ use std::{os::unix::net::UnixStream, str::FromStr};
fn secp256k1_test_suite() { fn secp256k1_test_suite() {
use k256::SecretKey; use k256::SecretKey;
let tests = test_data() let tests = test_data().unwrap().remove("secp256k1").unwrap();
.unwrap()
.remove("secp256k1")
.unwrap();
for seed_test in tests { for seed_test in tests {
let seed = seed_test.seed; let seed = seed_test.seed;
run_test(&seed, move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> { run_test(
for test in seed_test.tests { &seed,
let socket = UnixStream::connect(socket_path).unwrap(); move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
let mut client = Client::new(socket); for test in seed_test.tests {
let chain = DerivationPath::from_str(test.chain).unwrap();
let chain_len = chain.len();
if chain_len < 2 {
continue;
}
if chain.iter().take(2).any(|index| !index.is_hardened()) {
continue;
}
// Consistency check: ensure the server and the client can each derive the same
// key using an XPrv, for all but the last XPrv, which is verified after this
for i in 2..chain_len {
// FIXME: Keyfork will only allow one request per session
let socket = UnixStream::connect(socket_path).unwrap(); let socket = UnixStream::connect(socket_path).unwrap();
let mut client = Client::new(socket); let mut client = Client::new(socket);
let path = DerivationPath::from_str(test.chain).unwrap(); let chain = DerivationPath::from_str(test.chain).unwrap();
let left_path = path.inner()[..i] let chain_len = chain.len();
.iter() if chain_len < 2 {
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone())); continue;
let right_path = path.inner()[i..] }
.iter() if chain.iter().take(2).any(|index| !index.is_hardened()) {
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone())); continue;
let xprv = dbg!(client.request_xprv::<SecretKey>(&left_path)).unwrap(); }
let derived_xprv = xprv.derive_path(&right_path).unwrap(); // Consistency check: ensure the server and the client can each derive the same
let socket = UnixStream::connect(socket_path).unwrap(); // key using an XPrv, for all but the last XPrv, which is verified after this
let mut client = Client::new(socket); for i in 2..chain_len {
let keyforkd_xprv = client.request_xprv::<SecretKey>(&path).unwrap(); // FIXME: Keyfork will only allow one request per session
assert_eq!( let socket = UnixStream::connect(socket_path).unwrap();
derived_xprv, keyforkd_xprv, let mut client = Client::new(socket);
"{left_path} + {right_path} != {path}" let path = DerivationPath::from_str(test.chain).unwrap();
let left_path = path.inner()[..i]
.iter()
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
let right_path = path.inner()[i..]
.iter()
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
let xprv = dbg!(client.request_xprv::<SecretKey>(&left_path)).unwrap();
let derived_xprv = xprv.derive_path(&right_path).unwrap();
let socket = UnixStream::connect(socket_path).unwrap();
let mut client = Client::new(socket);
let keyforkd_xprv = client.request_xprv::<SecretKey>(&path).unwrap();
assert_eq!(
derived_xprv, keyforkd_xprv,
"{left_path} + {right_path} != {path}"
);
}
let req = DerivationRequest::new(
DerivationAlgorithm::Secp256k1,
&DerivationPath::from_str(test.chain).unwrap(),
); );
let response =
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
assert_eq!(&response.data, test.private_key.as_slice());
} }
let req = DerivationRequest::new( Ok(())
DerivationAlgorithm::Secp256k1, },
&DerivationPath::from_str(test.chain).unwrap(), )
);
let response =
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
assert_eq!(&response.data, test.private_key.as_slice());
}
Ok(())
})
.unwrap(); .unwrap();
} }
} }

View File

@ -4,6 +4,9 @@ version = "0.2.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -4,6 +4,9 @@ version = "0.1.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -89,27 +89,23 @@ pub async fn start_and_run_server(mnemonic: Mnemonic) -> Result<(), Box<dyn std:
let runtime_vars = std::env::vars() let runtime_vars = std::env::vars()
.filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str())) .filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str()))
.collect::<HashMap<String, String>>(); .collect::<HashMap<String, String>>();
let mut runtime_path: PathBuf; let runtime_path = if let Some(occupied) = runtime_vars.get("KEYFORKD_SOCKET_PATH") {
#[allow(clippy::single_match_else)] PathBuf::from(occupied)
match runtime_vars.get("KEYFORKD_SOCKET_PATH") { } else {
Some(occupied) => { let mut runtime_path = PathBuf::from(
runtime_path = PathBuf::from(occupied); runtime_vars
.get("XDG_RUNTIME_DIR")
.ok_or(KeyforkdError::NoSocketPath)?,
);
runtime_path.push("keyforkd");
#[cfg(feature = "tracing")]
debug!("ensuring directory exists: {}", runtime_path.display());
if !runtime_path.is_dir() {
tokio::fs::create_dir(&runtime_path).await?;
} }
None => { runtime_path.push("keyforkd.sock");
runtime_path = PathBuf::from( runtime_path
runtime_vars };
.get("XDG_RUNTIME_DIR")
.ok_or(KeyforkdError::NoSocketPath)?,
);
runtime_path.push("keyforkd");
#[cfg(feature = "tracing")]
debug!("ensuring directory exists: {}", runtime_path.display());
if !runtime_path.is_dir() {
tokio::fs::create_dir(&runtime_path).await?;
}
runtime_path.push("keyforkd.sock");
}
}
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
debug!( debug!(

View File

@ -162,6 +162,9 @@ mod tests {
.call(content.clone()) .call(content.clone())
.await .await
.unwrap(); .unwrap();
assert_eq!(result, serialize(&Result::<Test, Infallible>::Ok(test)).unwrap()); assert_eq!(
result,
serialize(&Result::<Test, Infallible>::Ok(test)).unwrap()
);
} }
} }

View File

@ -49,7 +49,8 @@ impl IsDisconnect for EncodeError {
} }
impl UnixServer { 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 /// # Errors
/// This function may return an error if the socket can't be bound. /// This function may return an error if the socket can't be bound.

View File

@ -39,7 +39,10 @@ impl Keyforkd {
/// Create a new instance of Keyfork from a given seed. /// Create a new instance of Keyfork from a given seed.
pub fn new(seed: Vec<u8>) -> Self { pub fn new(seed: Vec<u8>) -> Self {
if seed.len() < 16 { 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 { Self {
seed: Arc::new(seed), seed: Arc::new(seed),
@ -77,11 +80,11 @@ impl Service<Request> for Keyforkd {
.iter() .iter()
.take(2) .take(2)
.enumerate() .enumerate()
.find(|(_, index)| { .find(|(_, index)| !index.is_hardened())
!index.is_hardened()
})
{ {
return Err(DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into()) return Err(
DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into(),
);
} }
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@ -111,10 +114,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn properly_derives_secp256k1() { async fn properly_derives_secp256k1() {
let tests = test_data() let tests = test_data().unwrap().remove("secp256k1").unwrap();
.unwrap()
.remove("secp256k1")
.unwrap();
for per_seed in tests { for per_seed in tests {
let seed = &per_seed.seed; let seed = &per_seed.seed;
@ -174,7 +174,7 @@ mod tests {
} }
} }
#[should_panic] #[should_panic(expected = "InvalidDerivationLength(0)")]
#[tokio::test] #[tokio::test]
async fn errors_on_no_path() { async fn errors_on_no_path() {
let tests = [( let tests = [(
@ -200,7 +200,7 @@ mod tests {
} }
} }
#[should_panic] #[should_panic(expected = "InvalidDerivationLength(1)")]
#[tokio::test] #[tokio::test]
async fn errors_on_short_path() { async fn errors_on_short_path() {
let tests = [( let tests = [(

View File

@ -89,8 +89,11 @@ where
.expect(bug!("couldn't send server start signal")); .expect(bug!("couldn't send server start signal"));
let service = ServiceBuilder::new() let service = ServiceBuilder::new()
.layer(middleware::BincodeLayer::new()) .layer(middleware::BincodeLayer::new())
.service(Keyforkd::new(seed.to_vec())); .service(Keyforkd::new(seed.clone()));
server.run(service).await.expect(bug!("Unable to start service")); server
.run(service)
.await
.expect(bug!("Unable to start service"));
} }
}); });

View File

@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -4,6 +4,9 @@ version = "0.1.2"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -46,7 +46,10 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::discover_socket()?; let mut client = Client::discover_socket()?;
let request = DerivationRequest::new(algo, &path); let request = DerivationRequest::new(algo, &path);
let response = client.request(&request.into())?; let response = client.request(&request.into())?;
println!("{}", smex::encode(DerivationResponse::try_from(response)?.data)); println!(
"{}",
smex::encode(DerivationResponse::try_from(response)?.data)
);
Ok(()) Ok(())
} }

View File

@ -4,6 +4,9 @@ version = "0.1.5"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["bin"] default = ["bin"]

View File

@ -74,7 +74,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(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() { let primary_key_flags = match keys.first() {
Some(kf) if kf.for_certification() => kf, Some(kf) if kf.for_certification() => kf,
_ => return Err(Error::NotCert), _ => return Err(Error::NotCert),

View File

@ -2,8 +2,8 @@
use std::{env, process::ExitCode, str::FromStr}; use std::{env, process::ExitCode, str::FromStr};
use keyfork_derive_util::DerivationPath;
use keyfork_derive_path_data::paths; use keyfork_derive_path_data::paths;
use keyfork_derive_util::DerivationPath;
use keyforkd_client::Client; use keyforkd_client::Client;
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
@ -82,7 +82,10 @@ fn validate(
let index = paths::OPENPGP.inner().first().unwrap(); let index = paths::OPENPGP.inner().first().unwrap();
let path = DerivationPath::from_str(path)?; 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"); let given_index = path.iter().next().expect("checked .len() above");
assert_eq!( assert_eq!(
@ -117,7 +120,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
.map(|kt| kt.inner().clone()) .map(|kt| kt.inner().clone())
.collect::<Vec<_>>(); .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)?; let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;

View File

@ -4,6 +4,9 @@ version = "0.1.3"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -2,7 +2,7 @@
#![allow(clippy::unreadable_literal)] #![allow(clippy::unreadable_literal)]
use once_cell::sync::Lazy; use std::sync::LazyLock;
use keyfork_derive_util::{DerivationIndex, DerivationPath}; use keyfork_derive_util::{DerivationIndex, DerivationPath};
@ -11,7 +11,7 @@ pub mod paths {
use super::*; use super::*;
/// The default derivation path for OpenPGP. /// 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( DerivationPath::default().chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"), u32::from_be_bytes(*b"\x00pgp"),
true, true,
@ -19,7 +19,7 @@ pub mod paths {
}); });
/// The derivation path for OpenPGP certificates used for sharding. /// 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() DerivationPath::default()
.chain_push(DerivationIndex::new_unchecked( .chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"), u32::from_be_bytes(*b"\x00pgp"),
@ -32,7 +32,7 @@ pub mod paths {
}); });
/// The derivation path for OpenPGP certificates used for disaster recovery. /// 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() DerivationPath::default()
.chain_push(DerivationIndex::new_unchecked( .chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"), u32::from_be_bytes(*b"\x00pgp"),

View File

@ -4,6 +4,9 @@ version = "0.2.2"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -52,7 +52,7 @@ pub struct VariableLengthSeed<'a> {
} }
impl<'a> VariableLengthSeed<'a> { impl<'a> VariableLengthSeed<'a> {
/// Create a new VariableLengthSeed. /// Create a new `VariableLengthSeed`.
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
@ -167,6 +167,7 @@ where
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed); /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
/// ``` /// ```
#[allow(clippy::needless_pass_by_value)]
pub fn new(seed: impl as_private_key::AsPrivateKey) -> Result<Self> { pub fn new(seed: impl as_private_key::AsPrivateKey) -> Result<Self> {
Self::new_internal(seed.as_private_key()) 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( Self::from_parts(
private_key private_key
@ -223,13 +227,11 @@ where
/// ``` /// ```
pub fn from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Result<Self> { pub fn from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Result<Self> {
match K::from_bytes(key) { match K::from_bytes(key) {
Ok(key) => { Ok(key) => Ok(Self {
Ok(Self { private_key: key,
private_key: key, depth,
depth, chain_code,
chain_code, }),
})
}
Err(_) => Err(Error::InvalidKey), Err(_) => Err(Error::InvalidKey),
} }
} }

View File

@ -117,7 +117,7 @@ mod tests {
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
#[should_panic] #[should_panic(expected = "IndexTooLarge")]
fn fails_on_high_index() { fn fails_on_high_index() {
DerivationIndex::new(0x8000_0001, false).unwrap(); DerivationIndex::new(0x8000_0001, false).unwrap();
} }
@ -163,7 +163,7 @@ mod tests {
} }
#[test] #[test]
#[should_panic] #[should_panic(expected = "IndexTooLarge")]
fn from_str_fails_on_high_index() { fn from_str_fails_on_high_index() {
DerivationIndex::from_str(&0x8000_0001u32.to_string()).unwrap(); DerivationIndex::from_str(&0x8000_0001u32.to_string()).unwrap();
} }

View File

@ -1,4 +1,4 @@
#![allow(clippy::module_name_repetitions, clippy::must_use_candidate)] #![allow(clippy::module_name_repetitions)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
pub mod extended_key; pub mod extended_key;
@ -17,7 +17,10 @@ pub mod public_key;
mod tests; mod tests;
#[doc(inline)] #[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::{ pub use crate::{
index::{DerivationIndex, Error as IndexError}, index::{DerivationIndex, Error as IndexError},

View File

@ -11,7 +11,7 @@ pub enum Error {
/// The path could not be parsed due to a bad prefix. Paths must be in the format: /// 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 /// The prefix for the path must be `m`, and all indices must be integers between 0 and
/// 2^31. /// 2^31.
@ -35,8 +35,8 @@ impl DerivationPath {
self.path.iter() self.path.iter()
} }
/// The amount of segments in the DerivationPath. For consistency, a [`usize`] is returned, but /// The amount of segments in the [`DerivationPath`]. For consistency, a [`usize`] is returned,
/// BIP-0032 dictates that the depth should be no larger than `255`, [`u8::MAX`]. /// but BIP-0032 dictates that the depth should be no larger than `255`, [`u8::MAX`].
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.path.len() self.path.len()
} }
@ -134,7 +134,7 @@ mod tests {
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
#[should_panic] #[should_panic(expected = "UnknownPathPrefix")]
fn requires_master_path() { fn requires_master_path() {
DerivationPath::from_str("1234/5678'").unwrap(); DerivationPath::from_str("1234/5678'").unwrap();
} }

View File

@ -110,7 +110,7 @@ impl PublicKey for k256::PublicKey {
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self, Self::Err> { fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self, Self::Err> {
use k256::elliptic_curve::ScalarPrimitive; use k256::elliptic_curve::ScalarPrimitive;
use k256::{Secp256k1, Scalar}; use k256::{Scalar, Secp256k1};
// Construct a scalar from bytes // Construct a scalar from bytes
let scalar = ScalarPrimitive::<Secp256k1>::from_bytes(&other.into()); let scalar = ScalarPrimitive::<Secp256k1>::from_bytes(&other.into());
@ -156,7 +156,7 @@ pub struct TestPublicKey {
} }
impl TestPublicKey { impl TestPublicKey {
/// Create a new TestPublicKey from the given bytes. /// Create a new [`TestPublicKey`] from the given bytes.
#[allow(dead_code)] #[allow(dead_code)]
pub fn from_bytes(b: &[u8]) -> Self { pub fn from_bytes(b: &[u8]) -> Self {
Self { Self {

View File

@ -13,10 +13,7 @@ use keyfork_slip10_test_data::{test_data, Test};
fn secp256k1() { fn secp256k1() {
use k256::SecretKey; use k256::SecretKey;
let tests = test_data() let tests = test_data().unwrap().remove("secp256k1").unwrap();
.unwrap()
.remove("secp256k1")
.unwrap();
for per_seed in tests { for per_seed in tests {
let seed = &per_seed.seed; let seed = &per_seed.seed;
@ -105,7 +102,7 @@ fn ed25519() {
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
#[test] #[test]
#[should_panic] #[should_panic(expected = "HardenedDerivationRequired")]
fn panics_with_unhardened_derivation() { fn panics_with_unhardened_derivation() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
@ -117,7 +114,7 @@ fn panics_with_unhardened_derivation() {
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
#[test] #[test]
#[should_panic] #[should_panic(expected = "Depth")]
fn panics_at_depth() { fn panics_at_depth() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;

View File

@ -4,6 +4,9 @@ version = "0.3.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -35,7 +35,11 @@ fn run() -> Result<()> {
let openpgp = OpenPGP; let openpgp = OpenPGP;
let prompt_handler = default_handler()?; 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)); print!("{}", smex::encode(bytes));
Ok(()) Ok(())

View File

@ -8,7 +8,7 @@ use std::{
}; };
use keyfork_prompt::default_handler; 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>; 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 openpgp = OpenPGP;
let prompt_handler = default_handler()?; 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(()) Ok(())
} }

View File

@ -1,9 +1,6 @@
//! Combine OpenPGP shards using remote transport and output the hex-encoded secret. //! Combine OpenPGP shards using remote transport and output the hex-encoded secret.
use std::{ use std::{env, process::ExitCode};
env,
process::ExitCode,
};
use keyfork_shard::remote_decrypt; use keyfork_shard::remote_decrypt;
@ -16,7 +13,7 @@ fn run() -> Result<()> {
match args.as_slice() { match args.as_slice() {
[] => (), [] => (),
_ => panic!("Usage: {program_name}"), _ => panic!("Usage: {program_name}"),
}; }
let mut bytes = vec![]; let mut bytes = vec![];
remote_decrypt(&mut bytes)?; remote_decrypt(&mut bytes)?;

View File

@ -2,7 +2,7 @@
use std::{env, path::PathBuf, process::ExitCode, str::FromStr}; use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
use keyfork_shard::{Format, openpgp::OpenPGP}; use keyfork_shard::{openpgp::OpenPGP, Format};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Error { enum Error {
@ -52,7 +52,13 @@ fn run() -> Result<()> {
let openpgp = OpenPGP; 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(()) Ok(())
} }

View File

@ -1,5 +1,4 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
io::{Read, Write}, io::{Read, Write},
@ -92,9 +91,10 @@ pub trait KeyDiscovery<F: Format + ?Sized> {
/// # Errors /// # Errors
/// The method may return an error if private keys could not be loaded from the given /// 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 /// 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 /// `PrivateKeyData` type of the asssociated format should be either `()` (if the keys may
/// exist on-system) or an empty container (such as an empty Vec); in either case, this method /// never exist on-system) or an empty container (such as an empty Vec); in either case, this
/// _must not_ return an error if keys are accessible but can't be transferred into memory. /// 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>; 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 /// 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 /// 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 /// 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 /// will use the same `key_data` for both, ensuring an iteration of this method will match
/// iterations in methods called later. /// with iterations in methods called later.
/// ///
/// # Errors /// # Errors
/// The method may return an error if encryption to any of the public keys fails. /// 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 /// 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 /// 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. /// QR code; instead, a mnemonic prompt will be used.
#[allow(clippy::too_many_lines)]
fn decrypt_one_shard_for_transport( fn decrypt_one_shard_for_transport(
&self, &self,
private_key_discovery: Option<impl KeyDiscovery<Self>>, private_key_discovery: Option<impl KeyDiscovery<Self>>,
@ -326,41 +327,27 @@ pub trait Format {
if choice == RetryScanMnemonic::Continue { if choice == RetryScanMnemonic::Continue {
break; break;
} }
}; }
} }
} }
// if QR code scanning failed or was unavailable, read from a set of mnemonics // if QR code scanning failed or was unavailable, read from a set of mnemonics
let their_pubkey = match pubkey_data { let their_pubkey = if let Some(pubkey) = pubkey_data {
Some(pubkey) => pubkey, pubkey
None => { } else {
let validator = MnemonicValidator { let validator = MnemonicValidator {
word_length: Some(WordLength::Count(24)), word_length: Some(WordLength::Count(24)),
}; };
let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX)); let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX));
prompt_validated_wordlist::<English, _>( prompt_validated_wordlist::<English, _>(
&mut **prompt, &mut **prompt,
QRCODE_COULDNT_READ, QRCODE_COULDNT_READ,
3, 3,
&*validator.to_fn(), &*validator.to_fn(),
)? )?
.as_bytes() .as_bytes()
.try_into() .try_into()
.map_err(|_| InvalidData)? .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 // create our shared key
@ -401,7 +388,6 @@ pub trait Format {
// NOTE: Previous versions of Keyfork Shard would modify the padding bytes to avoid // 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 // duplicate mnemonic words. This version does not include that, and instead uses a
// repeated length byte. // repeated length byte.
#[allow(clippy::cast_possible_truncation)]
let mut plaintext_bytes = [u8::try_from(payload.len()).expect(bug!( let mut plaintext_bytes = [u8::try_from(payload.len()).expect(bug!(
"previously asserted length must be < {PLAINTEXT_LENGTH}", "previously asserted length must be < {PLAINTEXT_LENGTH}",
PLAINTEXT_LENGTH = PLAINTEXT_LENGTH PLAINTEXT_LENGTH = PLAINTEXT_LENGTH
@ -481,11 +467,11 @@ pub trait Format {
"must have less than u8::MAX public keys" "must have less than u8::MAX public keys"
); );
assert_eq!( assert_eq!(
max, max as usize,
public_keys.len() as u8, public_keys.len(),
"max must be equal to amount of public keys" "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"); assert!(max >= threshold, "threshold must not exceed max keys");
let header = self.format_encrypted_header(&signing_key, &public_keys, threshold)?; let header = self.format_encrypted_header(&signing_key, &public_keys, threshold)?;
@ -547,6 +533,7 @@ static QRCODE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
/// # Panics /// # Panics
/// The function may panic if it is given payloads generated using a version of Keyfork that is /// The function may panic if it is given payloads generated using a version of Keyfork that is
/// incompatible with the currently running version. /// 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>> { pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
let mut pm = keyfork_prompt::default_handler()?; let mut pm = keyfork_prompt::default_handler()?;
@ -627,30 +614,29 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
if choice == RetryScanMnemonic::Continue { if choice == RetryScanMnemonic::Continue {
break; break;
} }
}; }
} }
} }
let (pubkey, payload) = match (pubkey_data, payload_data) { let (pubkey, payload) = if let Some((pubkey, payload)) = pubkey_data.zip(payload_data) {
(Some(pubkey), Some(payload)) => (pubkey, payload), (pubkey, payload)
_ => { } else {
let validator = MnemonicSetValidator { let validator = MnemonicSetValidator {
word_lengths: [24, 39], word_lengths: [24, 39],
}; };
let [pubkey_mnemonic, payload_mnemonic] = prompt_validated_wordlist::<English, _>( let [pubkey_mnemonic, payload_mnemonic] = prompt_validated_wordlist::<English, _>(
&mut *pm, &mut *pm,
QRCODE_COULDNT_READ, QRCODE_COULDNT_READ,
3, 3,
&*validator.to_fn(), &*validator.to_fn(),
)?; )?;
let pubkey = pubkey_mnemonic let pubkey = pubkey_mnemonic
.as_bytes() .as_bytes()
.try_into() .try_into()
.map_err(|_| InvalidData)?; .map_err(|_| InvalidData)?;
let payload = payload_mnemonic.to_bytes(); let payload = payload_mnemonic.to_bytes();
(pubkey, payload) (pubkey, payload)
}
}; };
assert_eq!( assert_eq!(
@ -677,16 +663,13 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
let payload = shared_key.decrypt(nonce, payload.as_slice())?; let payload = shared_key.decrypt(nonce, payload.as_slice())?;
assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version"); assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version");
match &mut iter_count { if let Some(n) = &mut iter_count {
Some(n) => { // Must be > 0 to start loop, can't go lower
// Must be > 0 to start loop, can't go lower *n -= 1;
*n -= 1; } else {
} // NOTE: Should always be >= 1, < 256 due to Shamir constraints
None => { threshold = payload[1];
// NOTE: Should always be >= 1, < 256 due to Shamir constraints let _ = iter_count.insert(threshold - 1);
threshold = payload[1];
let _ = iter_count.insert(threshold - 1);
}
} }
let payload_len = payload.last().expect(bug!("payload should not be empty")); let payload_len = payload.last().expect(bug!("payload should not be empty"));

View File

@ -1,16 +1,15 @@
//! OpenPGP Shard functionality. //! OpenPGP Shard functionality.
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::{Read, Write}, io::{Read, Write},
path::Path, path::Path,
rc::Rc,
str::FromStr, str::FromStr,
sync::Mutex, sync::Mutex,
rc::Rc,
}; };
use blahaj::Share;
use keyfork_bug::bug; use keyfork_bug::bug;
use keyfork_derive_openpgp::{ use keyfork_derive_openpgp::{
derive_util::{DerivationPath, VariableLengthSeed}, derive_util::{DerivationPath, VariableLengthSeed},
@ -25,7 +24,7 @@ use openpgp::{
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper}, stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
Parse, Parse,
}, },
policy::{NullPolicy, StandardPolicy, Policy}, policy::{NullPolicy, Policy, StandardPolicy},
serialize::{ serialize::{
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer}, stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
Marshal, Marshal,
@ -34,7 +33,6 @@ use openpgp::{
KeyID, PacketPile, KeyID, PacketPile,
}; };
pub use sequoia_openpgp as openpgp; pub use sequoia_openpgp as openpgp;
use blahaj::Share;
mod keyring; mod keyring;
use keyring::Keyring; use keyring::Keyring;
@ -94,7 +92,7 @@ pub struct EncryptedMessage {
} }
impl 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 { pub fn new(pkesks: &mut Vec<PKESK>, seip: SEIP) -> Self {
Self { Self {
pkesks: std::mem::take(pkesks), pkesks: std::mem::take(pkesks),
@ -160,7 +158,7 @@ impl EncryptedMessage {
/// Decrypt the message with a Sequoia policy and decryptor. /// Decrypt the message with a Sequoia policy and decryptor.
/// ///
/// This method creates a container containing the packets and passes the serialized container /// 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 /// # Errors
/// The method may return an error if it is unable to rebuild the message to decrypt or if it /// 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 policy = StandardPolicy::new();
let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?; let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?;
if get_encryption_keys(&valid_cert).next().is_none() { 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()) Ok(certs.into_values().collect())
@ -265,7 +263,7 @@ impl Format for OpenPGP {
.derive_path(&path) .derive_path(&path)
.expect(bug!("valid derivation")); .expect(bug!("valid derivation"));
keyfork_derive_openpgp::derive( keyfork_derive_openpgp::derive(
xprv, &xprv,
&[KeyFlags::empty().set_certification().set_signing()], &[KeyFlags::empty().set_certification().set_signing()],
&userid, &userid,
) )
@ -450,8 +448,8 @@ impl Format for OpenPGP {
// We don't want to invalidate someone's keys just because the old sig expired. // We don't want to invalidate someone's keys just because the old sig expired.
let policy = NullPolicy::new(); let policy = NullPolicy::new();
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?; let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone());
let mut manager = SmartcardManager::new(prompt.clone())?; let mut manager = SmartcardManager::new(prompt.clone());
let mut encrypted_messages = encrypted_data.iter(); 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(); let left_from_threshold = threshold as usize - decrypted_messages.len();
if left_from_threshold > 0 { if left_from_threshold > 0 {
#[allow(clippy::cast_possible_truncation)]
let new_messages = decrypt_with_manager( 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, &mut messages,
&certs, &certs,
&policy, &policy,
@ -509,8 +507,8 @@ impl Format for OpenPGP {
) -> std::result::Result<(Share, u8), Self::Error> { ) -> std::result::Result<(Share, u8), Self::Error> {
let policy = NullPolicy::new(); let policy = NullPolicy::new();
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?; let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone());
let mut manager = SmartcardManager::new(prompt.clone())?; let mut manager = SmartcardManager::new(prompt.clone());
let mut encrypted_messages = encrypted_data.iter(); let mut encrypted_messages = encrypted_data.iter();
@ -557,8 +555,8 @@ impl Format for OpenPGP {
prompt: Rc<Mutex<Box<dyn PromptHandler>>>, prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
) -> std::result::Result<(u8, Vec<Self::PublicKey>), Self::Error> { ) -> std::result::Result<(u8, Vec<Self::PublicKey>), Self::Error> {
let policy = NullPolicy::new(); let policy = NullPolicy::new();
let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?; let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone());
let mut manager = SmartcardManager::new(prompt.clone())?; let mut manager = SmartcardManager::new(prompt.clone());
let mut encrypted_messages = encrypted_data.iter(); let mut encrypted_messages = encrypted_data.iter();
let metadata = encrypted_messages let metadata = encrypted_messages

View File

@ -1,5 +1,3 @@
#![allow(clippy::expect_fun_call)]
use std::{rc::Rc, sync::Mutex}; use std::{rc::Rc, sync::Mutex};
use keyfork_bug::{bug, POISONED_MUTEX}; use keyfork_bug::{bug, POISONED_MUTEX};
@ -25,8 +23,6 @@ pub enum Error {
Prompt(#[from] PromptError), Prompt(#[from] PromptError),
} }
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Keyring { pub struct Keyring {
full_certs: Vec<Cert>, full_certs: Vec<Cert>,
root: Option<Cert>, root: Option<Cert>,
@ -34,12 +30,12 @@ pub struct Keyring {
} }
impl Keyring { impl Keyring {
pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> { pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self {
Ok(Self { Self {
full_certs: certs.as_ref().to_vec(), full_certs: certs.as_ref().to_vec(),
root: Default::default(), root: Option::default(),
pm: p, pm: p,
}) }
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {

View File

@ -1,5 +1,3 @@
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
rc::Rc, rc::Rc,
@ -81,13 +79,13 @@ pub struct SmartcardManager {
} }
impl SmartcardManager { impl SmartcardManager {
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> { pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self {
Ok(Self { Self {
current_card: None, current_card: None,
root: None, root: None,
pm: p, pm: p,
pin_cache: Default::default(), pin_cache: HashMap::default(),
}) }
} }
// Sets the root cert, returning the old cert // Sets the root cert, returning the old cert
@ -177,10 +175,9 @@ impl SmartcardManager {
impl VerificationHelper for &mut SmartcardManager { impl VerificationHelper for &mut SmartcardManager {
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> { fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
#[allow(clippy::flat_map_option)]
Ok(ids Ok(ids
.iter() .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() .cloned()
.collect()) .collect())
} }
@ -281,8 +278,7 @@ impl DecryptionHelper for &mut SmartcardManager {
let temp_pin = prompt_validated_passphrase(&mut **prompt, &message, 3, &pin_validator)?; let temp_pin = prompt_validated_passphrase(&mut **prompt, &message, 3, &pin_validator)?;
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim()); let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
match verification_status { match verification_status {
#[allow(clippy::ignored_unit_patterns)] Ok(()) => {
Ok(_) => {
self.pin_cache.insert(fp.clone(), temp_pin.clone()); self.pin_cache.insert(fp.clone(), temp_pin.clone());
pin.replace(temp_pin); pin.replace(temp_pin);
} }

View File

@ -4,6 +4,9 @@ version = "0.3.3"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
[lints]
workspace = true
[features] [features]
default = [ default = [
"completion", "completion",

View File

@ -43,7 +43,7 @@ impl FromStr for Options {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() { if s.is_empty() {
return Ok(Default::default()) return Ok(Self::default());
} }
let values = s let values = s
.split(',') .split(',')

View File

@ -1,4 +1,4 @@
use super::{Keyfork, create}; use super::{create, Keyfork};
use clap::{Args, Parser, Subcommand, ValueEnum}; use clap::{Args, Parser, Subcommand, ValueEnum};
use std::{fmt::Display, io::Write, path::PathBuf}; use std::{fmt::Display, io::Write, path::PathBuf};
@ -26,9 +26,9 @@ pub trait Deriver {
fn derivation_path(&self) -> DerivationPath; 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)] #[derive(Subcommand, Clone, Debug)]
@ -138,12 +138,12 @@ impl std::str::FromStr for Slug {
impl Display for Slug { impl Display for Slug {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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() { match (self.0.inner() & (0b1 << 31)).to_be_bytes().as_slice() {
[0, 0, 0, 0] => Ok(()), [0, 0, 0, 0] => Ok(()),
[0, 0, 0, bytes @ ..] | [0, 0, bytes @ ..] | [0, bytes @ ..] | [bytes @ ..] => f [0, 0, 0, bytes @ ..] | [0, 0, bytes @ ..] | [0, bytes @ ..] | [bytes @ ..] => {
.write_str( f.write_str(std::str::from_utf8(bytes).expect("slug constructed from non-utf8"))
std::str::from_utf8(&bytes[..]).expect("slug constructed from non-utf8"), }
),
} }
} }
} }
@ -170,9 +170,9 @@ impl DeriveSubcommands {
let xprv = Client::discover_socket()? let xprv = Client::discover_socket()?
.request_xprv::<<OpenPGP as Deriver>::Prv>(&path.chain_push(account))?; .request_xprv::<<OpenPGP as Deriver>::Prv>(&path.chain_push(account))?;
if is_public { if is_public {
opgp.derive_public_with_xprv(writer, xprv) opgp.derive_public_with_xprv(writer, &xprv)
} else { } else {
opgp.derive_with_xprv(writer, xprv) opgp.derive_with_xprv(writer, &xprv)
} }
} }
DeriveSubcommands::Key(key) => { DeriveSubcommands::Key(key) => {
@ -180,9 +180,9 @@ impl DeriveSubcommands {
let xprv = Client::discover_socket()? let xprv = Client::discover_socket()?
.request_xprv::<<Key as Deriver>::Prv>(&path.chain_push(account))?; .request_xprv::<<Key as Deriver>::Prv>(&path.chain_push(account))?;
if is_public { if is_public {
key.derive_public_with_xprv(writer, xprv) key.derive_public_with_xprv(writer, &xprv)
} else { } else {
key.derive_with_xprv(writer, xprv) key.derive_with_xprv(writer, &xprv)
} }
} }
} }
@ -190,7 +190,7 @@ impl DeriveSubcommands {
} }
impl OpenPGP { 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![ let subkeys = vec![
KeyFlags::empty().set_certification(), KeyFlags::empty().set_certification(),
KeyFlags::empty().set_signing(), KeyFlags::empty().set_signing(),
@ -213,15 +213,12 @@ impl Deriver for OpenPGP {
self.derivation_path.derivation_path() 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 cert = self.cert_from_xprv(xprv)?;
let writer = match writer { let writer = if let Some(writer) = writer { writer } else {
Some(w) => w, let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
None => { let file = create(&path)?;
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); Box::new(file)
let file = create(&path)?;
Box::new(file)
}
}; };
let mut writer = Writer::new(writer, Kind::SecretKey)?; let mut writer = Writer::new(writer, Kind::SecretKey)?;
for packet in cert.as_tsk().into_packets() { for packet in cert.as_tsk().into_packets() {
@ -231,15 +228,12 @@ impl Deriver for OpenPGP {
Ok(()) 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 cert = self.cert_from_xprv(xprv)?;
let writer = match writer { let writer = if let Some(writer) = writer { writer } else {
Some(w) => w, let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
None => { let file = create(&path)?;
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); Box::new(file)
let file = create(&path)?;
Box::new(file)
}
}; };
let mut writer = Writer::new(writer, Kind::PublicKey)?; let mut writer = Writer::new(writer, Kind::PublicKey)?;
for packet in cert.into_packets2() { for packet in cert.into_packets2() {
@ -259,7 +253,7 @@ impl Deriver for Key {
DerivationPath::default().chain_push(self.slug.0.clone()) 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 { let (formatted, ext) = match self.format {
KeyFormat::Hex => (smex::encode(xprv.private_key().to_bytes()), "hex"), KeyFormat::Hex => (smex::encode(xprv.private_key().to_bytes()), "hex"),
KeyFormat::Base64 => { KeyFormat::Base64 => {
@ -278,7 +272,7 @@ impl Deriver for Key {
Ok(()) 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 { let (formatted, ext) = match self.format {
KeyFormat::Hex => (smex::encode(xprv.public_key().to_bytes()), "hex"), KeyFormat::Hex => (smex::encode(xprv.public_key().to_bytes()), "hex"),
KeyFormat::Base64 => { KeyFormat::Base64 => {

View File

@ -1,14 +1,13 @@
use super::{ use super::{
create, create,
derive::{self, Deriver}, derive::{self, Deriver},
provision, provision, Keyfork,
Keyfork,
}; };
use crate::{clap_ext::*, config, openpgp_card::factory_reset_current_card}; use crate::{clap_ext::*, config, openpgp_card::factory_reset_current_card};
use card_backend_pcsc::PcscBackend; use card_backend_pcsc::PcscBackend;
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum}; use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
fmt::Display, fmt::Display,
fs::File, fs::File,
io::{IsTerminal, Write}, io::{IsTerminal, Write},
@ -20,7 +19,7 @@ use keyfork_derive_openpgp::{
openpgp::{ openpgp::{
self, self,
armor::{Kind, Writer}, armor::{Kind, Writer},
packet::{UserID, signature::SignatureBuilder}, packet::{signature::SignatureBuilder, UserID},
policy::StandardPolicy, policy::StandardPolicy,
serialize::{ serialize::{
stream::{Encryptor2, LiteralWriter, Message, Recipient}, stream::{Encryptor2, LiteralWriter, Message, Recipient},
@ -165,7 +164,7 @@ pub enum Error {
MissingOption(&'static str), 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()) |e| Error::IOContext(e, path.to_path_buf())
} }
@ -228,13 +227,13 @@ pub enum MnemonicSubcommands {
/// ///
/// The following additional arguments are available: /// 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. /// 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`. /// 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 /// certificate input is `input.asc`, the generated Shardfile would be written to
/// `input.shard.asc`. /// `input.shard.asc`.
#[arg(long)] #[arg(long)]
@ -257,14 +256,14 @@ pub enum MnemonicSubcommands {
/// ///
/// The following additional arguments are required: /// 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. /// provided, files will be automatically generated for each certificate.
#[arg(long)] #[arg(long)]
shard_to_self: Option<ValueWithOptions<PathBuf>>, shard_to_self: Option<ValueWithOptions<PathBuf>>,
@ -303,24 +302,23 @@ fn determine_valid_output_path<T: AsRef<Path>>(
mid_ext: &str, mid_ext: &str,
optional_path: Option<T>, optional_path: Option<T>,
) -> PathBuf { ) -> PathBuf {
match optional_path { if let Some(p) = optional_path {
Some(p) => p.as_ref().to_path_buf(), p.as_ref().to_path_buf()
None => { } else {
let extension = match path.extension() { let extension = match path.extension() {
Some(ext) => format!("{mid_ext}.{ext}", ext = ext.to_string_lossy()), Some(ext) => format!("{mid_ext}.{ext}", ext = ext.to_string_lossy()),
None => format!("{mid_ext}.asc"), None => format!("{mid_ext}.asc"),
}; };
path.with_extension(extension) path.with_extension(extension)
}
} }
} }
fn is_extension_armored(path: &Path) -> bool { fn is_extension_armored(path: &Path) -> bool {
match path.extension().and_then(|s| s.to_str()) { match path.extension().and_then(|s| s.to_str()) {
Some("pgp") | Some("gpg") => false, Some("pgp" | "gpg") => false,
Some("asc") => true, 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!("use .gpg, .pgp, or .asc extension, or `armor=true`");
eprintln!("defaulting to armored"); eprintln!("defaulting to armored");
true true
@ -395,8 +393,11 @@ fn do_encrypt_to_self(
.clone() .clone()
.chain_push(account); .chain_push(account);
let cert = let cert = keyfork_derive_openpgp::derive(
keyfork_derive_openpgp::derive(xprv.derive_path(&derivation_path)?, &subkeys, &userid)?; &xprv.derive_path(&derivation_path)?,
&subkeys,
&userid,
)?;
certs.push(cert); certs.push(cert);
} }
@ -456,15 +457,12 @@ fn do_shard(
// if only one is set: true // if only one is set: true
if threshold.is_some() ^ max.is_some() { if threshold.is_some() ^ max.is_some() {
return Err(MissingThresholdOrMax)?; return Err(MissingThresholdOrMax.into());
} }
let (threshold, max) = match threshold.zip(max) { let (threshold, max) = if let Some(t) = threshold.zip(max) { t } else {
Some(t) => t, let len = u8::try_from(certs.len())?;
None => { (len, len)
let len = u8::try_from(certs.len())?;
(len, len)
}
}; };
let openpgp = keyfork_shard::openpgp::OpenPGP; 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 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) let xprv = XPrv::new(seed)
.expect("could not construct master key from seed") .expect("could not construct master key from seed")
.derive_path(&path)?; .derive_path(&path)?;
let userid = UserID::from(format!("Keyfork Shard {index}")); 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) Ok(cert)
} }
@ -611,7 +611,6 @@ fn do_shard_to_self(
.parse()?; .parse()?;
let cards_per_shard = options let cards_per_shard = options
.get("cards_per_shard") .get("cards_per_shard")
.as_deref()
.map(|cps| u8::from_str(cps)) .map(|cps| u8::from_str(cps))
.transpose()?; .transpose()?;
@ -626,8 +625,8 @@ fn do_shard_to_self(
for i in 0..cards_per_shard.unwrap_or(1) { for i in 0..cards_per_shard.unwrap_or(1) {
pm.prompt_message(keyfork_prompt::Message::Text(format!( pm.prompt_message(keyfork_prompt::Message::Text(format!(
"Please remove all keys and insert key #{} for user #{}", "Please remove all keys and insert key #{} for user #{}",
(i as u16) + 1, (u16::from(i)) + 1,
(index as u16) + 1, (u16::from(index)) + 1,
)))?; )))?;
let card_backend = loop { let card_backend = loop {
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? { if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
@ -671,7 +670,7 @@ fn do_shard_to_self(
let output = File::create(path)?; let output = File::create(path)?;
opgp.shard_and_encrypt( opgp.shard_and_encrypt(
threshold, threshold,
certs.len() as u8, u8::try_from(certs.len()).expect("provided more than u8::MAX certs"),
mnemonic.as_bytes(), mnemonic.as_bytes(),
&certs[..], &certs[..],
output, output,
@ -742,10 +741,10 @@ fn do_provision(
metadata @ None => { metadata @ None => {
*metadata = Some(config.clone()); *metadata = Some(config.clone());
} }
}; }
provision provision
.provisioner_name .provisioner_name
.provision_with_mnemonic(mnemonic, provisioner)?; .provision_with_mnemonic(mnemonic, &provisioner)?;
} }
Ok(()) Ok(())
@ -772,11 +771,11 @@ fn do_derive(
use keyfork_derive_openpgp::XPrv; use keyfork_derive_openpgp::XPrv;
let root_xprv = XPrv::new(mnemonic.generate_seed(None))?; let root_xprv = XPrv::new(mnemonic.generate_seed(None))?;
let account = DerivationIndex::new(*account_id, true)?; 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 { if *public {
opgp.derive_public_with_xprv(writer, derived)?; opgp.derive_public_with_xprv(writer, &derived_key)?;
} else { } else {
opgp.derive_with_xprv(writer, derived)?; opgp.derive_with_xprv(writer, &derived_key)?;
} }
} }
derive::Derive { derive::Derive {
@ -790,11 +789,11 @@ fn do_derive(
use keyfork_derive_openpgp::XPrv; use keyfork_derive_openpgp::XPrv;
let root_xprv = XPrv::new(mnemonic.generate_seed(None))?; let root_xprv = XPrv::new(mnemonic.generate_seed(None))?;
let account = DerivationIndex::new(*account_id, true)?; 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 { if *public {
key.derive_public_with_xprv(writer, derived)?; key.derive_public_with_xprv(writer, &derived_key)?;
} else { } else {
key.derive_with_xprv(writer, derived)?; key.derive_with_xprv(writer, &derived_key)?;
} }
} }
} }
@ -802,6 +801,7 @@ fn do_derive(
} }
impl MnemonicSubcommands { impl MnemonicSubcommands {
#[allow(clippy::too_many_lines)]
pub fn handle( pub fn handle(
&self, &self,
_m: &Mnemonic, _m: &Mnemonic,
@ -831,13 +831,13 @@ impl MnemonicSubcommands {
// * Sharding to existing, usable keys // * Sharding to existing, usable keys
// * Sharding to newly provisioned keys // * Sharding to newly provisioned keys
let mut will_print_mnemonic = 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 will_print_mnemonic = will_print_mnemonic
&& (encrypt_to_self.as_ref().is_none() || provision.as_ref().is_none()); && (encrypt_to_self.as_ref().is_none() || provision.as_ref().is_none());
will_print_mnemonic = will_print_mnemonic && shard_to.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() 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(); will_print_mnemonic = will_print_mnemonic && shard_to_self.is_none();
let mnemonic = source.handle(size)?; let mnemonic = source.handle(size)?;
@ -859,7 +859,7 @@ impl MnemonicSubcommands {
} }
if let Some(encrypt_to_self) = encrypt_to_self { 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 { if let Some(provision::Provision {
provisioner_name: provision::Provisioner::OpenPGPCard(_), provisioner_name: provision::Provisioner::OpenPGPCard(_),
account_id, account_id,
@ -884,7 +884,7 @@ impl MnemonicSubcommands {
!indices.is_empty(), !indices.is_empty(),
"neither derived nor provisioned accounts were found" "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 { if let Some(shard_to_self) = shard_to_self {
@ -938,7 +938,7 @@ impl MnemonicSubcommands {
} }
if will_print_mnemonic { if will_print_mnemonic {
println!("{}", mnemonic); println!("{mnemonic}");
} }
Ok(()) Ok(())
} }

View File

@ -7,7 +7,7 @@ mod recover;
mod shard; mod shard;
pub fn create(path: &std::path::Path) -> std::io::Result<std::fs::File> { 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) std::fs::File::create(path)
} }
@ -20,6 +20,7 @@ pub struct Keyfork {
pub command: KeyforkCommands, pub command: KeyforkCommands,
} }
#[allow(clippy::large_enum_variant)]
#[derive(Subcommand, Clone, Debug)] #[derive(Subcommand, Clone, Debug)]
pub enum KeyforkCommands { pub enum KeyforkCommands {
/// Derive keys of various formats. These commands require that the Keyfork server is running, /// Derive keys of various formats. These commands require that the Keyfork server is running,
@ -95,12 +96,7 @@ impl KeyforkCommands {
KeyforkCommands::Completion { shell } => { KeyforkCommands::Completion { shell } => {
let mut command = Keyfork::command(); let mut command = Keyfork::command();
let command_name = command.get_name().to_string(); let command_name = command.get_name().to_string();
clap_complete::generate( clap_complete::generate(*shell, &mut command, command_name, &mut std::io::stdout());
*shell,
&mut command,
command_name,
&mut std::io::stdout(),
);
} }
} }
Ok(()) Ok(())

View File

@ -38,7 +38,7 @@ impl Provisioner {
pub fn provision( pub fn provision(
&self, &self,
provisioner: config::Provisioner, provisioner: &config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
match self { match self {
Provisioner::OpenPGPCard(o) => { Provisioner::OpenPGPCard(o) => {
@ -49,7 +49,7 @@ impl Provisioner {
.chain_push(account_index); .chain_push(account_index);
let mut client = keyforkd_client::Client::discover_socket()?; let mut client = keyforkd_client::Client::discover_socket()?;
let xprv: XPrv = client.request_xprv(&path)?; let xprv: XPrv = client.request_xprv(&path)?;
o.provision(xprv, provisioner) o.provision(&xprv, provisioner)
} }
Provisioner::Shard(s) => { Provisioner::Shard(s) => {
type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey; type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey;
@ -59,7 +59,7 @@ impl Provisioner {
.chain_push(account_index); .chain_push(account_index);
let mut client = keyforkd_client::Client::discover_socket()?; let mut client = keyforkd_client::Client::discover_socket()?;
let xprv: XPrv = client.request_xprv(&path)?; 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( pub fn provision_with_mnemonic(
&self, &self,
mnemonic: &keyfork_mnemonic::Mnemonic, mnemonic: &keyfork_mnemonic::Mnemonic,
provisioner: config::Provisioner, provisioner: &config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
match self { match self {
Provisioner::OpenPGPCard(o) => { Provisioner::OpenPGPCard(o) => {
@ -77,7 +77,7 @@ impl Provisioner {
let path = <openpgp::OpenPGPCard as ProvisionExec>::derivation_prefix() let path = <openpgp::OpenPGPCard as ProvisionExec>::derivation_prefix()
.chain_push(account_index); .chain_push(account_index);
let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?; let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?;
o.provision(xprv, provisioner) o.provision(&xprv, provisioner)
} }
Provisioner::Shard(s) => { Provisioner::Shard(s) => {
type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey; type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey;
@ -86,7 +86,7 @@ impl Provisioner {
let path = <openpgp::Shard as ProvisionExec>::derivation_prefix() let path = <openpgp::Shard as ProvisionExec>::derivation_prefix()
.chain_push(account_index); .chain_push(account_index);
let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?; 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 { impl ValueEnum for Provisioner {
fn value_variants<'a>() -> &'a [Self] { 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> { fn to_possible_value(&self) -> Option<PossibleValue> {
@ -129,8 +132,8 @@ trait ProvisionExec {
/// Derive a key and deploy it to a target. /// Derive a key and deploy it to a target.
fn provision( fn provision(
&self, &self,
xprv: keyfork_derive_util::ExtendedPrivateKey<Self::PrivateKey>, xprv: &keyfork_derive_util::ExtendedPrivateKey<Self::PrivateKey>,
p: config::Provisioner, p: &config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>>; ) -> Result<(), Box<dyn std::error::Error>>;
} }
@ -186,7 +189,7 @@ impl TryFrom<Provision> for config::Provisioner {
Ok(Self { Ok(Self {
account: value.account_id, account: value.account_id,
identifier: value.identifier.ok_or(MissingField("identifier"))?, identifier: value.identifier.ok_or(MissingField("identifier"))?,
metadata: Default::default(), metadata: Option::default(),
}) })
} }
} }
@ -207,21 +210,20 @@ impl Provision {
} }
} }
None => { None => {
let provisioner_with_identifier = match self.identifier { let provisioner_with_identifier = if self.identifier.is_some() {
Some(_) => self.clone(), self.clone()
None => { } else {
let identifiers = self.provisioner_name.discover()?; let identifiers = self.provisioner_name.discover()?;
let [id] = &identifiers[..] else { let [id] = &identifiers[..] else {
panic!("invalid amount of identifiers; pass --identifier"); panic!("invalid amount of identifiers; pass --identifier");
}; };
Self { Self {
identifier: Some(id.0.clone()), identifier: Some(id.0.clone()),
..self.clone() ..self.clone()
}
} }
}; };
let config = config::Provisioner::try_from(provisioner_with_identifier)?; let config = config::Provisioner::try_from(provisioner_with_identifier)?;
self.provisioner_name.provision(config)?; self.provisioner_name.provision(&config)?;
} }
} }
Ok(()) Ok(())

View File

@ -22,7 +22,9 @@ use std::path::PathBuf;
#[error("Provisioner was unable to find a matching smartcard")] #[error("Provisioner was unable to find a matching smartcard")]
struct NoMatchingSmartcard; 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![]; let mut idents = vec![];
for backend in PcscBackend::cards(None)? { for backend in PcscBackend::cards(None)? {
let backend = backend?; let backend = backend?;
@ -37,8 +39,8 @@ fn discover_cards() -> Result<Vec<(String, Option<String>)>, Box<dyn std::error:
} }
fn provision_card( fn provision_card(
provisioner: config::Provisioner, provisioner: &config::Provisioner,
xprv: XPrv, xprv: &XPrv,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let mut pm = default_handler()?; let mut pm = default_handler()?;
@ -57,23 +59,24 @@ fn provision_card(
Some(userid) => UserID::from(userid.as_str()), Some(userid) => UserID::from(userid.as_str()),
None => UserID::from("Keyfork-Provisioned Key"), 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 if !provisioner
.metadata .metadata
.as_ref() .as_ref()
.is_some_and(|m| m.contains_key("_skip_cert_output")) .is_some_and(|m| m.contains_key("_skip_cert_output"))
{ {
let cert_output = match provisioner.metadata.as_ref().and_then(|m| m.get("output")) { let cert_output = if let Some(cert_output) =
Some(cert_output) => PathBuf::from(cert_output), provisioner.metadata.as_ref().and_then(|m| m.get("output"))
None => { {
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); PathBuf::from(cert_output)
eprintln!( } else {
"Writing OpenPGP certificate to: {path}", let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
path = path.display() eprintln!(
); "Writing OpenPGP certificate to: {path}",
path path = path.display()
} );
path
}; };
let cert_output_file = std::fs::File::create(cert_output)?; let cert_output_file = std::fs::File::create(cert_output)?;
@ -100,7 +103,7 @@ fn provision_card(
} }
if !has_provisioned { if !has_provisioned {
return Err(NoMatchingSmartcard)?; return Err(NoMatchingSmartcard.into());
} }
Ok(()) Ok(())
@ -122,8 +125,8 @@ impl ProvisionExec for OpenPGPCard {
fn provision( fn provision(
&self, &self,
xprv: XPrv, xprv: &XPrv,
provisioner: config::Provisioner, provisioner: &config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
provision_card(provisioner, xprv) provision_card(provisioner, xprv)
} }
@ -145,8 +148,8 @@ impl ProvisionExec for Shard {
fn provision( fn provision(
&self, &self,
xprv: XPrv, xprv: &XPrv,
provisioner: config::Provisioner, provisioner: &config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
provision_card(provisioner, xprv) provision_card(provisioner, xprv)
} }

View File

@ -1,10 +1,10 @@
use super::Keyfork; use super::Keyfork;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::path::PathBuf;
use nix::{ use nix::{
sys::wait::waitpid, sys::wait::waitpid,
unistd::{fork, ForkResult}, unistd::{fork, ForkResult},
}; };
use std::path::PathBuf;
use keyfork_mnemonic::{English, Mnemonic}; use keyfork_mnemonic::{English, Mnemonic};
use keyfork_prompt::{ use keyfork_prompt::{
@ -86,7 +86,7 @@ pub struct Recover {
command: RecoverSubcommands, command: RecoverSubcommands,
/// Daemonize the server once started, restoring control back to the shell. /// Daemonize the server once started, restoring control back to the shell.
#[arg(long, global=true)] #[arg(long, global = true)]
daemon: bool, daemon: bool,
} }
@ -102,12 +102,12 @@ impl Recover {
// wait for the child to die, so we don't exit prematurely // wait for the child to die, so we don't exit prematurely
waitpid(Some(child), None)?; waitpid(Some(child), None)?;
return Ok(()); return Ok(());
}, }
ForkResult::Child => { ForkResult::Child => {
if let ForkResult::Parent { .. } = unsafe { fork() }? { if let ForkResult::Parent { .. } = unsafe { fork() }? {
return Ok(()); return Ok(());
} }
}, }
} }
} }
tokio::runtime::Builder::new_multi_thread() tokio::runtime::Builder::new_multi_thread()

View File

@ -109,14 +109,15 @@ impl ShardExec for OpenPGP {
output: &mut impl Write, output: &mut impl Write,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
use keyfork_derive_openpgp::openpgp::{ use keyfork_derive_openpgp::openpgp::{
armor::{Kind, Writer},
serialize::Marshal, serialize::Marshal,
armor::{Writer, Kind},
}; };
let openpgp = keyfork_shard::openpgp::OpenPGP; let openpgp = keyfork_shard::openpgp::OpenPGP;
let prompt = default_handler()?; 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)?; let mut writer = Writer::new(output_pubkeys, Kind::PublicKey)?;
for cert in certs { for cert in certs {
cert.serialize(&mut writer)?; cert.serialize(&mut writer)?;
@ -187,7 +188,7 @@ pub enum ShardSubcommands {
/// The path to discover private keys from. /// The path to discover private keys from.
key_discovery: Option<PathBuf>, key_discovery: Option<PathBuf>,
} },
} }
impl ShardSubcommands { impl ShardSubcommands {
@ -256,7 +257,11 @@ impl ShardSubcommands {
None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), 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)?; let shard_content = std::fs::read_to_string(shardfile)?;
if shard_content.contains("BEGIN PGP MESSAGE") { if shard_content.contains("BEGIN PGP MESSAGE") {
let _ = format.insert(Format::OpenPGP(OpenPGP)); let _ = format.insert(Format::OpenPGP(OpenPGP));

View File

@ -1,5 +1,4 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
use std::process::ExitCode; use std::process::ExitCode;
@ -8,9 +7,9 @@ use clap::Parser;
use keyfork_bin::{Bin, ClosureBin}; use keyfork_bin::{Bin, ClosureBin};
pub mod clap_ext;
mod cli; mod cli;
mod config; mod config;
pub mod clap_ext;
mod openpgp_card; mod openpgp_card;
fn main() -> ExitCode { fn main() -> ExitCode {

View File

@ -34,11 +34,10 @@ pub fn get_new_pins(
3, 3,
&user_pin_validator, &user_pin_validator,
)?; )?;
if user_pin != validated_user_pin { if user_pin == validated_user_pin {
pm.prompt_message(Message::Text("User PINs did not match. Retrying.".into()))?;
} else {
break user_pin; break user_pin;
} }
pm.prompt_message(Message::Text("User PINs did not match. Retrying.".into()))?;
}; };
let admin_pin = loop { let admin_pin = loop {
@ -54,11 +53,10 @@ pub fn get_new_pins(
3, 3,
&admin_pin_validator, &admin_pin_validator,
)?; )?;
if admin_pin != validated_admin_pin { if admin_pin == validated_admin_pin {
pm.prompt_message(Message::Text("Admin PINs did not match. Retrying.".into()))?;
} else {
break admin_pin; break admin_pin;
} }
pm.prompt_message(Message::Text("Admin PINs did not match. Retrying.".into()))?;
}; };
Ok((user_pin, admin_pin)) Ok((user_pin, admin_pin))

View File

@ -5,6 +5,9 @@ repository = "https://git.distrust.co/public/keyfork"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -1,7 +1,5 @@
//! Encoding and decoding QR codes. //! Encoding and decoding QR codes.
#![allow(clippy::expect_fun_call)]
use keyfork_bug as bug; use keyfork_bug as bug;
use bug::POISONED_MUTEX; use bug::POISONED_MUTEX;
@ -39,10 +37,10 @@ pub enum QRCodeScanError {
/// The camera could not load the requested format. /// The camera could not load the requested format.
#[error("Camera could not use {expected} format, instead used {actual}")] #[error("Camera could not use {expected} format, instead used {actual}")]
CameraGaveBadFormat { CameraGaveBadFormat {
/// The expected format, in FourCC format. /// The expected format, in `FourCC` format.
expected: String, expected: String,
/// The actual format, in FourCC format. /// The actual format, in `FourCC` format.
actual: String, 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) { fn dbg_elapsed(count: u64, instant: Instant) {
let elapsed = instant.elapsed().as_secs(); let elapsed = instant.elapsed().as_secs();
let framerate = count as f64 / elapsed as f64; let framerate = count as f64 / elapsed as f64;
eprintln!("framerate: {count}/{elapsed} = {framerate}"); eprintln!("framerate: {count}/{elapsed} = {framerate}");
std::thread::sleep(std::time::Duration::from_secs(5)); std::thread::sleep(std::time::Duration::from_secs(5));
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -5,10 +5,13 @@ repository = "https://git.distrust.co/public/keyfork"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
[build-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" pkg-config = "0.3"

View File

@ -45,7 +45,7 @@ fn main() -> Result<()> {
if let Err(e) = generate_bindings_file() { if let Err(e) = generate_bindings_file() {
eprintln!("Building zbar-sys failed: {e}"); eprintln!("Building zbar-sys failed: {e}");
eprintln!("Ensure zbar headers, libclang, and pkg-config are installed"); eprintln!("Ensure zbar headers, libclang, and pkg-config are installed");
return Err(e) return Err(e);
} }
Ok(()) Ok(())

View File

@ -1,4 +1,5 @@
#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] #![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]
#![allow(missing_docs)] #![allow(missing_docs)]
#![allow(clippy::unreadable_literal, clippy::pub_underscore_fields)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs")); include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View File

@ -5,6 +5,9 @@ repository = "https://git.distrust.co/public/keyfork"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -7,7 +7,7 @@ use std::{
use keyfork_zbar::{image::Image, image_scanner::ImageScanner}; use keyfork_zbar::{image::Image, image_scanner::ImageScanner};
use image::io::Reader as ImageReader; use image::ImageReader;
use v4l::{ use v4l::{
buffer::Type, buffer::Type,
io::{traits::CaptureStream, userptr::Stream}, io::{traits::CaptureStream, userptr::Stream},

View File

@ -20,16 +20,13 @@ impl Image {
/// Link: [`sys::zbar_image_set_format`] /// 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 /// ```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 = std::os::raw::c_ulong::from(u32::from_le_bytes(*fourcc));
| ((fourcc[1] as u64) << 8)
| ((fourcc[2] as u64) << 16)
| ((fourcc[3] as u64) << 24);
unsafe { sys::zbar_image_set_format(self.inner, 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`] /// Accepts raw data in the configured format. See: [`Image::set_format`]
fn set_data(&mut self, data: Vec<u8>) { fn set_data(&mut self, data: Vec<u8>) {
unsafe { unsafe {
sys::zbar_image_set_data( sys::zbar_image_set_data(self.inner, data.as_ptr().cast(), data.len() as u64, None);
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 // keep data in self to avoid use after free when data goes out of scope
let _ = self.inner_data.insert(data); let _ = self.inner_data.insert(data);

View File

@ -22,7 +22,7 @@ pub struct ImageScanner {
} }
impl ImageScanner { impl ImageScanner {
/// create a new ImageScanner. /// Create a new `ImageScanner`.
/// ///
/// Link: [`sys::zbar_image_scanner_create`] /// Link: [`sys::zbar_image_scanner_create`]
pub fn new() -> Self { 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`] /// Link: [`sys::zbar_image_scanner_set_config`]
/// ///
@ -58,10 +58,7 @@ impl ImageScanner {
/// Link: [`sys::zbar_scan_image`] /// Link: [`sys::zbar_scan_image`]
/// ///
/// TODO: return an iterator over scanned values /// TODO: return an iterator over scanned values
pub fn scan_image( pub fn scan_image(&mut self, image: &Image) -> Vec<Symbol> {
&mut self,
image: &Image,
) -> Vec<Symbol> {
unsafe { sys::zbar_scan_image(self.inner, image.inner) }; unsafe { sys::zbar_scan_image(self.inner, image.inner) };
let mut result = vec![]; let mut result = vec![];
let mut symbol = unsafe { sys::zbar_image_first_symbol(image.inner) }; 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 = unsafe { sys::zbar_symbol_get_data(symbol) };
let symbol_data_len = unsafe { sys::zbar_symbol_get_data_length(symbol) }; let symbol_data_len = unsafe { sys::zbar_symbol_get_data_length(symbol) };
let symbol_slice = unsafe { 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)); result.push(Symbol::new(symbol_type, symbol_slice));
symbol = unsafe { sys::zbar_symbol_next(symbol) }; symbol = unsafe { sys::zbar_symbol_next(symbol) };

View File

@ -11,6 +11,6 @@ pub use sys::zbar_config_e as Config;
pub use sys::zbar_modifier_e as Modifier; pub use sys::zbar_modifier_e as Modifier;
pub use sys::zbar_orientation_e as Orientation; pub use sys::zbar_orientation_e as Orientation;
pub mod image_scanner;
pub mod image; pub mod image;
pub mod image_scanner;
pub mod symbol; pub mod symbol;

View File

@ -1,5 +1,7 @@
//! A Symbol represents some form of encoded data. //! A Symbol represents some form of encoded data.
#![allow(clippy::used_underscore_binding)]
use super::sys; use super::sys;
/// The type of symbol (i.e. what type of barcode or QR code). /// The type of symbol (i.e. what type of barcode or QR code).

View File

@ -5,6 +5,9 @@ edition = "2021"
publish = false publish = false
license = "MIT" license = "MIT"
[lints]
workspace = true
[dependencies] [dependencies]
assert_cmd = "2.0.16" assert_cmd = "2.0.16"
keyforkd = { workspace = true, features = ["default"] } keyforkd = { workspace = true, features = ["default"] }

View File

@ -52,7 +52,5 @@ fn test() {
key.alive().expect("is live after being generated"); key.alive().expect("is live after being generated");
key.parts_into_secret().expect("has secret keys"); key.parts_into_secret().expect("has secret keys");
} }
if !key_formats.is_empty() { assert!(key_formats.is_empty(), "remaining key formats: {key_formats:?}");
panic!("remaining key formats: {key_formats:?}");
}
} }

View File

@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -45,6 +45,7 @@ use std::process::ExitCode;
/// A result that may contain any error. /// A result that may contain any error.
pub type ProcessResult<T = ()> = Result<T, Box<dyn std::error::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>) { fn report_err(e: Box<dyn std::error::Error>) {
eprintln!("Unable to run command: {e}"); eprintln!("Unable to run command: {e}");
let mut source = e.source(); let mut source = e.source();
@ -68,7 +69,7 @@ pub trait Bin {
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
fn validate_args(&self, args: impl Iterator<Item = String>) -> ProcessResult<Self::Args>; fn validate_args(&self, args: impl Iterator<Item = String>) -> ProcessResult<Self::Args>;
/// Run the binary /// Run the binary
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
fn run(&self, args: Self::Args) -> ProcessResult; fn run(&self, args: Self::Args) -> ProcessResult;
@ -102,10 +103,13 @@ pub trait Bin {
/// A Bin that doesn't take any arguments. /// A Bin that doesn't take any arguments.
pub struct ClosureBin<F: Fn() -> ProcessResult> { 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. /// Create a new Bin from a closure.
/// ///
/// # Examples /// # Examples
@ -120,13 +124,14 @@ impl<F> ClosureBin<F> where F: Fn() -> ProcessResult {
/// bin.main(); /// bin.main();
/// ``` /// ```
pub fn new(closure: F) -> Self { pub fn new(closure: F) -> Self {
Self { Self { closure }
closure
}
} }
} }
impl<F> Bin for ClosureBin<F> where F: Fn() -> ProcessResult { impl<F> Bin for ClosureBin<F>
where
F: Fn() -> ProcessResult,
{
type Args = (); type Args = ();
fn validate_args(&self, _args: impl Iterator<Item = String>) -> ProcessResult<Self::Args> { fn validate_args(&self, _args: impl Iterator<Item = String>) -> ProcessResult<Self::Args> {

View File

@ -4,6 +4,9 @@ version = "0.1.1"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -14,6 +14,9 @@ edition = "2021"
rust-version = "1.58.0" rust-version = "1.58.0"
# categories = ["command-line-interface", "command-line-utilities"] # categories = ["command-line-interface", "command-line-utilities"]
[lints]
workspace = true
# [lib] # [lib]
# name = "crossterm" # name = "crossterm"
# path = "src/lib.rs" # path = "src/lib.rs"

View File

@ -10,4 +10,4 @@ readme = "README.md"
publish = false publish = false
[dependencies] [dependencies]
crossterm = { path = "../../" } crossterm = { path = "../../" }

View File

@ -61,6 +61,7 @@ impl Filter for EventFilter {
} }
} }
#[allow(dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct InternalEventFilter; pub(crate) struct InternalEventFilter;

View File

@ -1,5 +1,6 @@
#![allow(missing_docs, clippy::missing_errors_doc, clippy::missing_panics_doc)] #![allow(missing_docs, clippy::missing_errors_doc, clippy::missing_panics_doc)]
#![deny(unused_imports, unused_must_use)] #![deny(unused_imports, unused_must_use)]
#![allow(clippy::pedantic, clippy::all, unexpected_cfgs)]
//! # Cross-platform Terminal Manipulation Library //! # Cross-platform Terminal Manipulation Library
//! //!

View File

@ -140,8 +140,11 @@ mod tests {
#[test] #[test]
fn test_attributes_const() { fn test_attributes_const() {
const ATTRIBUTES: Attributes = Attributes::none().with(Attribute::Bold).with(Attribute::Italic).without(Attribute::Bold); const ATTRIBUTES: Attributes = Attributes::none()
assert!(!ATTRIBUTES.has(Attribute::Bold)); .with(Attribute::Bold)
assert!(ATTRIBUTES.has(Attribute::Italic)); .with(Attribute::Italic)
.without(Attribute::Bold);
assert!(!ATTRIBUTES.has(Attribute::Bold));
assert!(ATTRIBUTES.has(Attribute::Italic));
} }
} }

View File

@ -108,7 +108,10 @@ pub struct FdTerminal {
stored_termios: Option<libc::termios>, 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 { fn from(value: T) -> Self {
Self { Self {
fd: value.as_raw_fd(), fd: value.as_raw_fd(),

View File

@ -4,6 +4,9 @@ version = "0.1.2"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -41,7 +41,7 @@ fn ensure_offline() {
.to_str() .to_str()
.expect(bug!("Unable to decode UTF-8 filepath")) .expect(bug!("Unable to decode UTF-8 filepath"))
.split('/') .split('/')
.last() .next_back()
.expect(bug!("No data in file path")) .expect(bug!("No data in file path"))
== "lo" == "lo"
{ {

View File

@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -6,6 +6,9 @@ repository = "https://git.distrust.co/public/keyfork"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
[features] [features]
default = ["bin"] default = ["bin"]
bin = ["smex"] bin = ["smex"]

View File

@ -8,7 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
input.read_line(&mut line)?; input.read_line(&mut line)?;
let decoded = smex::decode(line.trim())?; let decoded = smex::decode(line.trim())?;
let mnemonic = Mnemonic::from_raw_bytes(&decoded) ; let mnemonic = Mnemonic::from_raw_bytes(&decoded);
println!("{mnemonic}"); println!("{mnemonic}");

View File

@ -114,7 +114,9 @@ impl Wordlist for English {
let mut words = wordlist_file.lines().skip(1).map(|x| x.trim().to_string()); let mut words = wordlist_file.lines().skip(1).map(|x| x.trim().to_string());
English { English {
words: std::array::from_fn(|_| { 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)); 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 /// 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. /// A clone of the internal representation of the decoded data.
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
self.data.to_vec() self.data.clone()
} }
/// A clone of the internal representation of the decoded data. /// A clone of the internal representation of the decoded data.
pub fn to_vec(&self) -> Vec<u8> { 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. /// 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. /// Create a BIP-0032 seed from the provided data and an optional passphrase.
/// ///
/// # Panics /// # Panics
/// The function may panic if the HmacSha512 function returns an error. The only error the /// 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. /// `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] { pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> [u8; 64] {
let passphrase = passphrase.into(); let passphrase = passphrase.into();
@ -603,7 +605,7 @@ mod tests {
} }
#[test] #[test]
#[should_panic] #[should_panic(expected = "bytes.len() <= 1024")]
fn fails_over_8192_bits() { fn fails_over_8192_bits() {
let entropy = &mut [0u8; 1024 + 4]; let entropy = &mut [0u8; 1024 + 4];
let mut random = std::fs::File::open("/dev/urandom").unwrap(); let mut random = std::fs::File::open("/dev/urandom").unwrap();
@ -612,7 +614,7 @@ mod tests {
} }
#[test] #[test]
#[should_panic] #[should_panic(expected = "bytes.len() % 4 == 0")]
fn fails_over_invalid_size() { fn fails_over_invalid_size() {
let entropy = &mut [0u8; 255]; let entropy = &mut [0u8; 255];
let mut random = std::fs::File::open("/dev/urandom").unwrap(); let mut random = std::fs::File::open("/dev/urandom").unwrap();

View File

@ -6,6 +6,9 @@ repository = "https://git.distrust.co/public/keyfork"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -60,11 +60,7 @@ impl PromptHandler for Headless {
fn prompt_message(&mut self, prompt: Message) -> Result<()> { fn prompt_message(&mut self, prompt: Message) -> Result<()> {
match prompt { match prompt {
Message::Text(s) => { Message::Text(s) | Message::Data(s) => {
writeln!(&mut self.stderr, "{s}")?;
self.stderr.flush()?;
}
Message::Data(s) => {
writeln!(&mut self.stderr, "{s}")?; writeln!(&mut self.stderr, "{s}")?;
self.stderr.flush()?; self.stderr.flush()?;
} }

View File

@ -269,7 +269,7 @@ pub fn prompt_validated_passphrase<V>(
/// * `KEYFORK_PROMPT_TYPE=headless`: [`Headless`] /// * `KEYFORK_PROMPT_TYPE=headless`: [`Headless`]
/// ///
/// Otherwise, the following heuristics are followed: /// Otherwise, the following heuristics are followed:
/// * [`std::io::IsTerminal::is_terminal`]: [`DefaultTerminal`] /// * [`std::io::IsTerminal::is_terminal`][]: [`DefaultTerminal`]
/// * default: [`Headless`] /// * default: [`Headless`]
/// ///
/// # Errors /// # Errors

View File

@ -106,7 +106,7 @@ where
} }
fn consume(&mut self, amt: usize) { fn consume(&mut self, amt: usize) {
self.read.consume(amt) self.read.consume(amt);
} }
} }
@ -148,11 +148,14 @@ where
.queue(cursor::MoveTo(0, 0)) .queue(cursor::MoveTo(0, 0))
.expect(bug!("can't move to origin")); .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. /// A handler for a terminal.
#[allow(clippy::struct_field_names)]
pub struct Terminal<R, W> { pub struct Terminal<R, W> {
read: BufReader<R>, read: BufReader<R>,
write: W, write: W,
@ -257,8 +260,11 @@ where
let printable_space = (cols as usize) - prefix_length; let printable_space = (cols as usize) - prefix_length;
input.len() - (printable_space - 1) input.len() - (printable_space - 1)
}; };
terminal 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(terminal::Clear(terminal::ClearType::UntilNewLine))?
.queue(Print(&input[printable_start..]))? .queue(Print(&input[printable_start..]))?
.flush()?; .flush()?;
@ -349,12 +355,14 @@ where
KeyCode::Left | KeyCode::Up => { KeyCode::Left | KeyCode::Up => {
active_choice = active_choice.saturating_sub(1); active_choice = active_choice.saturating_sub(1);
} }
KeyCode::Right | KeyCode::Down => match choices.len().saturating_sub(active_choice) { KeyCode::Right | KeyCode::Down => {
0 | 1 => {} match choices.len().saturating_sub(active_choice) {
_ => { 0 | 1 => {}
active_choice += 1; _ => {
active_choice += 1;
}
} }
}, }
KeyCode::Enter => { KeyCode::Enter => {
return Ok(active_choice); return Ok(active_choice);
} }
@ -384,9 +392,7 @@ where
} }
Err(Error::Validation( Err(Error::Validation(
retries, retries,
last_error last_error.map_or_else(|| "Unknown".to_string(), |e| e.to_string()),
.map(|e| e.to_string())
.unwrap_or_else(|| "Unknown".to_string()),
)) ))
} }
@ -455,7 +461,7 @@ where
KeyCode::Char(c) => { KeyCode::Char(c) => {
input.push(c); input.push(c);
let entry_mode = std::env::var("KEYFORK_PROMPT_MNEMONIC_MODE"); 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); let word = input.split_whitespace().next_back().map(ToOwned::to_owned);
if let Some(steel_word) = word { if let Some(steel_word) = word {
if steel_word.len() >= 4 { if steel_word.len() >= 4 {
@ -476,10 +482,9 @@ where
let usable_space = cols as usize - prefix_length - 1; let usable_space = cols as usize - prefix_length - 1;
#[allow(clippy::cast_possible_truncation)]
terminal terminal
.queue(cursor::MoveToColumn( .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))? .queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
.flush()?; .flush()?;
@ -547,9 +552,7 @@ where
} }
Err(Error::Validation( Err(Error::Validation(
retries, retries,
last_error last_error.map_or_else(|| "Unknown".to_string(), |e| e.to_string()),
.map(|e| e.to_string())
.unwrap_or_else(|| "Unknown".to_string()),
)) ))
} }
@ -628,8 +631,7 @@ where
for line in text.lines() { for line in text.lines() {
let mut written_chars = 0; let mut written_chars = 0;
for word in line.split_whitespace() { for word in line.split_whitespace() {
#[allow(clippy::cast_possible_truncation)] let len = u16::try_from(word.len()).unwrap_or(u16::MAX);
let len = std::cmp::min(u16::MAX as usize, word.len()) as u16;
written_chars += len + 1; written_chars += len + 1;
if written_chars > cols { if written_chars > cols {
terminal.queue(cursor::MoveToNextLine(1))?; terminal.queue(cursor::MoveToNextLine(1))?;

View File

@ -1,6 +1,7 @@
//! Validator and parser types. //! Validator and parser types.
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
/// A trait to create validator functions. /// A trait to create validator functions.
@ -87,22 +88,20 @@ impl Validator for SecurePinValidator {
if !range.contains(&ch) { if !range.contains(&ch) {
return Err(Box::new(PinError::InvalidCharacters(ch, index))); return Err(Box::new(PinError::InvalidCharacters(ch, index)));
} }
if [-1, 1].contains(&(ch as i32 - last_char)) if [-1, 1].contains(&(ch as i32 - last_char)) && !ignore_sequential_characters {
&& !ignore_sequential_characters
{
score += 1; score += 1;
} }
last_char = ch as i32; last_char = ch as i32;
} }
let mut chars = s.chars().collect::<Vec<_>>(); let mut chars = s.chars().collect::<Vec<_>>();
chars.sort(); chars.sort_unstable();
chars.dedup(); chars.dedup();
if !ignore_repeated_characters { if !ignore_repeated_characters {
// SAFETY: the amount of characters can't have _increased_ since deduping // SAFETY: the amount of characters can't have _increased_ since deduping
score += s.chars().count() - chars.len(); score += s.chars().count() - chars.len();
} }
if score * 2 > s.chars().count() { if score * 2 > s.chars().count() {
return Err(Box::new(PinError::InsecurePIN)) return Err(Box::new(PinError::InsecurePIN));
} }
Ok(s) Ok(s)
}) })
@ -204,19 +203,16 @@ pub mod mnemonic {
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Mnemonic, Box<dyn std::error::Error>>> { fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Mnemonic, Box<dyn std::error::Error>>> {
let word_length = self.word_length.clone(); let word_length = self.word_length.clone();
Box::new(move |s: String| match word_length.as_ref() { Box::new(move |s: String| if let Some(wl) = word_length.as_ref() {
Some(wl) => { let count = s.split_whitespace().count();
let count = s.split_whitespace().count(); if !wl.matches(count) {
if !wl.matches(count) { return Err(Box::new(Self::Error::InvalidLength(count, wl.clone())));
return Err(Box::new(Self::Error::InvalidLength(count, wl.clone())));
}
let m = Mnemonic::from_str(&s)?;
Ok(m)
}
None => {
let m = Mnemonic::from_str(&s)?;
Ok(m)
} }
let m = Mnemonic::from_str(&s)?;
Ok(m)
} else {
let m = Mnemonic::from_str(&s)?;
Ok(m)
}) })
} }
} }

View File

@ -4,5 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
[dependencies] [dependencies]
smex = { workspace = true } smex = { workspace = true }

View File

@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -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" }, #{ 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-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 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. # If this is false, then it uses a built-in git library.
@ -96,7 +97,7 @@ allow = [
"BSD-3-Clause", "BSD-3-Clause",
"ISC", "ISC",
"CC0-1.0", "CC0-1.0",
"Unicode-DFS-2016", # "Unicode-DFS-2016",
"LGPL-2.0", "LGPL-2.0",
"LGPL-3.0", "LGPL-3.0",
"Unicode-3.0", "Unicode-3.0",
@ -115,6 +116,11 @@ exceptions = [
#{ allow = ["Zlib"], crate = "adler32" }, #{ allow = ["Zlib"], crate = "adler32" },
{ allow = ["BSL-1.0"], name = "xxhash-rust", version = "*" }, { allow = ["BSL-1.0"], name = "xxhash-rust", version = "*" },
{ allow = ["Zlib"], name = "foldhash", 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, # Some crates don't have (easily) machine readable licensing information,