Compare commits

..

11 Commits

17 changed files with 92 additions and 46 deletions

8
Cargo.lock generated
View File

@ -1764,7 +1764,7 @@ dependencies = [
[[package]] [[package]]
name = "keyfork-derive-util" name = "keyfork-derive-util"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"digest", "digest",
"ed25519-dalek", "ed25519-dalek",
@ -1782,7 +1782,7 @@ dependencies = [
[[package]] [[package]]
name = "keyfork-entropy" name = "keyfork-entropy"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"keyfork-bug", "keyfork-bug",
"smex", "smex",
@ -1824,7 +1824,7 @@ dependencies = [
[[package]] [[package]]
name = "keyfork-qrcode" name = "keyfork-qrcode"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"image", "image",
"keyfork-bug", "keyfork-bug",
@ -1886,7 +1886,7 @@ dependencies = [
[[package]] [[package]]
name = "keyforkd" name = "keyforkd"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"bincode", "bincode",
"hex-literal", "hex-literal",

View File

@ -25,6 +25,9 @@ fn secp256k1_test_suite() {
if chain_len < 2 { if chain_len < 2 {
continue; continue;
} }
if chain.iter().take(2).any(|index| !index.is_hardened()) {
continue;
}
// Consistency check: ensure the server and the client can each derive the same // Consistency check: ensure the server and the client can each derive the same
// key using an XPrv, for all but the last XPrv, which is verified after this // key using an XPrv, for all but the last XPrv, which is verified after this
for i in 2..chain_len { for i in 2..chain_len {

View File

@ -43,6 +43,10 @@ pub enum DerivationError {
#[error("Invalid derivation length: Expected at least 2, actual: {0}")] #[error("Invalid derivation length: Expected at least 2, actual: {0}")]
InvalidDerivationLength(usize), InvalidDerivationLength(usize),
/// The derivation request did not use hardened derivation on the 2 highest indexes.
#[error("Invalid derivation paths: expected index #{0} (1) to be hardened")]
InvalidDerivationPath(usize, u32),
/// An error occurred while deriving data. /// An error occurred while deriving data.
#[error("Derivation error: {0}")] #[error("Derivation error: {0}")]
Derivation(String), Derivation(String),

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyforkd" name = "keyforkd"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"

View File

@ -69,6 +69,18 @@ impl Service<Request> for Keyforkd {
return Err(DerivationError::InvalidDerivationLength(len).into()); return Err(DerivationError::InvalidDerivationLength(len).into());
} }
if let Some((i, unhardened_index)) = req
.path()
.iter()
.take(2)
.enumerate()
.find(|(_, index)| {
!index.is_hardened()
})
{
return Err(DerivationError::InvalidDerivationPath(i, unhardened_index.inner()).into())
}
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
if let Some(target) = guess_target(req.path()) { if let Some(target) = guess_target(req.path()) {
info!("Deriving path: {target}"); info!("Deriving path: {target}");
@ -110,6 +122,9 @@ mod tests {
if chain.len() < 2 { if chain.len() < 2 {
continue; continue;
} }
if chain.iter().take(2).any(|index| !index.is_hardened()) {
continue;
}
let req = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain); let req = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
let response: DerivationResponse = keyforkd let response: DerivationResponse = keyforkd
.ready() .ready()

View File

@ -61,7 +61,7 @@ where
)); ));
let socket_dir = tempfile::tempdir().expect(bug!("can't create tempdir")); 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 { let result = rt.block_on(async move {
let (tx, mut rx) = tokio::sync::mpsc::channel(1); let (tx, mut rx) = tokio::sync::mpsc::channel(1);
let server_handle = tokio::spawn({ let server_handle = tokio::spawn({
let socket_path = socket_path.clone(); let socket_path = socket_path.clone();
@ -87,8 +87,13 @@ where
let result = test_handle.await; let result = test_handle.await;
server_handle.abort(); server_handle.abort();
result result
}) });
.expect(bug!("runtime could not join all threads")) if let Err(e) = result {
if e.is_panic() {
std::panic::resume_unwind(e.into_panic());
}
}
Ok(())
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyfork-derive-util" name = "keyfork-derive-util"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -11,8 +11,8 @@ const KEY_SIZE: usize = 256;
/// Errors associated with creating or deriving Extended Public Keys. /// Errors associated with creating or deriving Extended Public Keys.
#[derive(Error, Clone, Debug)] #[derive(Error, Clone, Debug)]
pub enum Error { pub enum Error {
/// BIP-0032 does not support deriving public keys from hardened private keys. /// BIP-0032 does not support hardened public key derivation from parent public keys.
#[error("Public keys may not be derived when hardened")] #[error("Hardened child public keys may not be derived from parent public keys")]
HardenedIndex, HardenedIndex,
/// The maximum depth for key derivation has been reached. The supported maximum depth is 255. /// The maximum depth for key derivation has been reached. The supported maximum depth is 255.

View File

@ -85,7 +85,7 @@ pub trait PrivateKey: Sized {
/// # Errors /// # Errors
/// ///
/// An error may be returned if: /// An error may be returned if:
/// * A nonzero `other` is provided. /// * An all-zero `other` is provided.
/// * An error specific to the given algorithm was encountered. /// * An error specific to the given algorithm was encountered.
fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err>; fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err>;
@ -180,7 +180,6 @@ impl PrivateKey for ed25519_dalek::SigningKey {
use crate::public_key::TestPublicKey; use crate::public_key::TestPublicKey;
#[doc(hidden)]
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct TestPrivateKey { pub struct TestPrivateKey {
key: [u8; 32], key: [u8; 32],

View File

@ -42,7 +42,7 @@ pub trait PublicKey: Sized {
/// # Errors /// # Errors
/// ///
/// An error may be returned if: /// An error may be returned if:
/// * A nonzero `other` is provided. /// * An all-zero `other` is provided.
/// * An error specific to the given algorithm was encountered. /// * An error specific to the given algorithm was encountered.
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self, Self::Err>; fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self, Self::Err>;
@ -142,7 +142,6 @@ impl PublicKey for VerifyingKey {
} }
} }
#[doc(hidden)]
#[derive(Clone)] #[derive(Clone)]
pub struct TestPublicKey { pub struct TestPublicKey {
pub(crate) key: [u8; 33], pub(crate) key: [u8; 33],

View File

@ -57,7 +57,7 @@ pub enum DerivationAlgorithm {
#[allow(missing_docs)] #[allow(missing_docs)]
Secp256k1, Secp256k1,
#[doc(hidden)] #[doc(hidden)]
Internal, TestAlgorithm,
} }
impl DerivationAlgorithm { impl DerivationAlgorithm {
@ -86,7 +86,7 @@ impl DerivationAlgorithm {
&derived_key, &derived_key,
)) ))
} }
Self::Internal => { Self::TestAlgorithm => {
let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed); let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
@ -120,7 +120,7 @@ pub trait AsAlgorithm: PrivateKey {
impl AsAlgorithm for TestPrivateKey { impl AsAlgorithm for TestPrivateKey {
fn as_algorithm() -> DerivationAlgorithm { fn as_algorithm() -> DerivationAlgorithm {
DerivationAlgorithm::Internal DerivationAlgorithm::TestAlgorithm
} }
} }
@ -144,7 +144,7 @@ impl DerivationRequest {
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let algo: DerivationAlgorithm = // /// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal; /// # DerivationAlgorithm::TestAlgorithm;
/// let path: DerivationPath = // /// let path: DerivationPath = //
/// # DerivationPath::default(); /// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path); /// let request = DerivationRequest::new(algo, &path);
@ -169,7 +169,7 @@ impl DerivationRequest {
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let algo: DerivationAlgorithm = // /// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal; /// # DerivationAlgorithm::TestAlgorithm;
/// let path: DerivationPath = // /// let path: DerivationPath = //
/// # DerivationPath::default(); /// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path); /// let request = DerivationRequest::new(algo, &path);
@ -199,7 +199,7 @@ impl DerivationRequest {
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
/// # )?; /// # )?;
/// let algo: DerivationAlgorithm = // /// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal; /// # DerivationAlgorithm::TestAlgorithm;
/// let path: DerivationPath = // /// let path: DerivationPath = //
/// # DerivationPath::default(); /// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path); /// let request = DerivationRequest::new(algo, &path);
@ -228,7 +228,7 @@ impl DerivationRequest {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let algo: DerivationAlgorithm = // /// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal; /// # DerivationAlgorithm::TestAlgorithm;
/// let path: DerivationPath = // /// let path: DerivationPath = //
/// # DerivationPath::default(); /// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path); /// let request = DerivationRequest::new(algo, &path);

View File

@ -84,13 +84,23 @@ impl<P: PromptHandler> VerificationHelper for &mut Keyring<P> {
aead_algo, aead_algo,
} => {} } => {}
MessageLayer::SignatureGroup { results } => { MessageLayer::SignatureGroup { results } => {
for result in results { match &results[..] {
if let Err(e) = result { [Ok(_)] => {
return Ok(());
}
_ => {
// FIXME: anyhow leak: VerificationError impl std::error::Error // FIXME: anyhow leak: VerificationError impl std::error::Error
// return Err(e.context("Invalid signature")); // return Err(e.context("Invalid signature"));
return Err(anyhow::anyhow!("Error validating signature; either multiple signatures were passed or the single signature was not valid"));
}
}
/*
for result in results {
if let Err(e) = result {
return Err(anyhow::anyhow!("Invalid signature: {e}")); return Err(anyhow::anyhow!("Invalid signature: {e}"));
} }
} }
*/
} }
} }
} }

View File

@ -193,12 +193,23 @@ impl<P: PromptHandler> VerificationHelper for &mut SmartcardManager<P> {
aead_algo, aead_algo,
} => {} } => {}
MessageLayer::SignatureGroup { results } => { MessageLayer::SignatureGroup { results } => {
match &results[..] {
[Ok(_)] => {
return Ok(());
}
_ => {
// FIXME: anyhow leak: VerificationError impl std::error::Error
// return Err(e.context("Invalid signature"));
return Err(anyhow::anyhow!("Error validating signature; either multiple signatures were passed or the single signature was not valid"));
}
}
/*
for result in results { for result in results {
if let Err(e) = result { if let Err(e) = result {
// FIXME: anyhow leak return Err(anyhow::anyhow!("Invalid signature: {e}"));
return Err(anyhow::anyhow!("Verification error: {}", e.to_string()));
} }
} }
*/
} }
} }
} }
@ -264,8 +275,8 @@ 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 = let temp_pin = self
self.pm .pm
.lock() .lock()
.expect(bug!(POISONED_MUTEX)) .expect(bug!(POISONED_MUTEX))
.prompt_validated_passphrase(&message, 3, &pin_validator)?; .prompt_validated_passphrase(&message, 3, &pin_validator)?;

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyfork-qrcode" name = "keyfork-qrcode"
version = "0.1.0" version = "0.1.1"
repository = "https://git.distrust.co/public/keyfork" repository = "https://git.distrust.co/public/keyfork"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -5,7 +5,7 @@ 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},
time::{Duration, SystemTime}, time::{Duration, Instant},
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use v4l::{ use v4l::{
@ -110,11 +110,10 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QR
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)?;
let start = SystemTime::now(); let start = Instant::now();
while SystemTime::now() while Instant::now()
.duration_since(start) .duration_since(start)
.unwrap_or(Duration::from_secs(0))
< timeout < timeout
{ {
let (buffer, _) = stream.next()?; let (buffer, _) = stream.next()?;
@ -141,12 +140,11 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QR
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)?;
let start = SystemTime::now(); let start = Instant::now();
let mut scanner = keyfork_zbar::image_scanner::ImageScanner::new(); let mut scanner = keyfork_zbar::image_scanner::ImageScanner::new();
while SystemTime::now() while Instant::now()
.duration_since(start) .duration_since(start)
.unwrap_or(Duration::from_secs(0))
< timeout < timeout
{ {
let (buffer, _) = stream.next()?; let (buffer, _) = stream.next()?;

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyfork-entropy" name = "keyfork-entropy"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -10,10 +10,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
bit_size % 8 == 0, bit_size % 8 == 0,
"Bit size must be divisible by 8, got: {bit_size}" "Bit size must be divisible by 8, got: {bit_size}"
); );
assert!( match bit_size {
bit_size <= 256, 128 | 256 | 512 => {}
"Maximum supported bit size is 256, got: {bit_size}" _ => {
); eprintln!("reading entropy of uncommon size: {bit_size}");
}
}
let entropy = keyfork_entropy::generate_entropy_of_size(bit_size / 8)?; let entropy = keyfork_entropy::generate_entropy_of_size(bit_size / 8)?;
println!("{}", smex::encode(entropy)); println!("{}", smex::encode(entropy));