Compare commits

...

5 Commits

84 changed files with 1116 additions and 926 deletions

976
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,23 @@ members = [
"crates/tests",
]
[workspace.lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "warn", priority = -1 }
# used often in tests
wildcard_imports = { level = "allow"}
# annoying
must_use_candidate = "allow"
return_self_not_must_use = "allow"
# sometimes i like the logical flow of keeping things in an "else"
redundant_else = "allow"
# i hate using `.unwrap_or_else(|| keyfork_bug::bug!())`
expect_fun_call = "allow"
[workspace.dependencies]
# Keyfork dependencies
@ -84,4 +101,3 @@ debug = true
[profile.dev.package.keyfork-qrcode]
opt-level = 3
debug = true

1
clippy.toml Normal file
View File

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

View File

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

View File

@ -26,7 +26,7 @@
//!
//! ### Request: Derive Key
//!
//! The client creates a derivation path of at least two indices and requests a derived XPrv
//! The client creates a derivation path of at least two indices and requests a derived `XPrv`
//! (Extended Private Key) from the server.
//!
//! ```rust
@ -68,7 +68,7 @@
//! ## Extended Private Keys
//!
//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended
//! Private Key (often shortened to "XPrv") has been created, further derivations can be performed.
//! Private Key (often shortened to `XPrv`) has been created, further derivations can be performed.
//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two
//! will be derived similarly between the server and the client.
//!
@ -117,7 +117,7 @@
//!
//! ## Testing Infrastructure
//!
//! In tests, the `keyforkd::test_util` module and TestPrivateKeys can be used. These provide
//! In tests, the `keyforkd::test_util` module and `TestPrivateKeys` can be used. These provide
//! useful utilities for writing tests that interact with the Keyfork Server without needing to
//! manually create the server for the purpose of the test. The `run_test` method can be used to
//! run a test, which can handle both returning errors and correctly translating panics (though,
@ -199,6 +199,10 @@ pub enum Error {
#[error("Socket was unable to connect to {1}: {0} (make sure keyforkd is running)")]
Connect(std::io::Error, PathBuf),
/// The path of the derived key was of an invalid length.
#[error("Derived key path is of invalid length")]
InvalidPathLength(#[from] std::num::TryFromIntError),
/// Data could not be written to, or read from, the socket.
#[error("Could not write to or from the socket: {0}")]
Io(#[from] std::io::Error),
@ -237,19 +241,15 @@ pub fn get_socket() -> Result<UnixStream, Error> {
.filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str()))
.collect::<HashMap<String, String>>();
let mut socket_path: PathBuf;
#[allow(clippy::single_match_else)]
match socket_vars.get("KEYFORKD_SOCKET_PATH") {
Some(occupied) => {
socket_path = PathBuf::from(occupied);
}
None => {
socket_path = PathBuf::from(
socket_vars
.get("XDG_RUNTIME_DIR")
.ok_or(Error::EnvVarsNotFound)?,
);
socket_path.extend(["keyforkd", "keyforkd.sock"]);
}
if let Some(occupied) = socket_vars.get("KEYFORKD_SOCKET_PATH") {
socket_path = PathBuf::from(occupied);
} else {
socket_path = PathBuf::from(
socket_vars
.get("XDG_RUNTIME_DIR")
.ok_or(Error::EnvVarsNotFound)?,
);
socket_path.extend(["keyforkd", "keyforkd.sock"]);
}
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
}
@ -266,7 +266,7 @@ pub struct Client {
impl Client {
/// Create a new client from a given already-connected [`UnixStream`]. This function is
/// provided in case a specific UnixStream has to be used; otherwise,
/// provided in case a specific `UnixStream` has to be used; otherwise,
/// [`Client::discover_socket`] should be preferred.
///
/// # Examples
@ -344,7 +344,7 @@ impl Client {
return Err(Error::InvalidResponse);
}
let depth = path.len() as u8;
let depth = u8::try_from(path.len())?;
ExtendedPrivateKey::from_parts(&d.data, depth, d.chain_code)
.map_err(|_| Error::InvalidKey)
}

View File

@ -9,58 +9,58 @@ use std::{os::unix::net::UnixStream, str::FromStr};
fn secp256k1_test_suite() {
use k256::SecretKey;
let tests = test_data()
.unwrap()
.remove("secp256k1")
.unwrap();
let tests = test_data().unwrap().remove("secp256k1").unwrap();
for seed_test in tests {
let seed = seed_test.seed;
run_test(&seed, move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
for test in seed_test.tests {
let socket = UnixStream::connect(socket_path).unwrap();
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
run_test(
&seed,
move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
for test in seed_test.tests {
let socket = UnixStream::connect(socket_path).unwrap();
let mut client = Client::new(socket);
let path = DerivationPath::from_str(test.chain).unwrap();
let left_path = path.inner()[..i]
.iter()
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
let right_path = path.inner()[i..]
.iter()
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
let xprv = dbg!(client.request_xprv::<SecretKey>(&left_path)).unwrap();
let derived_xprv = xprv.derive_path(&right_path).unwrap();
let socket = UnixStream::connect(socket_path).unwrap();
let mut client = Client::new(socket);
let keyforkd_xprv = client.request_xprv::<SecretKey>(&path).unwrap();
assert_eq!(
derived_xprv, keyforkd_xprv,
"{left_path} + {right_path} != {path}"
let 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 mut client = Client::new(socket);
let path = DerivationPath::from_str(test.chain).unwrap();
let left_path = path.inner()[..i]
.iter()
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
let right_path = path.inner()[i..]
.iter()
.fold(DerivationPath::default(), |p, i| p.chain_push(i.clone()));
let xprv = dbg!(client.request_xprv::<SecretKey>(&left_path)).unwrap();
let derived_xprv = xprv.derive_path(&right_path).unwrap();
let socket = UnixStream::connect(socket_path).unwrap();
let mut client = Client::new(socket);
let keyforkd_xprv = client.request_xprv::<SecretKey>(&path).unwrap();
assert_eq!(
derived_xprv, keyforkd_xprv,
"{left_path} + {right_path} != {path}"
);
}
let req = DerivationRequest::new(
DerivationAlgorithm::Secp256k1,
&DerivationPath::from_str(test.chain).unwrap(),
);
let response =
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
assert_eq!(&response.data, test.private_key.as_slice());
}
let req = DerivationRequest::new(
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(())
})
Ok(())
},
)
.unwrap();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,8 @@ impl IsDisconnect for EncodeError {
}
impl UnixServer {
/// Bind a socket to the given `address` and create a [`UnixServer`]. This function also creates a ctrl_c handler to automatically clean up the socket file.
/// Bind a socket to the given `address` and create a [`UnixServer`]. This function also
/// creates a `ctrl_c` handler to automatically clean up the socket file.
///
/// # Errors
/// This function may return an error if the socket can't be bound.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -74,7 +74,7 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
///
/// # Errors
/// The function may error for any condition mentioned in [`Error`].
pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
pub fn derive(xprv: &XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
let primary_key_flags = match keys.first() {
Some(kf) if kf.for_certification() => kf,
_ => return Err(Error::NotCert),

View File

@ -2,8 +2,8 @@
use std::{env, process::ExitCode, str::FromStr};
use keyfork_derive_util::DerivationPath;
use keyfork_derive_path_data::paths;
use keyfork_derive_util::DerivationPath;
use keyforkd_client::Client;
use ed25519_dalek::SigningKey;
@ -82,7 +82,10 @@ fn validate(
let index = paths::OPENPGP.inner().first().unwrap();
let path = DerivationPath::from_str(path)?;
assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
assert!(
path.len() >= 2,
"Expected path of at least m/{index}/account_id'"
);
let given_index = path.iter().next().expect("checked .len() above");
assert_eq!(
@ -117,7 +120,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
.map(|kt| kt.inner().clone())
.collect::<Vec<_>>();
let cert = keyfork_derive_openpgp::derive(derived_xprv, subkeys.as_slice(), &default_userid)?;
let cert = keyfork_derive_openpgp::derive(&derived_xprv, subkeys.as_slice(), &default_userid)?;
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#![allow(clippy::module_name_repetitions, clippy::must_use_candidate)]
#![allow(clippy::module_name_repetitions)]
#![doc = include_str!("../README.md")]
pub mod extended_key;
@ -17,7 +17,10 @@ pub mod public_key;
mod tests;
#[doc(inline)]
pub use crate::extended_key::{private_key::{ExtendedPrivateKey, Error as XPrvError, VariableLengthSeed}, public_key::{ExtendedPublicKey, Error as XPubError}};
pub use crate::extended_key::{
private_key::{Error as XPrvError, ExtendedPrivateKey, VariableLengthSeed},
public_key::{Error as XPubError, ExtendedPublicKey},
};
pub use crate::{
index::{DerivationIndex, Error as IndexError},

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,11 @@ fn run() -> Result<()> {
let openpgp = OpenPGP;
let prompt_handler = default_handler()?;
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?;
let bytes = openpgp.decrypt_all_shards_to_secret(
key_discovery.as_deref(),
messages_file,
prompt_handler,
)?;
print!("{}", smex::encode(bytes));
Ok(())

View File

@ -8,7 +8,7 @@ use std::{
};
use keyfork_prompt::default_handler;
use keyfork_shard::{Format, openpgp::OpenPGP};
use keyfork_shard::{openpgp::OpenPGP, Format};
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -35,7 +35,11 @@ fn run() -> Result<()> {
let openpgp = OpenPGP;
let prompt_handler = default_handler()?;
openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?;
openpgp.decrypt_one_shard_for_transport(
key_discovery.as_deref(),
messages_file,
prompt_handler,
)?;
Ok(())
}

View File

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

View File

@ -2,7 +2,7 @@
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
use keyfork_shard::{Format, openpgp::OpenPGP};
use keyfork_shard::{openpgp::OpenPGP, Format};
#[derive(Clone, Debug)]
enum Error {
@ -52,7 +52,13 @@ fn run() -> Result<()> {
let openpgp = OpenPGP;
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
openpgp.shard_and_encrypt(
threshold,
max,
&input,
key_discovery.as_path(),
std::io::stdout(),
)?;
Ok(())
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,10 +5,13 @@ repository = "https://git.distrust.co/public/keyfork"
edition = "2021"
license = "MIT"
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[build-dependencies]
bindgen = { version = "0.68", default-features = false, features = ["runtime"] }
bindgen = { version = "0.70", default-features = false, features = ["runtime"] }
pkg-config = "0.3"

View File

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

View File

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

View File

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

View File

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

View File

@ -20,16 +20,13 @@ impl Image {
/// Link: [`sys::zbar_image_set_format`]
///
/// A FourCC code can be given in the format:
/// A `FourCC` code can be given in the format:
///
/// ```rust,ignore
/// self.set_format(b"Y800")
/// ```
pub(crate) fn set_format(&mut self, fourcc: &[u8; 4]) {
let fourcc: u64 = fourcc[0] as u64
| ((fourcc[1] as u64) << 8)
| ((fourcc[2] as u64) << 16)
| ((fourcc[3] as u64) << 24);
let fourcc = std::os::raw::c_ulong::from(u32::from_le_bytes(*fourcc));
unsafe { sys::zbar_image_set_format(self.inner, fourcc) }
}
@ -43,12 +40,7 @@ impl Image {
/// Accepts raw data in the configured format. See: [`Image::set_format`]
fn set_data(&mut self, data: Vec<u8>) {
unsafe {
sys::zbar_image_set_data(
self.inner,
data.as_ptr().cast(),
data.len() as u64,
None,
)
sys::zbar_image_set_data(self.inner, data.as_ptr().cast(), data.len() as u64, None);
}
// keep data in self to avoid use after free when data goes out of scope
let _ = self.inner_data.insert(data);

View File

@ -22,7 +22,7 @@ pub struct ImageScanner {
}
impl ImageScanner {
/// create a new ImageScanner.
/// Create a new `ImageScanner`.
///
/// Link: [`sys::zbar_image_scanner_create`]
pub fn new() -> Self {
@ -31,7 +31,7 @@ impl ImageScanner {
}
}
/// Set a configuration option for the ImageScanner.
/// Set a configuration option.
///
/// Link: [`sys::zbar_image_scanner_set_config`]
///
@ -58,10 +58,7 @@ impl ImageScanner {
/// Link: [`sys::zbar_scan_image`]
///
/// TODO: return an iterator over scanned values
pub fn scan_image(
&mut self,
image: &Image,
) -> Vec<Symbol> {
pub fn scan_image(&mut self, image: &Image) -> Vec<Symbol> {
unsafe { sys::zbar_scan_image(self.inner, image.inner) };
let mut result = vec![];
let mut symbol = unsafe { sys::zbar_image_first_symbol(image.inner) };
@ -70,7 +67,7 @@ impl ImageScanner {
let symbol_data = unsafe { sys::zbar_symbol_get_data(symbol) };
let symbol_data_len = unsafe { sys::zbar_symbol_get_data_length(symbol) };
let symbol_slice = unsafe {
std::slice::from_raw_parts(symbol_data as *const u8, symbol_data_len as usize)
std::slice::from_raw_parts(symbol_data.cast::<u8>(), symbol_data_len as usize)
};
result.push(Symbol::new(symbol_type, symbol_slice));
symbol = unsafe { sys::zbar_symbol_next(symbol) };

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_orientation_e as Orientation;
pub mod image_scanner;
pub mod image;
pub mod image_scanner;
pub mod symbol;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -108,7 +108,10 @@ pub struct FdTerminal {
stored_termios: Option<libc::termios>,
}
impl<T> From<T> for FdTerminal where T: os::fd::AsRawFd {
impl<T> From<T> for FdTerminal
where
T: os::fd::AsRawFd,
{
fn from(value: T) -> Self {
Self {
fd: value.as_raw_fd(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -114,7 +114,9 @@ impl Wordlist for English {
let mut words = wordlist_file.lines().skip(1).map(|x| x.trim().to_string());
English {
words: std::array::from_fn(|_| {
words.next().expect(bug!("wordlist {} should have 2048 words"))
words
.next()
.expect(bug!("wordlist {} should have 2048 words"))
}),
}
})
@ -283,7 +285,7 @@ where
return Err(MnemonicGenerationError::InvalidByteLength(bit_count));
}
Ok( Self::from_raw_bytes(bytes) )
Ok(Self::from_raw_bytes(bytes))
}
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data may be of a size
@ -380,12 +382,12 @@ where
/// A clone of the internal representation of the decoded data.
pub fn to_bytes(&self) -> Vec<u8> {
self.data.to_vec()
self.data.clone()
}
/// A clone of the internal representation of the decoded data.
pub fn to_vec(&self) -> Vec<u8> {
self.data.to_vec()
self.data.clone()
}
/// Conver the Mnemonic into the internal representation of the decoded data.
@ -419,8 +421,8 @@ where
/// Create a BIP-0032 seed from the provided data and an optional passphrase.
///
/// # Panics
/// The function may panic if the HmacSha512 function returns an error. The only error the
/// HmacSha512 function should return is an invalid length, which should not be possible.
/// The function may panic if the `HmacSha512` function returns an error. The only error the
/// `HmacSha512` function should return is an invalid length, which should not be possible.
pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> [u8; 64] {
let passphrase = passphrase.into();
@ -603,7 +605,7 @@ mod tests {
}
#[test]
#[should_panic]
#[should_panic(expected = "bytes.len() <= 1024")]
fn fails_over_8192_bits() {
let entropy = &mut [0u8; 1024 + 4];
let mut random = std::fs::File::open("/dev/urandom").unwrap();
@ -612,7 +614,7 @@ mod tests {
}
#[test]
#[should_panic]
#[should_panic(expected = "bytes.len() % 4 == 0")]
fn fails_over_invalid_size() {
let entropy = &mut [0u8; 255];
let mut random = std::fs::File::open("/dev/urandom").unwrap();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,6 +76,7 @@ ignore = [
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2023-0071", reason = "Not applicable, vulnerable path is not used" },
{ id = "RUSTSEC-2025-0011", reason = "No alternative available" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
@ -96,7 +97,7 @@ allow = [
"BSD-3-Clause",
"ISC",
"CC0-1.0",
"Unicode-DFS-2016",
# "Unicode-DFS-2016",
"LGPL-2.0",
"LGPL-3.0",
"Unicode-3.0",
@ -115,6 +116,11 @@ exceptions = [
#{ allow = ["Zlib"], crate = "adler32" },
{ allow = ["BSL-1.0"], name = "xxhash-rust", version = "*" },
{ allow = ["Zlib"], name = "foldhash", version = "*" },
{ allow = ["AGPL-3.0"], name = "keyforkd", version = "*" },
{ allow = ["AGPL-3.0"], name = "keyfork-shard", version = "*" },
{ allow = ["AGPL-3.0"], name = "keyfork-derive-openpgp", version = "*" },
{ allow = ["AGPL-3.0"], name = "keyfork-derive-key", version = "*" },
{ allow = ["AGPL-3.0"], name = "keyfork", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,