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

View File

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

View File

@ -4,9 +4,6 @@ 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,10 +199,6 @@ 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),
@ -241,9 +237,12 @@ 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;
if let Some(occupied) = socket_vars.get("KEYFORKD_SOCKET_PATH") {
#[allow(clippy::single_match_else)]
match socket_vars.get("KEYFORKD_SOCKET_PATH") {
Some(occupied) => {
socket_path = PathBuf::from(occupied);
} else {
}
None => {
socket_path = PathBuf::from(
socket_vars
.get("XDG_RUNTIME_DIR")
@ -251,6 +250,7 @@ pub fn get_socket() -> Result<UnixStream, Error> {
);
socket_path.extend(["keyforkd", "keyforkd.sock"]);
}
}
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
}
@ -266,7 +266,7 @@ pub struct Client {
impl Client {
/// Create a new client from a given already-connected [`UnixStream`]. This function is
/// provided in case a specific `UnixStream` has to be used; otherwise,
/// provided in case a specific UnixStream has to be used; otherwise,
/// [`Client::discover_socket`] should be preferred.
///
/// # Examples
@ -344,7 +344,7 @@ impl Client {
return Err(Error::InvalidResponse);
}
let depth = u8::try_from(path.len())?;
let depth = path.len() as u8;
ExtendedPrivateKey::from_parts(&d.data, depth, d.chain_code)
.map_err(|_| Error::InvalidKey)
}

View File

@ -9,13 +9,14 @@ use std::{os::unix::net::UnixStream, str::FromStr};
fn secp256k1_test_suite() {
use k256::SecretKey;
let tests = test_data().unwrap().remove("secp256k1").unwrap();
let tests = test_data()
.unwrap()
.remove("secp256k1")
.unwrap();
for seed_test in tests {
let seed = seed_test.seed;
run_test(
&seed,
move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
run_test(&seed, move |socket_path| -> Result<(), Box<dyn std::error::Error + Send>> {
for test in seed_test.tests {
let socket = UnixStream::connect(socket_path).unwrap();
let mut client = Client::new(socket);
@ -59,8 +60,7 @@ fn secp256k1_test_suite() {
assert_eq!(&response.data, test.private_key.as_slice());
}
Ok(())
},
)
})
.unwrap();
}
}

View File

@ -4,9 +4,6 @@ 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,9 +4,6 @@ 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,10 +89,14 @@ 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 runtime_path = if let Some(occupied) = runtime_vars.get("KEYFORKD_SOCKET_PATH") {
PathBuf::from(occupied)
} else {
let mut runtime_path = PathBuf::from(
let mut runtime_path: PathBuf;
#[allow(clippy::single_match_else)]
match runtime_vars.get("KEYFORKD_SOCKET_PATH") {
Some(occupied) => {
runtime_path = PathBuf::from(occupied);
}
None => {
runtime_path = PathBuf::from(
runtime_vars
.get("XDG_RUNTIME_DIR")
.ok_or(KeyforkdError::NoSocketPath)?,
@ -104,8 +108,8 @@ pub async fn start_and_run_server(mnemonic: Mnemonic) -> Result<(), Box<dyn std:
tokio::fs::create_dir(&runtime_path).await?;
}
runtime_path.push("keyforkd.sock");
runtime_path
};
}
}
#[cfg(feature = "tracing")]
debug!(

View File

@ -162,9 +162,6 @@ 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,8 +49,7 @@ 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,10 +39,7 @@ 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),
@ -80,11 +77,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")]
@ -114,7 +111,10 @@ 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(expected = "InvalidDerivationLength(0)")]
#[should_panic]
#[tokio::test]
async fn errors_on_no_path() {
let tests = [(
@ -200,7 +200,7 @@ mod tests {
}
}
#[should_panic(expected = "InvalidDerivationLength(1)")]
#[should_panic]
#[tokio::test]
async fn errors_on_short_path() {
let tests = [(

View File

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

View File

@ -4,9 +4,6 @@ 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,9 +4,6 @@ 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,10 +46,7 @@ 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,9 +4,6 @@ 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_path_data::paths;
use keyfork_derive_util::DerivationPath;
use keyfork_derive_path_data::paths;
use keyforkd_client::Client;
use ed25519_dalek::SigningKey;
@ -82,10 +82,7 @@ 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!(
@ -120,7 +117,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,9 +4,6 @@ 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 std::sync::LazyLock;
use once_cell::sync::Lazy;
use keyfork_derive_util::{DerivationIndex, DerivationPath};
@ -11,7 +11,7 @@ pub mod paths {
use super::*;
/// 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(
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: LazyLock<DerivationPath> = LazyLock::new(|| {
pub static OPENPGP_SHARD: Lazy<DerivationPath> = Lazy::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: LazyLock<DerivationPath> = LazyLock::new(|| {
pub static OPENPGP_DISASTER_RECOVERY: Lazy<DerivationPath> = Lazy::new(|| {
DerivationPath::default()
.chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"),

View File

@ -4,9 +4,6 @@ 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,7 +167,6 @@ 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())
}
@ -190,10 +189,7 @@ 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
@ -227,11 +223,13 @@ where
/// ```
pub fn from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Result<Self> {
match K::from_bytes(key) {
Ok(key) => Ok(Self {
Ok(key) => {
Ok(Self {
private_key: key,
depth,
chain_code,
}),
})
}
Err(_) => Err(Error::InvalidKey),
}
}

View File

@ -117,7 +117,7 @@ mod tests {
use std::str::FromStr;
#[test]
#[should_panic(expected = "IndexTooLarge")]
#[should_panic]
fn fails_on_high_index() {
DerivationIndex::new(0x8000_0001, false).unwrap();
}
@ -163,7 +163,7 @@ mod tests {
}
#[test]
#[should_panic(expected = "IndexTooLarge")]
#[should_panic]
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)]
#![allow(clippy::module_name_repetitions, clippy::must_use_candidate)]
#![doc = include_str!("../README.md")]
pub mod extended_key;
@ -17,10 +17,7 @@ pub mod public_key;
mod tests;
#[doc(inline)]
pub use crate::extended_key::{
private_key::{Error as XPrvError, ExtendedPrivateKey, VariableLengthSeed},
public_key::{Error as XPubError, ExtendedPublicKey},
};
pub use crate::extended_key::{private_key::{ExtendedPrivateKey, Error as XPrvError, VariableLengthSeed}, public_key::{ExtendedPublicKey, Error as XPubError}};
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(expected = "UnknownPathPrefix")]
#[should_panic]
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::{Scalar, Secp256k1};
use k256::{Secp256k1, Scalar};
// 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,7 +13,10 @@ 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;
@ -102,7 +105,7 @@ fn ed25519() {
#[cfg(feature = "ed25519")]
#[test]
#[should_panic(expected = "HardenedDerivationRequired")]
#[should_panic]
fn panics_with_unhardened_derivation() {
use ed25519_dalek::SigningKey;
@ -114,7 +117,7 @@ fn panics_with_unhardened_derivation() {
#[cfg(feature = "ed25519")]
#[test]
#[should_panic(expected = "Depth")]
#[should_panic]
fn panics_at_depth() {
use ed25519_dalek::SigningKey;

View File

@ -4,9 +4,6 @@ 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,11 +35,7 @@ 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::{openpgp::OpenPGP, Format};
use keyfork_shard::{Format, openpgp::OpenPGP};
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 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,6 +1,9 @@
//! 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;
@ -13,7 +16,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::{openpgp::OpenPGP, Format};
use keyfork_shard::{Format, openpgp::OpenPGP};
#[derive(Clone, Debug)]
enum Error {
@ -52,13 +52,7 @@ 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,4 +1,5 @@
#![doc = include_str!("../README.md")]
#![allow(clippy::expect_fun_call)]
use std::{
io::{Read, Write},
@ -91,10 +92,9 @@ 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,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
/// 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>>,
@ -327,14 +326,14 @@ pub trait Format {
if choice == RetryScanMnemonic::Continue {
break;
}
}
};
}
}
// if QR code scanning failed or was unavailable, read from a set of mnemonics
let their_pubkey = if let Some(pubkey) = pubkey_data {
pubkey
} else {
let their_pubkey = match pubkey_data {
Some(pubkey) => pubkey,
None => {
let validator = MnemonicValidator {
word_length: Some(WordLength::Count(24)),
};
@ -348,6 +347,20 @@ pub trait Format {
.as_bytes()
.try_into()
.map_err(|_| InvalidData)?
/*
prompt
.lock()
.expect(bug!(POISONED_MUTEX))
.prompt_validated_wordlist::<English, _>(
QRCODE_COULDNT_READ,
3,
validator.to_fn(),
)?
.as_bytes()
.try_into()
.map_err(|_| InvalidData)?
*/
}
};
// create our shared key
@ -388,6 +401,7 @@ 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
@ -467,11 +481,11 @@ pub trait Format {
"must have less than u8::MAX public keys"
);
assert_eq!(
max as usize,
public_keys.len(),
max,
public_keys.len() as u8,
"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");
let header = self.format_encrypted_header(&signing_key, &public_keys, threshold)?;
@ -533,7 +547,6 @@ 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()?;
@ -614,13 +627,13 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
if choice == RetryScanMnemonic::Continue {
break;
}
}
};
}
}
let (pubkey, payload) = if let Some((pubkey, payload)) = pubkey_data.zip(payload_data) {
(pubkey, payload)
} else {
let (pubkey, payload) = match (pubkey_data, payload_data) {
(Some(pubkey), Some(payload)) => (pubkey, payload),
_ => {
let validator = MnemonicSetValidator {
word_lengths: [24, 39],
};
@ -637,6 +650,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
.map_err(|_| InvalidData)?;
let payload = payload_mnemonic.to_bytes();
(pubkey, payload)
}
};
assert_eq!(
@ -663,14 +677,17 @@ 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");
if let Some(n) = &mut iter_count {
match &mut iter_count {
Some(n) => {
// Must be > 0 to start loop, can't go lower
*n -= 1;
} else {
}
None => {
// NOTE: Should always be >= 1, < 256 due to Shamir constraints
threshold = payload[1];
let _ = iter_count.insert(threshold - 1);
}
}
let payload_len = payload.last().expect(bug!("payload should not be empty"));
shares.push(payload[HUNK_OFFSET..usize::from(*payload_len)].to_vec());

View File

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

View File

@ -1,3 +1,5 @@
#![allow(clippy::expect_fun_call)]
use std::{
collections::{HashMap, HashSet},
rc::Rc,
@ -79,13 +81,13 @@ pub struct SmartcardManager {
}
impl SmartcardManager {
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Self {
Self {
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
Ok(Self {
current_card: None,
root: None,
pm: p,
pin_cache: HashMap::default(),
}
pin_cache: Default::default(),
})
}
// Sets the root cert, returning the old cert
@ -175,9 +177,10 @@ 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()
.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()
.collect())
}
@ -278,7 +281,8 @@ 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 {
Ok(()) => {
#[allow(clippy::ignored_unit_patterns)]
Ok(_) => {
self.pin_cache.insert(fp.clone(), temp_pin.clone());
pin.replace(temp_pin);
}

View File

@ -4,9 +4,6 @@ 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(Self::default());
return Ok(Default::default())
}
let values = s
.split(',')

View File

@ -1,4 +1,4 @@
use super::{create, Keyfork};
use super::{Keyfork, create};
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,12 +213,15 @@ 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 = if let Some(writer) = writer { writer } else {
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 mut writer = Writer::new(writer, Kind::SecretKey)?;
for packet in cert.as_tsk().into_packets() {
@ -228,12 +231,15 @@ 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 = if let Some(writer) = writer { writer } else {
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 mut writer = Writer::new(writer, Kind::PublicKey)?;
for packet in cert.into_packets2() {
@ -253,7 +259,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 => {
@ -272,7 +278,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,13 +1,14 @@
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, HashSet},
collections::HashMap,
fmt::Display,
fs::File,
io::{IsTerminal, Write},
@ -19,7 +20,7 @@ use keyfork_derive_openpgp::{
openpgp::{
self,
armor::{Kind, Writer},
packet::{signature::SignatureBuilder, UserID},
packet::{UserID, signature::SignatureBuilder},
policy::StandardPolicy,
serialize::{
stream::{Encryptor2, LiteralWriter, Message, Recipient},
@ -164,7 +165,7 @@ pub enum Error {
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())
}
@ -227,13 +228,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)]
@ -256,13 +257,13 @@ pub enum MnemonicSubcommands {
///
/// The following additional arguments are required:
///
/// * `threshold`, m: the minimum amount of shares required to reconstitute the shard.
/// * threshold, m: the minimum amount of shares required to reconstitute the shard.
///
/// * `max`, n: the maximum amount of shares.
/// * max, n: the maximum amount of shares.
///
/// * `cards_per_shard`: the amount of OpenPGP smartcards to provision per shardholder.
/// * cards_per_shard: the amount of OpenPGP smartcards to provision per shardholder.
///
/// * `cert_output`: the file to write all generated OpenPGP certificates to; if not
/// * cert_output: the file to write all generated OpenPGP certificates to; if not
/// provided, files will be automatically generated for each certificate.
#[arg(long)]
shard_to_self: Option<ValueWithOptions<PathBuf>>,
@ -302,23 +303,24 @@ fn determine_valid_output_path<T: AsRef<Path>>(
mid_ext: &str,
optional_path: Option<T>,
) -> PathBuf {
if let Some(p) = optional_path {
p.as_ref().to_path_buf()
} else {
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)
}
}
}
fn is_extension_armored(path: &Path) -> bool {
match path.extension().and_then(|s| s.to_str()) {
Some("pgp" | "gpg") => false,
Some("pgp") | Some("gpg") => false,
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!("defaulting to armored");
true
@ -393,11 +395,8 @@ 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);
}
@ -457,12 +456,15 @@ fn do_shard(
// if only one is set: true
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) {
Some(t) => t,
None => {
let len = u8::try_from(certs.len())?;
(len, len)
}
};
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 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,6 +611,7 @@ fn do_shard_to_self(
.parse()?;
let cards_per_shard = options
.get("cards_per_shard")
.as_deref()
.map(|cps| u8::from_str(cps))
.transpose()?;
@ -625,8 +626,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 #{}",
(u16::from(i)) + 1,
(u16::from(index)) + 1,
(i as u16) + 1,
(index as u16) + 1,
)))?;
let card_backend = loop {
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
@ -670,7 +671,7 @@ fn do_shard_to_self(
let output = File::create(path)?;
opgp.shard_and_encrypt(
threshold,
u8::try_from(certs.len()).expect("provided more than u8::MAX certs"),
certs.len() as u8,
mnemonic.as_bytes(),
&certs[..],
output,
@ -741,10 +742,10 @@ fn do_provision(
metadata @ None => {
*metadata = Some(config.clone());
}
}
};
provision
.provisioner_name
.provision_with_mnemonic(mnemonic, &provisioner)?;
.provision_with_mnemonic(mnemonic, provisioner)?;
}
Ok(())
@ -771,11 +772,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_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 {
opgp.derive_public_with_xprv(writer, &derived_key)?;
opgp.derive_public_with_xprv(writer, derived)?;
} else {
opgp.derive_with_xprv(writer, &derived_key)?;
opgp.derive_with_xprv(writer, derived)?;
}
}
derive::Derive {
@ -789,11 +790,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_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 {
key.derive_public_with_xprv(writer, &derived_key)?;
key.derive_public_with_xprv(writer, derived)?;
} else {
key.derive_with_xprv(writer, &derived_key)?;
key.derive_with_xprv(writer, derived)?;
}
}
}
@ -801,7 +802,6 @@ 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(Vec::is_empty);
encrypt_to.is_none() || encrypt_to.as_ref().is_some_and(|e| e.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(Vec::is_empty);
|| shard_to.as_ref().is_some_and(|s| s.is_empty());
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();
let mnemonic = source.handle(size)?;
@ -859,7 +859,7 @@ impl MnemonicSubcommands {
}
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 {
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,7 +20,6 @@ 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,
@ -96,7 +95,12 @@ 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,10 +94,7 @@ 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> {
@ -132,8 +129,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>>;
}
@ -189,7 +186,7 @@ impl TryFrom<Provision> for config::Provisioner {
Ok(Self {
account: value.account_id,
identifier: value.identifier.ok_or(MissingField("identifier"))?,
metadata: Option::default(),
metadata: Default::default(),
})
}
}
@ -210,9 +207,9 @@ impl Provision {
}
}
None => {
let provisioner_with_identifier = if self.identifier.is_some() {
self.clone()
} else {
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");
@ -221,9 +218,10 @@ impl Provision {
identifier: Some(id.0.clone()),
..self.clone()
}
}
};
let config = config::Provisioner::try_from(provisioner_with_identifier)?;
self.provisioner_name.provision(&config)?;
self.provisioner_name.provision(config)?;
}
}
Ok(())

View File

@ -22,9 +22,7 @@ use std::path::PathBuf;
#[error("Provisioner was unable to find a matching smartcard")]
struct NoMatchingSmartcard;
pub type CardList = Vec<(String, Option<String>)>;
fn discover_cards() -> Result<CardList, Box<dyn std::error::Error>> {
fn discover_cards() -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
let mut idents = vec![];
for backend in PcscBackend::cards(None)? {
let backend = backend?;
@ -39,8 +37,8 @@ fn discover_cards() -> Result<CardList, Box<dyn std::error::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()?;
@ -59,24 +57,23 @@ fn provision_card(
Some(userid) => UserID::from(userid.as_str()),
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
.metadata
.as_ref()
.is_some_and(|m| m.contains_key("_skip_cert_output"))
{
let cert_output = if let Some(cert_output) =
provisioner.metadata.as_ref().and_then(|m| m.get("output"))
{
PathBuf::from(cert_output)
} else {
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_file = std::fs::File::create(cert_output)?;
@ -103,7 +100,7 @@ fn provision_card(
}
if !has_provisioned {
return Err(NoMatchingSmartcard.into());
return Err(NoMatchingSmartcard)?;
}
Ok(())
@ -125,8 +122,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)
}
@ -148,8 +145,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,15 +109,14 @@ 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)?;
@ -188,7 +187,7 @@ pub enum ShardSubcommands {
/// The path to discover private keys from.
key_discovery: Option<PathBuf>,
},
}
}
impl ShardSubcommands {
@ -257,11 +256,7 @@ 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,4 +1,5 @@
#![doc = include_str!("../README.md")]
#![allow(clippy::module_name_repetitions)]
use std::process::ExitCode;
@ -7,9 +8,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,10 +34,11 @@ pub fn get_new_pins(
3,
&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;
}
pm.prompt_message(Message::Text("User PINs did not match. Retrying.".into()))?;
};
let admin_pin = loop {
@ -53,10 +54,11 @@ pub fn get_new_pins(
3,
&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;
}
pm.prompt_message(Message::Text("Admin PINs did not match. Retrying.".into()))?;
};
Ok((user_pin, admin_pin))

View File

@ -5,9 +5,6 @@ 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,5 +1,7 @@
//! Encoding and decoding QR codes.
#![allow(clippy::expect_fun_call)]
use keyfork_bug as bug;
use bug::POISONED_MUTEX;
@ -37,10 +39,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,
},
@ -163,12 +165,13 @@ mod rqrr {
}
}
#[allow(dead_code, clippy::cast_precision_loss)]
#[allow(dead_code)]
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,13 +5,10 @@ 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.70", default-features = false, features = ["runtime"] }
bindgen = { version = "0.68", 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,5 +1,4 @@
#![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,9 +5,6 @@ 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::ImageReader;
use image::io::Reader as ImageReader;
use v4l::{
buffer::Type,
io::{traits::CaptureStream, userptr::Stream},

View File

@ -20,13 +20,16 @@ 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 = 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) }
}
@ -40,7 +43,12 @@ 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.
/// Set a configuration option for the ImageScanner.
///
/// Link: [`sys::zbar_image_scanner_set_config`]
///
@ -58,7 +58,10 @@ 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) };
@ -67,7 +70,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.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));
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;
pub mod image_scanner;
pub mod image;
pub mod symbol;

View File

@ -1,7 +1,5 @@
//! 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,9 +5,6 @@ edition = "2021"
publish = false
license = "MIT"
[lints]
workspace = true
[dependencies]
assert_cmd = "2.0.16"
keyforkd = { workspace = true, features = ["default"] }

View File

@ -52,5 +52,7 @@ fn test() {
key.alive().expect("is live after being generated");
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"
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,7 +45,6 @@ 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();
@ -103,13 +102,10 @@ 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
@ -124,14 +120,13 @@ where
/// 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,9 +4,6 @@ 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,9 +14,6 @@ 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

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

View File

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

View File

@ -108,10 +108,7 @@ 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,9 +4,6 @@ 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('/')
.next_back()
.last()
.expect(bug!("No data in file path"))
== "lo"
{

View File

@ -4,9 +4,6 @@ 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,9 +6,6 @@ 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,9 +114,7 @@ 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"))
}),
}
})
@ -285,7 +283,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
@ -382,12 +380,12 @@ where
/// A clone of the internal representation of the decoded data.
pub fn to_bytes(&self) -> Vec<u8> {
self.data.clone()
self.data.to_vec()
}
/// A clone of the internal representation of the decoded data.
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.
@ -421,8 +419,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();
@ -605,7 +603,7 @@ mod tests {
}
#[test]
#[should_panic(expected = "bytes.len() <= 1024")]
#[should_panic]
fn fails_over_8192_bits() {
let entropy = &mut [0u8; 1024 + 4];
let mut random = std::fs::File::open("/dev/urandom").unwrap();
@ -614,7 +612,7 @@ mod tests {
}
#[test]
#[should_panic(expected = "bytes.len() % 4 == 0")]
#[should_panic]
fn fails_over_invalid_size() {
let entropy = &mut [0u8; 255];
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"
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,7 +60,11 @@ impl PromptHandler for Headless {
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
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}")?;
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,14 +148,11 @@ 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,
@ -260,11 +257,8 @@ where
let printable_space = (cols as usize) - prefix_length;
input.len() - (printable_space - 1)
};
terminal
.queue(cursor::MoveToColumn(
u16::try_from(prefix_length).unwrap_or(u16::MAX),
))?
.queue(cursor::MoveToColumn(prefix_length as u16))?
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
.queue(Print(&input[printable_start..]))?
.flush()?;
@ -355,14 +349,12 @@ where
KeyCode::Left | KeyCode::Up => {
active_choice = active_choice.saturating_sub(1);
}
KeyCode::Right | KeyCode::Down => {
match choices.len().saturating_sub(active_choice) {
KeyCode::Right | KeyCode::Down => match choices.len().saturating_sub(active_choice) {
0 | 1 => {}
_ => {
active_choice += 1;
}
}
}
},
KeyCode::Enter => {
return Ok(active_choice);
}
@ -392,7 +384,9 @@ where
}
Err(Error::Validation(
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) => {
input.push(c);
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);
if let Some(steel_word) = word {
if steel_word.len() >= 4 {
@ -482,9 +476,10 @@ where
let usable_space = cols as usize - prefix_length - 1;
#[allow(clippy::cast_possible_truncation)]
terminal
.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))?
.flush()?;
@ -552,7 +547,9 @@ where
}
Err(Error::Validation(
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() {
let mut written_chars = 0;
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;
if written_chars > cols {
terminal.queue(cursor::MoveToNextLine(1))?;

View File

@ -1,7 +1,6 @@
//! Validator and parser types.
#![allow(clippy::type_complexity)]
use std::ops::RangeInclusive;
/// A trait to create validator functions.
@ -88,20 +87,22 @@ 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_unstable();
chars.sort();
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)
})
@ -203,16 +204,19 @@ 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| if let Some(wl) = word_length.as_ref() {
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)
} else {
}
None => {
let m = Mnemonic::from_str(&s)?;
Ok(m)
}
})
}
}

View File

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

View File

@ -4,9 +4,6 @@ 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,7 +76,6 @@ 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.
@ -97,7 +96,7 @@ allow = [
"BSD-3-Clause",
"ISC",
"CC0-1.0",
# "Unicode-DFS-2016",
"Unicode-DFS-2016",
"LGPL-2.0",
"LGPL-3.0",
"Unicode-3.0",
@ -116,11 +115,6 @@ 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,