Compare commits

..

No commits in common. "e7a776f59f675f11d617c9ae7a8e8c4ec5e04d2e" and "c6e274c4da0bf608f90a15aadeab2da53f29ffd8" have entirely different histories.

84 changed files with 924 additions and 1114 deletions

972
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,23 +26,6 @@ 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
@ -101,3 +84,4 @@ debug = true
[profile.dev.package.keyfork-qrcode] [profile.dev.package.keyfork-qrcode]
opt-level = 3 opt-level = 3
debug = true debug = true

View File

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

View File

@ -4,9 +4,6 @@ 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,10 +199,6 @@ 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),
@ -241,15 +237,19 @@ 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;
if let Some(occupied) = socket_vars.get("KEYFORKD_SOCKET_PATH") { #[allow(clippy::single_match_else)]
socket_path = PathBuf::from(occupied); match socket_vars.get("KEYFORKD_SOCKET_PATH") {
} else { Some(occupied) => {
socket_path = PathBuf::from( socket_path = PathBuf::from(occupied);
socket_vars }
.get("XDG_RUNTIME_DIR") None => {
.ok_or(Error::EnvVarsNotFound)?, socket_path = PathBuf::from(
); socket_vars
socket_path.extend(["keyforkd", "keyforkd.sock"]); .get("XDG_RUNTIME_DIR")
.ok_or(Error::EnvVarsNotFound)?,
);
socket_path.extend(["keyforkd", "keyforkd.sock"]);
}
} }
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path)) 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 = u8::try_from(path.len())?; let depth = path.len() as u8;
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().unwrap().remove("secp256k1").unwrap(); let tests = test_data()
.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( run_test(&seed, move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
&seed, for test in seed_test.tests {
move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> { let socket = UnixStream::connect(socket_path).unwrap();
for test in seed_test.tests { let mut client = Client::new(socket);
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 chain = DerivationPath::from_str(test.chain).unwrap(); let path = DerivationPath::from_str(test.chain).unwrap();
let chain_len = chain.len(); let left_path = path.inner()[..i]
if chain_len < 2 { .iter()
continue; .fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
} let right_path = path.inner()[i..]
if chain.iter().take(2).any(|index| !index.is_hardened()) { .iter()
continue; .fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
} let xprv = dbg!(client.request_xprv::<SecretKey>(&left_path)).unwrap();
// Consistency check: ensure the server and the client can each derive the same let derived_xprv = xprv.derive_path(&right_path).unwrap();
// key using an XPrv, for all but the last XPrv, which is verified after this let socket = UnixStream::connect(socket_path).unwrap();
for i in 2..chain_len { let mut client = Client::new(socket);
// FIXME: Keyfork will only allow one request per session let keyforkd_xprv = client.request_xprv::<SecretKey>(&path).unwrap();
let socket = UnixStream::connect(socket_path).unwrap(); assert_eq!(
let mut client = Client::new(socket); derived_xprv, keyforkd_xprv,
let path = DerivationPath::from_str(test.chain).unwrap(); "{left_path} + {right_path} != {path}"
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());
} }
Ok(()) 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());
}
Ok(())
})
.unwrap(); .unwrap();
} }
} }

View File

@ -4,9 +4,6 @@ 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,9 +4,6 @@ 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,23 +89,27 @@ 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 runtime_path = if let Some(occupied) = runtime_vars.get("KEYFORKD_SOCKET_PATH") { let mut runtime_path: PathBuf;
PathBuf::from(occupied) #[allow(clippy::single_match_else)]
} else { match runtime_vars.get("KEYFORKD_SOCKET_PATH") {
let mut runtime_path = PathBuf::from( Some(occupied) => {
runtime_vars runtime_path = PathBuf::from(occupied);
.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"); None => {
runtime_path runtime_path = PathBuf::from(
}; 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,9 +162,6 @@ mod tests {
.call(content.clone()) .call(content.clone())
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(result, serialize(&Result::<Test, Infallible>::Ok(test)).unwrap());
result,
serialize(&Result::<Test, Infallible>::Ok(test)).unwrap()
);
} }
} }

View File

@ -49,8 +49,7 @@ impl IsDisconnect for EncodeError {
} }
impl UnixServer { impl UnixServer {
/// Bind a socket to the given `address` and create a [`UnixServer`]. This function also /// 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.
/// 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,10 +39,7 @@ 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!( warn!("Entropy size is lower than 128 bits: {} bits.", seed.len() * 8);
"Entropy size is lower than 128 bits: {} bits.",
seed.len() * 8
);
} }
Self { Self {
seed: Arc::new(seed), seed: Arc::new(seed),
@ -80,11 +77,11 @@ impl Service<Request> for Keyforkd {
.iter() .iter()
.take(2) .take(2)
.enumerate() .enumerate()
.find(|(_, index)| !index.is_hardened()) .find(|(_, index)| {
!index.is_hardened()
})
{ {
return Err( return Err(DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into())
DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into(),
);
} }
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@ -114,7 +111,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn properly_derives_secp256k1() { async fn properly_derives_secp256k1() {
let tests = test_data().unwrap().remove("secp256k1").unwrap(); let tests = test_data()
.unwrap()
.remove("secp256k1")
.unwrap();
for per_seed in tests { for per_seed in tests {
let seed = &per_seed.seed; let seed = &per_seed.seed;
@ -174,7 +174,7 @@ mod tests {
} }
} }
#[should_panic(expected = "InvalidDerivationLength(0)")] #[should_panic]
#[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(expected = "InvalidDerivationLength(1)")] #[should_panic]
#[tokio::test] #[tokio::test]
async fn errors_on_short_path() { async fn errors_on_short_path() {
let tests = [( let tests = [(

View File

@ -89,11 +89,8 @@ 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.clone())); .service(Keyforkd::new(seed.to_vec()));
server server.run(service).await.expect(bug!("Unable to start service"));
.run(service)
.await
.expect(bug!("Unable to start service"));
} }
}); });

View File

@ -4,9 +4,6 @@ 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,9 +4,6 @@ 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,10 +46,7 @@ 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!( println!("{}", smex::encode(DerivationResponse::try_from(response)?.data));
"{}",
smex::encode(DerivationResponse::try_from(response)?.data)
);
Ok(()) Ok(())
} }

View File

@ -4,9 +4,6 @@ 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_path_data::paths;
use keyfork_derive_util::DerivationPath; use keyfork_derive_util::DerivationPath;
use keyfork_derive_path_data::paths;
use keyforkd_client::Client; use keyforkd_client::Client;
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
@ -82,10 +82,7 @@ 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!( assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
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!(
@ -120,7 +117,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,9 +4,6 @@ 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 std::sync::LazyLock; use once_cell::sync::Lazy;
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: LazyLock<DerivationPath> = LazyLock::new(|| { pub static OPENPGP: Lazy<DerivationPath> = Lazy::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: LazyLock<DerivationPath> = LazyLock::new(|| { pub static OPENPGP_SHARD: Lazy<DerivationPath> = Lazy::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: LazyLock<DerivationPath> = LazyLock::new(|| { pub static OPENPGP_DISASTER_RECOVERY: Lazy<DerivationPath> = Lazy::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,9 +4,6 @@ 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,7 +167,6 @@ 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())
} }
@ -190,10 +189,7 @@ where
} }
} }
assert!( assert!(has_any_nonzero, bug!("hmac function returned all-zero master key"));
has_any_nonzero,
bug!("hmac function returned all-zero master key")
);
Self::from_parts( Self::from_parts(
private_key private_key
@ -227,11 +223,13 @@ 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(Self { Ok(key) => {
private_key: key, Ok(Self {
depth, private_key: key,
chain_code, depth,
}), 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(expected = "IndexTooLarge")] #[should_panic]
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(expected = "IndexTooLarge")] #[should_panic]
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)] #![allow(clippy::module_name_repetitions, clippy::must_use_candidate)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
pub mod extended_key; pub mod extended_key;
@ -17,10 +17,7 @@ pub mod public_key;
mod tests; mod tests;
#[doc(inline)] #[doc(inline)]
pub use crate::extended_key::{ pub use crate::extended_key::{private_key::{ExtendedPrivateKey, Error as XPrvError, VariableLengthSeed}, public_key::{ExtendedPublicKey, Error as XPubError}};
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, /// The amount of segments in the DerivationPath. For consistency, a [`usize`] is returned, but
/// but BIP-0032 dictates that the depth should be no larger than `255`, [`u8::MAX`]. /// 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(expected = "UnknownPathPrefix")] #[should_panic]
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::{Scalar, Secp256k1}; use k256::{Secp256k1, Scalar};
// 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,7 +13,10 @@ use keyfork_slip10_test_data::{test_data, Test};
fn secp256k1() { fn secp256k1() {
use k256::SecretKey; use k256::SecretKey;
let tests = test_data().unwrap().remove("secp256k1").unwrap(); let tests = test_data()
.unwrap()
.remove("secp256k1")
.unwrap();
for per_seed in tests { for per_seed in tests {
let seed = &per_seed.seed; let seed = &per_seed.seed;
@ -102,7 +105,7 @@ fn ed25519() {
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
#[test] #[test]
#[should_panic(expected = "HardenedDerivationRequired")] #[should_panic]
fn panics_with_unhardened_derivation() { fn panics_with_unhardened_derivation() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
@ -114,7 +117,7 @@ fn panics_with_unhardened_derivation() {
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
#[test] #[test]
#[should_panic(expected = "Depth")] #[should_panic]
fn panics_at_depth() { fn panics_at_depth() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;

View File

@ -4,9 +4,6 @@ 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,11 +35,7 @@ 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( let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?;
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::{openpgp::OpenPGP, Format}; use keyfork_shard::{Format, openpgp::OpenPGP};
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,11 +35,7 @@ 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( openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?;
key_discovery.as_deref(),
messages_file,
prompt_handler,
)?;
Ok(()) Ok(())
} }

View File

@ -1,6 +1,9 @@
//! 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::{env, process::ExitCode}; use std::{
env,
process::ExitCode,
};
use keyfork_shard::remote_decrypt; use keyfork_shard::remote_decrypt;
@ -13,7 +16,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::{openpgp::OpenPGP, Format}; use keyfork_shard::{Format, openpgp::OpenPGP};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Error { enum Error {
@ -52,13 +52,7 @@ fn run() -> Result<()> {
let openpgp = OpenPGP; let openpgp = OpenPGP;
openpgp.shard_and_encrypt( openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
threshold,
max,
&input,
key_discovery.as_path(),
std::io::stdout(),
)?;
Ok(()) Ok(())
} }

View File

@ -1,4 +1,5 @@
#![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},
@ -91,10 +92,9 @@ 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 /// PrivateKeyData type of the asssociated format should be either `()` (if the keys may never
/// never exist on-system) or an empty container (such as an empty Vec); in either case, this /// exist on-system) or an empty container (such as an empty Vec); in either case, this method
/// method _must not_ return an error if keys are accessible but can't be transferred into /// _must not_ return an error if keys are accessible but can't be transferred into memory.
/// 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 /// will use the same key_data for both, ensuring an iteration of this method will match with
/// with iterations in methods called later. /// 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,7 +263,6 @@ 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>>,
@ -327,27 +326,41 @@ 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 = if let Some(pubkey) = pubkey_data { let their_pubkey = match pubkey_data {
pubkey Some(pubkey) => pubkey,
} else { None => {
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
@ -388,6 +401,7 @@ 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
@ -467,11 +481,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 as usize, max,
public_keys.len(), public_keys.len() as u8,
"max must be equal to amount of public keys" "max must be equal to amount of public keys"
); );
let max = u8::try_from(public_keys.len()).expect(bug!("invalid max: {max}", max = max)); let max = public_keys.len() as u8;
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)?;
@ -533,7 +547,6 @@ 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()?;
@ -614,29 +627,30 @@ 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) = if let Some((pubkey, payload)) = pubkey_data.zip(payload_data) { let (pubkey, payload) = match (pubkey_data, payload_data) {
(pubkey, payload) (Some(pubkey), Some(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!(
@ -663,13 +677,16 @@ 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");
if let Some(n) = &mut iter_count { match &mut iter_count {
// Must be > 0 to start loop, can't go lower Some(n) => {
*n -= 1; // Must be > 0 to start loop, can't go lower
} else { *n -= 1;
// NOTE: Should always be >= 1, < 256 due to Shamir constraints }
threshold = payload[1]; None => {
let _ = iter_count.insert(threshold - 1); // NOTE: Should always be >= 1, < 256 due to Shamir constraints
threshold = payload[1];
let _ = iter_count.insert(threshold - 1);
}
} }
let payload_len = payload.last().expect(bug!("payload should not be empty")); let payload_len = payload.last().expect(bug!("payload should not be empty"));

View File

@ -1,15 +1,16 @@
//! 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},
@ -24,7 +25,7 @@ use openpgp::{
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper}, stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
Parse, Parse,
}, },
policy::{NullPolicy, Policy, StandardPolicy}, policy::{NullPolicy, StandardPolicy, Policy},
serialize::{ serialize::{
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer}, stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
Marshal, Marshal,
@ -33,6 +34,7 @@ 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;
@ -92,7 +94,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),
@ -158,7 +160,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
@ -236,7 +238,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())
@ -263,7 +265,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,
) )
@ -448,8 +450,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();
@ -480,9 +482,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(
u8::try_from(left_from_threshold) left_from_threshold as u8,
.expect(bug!("threshold too large: {}", left_from_threshold)),
&mut messages, &mut messages,
&certs, &certs,
&policy, &policy,
@ -507,8 +509,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();
@ -555,8 +557,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,3 +1,5 @@
#![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};
@ -23,6 +25,8 @@ 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>,
@ -30,12 +34,12 @@ pub struct Keyring {
} }
impl Keyring { impl Keyring {
pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self { pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
Self { Ok(Self {
full_certs: certs.as_ref().to_vec(), full_certs: certs.as_ref().to_vec(),
root: Option::default(), root: Default::default(),
pm: p, pm: p,
} })
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {

View File

@ -1,3 +1,5 @@
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
rc::Rc, rc::Rc,
@ -79,13 +81,13 @@ pub struct SmartcardManager {
} }
impl SmartcardManager { impl SmartcardManager {
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self { pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
Self { Ok(Self {
current_card: None, current_card: None,
root: None, root: None,
pm: p, pm: p,
pin_cache: HashMap::default(), pin_cache: Default::default(),
} })
} }
// Sets the root cert, returning the old cert // Sets the root cert, returning the old cert
@ -175,9 +177,10 @@ 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()
.filter_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh)) .flat_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh))
.cloned() .cloned()
.collect()) .collect())
} }
@ -278,7 +281,8 @@ 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 {
Ok(()) => { #[allow(clippy::ignored_unit_patterns)]
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,9 +4,6 @@ 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(Self::default()); return Ok(Default::default())
} }
let values = s let values = s
.split(',') .split(',')

View File

@ -1,4 +1,4 @@
use super::{create, Keyfork}; use super::{Keyfork, create};
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 @ ..] => { [0, 0, 0, bytes @ ..] | [0, 0, bytes @ ..] | [0, bytes @ ..] | [bytes @ ..] => f
f.write_str(std::str::from_utf8(bytes).expect("slug constructed from non-utf8")) .write_str(
} 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,12 +213,15 @@ 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 = if let Some(writer) = writer { writer } else { let writer = match writer {
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); Some(w) => w,
let file = create(&path)?; None => {
Box::new(file) let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
let file = create(&path)?;
Box::new(file)
}
}; };
let mut writer = Writer::new(writer, Kind::SecretKey)?; let mut writer = Writer::new(writer, Kind::SecretKey)?;
for packet in cert.as_tsk().into_packets() { for packet in cert.as_tsk().into_packets() {
@ -228,12 +231,15 @@ 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 = if let Some(writer) = writer { writer } else { let writer = match writer {
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); Some(w) => w,
let file = create(&path)?; None => {
Box::new(file) let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
let file = create(&path)?;
Box::new(file)
}
}; };
let mut writer = Writer::new(writer, Kind::PublicKey)?; let mut writer = Writer::new(writer, Kind::PublicKey)?;
for packet in cert.into_packets2() { for packet in cert.into_packets2() {
@ -253,7 +259,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 => {
@ -272,7 +278,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,13 +1,14 @@
use super::{ use super::{
create, create,
derive::{self, Deriver}, derive::{self, Deriver},
provision, Keyfork, provision,
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, HashSet}, collections::HashMap,
fmt::Display, fmt::Display,
fs::File, fs::File,
io::{IsTerminal, Write}, io::{IsTerminal, Write},
@ -19,7 +20,7 @@ use keyfork_derive_openpgp::{
openpgp::{ openpgp::{
self, self,
armor::{Kind, Writer}, armor::{Kind, Writer},
packet::{signature::SignatureBuilder, UserID}, packet::{UserID, signature::SignatureBuilder},
policy::StandardPolicy, policy::StandardPolicy,
serialize::{ serialize::{
stream::{Encryptor2, LiteralWriter, Message, Recipient}, stream::{Encryptor2, LiteralWriter, Message, Recipient},
@ -164,7 +165,7 @@ pub enum Error {
MissingOption(&'static str), MissingOption(&'static str),
} }
fn context_stub(path: &Path) -> impl Fn(std::io::Error) -> Error + use<'_> { fn context_stub<'a>(path: &'a Path) -> impl Fn(std::io::Error) -> Error + 'a {
|e| Error::IOContext(e, path.to_path_buf()) |e| Error::IOContext(e, path.to_path_buf())
} }
@ -227,13 +228,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)]
@ -256,14 +257,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>>,
@ -302,23 +303,24 @@ fn determine_valid_output_path<T: AsRef<Path>>(
mid_ext: &str, mid_ext: &str,
optional_path: Option<T>, optional_path: Option<T>,
) -> PathBuf { ) -> PathBuf {
if let Some(p) = optional_path { match optional_path {
p.as_ref().to_path_buf() Some(p) => p.as_ref().to_path_buf(),
} else { None => {
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" | "gpg") => false, Some("pgp") | Some("gpg") => false,
Some("asc") => true, Some("asc") => true,
_ => { _ => {
eprintln!("unable to determine whether to armor file: {path}", path = path.display()); eprintln!("unable to determine whether to armor file: {path:?}");
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
@ -393,11 +395,8 @@ fn do_encrypt_to_self(
.clone() .clone()
.chain_push(account); .chain_push(account);
let cert = keyfork_derive_openpgp::derive( let cert =
&xprv.derive_path(&derivation_path)?, keyfork_derive_openpgp::derive(xprv.derive_path(&derivation_path)?, &subkeys, &userid)?;
&subkeys,
&userid,
)?;
certs.push(cert); certs.push(cert);
} }
@ -457,12 +456,15 @@ 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.into()); return Err(MissingThresholdOrMax)?;
} }
let (threshold, max) = if let Some(t) = threshold.zip(max) { t } else { let (threshold, max) = match threshold.zip(max) {
let len = u8::try_from(certs.len())?; Some(t) => t,
(len, len) None => {
let len = u8::try_from(certs.len())?;
(len, len)
}
}; };
let openpgp = keyfork_shard::openpgp::OpenPGP; let openpgp = keyfork_shard::openpgp::OpenPGP;
@ -542,14 +544,12 @@ 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 let path = keyfork_derive_path_data::paths::OPENPGP_SHARD.clone().chain_push(subkey);
.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,6 +611,7 @@ 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()?;
@ -625,8 +626,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 #{}",
(u16::from(i)) + 1, (i as u16) + 1,
(u16::from(index)) + 1, (index as u16) + 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()? {
@ -670,7 +671,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,
u8::try_from(certs.len()).expect("provided more than u8::MAX certs"), certs.len() as u8,
mnemonic.as_bytes(), mnemonic.as_bytes(),
&certs[..], &certs[..],
output, output,
@ -741,10 +742,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(())
@ -771,11 +772,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_key = root_xprv.derive_path(&opgp.derivation_path().chain_push(account))?; let derived = root_xprv.derive_path(&opgp.derivation_path().chain_push(account))?;
if *public { if *public {
opgp.derive_public_with_xprv(writer, &derived_key)?; opgp.derive_public_with_xprv(writer, derived)?;
} else { } else {
opgp.derive_with_xprv(writer, &derived_key)?; opgp.derive_with_xprv(writer, derived)?;
} }
} }
derive::Derive { derive::Derive {
@ -789,11 +790,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_key = root_xprv.derive_path(&key.derivation_path().chain_push(account))?; let derived = root_xprv.derive_path(&key.derivation_path().chain_push(account))?;
if *public { if *public {
key.derive_public_with_xprv(writer, &derived_key)?; key.derive_public_with_xprv(writer, derived)?;
} else { } else {
key.derive_with_xprv(writer, &derived_key)?; key.derive_with_xprv(writer, derived)?;
} }
} }
} }
@ -801,7 +802,6 @@ 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(Vec::is_empty); encrypt_to.is_none() || encrypt_to.as_ref().is_some_and(|e| e.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(Vec::is_empty); || shard_to.as_ref().is_some_and(|s| s.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(Vec::is_empty); || shard.as_ref().is_some_and(|s| s.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: HashSet<u32> = HashSet::default(); let mut accounts: std::collections::HashSet<u32> = Default::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,7 +20,6 @@ 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,
@ -96,7 +95,12 @@ 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(*shell, &mut command, command_name, &mut std::io::stdout()); clap_complete::generate(
*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,10 +94,7 @@ 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> {
@ -132,8 +129,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>>;
} }
@ -189,7 +186,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: Option::default(), metadata: Default::default(),
}) })
} }
} }
@ -210,20 +207,21 @@ impl Provision {
} }
} }
None => { None => {
let provisioner_with_identifier = if self.identifier.is_some() { let provisioner_with_identifier = match self.identifier {
self.clone() Some(_) => self.clone(),
} else { None => {
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,9 +22,7 @@ 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;
pub type CardList = Vec<(String, Option<String>)>; fn discover_cards() -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
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?;
@ -39,8 +37,8 @@ fn discover_cards() -> Result<CardList, Box<dyn std::error::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()?;
@ -59,24 +57,23 @@ 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, &subkeys, &userid)?; let cert = keyfork_derive_openpgp::derive(xprv.clone(), &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 = if let Some(cert_output) = let cert_output = match provisioner.metadata.as_ref().and_then(|m| m.get("output")) {
provisioner.metadata.as_ref().and_then(|m| m.get("output")) Some(cert_output) => PathBuf::from(cert_output),
{ None => {
PathBuf::from(cert_output) let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc");
} else { eprintln!(
let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); "Writing OpenPGP certificate to: {path}",
eprintln!( path = path.display()
"Writing OpenPGP certificate to: {path}", );
path = path.display() path
); }
path
}; };
let cert_output_file = std::fs::File::create(cert_output)?; let cert_output_file = std::fs::File::create(cert_output)?;
@ -103,7 +100,7 @@ fn provision_card(
} }
if !has_provisioned { if !has_provisioned {
return Err(NoMatchingSmartcard.into()); return Err(NoMatchingSmartcard)?;
} }
Ok(()) Ok(())
@ -125,8 +122,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)
} }
@ -148,8 +145,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,15 +109,14 @@ 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) = let (threshold, certs) = openpgp.decrypt_metadata_from_file(key_discovery, input, prompt)?;
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)?;
@ -188,7 +187,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 {
@ -257,11 +256,7 @@ impl ShardSubcommands {
None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), None => panic!("{COULD_NOT_DETERMINE_FORMAT}"),
} }
} }
ShardSubcommands::Metadata { ShardSubcommands::Metadata { shardfile, output_pubkeys, key_discovery } => {
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,4 +1,5 @@
#![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;
@ -7,9 +8,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,10 +34,11 @@ 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 {
@ -53,10 +54,11 @@ 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,9 +5,6 @@ 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,5 +1,7 @@
//! 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;
@ -37,10 +39,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,
}, },
@ -163,12 +165,13 @@ mod rqrr {
} }
} }
#[allow(dead_code, clippy::cast_precision_loss)] #[allow(dead_code)]
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,13 +5,10 @@ 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.70", default-features = false, features = ["runtime"] } bindgen = { version = "0.68", 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,5 +1,4 @@
#![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,9 +5,6 @@ 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::ImageReader; use image::io::Reader as ImageReader;
use v4l::{ use v4l::{
buffer::Type, buffer::Type,
io::{traits::CaptureStream, userptr::Stream}, io::{traits::CaptureStream, userptr::Stream},

View File

@ -20,13 +20,16 @@ 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 = std::os::raw::c_ulong::from(u32::from_le_bytes(*fourcc)); let fourcc: u64 = fourcc[0] as u64
| ((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) }
} }
@ -40,7 +43,12 @@ 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(self.inner, data.as_ptr().cast(), data.len() as u64, None); sys::zbar_image_set_data(
self.inner,
data.as_ptr().cast(),
data.len() as u64,
None,
)
} }
// keep data in self to avoid use after free when data goes out of scope // 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. /// Set a configuration option for the ImageScanner.
/// ///
/// Link: [`sys::zbar_image_scanner_set_config`] /// Link: [`sys::zbar_image_scanner_set_config`]
/// ///
@ -58,7 +58,10 @@ 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(&mut self, image: &Image) -> Vec<Symbol> { pub fn scan_image(
&mut self,
image: &Image,
) -> Vec<Symbol> {
unsafe { sys::zbar_scan_image(self.inner, image.inner) }; 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) };
@ -67,7 +70,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.cast::<u8>(), symbol_data_len as usize) std::slice::from_raw_parts(symbol_data as *const 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;
pub mod image_scanner; pub mod image_scanner;
pub mod image;
pub mod symbol; pub mod symbol;

View File

@ -1,7 +1,5 @@
//! 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,9 +5,6 @@ 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,5 +52,7 @@ 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");
} }
assert!(key_formats.is_empty(), "remaining key formats: {key_formats:?}"); if !key_formats.is_empty() {
panic!("remaining key formats: {key_formats:?}");
}
} }

View File

@ -4,9 +4,6 @@ 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,7 +45,6 @@ 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();
@ -103,13 +102,10 @@ 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> impl<F> ClosureBin<F> where F: Fn() -> ProcessResult {
where
F: Fn() -> ProcessResult,
{
/// Create a new Bin from a closure. /// Create a new Bin from a closure.
/// ///
/// # Examples /// # Examples
@ -124,14 +120,13 @@ where
/// bin.main(); /// bin.main();
/// ``` /// ```
pub fn new(closure: F) -> Self { pub fn new(closure: F) -> Self {
Self { closure } Self {
closure
}
} }
} }
impl<F> Bin for ClosureBin<F> impl<F> Bin for ClosureBin<F> where F: Fn() -> ProcessResult {
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,9 +4,6 @@ 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,9 +14,6 @@ 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

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

View File

@ -1,6 +1,5 @@
#![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,11 +140,8 @@ mod tests {
#[test] #[test]
fn test_attributes_const() { fn test_attributes_const() {
const ATTRIBUTES: Attributes = Attributes::none() const ATTRIBUTES: Attributes = Attributes::none().with(Attribute::Bold).with(Attribute::Italic).without(Attribute::Bold);
.with(Attribute::Bold) assert!(!ATTRIBUTES.has(Attribute::Bold));
.with(Attribute::Italic) assert!(ATTRIBUTES.has(Attribute::Italic));
.without(Attribute::Bold);
assert!(!ATTRIBUTES.has(Attribute::Bold));
assert!(ATTRIBUTES.has(Attribute::Italic));
} }
} }

View File

@ -108,10 +108,7 @@ pub struct FdTerminal {
stored_termios: Option<libc::termios>, stored_termios: Option<libc::termios>,
} }
impl<T> From<T> for FdTerminal impl<T> From<T> for FdTerminal where T: os::fd::AsRawFd {
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,9 +4,6 @@ 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('/')
.next_back() .last()
.expect(bug!("No data in file path")) .expect(bug!("No data in file path"))
== "lo" == "lo"
{ {

View File

@ -4,9 +4,6 @@ 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,9 +6,6 @@ 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,9 +114,7 @@ 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 words.next().expect(bug!("wordlist {} should have 2048 words"))
.next()
.expect(bug!("wordlist {} should have 2048 words"))
}), }),
} }
}) })
@ -285,7 +283,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
@ -382,12 +380,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.clone() self.data.to_vec()
} }
/// 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.clone() self.data.to_vec()
} }
/// Conver the Mnemonic into the internal representation of the decoded data. /// Conver the Mnemonic into the internal representation of the decoded data.
@ -421,8 +419,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();
@ -605,7 +603,7 @@ mod tests {
} }
#[test] #[test]
#[should_panic(expected = "bytes.len() <= 1024")] #[should_panic]
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();
@ -614,7 +612,7 @@ mod tests {
} }
#[test] #[test]
#[should_panic(expected = "bytes.len() % 4 == 0")] #[should_panic]
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,9 +6,6 @@ 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,7 +60,11 @@ 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::Data(s) => { Message::Text(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,14 +148,11 @@ 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 self.write.flush().expect(bug!("can't execute terminal reset commands"));
.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,
@ -260,11 +257,8 @@ 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( .queue(cursor::MoveToColumn(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))?
.queue(Print(&input[printable_start..]))? .queue(Print(&input[printable_start..]))?
.flush()?; .flush()?;
@ -355,14 +349,12 @@ 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 => { KeyCode::Right | KeyCode::Down => match choices.len().saturating_sub(active_choice) {
match choices.len().saturating_sub(active_choice) { 0 | 1 => {}
0 | 1 => {} _ => {
_ => { active_choice += 1;
active_choice += 1;
}
} }
} },
KeyCode::Enter => { KeyCode::Enter => {
return Ok(active_choice); return Ok(active_choice);
} }
@ -392,7 +384,9 @@ where
} }
Err(Error::Validation( Err(Error::Validation(
retries, retries,
last_error.map_or_else(|| "Unknown".to_string(), |e| e.to_string()), last_error
.map(|e| e.to_string())
.unwrap_or_else(|| "Unknown".to_string()),
)) ))
} }
@ -461,7 +455,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.eq_ignore_ascii_case("steel")) { if entry_mode.is_ok_and(|mode| mode.to_ascii_lowercase() == "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 {
@ -482,9 +476,10 @@ 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(
u16::try_from(prefix_length).unwrap_or(u16::MAX), std::cmp::min(u16::MAX as usize, prefix_length) as u16,
))? ))?
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))? .queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
.flush()?; .flush()?;
@ -552,7 +547,9 @@ where
} }
Err(Error::Validation( Err(Error::Validation(
retries, retries,
last_error.map_or_else(|| "Unknown".to_string(), |e| e.to_string()), last_error
.map(|e| e.to_string())
.unwrap_or_else(|| "Unknown".to_string()),
)) ))
} }
@ -631,7 +628,8 @@ 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() {
let len = u16::try_from(word.len()).unwrap_or(u16::MAX); #[allow(clippy::cast_possible_truncation)]
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,7 +1,6 @@
//! 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.
@ -88,20 +87,22 @@ 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)) && !ignore_sequential_characters { if [-1, 1].contains(&(ch as i32 - last_char))
&& !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_unstable(); chars.sort();
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)
}) })
@ -203,16 +204,19 @@ 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| if let Some(wl) = word_length.as_ref() { Box::new(move |s: String| match word_length.as_ref() {
let count = s.split_whitespace().count(); Some(wl) => {
if !wl.matches(count) { let count = s.split_whitespace().count();
return Err(Box::new(Self::Error::InvalidLength(count, wl.clone()))); if !wl.matches(count) {
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,8 +4,5 @@ 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,9 +4,6 @@ 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,7 +76,6 @@ 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.
@ -97,7 +96,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",
@ -116,11 +115,6 @@ 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,