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,24 +307,27 @@ 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(
concat!(
"A QR code will be displayed after this prompt. ",
"Send the QR code back to the operator combining the shards. ",
"Nobody else should scan this QR code."
)
.to_string(),
))?;
prompt prompt
.lock() .lock()
.unwrap() .expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(
concat!(
"A QR code will be displayed after this prompt. ",
"Send the QR code back to the operator combining the shards. ",
"Nobody else should scan this QR code."
)
.to_string(),
))?;
prompt
.lock()
.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,11 +264,11 @@ 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 {
#[allow(clippy::ignored_unit_patterns)] #[allow(clippy::ignored_unit_patterns)]
@ -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)
let mut num = 0usize; .peekable()
for i in 0..11 { .map(|chunk| {
num += usize::from(chunk[10 - i]) << i; let mut num = 0usize;
} for i in 0..11 {
num num += usize::from(chunk[10 - i]) << i;
}).collect() }
num
})
.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")))
}) })
} }
} }