Compare commits
11 Commits
4da3eadcca
...
08a66e2365
Author | SHA1 | Date |
---|---|---|
Ryan Heywood | 08a66e2365 | |
Ryan Heywood | 6fa434e89c | |
Ryan Heywood | 68f07f6f02 | |
Ryan Heywood | 9394500f2f | |
Ryan Heywood | 2bca0a1580 | |
Ryan Heywood | 5438f4e111 | |
Ryan Heywood | 71b6e4ed0c | |
Ryan Heywood | 4f4e3cfc65 | |
Ryan Heywood | 194d475d59 | |
Ryan Heywood | 40551a5c26 | |
Ryan Heywood | fa125e7cbe |
|
@ -341,6 +341,12 @@ version = "0.21.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1497,9 +1503,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.59"
|
version = "0.1.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
|
@ -1758,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",
|
||||||
|
@ -1776,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",
|
||||||
|
@ -1818,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",
|
||||||
|
@ -1830,10 +1836,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork-shard"
|
name = "keyfork-shard"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64 0.22.0",
|
||||||
"card-backend",
|
"card-backend",
|
||||||
"card-backend-pcsc",
|
"card-backend-pcsc",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
|
@ -1879,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",
|
||||||
|
@ -2103,9 +2110,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.10"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -2886,7 +2893,7 @@ dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
"block-padding",
|
"block-padding",
|
||||||
"blowfish",
|
"blowfish",
|
||||||
"buffered-reader",
|
"buffered-reader",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keyfork-shard"
|
name = "keyfork-shard"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
|
|
||||||
|
@ -37,3 +37,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 }
|
||||||
|
base64 = "0.22.0"
|
||||||
|
|
|
@ -7,22 +7,28 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use aes_gcm::{
|
use aes_gcm::{
|
||||||
aead::{consts::U12, Aead, AeadCore, OsRng},
|
aead::{consts::U12, Aead},
|
||||||
Aes256Gcm, KeyInit, Nonce,
|
Aes256Gcm, KeyInit, Nonce,
|
||||||
};
|
};
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
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, MnemonicValidator, WordLength},
|
||||||
|
Validator,
|
||||||
|
},
|
||||||
Message as PromptMessage, PromptHandler, Terminal,
|
Message as PromptMessage, PromptHandler, Terminal,
|
||||||
};
|
};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use sharks::{Share, Sharks};
|
use sharks::{Share, Sharks};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
use base64::prelude::{BASE64_STANDARD, Engine};
|
||||||
|
|
||||||
// 256 bit share encrypted is 49 bytes, couple more bytes before we reach max size
|
// 32-byte share, 1-byte index, 1-byte threshold, 1-byte version == 36 bytes
|
||||||
const ENC_LEN: u8 = 4 * 16;
|
// Encrypted, is 52 bytes
|
||||||
|
const PLAINTEXT_LENGTH: u8 = 36;
|
||||||
|
const ENCRYPTED_LENGTH: u8 = PLAINTEXT_LENGTH + 16;
|
||||||
|
|
||||||
#[cfg(feature = "openpgp")]
|
#[cfg(feature = "openpgp")]
|
||||||
pub mod openpgp;
|
pub mod openpgp;
|
||||||
|
@ -194,7 +200,6 @@ pub trait Format {
|
||||||
let encrypted_messages = self.parse_shard_file(reader)?;
|
let encrypted_messages = self.parse_shard_file(reader)?;
|
||||||
|
|
||||||
// establish AES-256-GCM key via ECDH
|
// establish AES-256-GCM key via ECDH
|
||||||
let mut nonce_data: Option<[u8; 12]> = None;
|
|
||||||
let mut pubkey_data: Option<[u8; 32]> = None;
|
let mut pubkey_data: Option<[u8; 32]> = None;
|
||||||
|
|
||||||
// receive remote data via scanning QR code from camera
|
// receive remote data via scanning QR code from camera
|
||||||
|
@ -204,12 +209,11 @@ pub trait Format {
|
||||||
.lock()
|
.lock()
|
||||||
.expect(bug!(POISONED_MUTEX))
|
.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(qrcode_content)) =
|
||||||
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
|
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
|
||||||
{
|
{
|
||||||
let decoded_data = smex::decode(&hex)?;
|
let decoded_data = BASE64_STANDARD.decode(qrcode_content).unwrap();
|
||||||
nonce_data = Some(decoded_data[..12].try_into().map_err(|_| InvalidData)?);
|
pubkey_data = Some(decoded_data.try_into().map_err(|_| InvalidData)?)
|
||||||
pubkey_data = Some(decoded_data[12..].try_into().map_err(|_| InvalidData)?)
|
|
||||||
} else {
|
} else {
|
||||||
prompt
|
prompt
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -219,30 +223,23 @@ pub trait Format {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if QR code scanning failed or was unavailable, read from a set of mnemonics
|
// if QR code scanning failed or was unavailable, read from a set of mnemonics
|
||||||
let (nonce, their_pubkey) = match (nonce_data, pubkey_data) {
|
let their_pubkey = match pubkey_data {
|
||||||
(Some(nonce), Some(pubkey)) => (nonce, pubkey),
|
Some(pubkey) => pubkey,
|
||||||
_ => {
|
None => {
|
||||||
let validator = MnemonicSetValidator {
|
let validator = MnemonicValidator {
|
||||||
word_lengths: [9, 24],
|
word_length: Some(WordLength::Count(24)),
|
||||||
};
|
};
|
||||||
let [nonce_mnemonic, pubkey_mnemonic] = prompt
|
prompt
|
||||||
.lock()
|
.lock()
|
||||||
.expect(bug!(POISONED_MUTEX))
|
.expect(bug!(POISONED_MUTEX))
|
||||||
.prompt_validated_wordlist::<English, _>(
|
.prompt_validated_wordlist::<English, _>(
|
||||||
QRCODE_COULDNT_READ,
|
QRCODE_COULDNT_READ,
|
||||||
3,
|
3,
|
||||||
validator.to_fn(),
|
validator.to_fn(),
|
||||||
)?;
|
)?
|
||||||
|
|
||||||
let nonce = nonce_mnemonic
|
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| InvalidData)?;
|
.map_err(|_| InvalidData)?
|
||||||
let pubkey = pubkey_mnemonic
|
|
||||||
.as_bytes()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| InvalidData)?;
|
|
||||||
(nonce, pubkey)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -253,9 +250,14 @@ pub trait Format {
|
||||||
.diffie_hellman(&PublicKey::from(their_pubkey))
|
.diffie_hellman(&PublicKey::from(their_pubkey))
|
||||||
.to_bytes();
|
.to_bytes();
|
||||||
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
||||||
let mut hkdf_output = [0u8; 256 / 8];
|
|
||||||
hkdf.expand(&[], &mut hkdf_output)?;
|
let mut shared_key_data = [0u8; 256 / 8];
|
||||||
let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?;
|
hkdf.expand(b"key", &mut shared_key_data)?;
|
||||||
|
let shared_key = Aes256Gcm::new_from_slice(&shared_key_data)?;
|
||||||
|
|
||||||
|
let mut nonce_data = [0u8; 12];
|
||||||
|
hkdf.expand(b"nonce", &mut nonce_data)?;
|
||||||
|
let nonce = Nonce::<U12>::from_slice(&nonce_data);
|
||||||
|
|
||||||
// decrypt a single shard and create the payload
|
// decrypt a single shard and create the payload
|
||||||
let (share, threshold) =
|
let (share, threshold) =
|
||||||
|
@ -264,49 +266,41 @@ pub trait Format {
|
||||||
payload.insert(0, HUNK_VERSION);
|
payload.insert(0, HUNK_VERSION);
|
||||||
payload.insert(1, threshold);
|
payload.insert(1, threshold);
|
||||||
assert!(
|
assert!(
|
||||||
payload.len() <= ENC_LEN as usize,
|
payload.len() < PLAINTEXT_LENGTH as usize,
|
||||||
"invalid share length (too long, max {ENC_LEN} bytes)"
|
"invalid share length (too long, max {PLAINTEXT_LENGTH} bytes)"
|
||||||
);
|
);
|
||||||
|
|
||||||
// encrypt data
|
// convert plaintext to static-size payload
|
||||||
let nonce = Nonce::<U12>::from_slice(&nonce);
|
|
||||||
let payload_bytes = shared_key.encrypt(nonce, payload.as_slice())?;
|
|
||||||
|
|
||||||
// convert data to a static-size payload
|
|
||||||
// NOTE: Padding length is less than u8::MAX because ENC_LEN < u8::MAX
|
|
||||||
#[allow(clippy::assertions_on_constants)]
|
#[allow(clippy::assertions_on_constants)]
|
||||||
{
|
{
|
||||||
assert!(ENC_LEN < u8::MAX, "padding byte can be u8");
|
assert!(PLAINTEXT_LENGTH < u8::MAX, "length byte can be u8");
|
||||||
}
|
}
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
let mut out_bytes = [payload_bytes.len() as u8; ENC_LEN as usize];
|
|
||||||
assert!(
|
|
||||||
payload_bytes.len() < out_bytes.len(),
|
|
||||||
"encrypted payload larger than acceptable limit"
|
|
||||||
);
|
|
||||||
out_bytes[..payload_bytes.len()].clone_from_slice(&payload_bytes);
|
|
||||||
|
|
||||||
// NOTE: This previously used a single repeated value as the padding byte, but resulted in
|
// NOTE: Previous versions of Keyfork Shard would modify the padding bytes to avoid
|
||||||
// difficulty when entering in prompts manually, as one's place could be lost due to
|
// duplicate mnemonic words. This version does not include that, and instead uses a
|
||||||
// repeated keywords. This is resolved below by having sequentially increasing numbers up to
|
// repeated length byte.
|
||||||
// but not including the last byte.
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
for (i, byte) in (out_bytes[payload_bytes.len()..(ENC_LEN as usize - 1)])
|
let mut plaintext_bytes = [u8::try_from(payload.len()).expect(bug!(
|
||||||
.iter_mut()
|
"previously asserted length must be < {PLAINTEXT_LENGTH}",
|
||||||
.enumerate()
|
PLAINTEXT_LENGTH = PLAINTEXT_LENGTH
|
||||||
{
|
)); PLAINTEXT_LENGTH as usize];
|
||||||
*byte = (i % u8::MAX as usize) as u8;
|
plaintext_bytes[..payload.len()].clone_from_slice(&payload);
|
||||||
}
|
|
||||||
|
// encrypt data
|
||||||
|
let encrypted_bytes = shared_key.encrypt(nonce, plaintext_bytes.as_slice())?;
|
||||||
|
|
||||||
|
assert_eq!(encrypted_bytes.len(), ENCRYPTED_LENGTH as usize);
|
||||||
|
|
||||||
// safety: size of out_bytes is constant and always % 4 == 0
|
// safety: size of out_bytes is constant and always % 4 == 0
|
||||||
let payload_mnemonic = unsafe { Mnemonic::from_raw_bytes(&out_bytes) };
|
let payload_mnemonic = unsafe { Mnemonic::from_raw_bytes(&encrypted_bytes) };
|
||||||
|
dbg!(payload_mnemonic.words().len());
|
||||||
|
|
||||||
#[cfg(feature = "qrcode")]
|
#[cfg(feature = "qrcode")]
|
||||||
{
|
{
|
||||||
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
||||||
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(&BASE64_STANDARD.encode(qrcode_data), ErrorCorrection::Highest) {
|
||||||
prompt
|
prompt
|
||||||
.lock()
|
.lock()
|
||||||
.expect(bug!(POISONED_MUTEX))
|
.expect(bug!(POISONED_MUTEX))
|
||||||
|
@ -401,7 +395,7 @@ pub struct InvalidData;
|
||||||
/// 1 byte: Version
|
/// 1 byte: Version
|
||||||
/// 1 byte: Threshold
|
/// 1 byte: Threshold
|
||||||
/// Data: &[u8]
|
/// Data: &[u8]
|
||||||
pub(crate) const HUNK_VERSION: u8 = 1;
|
pub(crate) const HUNK_VERSION: u8 = 2;
|
||||||
pub(crate) const HUNK_OFFSET: usize = 2;
|
pub(crate) const HUNK_OFFSET: usize = 2;
|
||||||
|
|
||||||
const QRCODE_PROMPT: &str = "Press enter, then present QR code to camera.";
|
const QRCODE_PROMPT: &str = "Press enter, then present QR code to camera.";
|
||||||
|
@ -432,17 +426,14 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
|
|
||||||
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
|
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
|
||||||
iter += 1;
|
iter += 1;
|
||||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
|
||||||
let nonce_mnemonic = unsafe { Mnemonic::from_raw_bytes(nonce.as_slice()) };
|
|
||||||
let our_key = EphemeralSecret::random();
|
let our_key = EphemeralSecret::random();
|
||||||
let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
|
let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
|
||||||
|
|
||||||
#[cfg(feature = "qrcode")]
|
#[cfg(feature = "qrcode")]
|
||||||
{
|
{
|
||||||
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
||||||
let mut qrcode_data = nonce_mnemonic.to_bytes();
|
let qrcode_data = key_mnemonic.to_bytes();
|
||||||
qrcode_data.extend(key_mnemonic.as_bytes());
|
if let Ok(qrcode) = qrencode(&BASE64_STANDARD.encode(qrcode_data), ErrorCorrection::Highest) {
|
||||||
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) {
|
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
concat!(
|
concat!(
|
||||||
"A QR code will be displayed after this prompt. ",
|
"A QR code will be displayed after this prompt. ",
|
||||||
|
@ -458,10 +449,9 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
concat!(
|
concat!(
|
||||||
"Upon request, these words should be sent to shardholder {iter}: ",
|
"Upon request, these words should be sent to shardholder {iter}: ",
|
||||||
"{nonce_mnemonic} {key_mnemonic}"
|
"{key_mnemonic}"
|
||||||
),
|
),
|
||||||
iter = iter,
|
iter = iter,
|
||||||
nonce_mnemonic = nonce_mnemonic,
|
|
||||||
key_mnemonic = key_mnemonic,
|
key_mnemonic = key_mnemonic,
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
|
@ -471,10 +461,10 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
#[cfg(feature = "qrcode")]
|
#[cfg(feature = "qrcode")]
|
||||||
{
|
{
|
||||||
pm.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
|
pm.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
|
||||||
if let Ok(Some(hex)) =
|
if let Ok(Some(qrcode_content)) =
|
||||||
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(QRCODE_TIMEOUT), 0)
|
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(QRCODE_TIMEOUT), 0)
|
||||||
{
|
{
|
||||||
let decoded_data = smex::decode(&hex)?;
|
let decoded_data = BASE64_STANDARD.decode(qrcode_content).unwrap();
|
||||||
let _ = pubkey_data.insert(decoded_data[..32].try_into().map_err(|_| InvalidData)?);
|
let _ = pubkey_data.insert(decoded_data[..32].try_into().map_err(|_| InvalidData)?);
|
||||||
let _ = payload_data.insert(decoded_data[32..].to_vec());
|
let _ = payload_data.insert(decoded_data[32..].to_vec());
|
||||||
} else {
|
} else {
|
||||||
|
@ -486,7 +476,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
(Some(pubkey), Some(payload)) => (pubkey, payload),
|
(Some(pubkey), Some(payload)) => (pubkey, payload),
|
||||||
_ => {
|
_ => {
|
||||||
let validator = MnemonicSetValidator {
|
let validator = MnemonicSetValidator {
|
||||||
word_lengths: [24, 48],
|
word_lengths: [24, 39],
|
||||||
};
|
};
|
||||||
|
|
||||||
let [pubkey_mnemonic, payload_mnemonic] = pm
|
let [pubkey_mnemonic, payload_mnemonic] = pm
|
||||||
|
@ -504,14 +494,24 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
payload.len(),
|
||||||
|
ENCRYPTED_LENGTH as usize,
|
||||||
|
bug!("invalid payload data")
|
||||||
|
);
|
||||||
|
|
||||||
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)).to_bytes();
|
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)).to_bytes();
|
||||||
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
||||||
let mut hkdf_output = [0u8; 256 / 8];
|
|
||||||
hkdf.expand(&[], &mut hkdf_output)?;
|
|
||||||
let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?;
|
|
||||||
|
|
||||||
let payload =
|
let mut shared_key_data = [0u8; 256 / 8];
|
||||||
shared_key.decrypt(&nonce, &payload[..payload[payload.len() - 1] as usize])?;
|
hkdf.expand(b"key", &mut shared_key_data)?;
|
||||||
|
let shared_key = Aes256Gcm::new_from_slice(&shared_key_data)?;
|
||||||
|
|
||||||
|
let mut nonce_data = [0u8; 12];
|
||||||
|
hkdf.expand(b"nonce", &mut nonce_data)?;
|
||||||
|
let nonce = Nonce::<U12>::from_slice(&nonce_data);
|
||||||
|
|
||||||
|
let payload = shared_key.decrypt(nonce, payload.as_slice())?;
|
||||||
assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version");
|
assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version");
|
||||||
|
|
||||||
match &mut iter_count {
|
match &mut iter_count {
|
||||||
|
@ -526,7 +526,8 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shares.push(payload[HUNK_OFFSET..].to_vec());
|
let payload_len = payload.last().expect(bug!("payload should not be empty"));
|
||||||
|
shares.push(payload[HUNK_OFFSET..usize::from(*payload_len)].to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
let shares = shares
|
let shares = shares
|
||||||
|
|
|
@ -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}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -32,7 +32,7 @@ keyfork-entropy = { version = "0.1.0", path = "../util/keyfork-entropy", registr
|
||||||
keyfork-mnemonic-util = { version = "0.2.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" }
|
keyfork-mnemonic-util = { version = "0.2.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" }
|
||||||
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" }
|
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" }
|
||||||
keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" }
|
keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" }
|
||||||
keyfork-shard = { version = "0.1.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" }
|
keyfork-shard = { version = "0.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" }
|
||||||
smex = { version = "0.1.0", path = "../util/smex", registry = "distrust" }
|
smex = { version = "0.1.0", path = "../util/smex", registry = "distrust" }
|
||||||
|
|
||||||
clap = { version = "4.4.2", features = ["derive", "env", "wrap_help"] }
|
clap = { version = "4.4.2", features = ["derive", "env", "wrap_help"] }
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
Loading…
Reference in New Issue