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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
use crate::{DerivationIndex, DerivationPath, ExtendedPublicKey, PrivateKey, PublicKey};
use keyfork_bug::bug;
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha512;
@ -124,7 +126,7 @@ mod serde_with {
let variable_len_bytes = <&[u8]>::deserialize(deserializer)?;
let bytes: [u8; 32] = variable_len_bytes
.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))
}
}
@ -171,7 +173,7 @@ where
fn new_internal(seed: &[u8]) -> Self {
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)
.finalize()
.into_bytes();
@ -180,10 +182,10 @@ where
Self::new_from_parts(
private_key
.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,
// 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 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() {
hmac.update(&[0]);
hmac.update(&self.private_key.to_bytes());
@ -423,7 +425,7 @@ where
.derive_child(
&private_key
.try_into()
.expect("Invalid length for private key"),
.expect(bug!("Invalid length for private key")),
)
.map_err(|_| Error::Derivation)?;
@ -432,7 +434,7 @@ where
depth,
chain_code: chain_code
.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 thiserror::Error;
use keyfork_bug::bug;
const KEY_SIZE: usize = 256;
/// 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 derived_key = self
.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)?;
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 {
public_key: derived_key,

View File

@ -2,6 +2,8 @@ use crate::PublicKey;
use thiserror::Error;
use keyfork_bug::bug;
pub(crate) type PrivateKeyBytes = [u8; 32];
/// Functions required to use an `ExtendedPrivateKey`.
@ -115,7 +117,7 @@ impl PrivateKey for k256::SecretKey {
}
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 {
@ -134,13 +136,13 @@ impl PrivateKey for k256::SecretKey {
let other = *other;
// Checked: See above nonzero check
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();
Ok(
Option::<NonZeroScalar>::from(NonZeroScalar::new(derived_scalar))
.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 thiserror::Error;
use keyfork_bug::bug;
pub(crate) type PublicKeyBytes = [u8; 33];
/// Functions required to use an `ExtendedPublicKey`.
@ -63,7 +65,7 @@ pub trait PublicKey: Sized {
// Note: Safety assured by type returned from Ripemd160
hash[..4]
.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
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);
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)]
pub fn from_bytes(b: &[u8]) -> Self {
Self {
key: b.try_into().unwrap(),
key: b
.try_into()
.expect(bug!("invalid size when constructing 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 = { version = "0.4.0", optional = true }
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")]
#![allow(clippy::expect_fun_call)]
use std::{
io::{stdin, stdout, Read, Write},
@ -10,6 +11,7 @@ use aes_gcm::{
Aes256Gcm, KeyInit, Nonce,
};
use hkdf::Hkdf;
use keyfork_bug::{bug, POISONED_MUTEX};
use keyfork_mnemonic_util::{English, Mnemonic};
use keyfork_prompt::{
validators::{mnemonic::MnemonicSetValidator, Validator},
@ -200,7 +202,7 @@ pub trait Format {
{
prompt
.lock()
.unwrap()
.expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
if let Ok(Some(hex)) =
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
@ -211,7 +213,7 @@ pub trait Format {
} else {
prompt
.lock()
.unwrap()
.expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(QRCODE_ERROR.to_string()))?;
};
}
@ -225,7 +227,7 @@ pub trait Format {
};
let [nonce_mnemonic, pubkey_mnemonic] = prompt
.lock()
.unwrap()
.expect(bug!(POISONED_MUTEX))
.prompt_validated_wordlist::<English, _>(
QRCODE_COULDNT_READ,
3,
@ -305,7 +307,10 @@ pub trait Format {
let mut qrcode_data = our_pubkey_mnemonic.to_bytes();
qrcode_data.extend(payload_mnemonic.as_bytes());
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!(
"A QR code will be displayed after this prompt. ",
"Send the QR code back to the operator combining the shards. ",
@ -315,14 +320,14 @@ pub trait Format {
))?;
prompt
.lock()
.unwrap()
.expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Data(qrcode))?;
}
}
prompt
.lock()
.unwrap()
.expect(bug!(POISONED_MUTEX))
.prompt_message(PromptMessage::Text(format!(
"Upon request, these words should be sent: {our_pubkey_mnemonic} {payload_mnemonic}"
)))?;

View File

@ -1,14 +1,17 @@
//! OpenPGP Shard functionality.
#![allow(clippy::expect_fun_call)]
use std::{
collections::HashMap,
io::{Read, Write},
marker::PhantomData,
path::Path,
str::FromStr,
sync::{Arc, Mutex},
marker::PhantomData,
};
use keyfork_bug::bug;
use keyfork_derive_openpgp::{
derive_util::{DerivationPath, VariableLengthSeed},
XPrv,
@ -186,9 +189,7 @@ pub struct OpenPGP<P: PromptHandler> {
impl<P: PromptHandler> OpenPGP<P> {
#[allow(clippy::new_without_default, missing_docs)]
pub fn new() -> Self {
Self {
p: PhantomData,
}
Self { 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> {
type Error = Error;
type PublicKey = Cert;
@ -235,16 +238,16 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
let seed = VariableLengthSeed::new(seed);
// build cert to sign encrypted shares
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)
.derive_path(&path)
.expect("valid derivation");
.expect(bug!("valid derivation"));
keyfork_derive_openpgp::derive(
xprv,
&[KeyFlags::empty().set_certification().set_signing()],
&userid,
)
.expect("valid cert creation")
.expect(bug!("valid cert creation"))
}
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
signing_key
.serialize(&mut pp)
.expect("serialize cert into bytes");
.expect(bug!("serialize cert into bytes"));
for cert in key_data {
cert.serialize(&mut pp)
.expect("serialize pubkey into bytes");
.expect(bug!("serialize pubkey into bytes"));
}
// verify packet pile
let mut iter = openpgp::cert::CertParser::from_bytes(&pp[SHARD_METADATA_OFFSET..])
.expect("should have certs");
let first_cert = iter.next().transpose().ok().flatten().expect("first cert");
.expect(bug!("should have certs"));
let first_cert = iter
.next()
.transpose()
.ok()
.flatten()
.expect(bug!("first cert"));
assert_eq!(signing_key, &first_cert);
for (packet_cert, cert) in iter.zip(key_data) {
assert_eq!(
&packet_cert.expect("parsed packet cert"),
&packet_cert.expect(bug!("parsed packet cert")),
cert,
"packet pile could not recreate cert: {}",
cert.fingerprint(),
@ -385,7 +393,7 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
.map_err(Error::Sequoia)?
.into_iter()
.next()
.expect("serialized message should be parseable");
.expect(bug!("serialized message should be parseable"));
Ok(message)
}
@ -425,7 +433,9 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
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 (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 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 (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
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) {
let message = message.decrypt_with(policy, &mut *manager)?;
decrypted_messages.insert(cert_keyid, message);

View File

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

View File

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

View File

@ -1,5 +1,7 @@
//! Encoding and decoding QR codes.
use keyfork_bug as bug;
use image::io::Reader as ImageReader;
use std::{
io::{Cursor, Write},
@ -98,11 +100,13 @@ pub fn qrencode(
Ok(result)
}
const VIDEO_FORMAT_READ_ERROR: &str = "Failed to read video device format";
/// Continuously scan the `index`-th camera for a QR code.
#[cfg(feature = "decode-backend-rqrr")]
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
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");
device.set_format(&fmt)?;
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")]
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
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");
device.set_format(&fmt)?;
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"]
[dependencies]
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug" }
smex = { version = "0.1.0", path = "../smex", optional = true }

View File

@ -1,5 +1,7 @@
//! Utilities for reading entropy from secure sources.
use keyfork_bug::bug;
use std::{
fs::{read_dir, read_to_string, File},
io::Read,
@ -9,15 +11,16 @@ static WARNING_LINKS: [&str; 1] =
["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"];
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
.split(' ')
.nth(2)
.expect("Unable to parse kernel version")
.expect(bug!("Unable to parse kernel version"))
.split('.')
.take(2)
.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>>();
let [major, minor, ..] = v.as_slice() else {
panic!("Unable to determine major and minor: {kernel_version}");
@ -30,22 +33,23 @@ fn ensure_safe_kernel_version() {
}
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 {
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
.as_os_str()
.to_str()
.expect("Unable to decode UTF-8 filepath")
.expect(bug!("Unable to decode UTF-8 filepath"))
.split('/')
.last()
.expect("No data in file path")
.expect(bug!("No data in file path"))
== "lo"
{
continue;
}
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");
}
}

View File

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

View File

@ -48,13 +48,9 @@
//! let new_mnemonic = Mnemonic::from_str(&mnemonic_text).unwrap();
//! ```
use std::{
error::Error,
fmt::Display,
str::FromStr,
sync::OnceLock,
marker::PhantomData,
};
use std::{error::Error, fmt::Display, marker::PhantomData, str::FromStr, sync::OnceLock};
use keyfork_bug::bug;
use hmac::Hmac;
use pbkdf2::pbkdf2;
@ -115,12 +111,11 @@ impl Wordlist for English {
fn get_singleton<'a>() -> &'a Self {
ENGLISH.get_or_init(|| {
let wordlist_file = include_str!("data/wordlist.txt");
let mut words = wordlist_file
.lines()
.skip(1)
.map(|x| x.trim().to_string());
let mut words = wordlist_file.lines().skip(1).map(|x| x.trim().to_string());
English {
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 salt = ["mnemonic", passphrase.unwrap_or("")].join("");
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()
}
@ -415,13 +413,16 @@ where
}
// 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;
for i in 0..11 {
num += usize::from(chunk[10 - i]) << i;
}
num
}).collect()
})
.collect()
}
}

View File

@ -13,6 +13,7 @@ default = ["mnemonic"]
mnemonic = ["keyfork-mnemonic-util"]
[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-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util", optional = true }
thiserror = "1.0.51"

View File

@ -13,6 +13,8 @@ use keyfork_crossterm::{
ExecutableCommand, QueueableCommand,
};
use keyfork_bug::bug;
use crate::{Error, Message, PromptHandler, Wordlist};
#[allow(missing_docs)]
@ -120,9 +122,9 @@ where
W: Write + AsRawFd,
{
fn drop(&mut self) {
self.write.execute(DisableBracketedPaste).unwrap();
self.write.execute(LeaveAlternateScreen).unwrap();
self.terminal.disable_raw_mode().unwrap();
self.write.execute(DisableBracketedPaste).expect(bug!("can't restore bracketed paste"));
self.write.execute(LeaveAlternateScreen).expect(bug!("can't leave alternate screen"));
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 keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError};
use keyfork_bug::bug;
/// A mnemonic could not be validated from the given input.
#[derive(thiserror::Error, Debug)]
@ -237,7 +238,7 @@ pub mod mnemonic {
Ok(output
.try_into()
.expect("vec with capacity of const N was not filled"))
.expect(bug!("vec with capacity of const N was not filled")))
})
}
}