keyfork-bug: initial commit, refactor use of unwrap() and expect() to use keyfork-bug

This commit is contained in:
Ryan Heywood 2024-02-20 20:39:28 -05:00
parent 354eae5a6a
commit 472d0288f9
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
25 changed files with 292 additions and 98 deletions

11
Cargo.lock generated
View File

@ -1700,6 +1700,10 @@ dependencies = [
"anyhow", "anyhow",
] ]
[[package]]
name = "keyfork-bug"
version = "0.1.0"
[[package]] [[package]]
name = "keyfork-crossterm" name = "keyfork-crossterm"
version = "0.27.1" version = "0.27.1"
@ -1761,6 +1765,7 @@ dependencies = [
"hex-literal", "hex-literal",
"hmac", "hmac",
"k256", "k256",
"keyfork-bug",
"keyfork-mnemonic-util", "keyfork-mnemonic-util",
"keyfork-slip10-test-data", "keyfork-slip10-test-data",
"ripemd", "ripemd",
@ -1773,6 +1778,7 @@ dependencies = [
name = "keyfork-entropy" name = "keyfork-entropy"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"keyfork-bug",
"smex", "smex",
] ]
@ -1793,6 +1799,7 @@ dependencies = [
"bip39", "bip39",
"hex", "hex",
"hmac", "hmac",
"keyfork-bug",
"pbkdf2", "pbkdf2",
"serde_json", "serde_json",
"sha2", "sha2",
@ -1803,6 +1810,7 @@ dependencies = [
name = "keyfork-prompt" name = "keyfork-prompt"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"keyfork-bug",
"keyfork-crossterm", "keyfork-crossterm",
"keyfork-mnemonic-util", "keyfork-mnemonic-util",
"thiserror", "thiserror",
@ -1813,6 +1821,7 @@ name = "keyfork-qrcode"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"image", "image",
"keyfork-bug",
"keyfork-zbar", "keyfork-zbar",
"rqrr", "rqrr",
"thiserror", "thiserror",
@ -1828,6 +1837,7 @@ dependencies = [
"card-backend", "card-backend",
"card-backend-pcsc", "card-backend-pcsc",
"hkdf", "hkdf",
"keyfork-bug",
"keyfork-derive-openpgp", "keyfork-derive-openpgp",
"keyfork-mnemonic-util", "keyfork-mnemonic-util",
"keyfork-prompt", "keyfork-prompt",
@ -1873,6 +1883,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"hex-literal", "hex-literal",
"keyfork-bug",
"keyfork-derive-path-data", "keyfork-derive-path-data",
"keyfork-derive-util", "keyfork-derive-util",
"keyfork-frame", "keyfork-frame",

View File

@ -15,6 +15,7 @@ members = [
"crates/qrcode/keyfork-zbar", "crates/qrcode/keyfork-zbar",
"crates/qrcode/keyfork-zbar-sys", "crates/qrcode/keyfork-zbar-sys",
"crates/util/keyfork-bin", "crates/util/keyfork-bin",
"crates/util/keyfork-bug",
"crates/util/keyfork-crossterm", "crates/util/keyfork-crossterm",
"crates/util/keyfork-entropy", "crates/util/keyfork-entropy",
"crates/util/keyfork-frame", "crates/util/keyfork-frame",

View File

@ -32,6 +32,7 @@ tower = { version = "0.4.13", features = ["tokio", "util"] }
thiserror = "1.0.47" thiserror = "1.0.47"
serde = { version = "1.0.186", features = ["derive"] } serde = { version = "1.0.186", features = ["derive"] }
tempfile = { version = "3.10.0", default-features = false } tempfile = { version = "3.10.0", default-features = false }
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug" }
[dev-dependencies] [dev-dependencies]
hex-literal = "0.4.1" hex-literal = "0.4.1"

View File

@ -7,6 +7,8 @@ use crate::{middleware, Keyforkd, ServiceBuilder, UnixServer};
use tokio::runtime::Builder; use tokio::runtime::Builder;
use keyfork_bug::bug;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("This error can never be instantiated")] #[error("This error can never be instantiated")]
#[doc(hidden)] #[doc(hidden)]
@ -54,8 +56,10 @@ where
.worker_threads(2) .worker_threads(2)
.enable_io() .enable_io()
.build() .build()
.expect("tokio threaded IO runtime"); .expect(bug!(
let socket_dir = tempfile::tempdir().expect("can't create tempdir"); "can't make tokio threaded IO runtime, should be enabled via feature flags"
));
let socket_dir = tempfile::tempdir().expect(bug!("can't create tempdir"));
let socket_path = socket_dir.path().join("keyforkd.sock"); let socket_path = socket_dir.path().join("keyforkd.sock");
rt.block_on(async move { rt.block_on(async move {
let (tx, mut rx) = tokio::sync::mpsc::channel(1); let (tx, mut rx) = tokio::sync::mpsc::channel(1);
@ -63,25 +67,28 @@ where
let socket_path = socket_path.clone(); let socket_path = socket_path.clone();
let seed = seed.to_vec(); let seed = seed.to_vec();
async move { async move {
let mut server = UnixServer::bind(&socket_path).expect("can't bind unix socket"); let mut server =
tx.send(()).await.expect("couldn't send server start signal"); UnixServer::bind(&socket_path).expect(bug!("can't bind unix socket"));
tx.send(())
.await
.expect(bug!("couldn't send server start signal"));
let service = ServiceBuilder::new() let service = ServiceBuilder::new()
.layer(middleware::BincodeLayer::new()) .layer(middleware::BincodeLayer::new())
.service(Keyforkd::new(seed.to_vec())); .service(Keyforkd::new(seed.to_vec()));
server.run(service).await.unwrap(); server.run(service).await.expect(bug!("Unable to start service"));
} }
}); });
rx.recv() rx.recv()
.await .await
.expect("can't receive server start signal from channel"); .expect(bug!("can't receive server start signal from channel"));
let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path)); let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path));
let result = test_handle.await; let result = test_handle.await;
server_handle.abort(); server_handle.abort();
result result
}) })
.expect("runtime could not join all threads") .expect(bug!("runtime could not join all threads"))
} }
#[cfg(test)] #[cfg(test)]

View File

@ -29,6 +29,7 @@ thiserror = "1.0.47"
# Optional, not personally audited # Optional, not personally audited
k256 = { version = "0.13.1", default-features = false, features = ["std", "arithmetic"], optional = true } k256 = { version = "0.13.1", default-features = false, features = ["std", "arithmetic"], optional = true }
ed25519-dalek = { version = "2.0.0", optional = true } ed25519-dalek = { version = "2.0.0", optional = true }
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug" }
[dev-dependencies] [dev-dependencies]
hex-literal = "0.4.1" hex-literal = "0.4.1"

View File

@ -1,5 +1,7 @@
use crate::{DerivationIndex, DerivationPath, ExtendedPublicKey, PrivateKey, PublicKey}; use crate::{DerivationIndex, DerivationPath, ExtendedPublicKey, PrivateKey, PublicKey};
use keyfork_bug::bug;
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::Sha512; use sha2::Sha512;
@ -124,7 +126,7 @@ mod serde_with {
let variable_len_bytes = <&[u8]>::deserialize(deserializer)?; let variable_len_bytes = <&[u8]>::deserialize(deserializer)?;
let bytes: [u8; 32] = variable_len_bytes let bytes: [u8; 32] = variable_len_bytes
.try_into() .try_into()
.expect("unable to parse serialized private key; no support for static len"); .expect(bug!("unable to parse serialized private key; no support for static len"));
Ok(K::from_bytes(&bytes)) Ok(K::from_bytes(&bytes))
} }
} }
@ -171,7 +173,7 @@ where
fn new_internal(seed: &[u8]) -> Self { fn new_internal(seed: &[u8]) -> Self {
let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>()) let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())
.expect("HmacSha512 InvalidLength should be infallible") .expect(bug!("HmacSha512 InvalidLength should be infallible"))
.chain_update(seed) .chain_update(seed)
.finalize() .finalize()
.into_bytes(); .into_bytes();
@ -180,10 +182,10 @@ where
Self::new_from_parts( Self::new_from_parts(
private_key private_key
.try_into() .try_into()
.expect("KEY_SIZE / 8 did not give a 32 byte slice"), .expect(bug!("KEY_SIZE / 8 did not give a 32 byte slice")),
0, 0,
// Checked: chain_code is always the same length, hash is static size // Checked: chain_code is always the same length, hash is static size
chain_code.try_into().expect("Invalid chain code length"), chain_code.try_into().expect(bug!("Invalid chain code length")),
) )
} }
@ -405,7 +407,7 @@ where
let depth = self.depth.checked_add(1).ok_or(Error::Depth)?; let depth = self.depth.checked_add(1).ok_or(Error::Depth)?;
let mut hmac = HmacSha512::new_from_slice(&self.chain_code) let mut hmac = HmacSha512::new_from_slice(&self.chain_code)
.expect("HmacSha512 InvalidLength should be infallible"); .expect(bug!("HmacSha512 InvalidLength should be infallible"));
if index.is_hardened() { if index.is_hardened() {
hmac.update(&[0]); hmac.update(&[0]);
hmac.update(&self.private_key.to_bytes()); hmac.update(&self.private_key.to_bytes());
@ -423,7 +425,7 @@ where
.derive_child( .derive_child(
&private_key &private_key
.try_into() .try_into()
.expect("Invalid length for private key"), .expect(bug!("Invalid length for private key")),
) )
.map_err(|_| Error::Derivation)?; .map_err(|_| Error::Derivation)?;
@ -432,7 +434,7 @@ where
depth, depth,
chain_code: chain_code chain_code: chain_code
.try_into() .try_into()
.expect("Invalid length for chain code"), .expect(bug!("Invalid length for chain code")),
}) })
} }
} }

View File

@ -4,6 +4,8 @@ use hmac::{Hmac, Mac};
use sha2::Sha512; use sha2::Sha512;
use thiserror::Error; use thiserror::Error;
use keyfork_bug::bug;
const KEY_SIZE: usize = 256; const KEY_SIZE: usize = 256;
/// Errors associated with creating or deriving Extended Public Keys. /// Errors associated with creating or deriving Extended Public Keys.
@ -142,9 +144,11 @@ where
let (child_key, chain_code) = hmac.split_at(KEY_SIZE / 8); let (child_key, chain_code) = hmac.split_at(KEY_SIZE / 8);
let derived_key = self let derived_key = self
.public_key .public_key
.derive_child(child_key.try_into().expect("Invalid key length")) .derive_child(child_key.try_into().expect(bug!("Invalid key length")))
.map_err(|_| Error::Derivation)?; .map_err(|_| Error::Derivation)?;
let chain_code = chain_code.try_into().expect("Invalid chain code length"); let chain_code = chain_code
.try_into()
.expect(bug!("Invalid chain code length"));
Ok(Self { Ok(Self {
public_key: derived_key, public_key: derived_key,

View File

@ -2,6 +2,8 @@ use crate::PublicKey;
use thiserror::Error; use thiserror::Error;
use keyfork_bug::bug;
pub(crate) type PrivateKeyBytes = [u8; 32]; pub(crate) type PrivateKeyBytes = [u8; 32];
/// Functions required to use an `ExtendedPrivateKey`. /// Functions required to use an `ExtendedPrivateKey`.
@ -115,7 +117,7 @@ impl PrivateKey for k256::SecretKey {
} }
fn from_bytes(b: &PrivateKeyBytes) -> Self { fn from_bytes(b: &PrivateKeyBytes) -> Self {
Self::from_slice(b).expect("Invalid private key bytes") Self::from_slice(b).expect(bug!("Invalid private key bytes"))
} }
fn to_bytes(&self) -> PrivateKeyBytes { fn to_bytes(&self) -> PrivateKeyBytes {
@ -134,13 +136,13 @@ impl PrivateKey for k256::SecretKey {
let other = *other; let other = *other;
// Checked: See above nonzero check // Checked: See above nonzero check
let scalar = Option::<NonZeroScalar>::from(NonZeroScalar::from_repr(other.into())) let scalar = Option::<NonZeroScalar>::from(NonZeroScalar::from_repr(other.into()))
.expect("Should have been able to get a NonZeroScalar"); .expect(bug!("Should have been able to get a NonZeroScalar"));
let derived_scalar = self.to_nonzero_scalar().as_ref() + scalar.as_ref(); let derived_scalar = self.to_nonzero_scalar().as_ref() + scalar.as_ref();
Ok( Ok(
Option::<NonZeroScalar>::from(NonZeroScalar::new(derived_scalar)) Option::<NonZeroScalar>::from(NonZeroScalar::new(derived_scalar))
.map(Into::into) .map(Into::into)
.expect("Should be able to make Key"), .expect(bug!("Should be able to make Key")),
) )
} }
} }

View File

@ -5,6 +5,8 @@ use ripemd::Ripemd160;
use sha2::Sha256; use sha2::Sha256;
use thiserror::Error; use thiserror::Error;
use keyfork_bug::bug;
pub(crate) type PublicKeyBytes = [u8; 33]; pub(crate) type PublicKeyBytes = [u8; 33];
/// Functions required to use an `ExtendedPublicKey`. /// Functions required to use an `ExtendedPublicKey`.
@ -63,7 +65,7 @@ pub trait PublicKey: Sized {
// Note: Safety assured by type returned from Ripemd160 // Note: Safety assured by type returned from Ripemd160
hash[..4] hash[..4]
.try_into() .try_into()
.expect("Ripemd160 returned too little data") .expect(bug!("Ripemd160 returned too little data"))
} }
} }
@ -108,10 +110,11 @@ impl PublicKey for k256::PublicKey {
} }
// Checked: See above // Checked: See above
let scalar = Option::<NonZeroScalar>::from(NonZeroScalar::from_repr(other.into())) let scalar = Option::<NonZeroScalar>::from(NonZeroScalar::from_repr(other.into()))
.expect("Should have been able to get a NonZeroScalar"); .expect(bug!("Should have been able to get a NonZeroScalar"));
let point = self.to_projective() + (AffinePoint::generator() * *scalar); let point = self.to_projective() + (AffinePoint::generator() * *scalar);
Ok(Self::from_affine(point.into()).expect("Could not from_affine after scalar arithmetic")) Ok(Self::from_affine(point.into())
.expect(bug!("Could not from_affine after scalar arithmetic")))
} }
} }
@ -150,10 +153,11 @@ impl TestPublicKey {
#[allow(dead_code)] #[allow(dead_code)]
pub fn from_bytes(b: &[u8]) -> Self { pub fn from_bytes(b: &[u8]) -> Self {
Self { Self {
key: b.try_into().unwrap(), key: b
.try_into()
.expect(bug!("invalid size when constructing TestPublicKey")),
} }
} }
} }
impl PublicKey for TestPublicKey { impl PublicKey for TestPublicKey {

View File

@ -36,3 +36,4 @@ card-backend-pcsc = { version = "0.5.0", optional = true }
openpgp-card-sequoia = { version = "0.2.0", optional = true, default-features = false } openpgp-card-sequoia = { version = "0.2.0", optional = true, default-features = false }
openpgp-card = { version = "0.4.0", optional = true } openpgp-card = { version = "0.4.0", optional = true }
sequoia-openpgp = { version = "1.17.0", optional = true, default-features = false } sequoia-openpgp = { version = "1.17.0", optional = true, default-features = false }
keyfork-bug = { version = "0.1.0", path = "../util/keyfork-bug" }

View File

@ -1,4 +1,5 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
io::{stdin, stdout, Read, Write}, io::{stdin, stdout, Read, Write},
@ -10,6 +11,7 @@ use aes_gcm::{
Aes256Gcm, KeyInit, Nonce, Aes256Gcm, KeyInit, Nonce,
}; };
use hkdf::Hkdf; use hkdf::Hkdf;
use keyfork_bug::{bug, POISONED_MUTEX};
use keyfork_mnemonic_util::{English, Mnemonic}; use keyfork_mnemonic_util::{English, Mnemonic};
use keyfork_prompt::{ use keyfork_prompt::{
validators::{mnemonic::MnemonicSetValidator, Validator}, validators::{mnemonic::MnemonicSetValidator, Validator},
@ -200,7 +202,7 @@ pub trait Format {
{ {
prompt prompt
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?; .prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
if let Ok(Some(hex)) = if let Ok(Some(hex)) =
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0) keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
@ -211,7 +213,7 @@ pub trait Format {
} else { } else {
prompt prompt
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(QRCODE_ERROR.to_string()))?; .prompt_message(PromptMessage::Text(QRCODE_ERROR.to_string()))?;
}; };
} }
@ -225,7 +227,7 @@ pub trait Format {
}; };
let [nonce_mnemonic, pubkey_mnemonic] = prompt let [nonce_mnemonic, pubkey_mnemonic] = prompt
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_validated_wordlist::<English, _>( .prompt_validated_wordlist::<English, _>(
QRCODE_COULDNT_READ, QRCODE_COULDNT_READ,
3, 3,
@ -305,7 +307,10 @@ pub trait Format {
let mut qrcode_data = our_pubkey_mnemonic.to_bytes(); let mut qrcode_data = our_pubkey_mnemonic.to_bytes();
qrcode_data.extend(payload_mnemonic.as_bytes()); qrcode_data.extend(payload_mnemonic.as_bytes());
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) { if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) {
prompt.lock().unwrap().prompt_message(PromptMessage::Text( prompt
.lock()
.expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(
concat!( concat!(
"A QR code will be displayed after this prompt. ", "A QR code will be displayed after this prompt. ",
"Send the QR code back to the operator combining the shards. ", "Send the QR code back to the operator combining the shards. ",
@ -315,14 +320,14 @@ pub trait Format {
))?; ))?;
prompt prompt
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Data(qrcode))?; .prompt_message(PromptMessage::Data(qrcode))?;
} }
} }
prompt prompt
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(format!( .prompt_message(PromptMessage::Text(format!(
"Upon request, these words should be sent: {our_pubkey_mnemonic} {payload_mnemonic}" "Upon request, these words should be sent: {our_pubkey_mnemonic} {payload_mnemonic}"
)))?; )))?;

View File

@ -1,14 +1,17 @@
//! OpenPGP Shard functionality. //! OpenPGP Shard functionality.
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::{Read, Write}, io::{Read, Write},
marker::PhantomData,
path::Path, path::Path,
str::FromStr, str::FromStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
marker::PhantomData,
}; };
use keyfork_bug::bug;
use keyfork_derive_openpgp::{ use keyfork_derive_openpgp::{
derive_util::{DerivationPath, VariableLengthSeed}, derive_util::{DerivationPath, VariableLengthSeed},
XPrv, XPrv,
@ -186,9 +189,7 @@ pub struct OpenPGP<P: PromptHandler> {
impl<P: PromptHandler> OpenPGP<P> { impl<P: PromptHandler> OpenPGP<P> {
#[allow(clippy::new_without_default, missing_docs)] #[allow(clippy::new_without_default, missing_docs)]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { p: PhantomData }
p: PhantomData,
}
} }
} }
@ -223,6 +224,8 @@ impl<P: PromptHandler> OpenPGP<P> {
} }
} }
const METADATA_MESSAGE_MISSING: &str = "Metadata message was not found in parsed packets";
impl<P: PromptHandler> Format for OpenPGP<P> { impl<P: PromptHandler> Format for OpenPGP<P> {
type Error = Error; type Error = Error;
type PublicKey = Cert; type PublicKey = Cert;
@ -235,16 +238,16 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
let seed = VariableLengthSeed::new(seed); let seed = VariableLengthSeed::new(seed);
// build cert to sign encrypted shares // build cert to sign encrypted shares
let userid = UserID::from("keyfork-sss"); let userid = UserID::from("keyfork-sss");
let path = DerivationPath::from_str("m/7366512'/0'").expect("valid derivation path"); let path = DerivationPath::from_str("m/7366512'/0'").expect(bug!("valid derivation path"));
let xprv = XPrv::new(seed) let xprv = XPrv::new(seed)
.derive_path(&path) .derive_path(&path)
.expect("valid derivation"); .expect(bug!("valid derivation"));
keyfork_derive_openpgp::derive( keyfork_derive_openpgp::derive(
xprv, xprv,
&[KeyFlags::empty().set_certification().set_signing()], &[KeyFlags::empty().set_certification().set_signing()],
&userid, &userid,
) )
.expect("valid cert creation") .expect(bug!("valid cert creation"))
} }
fn format_encrypted_header( fn format_encrypted_header(
@ -258,21 +261,26 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
// Note: Sequoia does not export private keys on a Cert, only on a TSK // Note: Sequoia does not export private keys on a Cert, only on a TSK
signing_key signing_key
.serialize(&mut pp) .serialize(&mut pp)
.expect("serialize cert into bytes"); .expect(bug!("serialize cert into bytes"));
for cert in key_data { for cert in key_data {
cert.serialize(&mut pp) cert.serialize(&mut pp)
.expect("serialize pubkey into bytes"); .expect(bug!("serialize pubkey into bytes"));
} }
// verify packet pile // verify packet pile
let mut iter = openpgp::cert::CertParser::from_bytes(&pp[SHARD_METADATA_OFFSET..]) let mut iter = openpgp::cert::CertParser::from_bytes(&pp[SHARD_METADATA_OFFSET..])
.expect("should have certs"); .expect(bug!("should have certs"));
let first_cert = iter.next().transpose().ok().flatten().expect("first cert"); let first_cert = iter
.next()
.transpose()
.ok()
.flatten()
.expect(bug!("first cert"));
assert_eq!(signing_key, &first_cert); assert_eq!(signing_key, &first_cert);
for (packet_cert, cert) in iter.zip(key_data) { for (packet_cert, cert) in iter.zip(key_data) {
assert_eq!( assert_eq!(
&packet_cert.expect("parsed packet cert"), &packet_cert.expect(bug!("parsed packet cert")),
cert, cert,
"packet pile could not recreate cert: {}", "packet pile could not recreate cert: {}",
cert.fingerprint(), cert.fingerprint(),
@ -385,7 +393,7 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
.map_err(Error::Sequoia)? .map_err(Error::Sequoia)?
.into_iter() .into_iter()
.next() .next()
.expect("serialized message should be parseable"); .expect(bug!("serialized message should be parseable"));
Ok(message) Ok(message)
} }
@ -425,7 +433,9 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
let mut encrypted_messages = encrypted_data.iter(); let mut encrypted_messages = encrypted_data.iter();
let metadata = encrypted_messages.next().expect("metdata"); let metadata = encrypted_messages
.next()
.expect(bug!(METADATA_MESSAGE_MISSING));
let metadata_content = decrypt_metadata(metadata, &policy, &mut keyring, &mut manager)?; let metadata_content = decrypt_metadata(metadata, &policy, &mut keyring, &mut manager)?;
let (threshold, root_cert, certs) = decode_metadata_v1(&metadata_content)?; let (threshold, root_cert, certs) = decode_metadata_v1(&metadata_content)?;
@ -482,7 +492,9 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
let mut encrypted_messages = encrypted_data.iter(); let mut encrypted_messages = encrypted_data.iter();
let metadata = encrypted_messages.next().expect("metadata"); let metadata = encrypted_messages
.next()
.expect(bug!(METADATA_MESSAGE_MISSING));
let metadata_content = decrypt_metadata(metadata, &policy, &mut keyring, &mut manager)?; let metadata_content = decrypt_metadata(metadata, &policy, &mut keyring, &mut manager)?;
let (threshold, root_cert, certs) = decode_metadata_v1(&metadata_content)?; let (threshold, root_cert, certs) = decode_metadata_v1(&metadata_content)?;
@ -625,7 +637,12 @@ fn decrypt_with_manager<P: PromptHandler>(
// Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message // Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
if let Some(fp) = manager.load_any_fingerprint(unused_fingerprints)? { if let Some(fp) = manager.load_any_fingerprint(unused_fingerprints)? {
let cert_keyid = cert_by_fingerprint.get(&fp).unwrap().clone(); let cert_keyid = cert_by_fingerprint
.get(&fp)
.expect(bug!(
"manager loaded fingerprint not from unused_fingerprints"
))
.clone();
if let Some(message) = messages.remove(&cert_keyid) { if let Some(message) = messages.remove(&cert_keyid) {
let message = message.decrypt_with(policy, &mut *manager)?; let message = message.decrypt_with(policy, &mut *manager)?;
decrypted_messages.insert(cert_keyid, message); decrypted_messages.insert(cert_keyid, message);

View File

@ -1,5 +1,8 @@
#![allow(clippy::expect_fun_call)]
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use keyfork_bug::{bug, POISONED_MUTEX};
use keyfork_prompt::{Error as PromptError, PromptHandler}; use keyfork_prompt::{Error as PromptError, PromptHandler};
use super::openpgp::{ use super::openpgp::{
@ -140,7 +143,7 @@ impl<P: PromptHandler> DecryptionHelper for &mut Keyring<P> {
let passphrase = self let passphrase = self
.pm .pm
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_passphrase(&message) .prompt_passphrase(&message)
.context("Decryption passphrase")?; .context("Decryption passphrase")?;
secret_key secret_key

View File

@ -1,8 +1,11 @@
#![allow(clippy::expect_fun_call)]
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use keyfork_bug::{bug, POISONED_MUTEX};
use keyfork_prompt::{ use keyfork_prompt::{
validators::{PinValidator, Validator}, validators::{PinValidator, Validator},
Error as PromptError, Message, PromptHandler, Error as PromptError, Message, PromptHandler,
@ -100,7 +103,7 @@ impl<P: PromptHandler> SmartcardManager<P> {
} }
self.pm self.pm
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(Message::Text( .prompt_message(Message::Text(
"No smart card was found. Please plug in a smart card and press enter" "No smart card was found. Please plug in a smart card and press enter"
.to_string(), .to_string(),
@ -160,7 +163,7 @@ impl<P: PromptHandler> SmartcardManager<P> {
self.pm self.pm
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(Message::Text( .prompt_message(Message::Text(
"Please plug in a smart card and press enter".to_string(), "Please plug in a smart card and press enter".to_string(),
))?; ))?;
@ -261,10 +264,10 @@ impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
} else { } else {
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ") format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
}; };
let temp_pin = self let temp_pin =
.pm self.pm
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_validated_passphrase(&message, 3, &pin_validator)?; .prompt_validated_passphrase(&message, 3, &pin_validator)?;
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim()); let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
match verification_status { match verification_status {
@ -277,7 +280,7 @@ impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => { Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => {
self.pm self.pm
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(Message::Text("Invalid PIN length entered.".to_string()))?; .prompt_message(Message::Text("Invalid PIN length entered.".to_string()))?;
} }
Err(_) => {} Err(_) => {}

View File

@ -8,13 +8,14 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = [] default = ["bin"]
bin = ["decode-backend-rqrr"] bin = ["decode-backend-rqrr"]
decode-backend-rqrr = ["dep:rqrr"] decode-backend-rqrr = ["dep:rqrr"]
decode-backend-zbar = ["dep:keyfork-zbar"] decode-backend-zbar = ["dep:keyfork-zbar"]
[dependencies] [dependencies]
image = { version = "0.24.7", default-features = false, features = ["jpeg"] } image = { version = "0.24.7", default-features = false, features = ["jpeg"] }
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug" }
keyfork-zbar = { version = "0.1.0", path = "../keyfork-zbar", optional = true } keyfork-zbar = { version = "0.1.0", path = "../keyfork-zbar", optional = true }
rqrr = { version = "0.6.0", optional = true } rqrr = { version = "0.6.0", optional = true }
thiserror = "1.0.56" thiserror = "1.0.56"

View File

@ -1,5 +1,7 @@
//! Encoding and decoding QR codes. //! Encoding and decoding QR codes.
use keyfork_bug as bug;
use image::io::Reader as ImageReader; use image::io::Reader as ImageReader;
use std::{ use std::{
io::{Cursor, Write}, io::{Cursor, Write},
@ -98,11 +100,13 @@ pub fn qrencode(
Ok(result) Ok(result)
} }
const VIDEO_FORMAT_READ_ERROR: &str = "Failed to read video device format";
/// Continuously scan the `index`-th camera for a QR code. /// Continuously scan the `index`-th camera for a QR code.
#[cfg(feature = "decode-backend-rqrr")] #[cfg(feature = "decode-backend-rqrr")]
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> { pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
let device = Device::new(index)?; let device = Device::new(index)?;
let mut fmt = device.format().expect("Failed to read format"); let mut fmt = device.format().unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR));
fmt.fourcc = FourCC::new(b"MPG1"); fmt.fourcc = FourCC::new(b"MPG1");
device.set_format(&fmt)?; device.set_format(&fmt)?;
let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?;
@ -133,7 +137,7 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QR
#[cfg(feature = "decode-backend-zbar")] #[cfg(feature = "decode-backend-zbar")]
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> { pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
let device = Device::new(index)?; let device = Device::new(index)?;
let mut fmt = device.format().expect("Failed to read format"); let mut fmt = device.format().unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR));
fmt.fourcc = FourCC::new(b"MPG1"); fmt.fourcc = FourCC::new(b"MPG1");
device.set_format(&fmt)?; device.set_format(&fmt)?;
let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?;

View File

@ -0,0 +1,8 @@
[package]
name = "keyfork-bug"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,108 @@
//! Keyfork Bug Reporting Utilities.
//!
//! # Examples
//!
//! ```rust
//! use std::{fs::File, io::Write};
//! use keyfork_bug as bug;
//!
//! let option = Some("hello world!");
//! let value = option.expect(bug::bug!("missing str value!"));
//!
//! let mut output_file = File::create("/dev/null").expect(bug::bug!("can't open /dev/null"));
//! output_file
//! .write_all(value.as_bytes())
//! .unwrap_or_else(bug::panic!("Can't write to file: {}", value));
//! ```
//!
//! ```rust,should_panic
//! use std::fs::File;
//! use keyfork_bug as bug;
//!
//! let mut output_file = File::open("/dev/nukk").expect(bug::bug!("can't open /dev/null"));
//! ```
/// The mutex was poisoned and is unusable.
pub const POISONED_MUTEX: &str = "The mutex was poisoned and is unusable";
/// Automatically generate a bug report message for Keyfork. This macro is intended to use when
/// using `Result::expect()` or `Option::expect()` to retrieve information about the callsite where
/// the bug was located.
///
/// # Examples
/// ```rust
/// use keyfork_bug::bug;
///
/// let option = Some(0u32);
/// let value = option.expect(bug!("missing u32 value!"));
/// ```
///
/// ```rust
/// use keyfork_bug::bug;
///
/// let error_message = "This is a really long error message that should not be in the macro.";
/// let option = Some(0u32);
/// let value = option.expect(bug!(error_message));
/// ```
///
/// ```rust,should_panic
/// use keyfork_bug::bug;
///
/// let option: Option<u32> = None;
/// let value = option.expect(bug!("missing u32 value!"));
/// ```
#[macro_export]
macro_rules! bug {
($input:literal) => {
concat!(
"Keyfork encountered a BUG at: [",
file!(),
":",
line!(),
":",
column!(),
"]: ",
$input,
"\n\nReport this bug to <team@distrust.co>, this behavior is unexpected!"
)
};
($input:ident) => {
format!(
concat!("Keyfork encountered a BUG at: [{file}:{line}:{column}]: {input}\n\n",
"Report this bug to <team@distrust.co>, this behavior is unexpected!"
),
file=file!(),
line=line!(),
column=column!(),
input=$input,
).as_str()
};
($($arg:tt)*) => {{
let message = format!($($arg)*);
$crate::bug!(message)
}};
}
/// Return a closure that, when called, panics with a bug report message for Keyfork. Returning a
/// closure can help handle the `clippy::expect_fun_call` lint. The closure accepts an error
/// argument, so it is suitable for being used with [`Result`] types instead of [`Option`] types.
///
/// # Examples
/// ```rust
/// use std::fs::File;
/// use keyfork_bug as bug;
///
/// let file = File::open("/dev/null").unwrap_or_else(bug::panic!("couldn't open /dev/null"));
/// ```
#[macro_export]
macro_rules! panic {
($input:literal) => { |e| {
std::panic!("{}\n{}", $crate::bug!($input), e)
}};
($input:ident) => { |e| {
std::panic!("{}\n{}", $crate::bug!($input), e)
}};
($($arg:tt)*) => { |e| {
std::panic!("{}\n{}", $crate::bug!($($arg)*), e)
}};
}

View File

@ -11,4 +11,5 @@ default = ["bin"]
bin = ["smex"] bin = ["smex"]
[dependencies] [dependencies]
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug" }
smex = { version = "0.1.0", path = "../smex", optional = true } smex = { version = "0.1.0", path = "../smex", optional = true }

View File

@ -1,5 +1,7 @@
//! Utilities for reading entropy from secure sources. //! Utilities for reading entropy from secure sources.
use keyfork_bug::bug;
use std::{ use std::{
fs::{read_dir, read_to_string, File}, fs::{read_dir, read_to_string, File},
io::Read, io::Read,
@ -9,15 +11,16 @@ static WARNING_LINKS: [&str; 1] =
["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"]; ["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"];
fn ensure_safe_kernel_version() { fn ensure_safe_kernel_version() {
let kernel_version = read_to_string("/proc/version").expect("/proc/version"); let kernel_version =
read_to_string("/proc/version").expect(bug!("Unable to open file: /proc/version"));
let v = kernel_version let v = kernel_version
.split(' ') .split(' ')
.nth(2) .nth(2)
.expect("Unable to parse kernel version") .expect(bug!("Unable to parse kernel version"))
.split('.') .split('.')
.take(2) .take(2)
.map(str::parse) .map(str::parse)
.map(|x| x.expect("Unable to parse kernel version number")) .map(|x| x.expect(bug!("Unable to parse kernel version number")))
.collect::<Vec<u32>>(); .collect::<Vec<u32>>();
let [major, minor, ..] = v.as_slice() else { let [major, minor, ..] = v.as_slice() else {
panic!("Unable to determine major and minor: {kernel_version}"); panic!("Unable to determine major and minor: {kernel_version}");
@ -30,22 +33,23 @@ fn ensure_safe_kernel_version() {
} }
fn ensure_offline() { fn ensure_offline() {
let paths = read_dir("/sys/class/net").expect("Unable to read network interfaces"); let paths = read_dir("/sys/class/net").expect(bug!("Unable to read network interfaces"));
for entry in paths { for entry in paths {
let mut path = entry.expect("Unable to read directory entry").path(); let mut path = entry.expect(bug!("Unable to read directory entry")).path();
if path if path
.as_os_str() .as_os_str()
.to_str() .to_str()
.expect("Unable to decode UTF-8 filepath") .expect(bug!("Unable to decode UTF-8 filepath"))
.split('/') .split('/')
.last() .last()
.expect("No data in file path") .expect(bug!("No data in file path"))
== "lo" == "lo"
{ {
continue; continue;
} }
path.push("operstate"); path.push("operstate");
let isup = read_to_string(&path).expect("Unable to read operstate of network interfaces"); let isup =
read_to_string(&path).expect(bug!("Unable to read operstate of network interfaces"));
assert_ne!(isup.trim(), "up", "No network interfaces should be up"); assert_ne!(isup.trim(), "up", "No network interfaces should be up");
} }
} }

View File

@ -17,6 +17,7 @@ sha2 = "0.10.7"
hmac = "0.12.1" hmac = "0.12.1"
pbkdf2 = "0.12.2" pbkdf2 = "0.12.2"
smex = { version = "0.1.0", path = "../smex", optional = true } smex = { version = "0.1.0", path = "../smex", optional = true }
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug" }
[dev-dependencies] [dev-dependencies]
bip39 = "2.0.0" bip39 = "2.0.0"

View File

@ -48,13 +48,9 @@
//! let new_mnemonic = Mnemonic::from_str(&mnemonic_text).unwrap(); //! let new_mnemonic = Mnemonic::from_str(&mnemonic_text).unwrap();
//! ``` //! ```
use std::{ use std::{error::Error, fmt::Display, marker::PhantomData, str::FromStr, sync::OnceLock};
error::Error,
fmt::Display, use keyfork_bug::bug;
str::FromStr,
sync::OnceLock,
marker::PhantomData,
};
use hmac::Hmac; use hmac::Hmac;
use pbkdf2::pbkdf2; use pbkdf2::pbkdf2;
@ -115,12 +111,11 @@ impl Wordlist for English {
fn get_singleton<'a>() -> &'a Self { fn get_singleton<'a>() -> &'a Self {
ENGLISH.get_or_init(|| { ENGLISH.get_or_init(|| {
let wordlist_file = include_str!("data/wordlist.txt"); let wordlist_file = include_str!("data/wordlist.txt");
let mut words = wordlist_file let mut words = wordlist_file.lines().skip(1).map(|x| x.trim().to_string());
.lines()
.skip(1)
.map(|x| x.trim().to_string());
English { English {
words: std::array::from_fn(|_| words.next().expect("wordlist has 2048 words")), words: std::array::from_fn(|_| {
words.next().expect(bug!("wordlist {} should have 2048 words"))
}),
} }
}) })
} }
@ -247,7 +242,10 @@ where
} }
} }
Ok(MnemonicBase { data, marker: PhantomData }) Ok(MnemonicBase {
data,
marker: PhantomData,
})
} }
} }
@ -390,7 +388,7 @@ where
let mnemonic = self.to_string(); let mnemonic = self.to_string();
let salt = ["mnemonic", passphrase.unwrap_or("")].join(""); let salt = ["mnemonic", passphrase.unwrap_or("")].join("");
pbkdf2::<Hmac<Sha512>>(mnemonic.as_bytes(), salt.as_bytes(), 2048, &mut seed) pbkdf2::<Hmac<Sha512>>(mnemonic.as_bytes(), salt.as_bytes(), 2048, &mut seed)
.expect("HmacSha512 InvalidLength should be infallible"); .expect(bug!("HmacSha512 InvalidLength should be infallible"));
seed.to_vec() seed.to_vec()
} }
@ -415,13 +413,16 @@ where
} }
// TODO: find a way to not have to collect to vec // TODO: find a way to not have to collect to vec
bits.chunks_exact(11).peekable().map(|chunk| { bits.chunks_exact(11)
.peekable()
.map(|chunk| {
let mut num = 0usize; let mut num = 0usize;
for i in 0..11 { for i in 0..11 {
num += usize::from(chunk[10 - i]) << i; num += usize::from(chunk[10 - i]) << i;
} }
num num
}).collect() })
.collect()
} }
} }

View File

@ -13,6 +13,7 @@ default = ["mnemonic"]
mnemonic = ["keyfork-mnemonic-util"] mnemonic = ["keyfork-mnemonic-util"]
[dependencies] [dependencies]
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug" }
keyfork-crossterm = { version = "0.27.1", path = "../keyfork-crossterm", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"] } keyfork-crossterm = { version = "0.27.1", path = "../keyfork-crossterm", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"] }
keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util", optional = true } keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util", optional = true }
thiserror = "1.0.51" thiserror = "1.0.51"

View File

@ -13,6 +13,8 @@ use keyfork_crossterm::{
ExecutableCommand, QueueableCommand, ExecutableCommand, QueueableCommand,
}; };
use keyfork_bug::bug;
use crate::{Error, Message, PromptHandler, Wordlist}; use crate::{Error, Message, PromptHandler, Wordlist};
#[allow(missing_docs)] #[allow(missing_docs)]
@ -120,9 +122,9 @@ where
W: Write + AsRawFd, W: Write + AsRawFd,
{ {
fn drop(&mut self) { fn drop(&mut self) {
self.write.execute(DisableBracketedPaste).unwrap(); self.write.execute(DisableBracketedPaste).expect(bug!("can't restore bracketed paste"));
self.write.execute(LeaveAlternateScreen).unwrap(); self.write.execute(LeaveAlternateScreen).expect(bug!("can't leave alternate screen"));
self.terminal.disable_raw_mode().unwrap(); self.terminal.disable_raw_mode().expect(bug!("can't disable raw mode"));
} }
} }

View File

@ -80,6 +80,7 @@ pub mod mnemonic {
use super::Validator; use super::Validator;
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError}; use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError};
use keyfork_bug::bug;
/// A mnemonic could not be validated from the given input. /// A mnemonic could not be validated from the given input.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -237,7 +238,7 @@ pub mod mnemonic {
Ok(output Ok(output
.try_into() .try_into()
.expect("vec with capacity of const N was not filled")) .expect(bug!("vec with capacity of const N was not filled")))
}) })
} }
} }