Compare commits

..

No commits in common. "f957b489b93e7ae5efbf6eee87466d917c08e6f3" and "f96ad114220c7519559a26fa950d318ea914246c" have entirely different histories.

4 changed files with 43 additions and 41 deletions

10
Cargo.lock generated
View File

@ -1497,9 +1497,9 @@ dependencies = [
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.60" version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"core-foundation-sys", "core-foundation-sys",
@ -1830,7 +1830,7 @@ dependencies = [
[[package]] [[package]]
name = "keyfork-shard" name = "keyfork-shard"
version = "0.2.0" version = "0.1.0"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
@ -2103,9 +2103,9 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.11" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",

View File

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

View File

@ -7,17 +7,14 @@ use std::{
}; };
use aes_gcm::{ use aes_gcm::{
aead::{consts::U12, Aead}, aead::{consts::U12, Aead, AeadCore, OsRng},
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::{ validators::{mnemonic::MnemonicSetValidator, Validator},
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
Validator,
},
Message as PromptMessage, PromptHandler, Terminal, Message as PromptMessage, PromptHandler, Terminal,
}; };
use sha2::Sha256; use sha2::Sha256;
@ -197,6 +194,7 @@ 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
@ -209,8 +207,9 @@ pub trait Format {
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)
{ {
let decoded_data = smex::decode(hex)?; let decoded_data = smex::decode(&hex)?;
pubkey_data = Some(decoded_data.try_into().map_err(|_| InvalidData)?) nonce_data = Some(decoded_data[..12].try_into().map_err(|_| InvalidData)?);
pubkey_data = Some(decoded_data[12..].try_into().map_err(|_| InvalidData)?)
} else { } else {
prompt prompt
.lock() .lock()
@ -220,23 +219,30 @@ 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 their_pubkey = match pubkey_data { let (nonce, their_pubkey) = match (nonce_data, pubkey_data) {
Some(pubkey) => pubkey, (Some(nonce), Some(pubkey)) => (nonce, pubkey),
None => { _ => {
let validator = MnemonicValidator { let validator = MnemonicSetValidator {
word_length: Some(WordLength::Count(24)), word_lengths: [9, 24],
}; };
prompt let [nonce_mnemonic, pubkey_mnemonic] = 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)
} }
}; };
@ -247,14 +253,9 @@ 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];
let mut shared_key_data = [0u8; 256 / 8]; hkdf.expand(&[], &mut hkdf_output)?;
hkdf.expand(b"key", &mut shared_key_data)?; let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?;
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) =
@ -268,6 +269,7 @@ pub trait Format {
); );
// encrypt data // encrypt data
let nonce = Nonce::<U12>::from_slice(&nonce);
let payload_bytes = shared_key.encrypt(nonce, payload.as_slice())?; let payload_bytes = shared_key.encrypt(nonce, payload.as_slice())?;
// convert data to a static-size payload // convert data to a static-size payload
@ -430,13 +432,16 @@ 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 qrcode_data = key_mnemonic.to_bytes(); let mut qrcode_data = nonce_mnemonic.to_bytes();
qrcode_data.extend(key_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) {
pm.prompt_message(PromptMessage::Text(format!( pm.prompt_message(PromptMessage::Text(format!(
concat!( concat!(
@ -453,9 +458,10 @@ 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}: ",
"{key_mnemonic}" "{nonce_mnemonic} {key_mnemonic}"
), ),
iter = iter, iter = iter,
nonce_mnemonic = nonce_mnemonic,
key_mnemonic = key_mnemonic, key_mnemonic = key_mnemonic,
)))?; )))?;
@ -500,16 +506,12 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
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 mut shared_key_data = [0u8; 256 / 8]; let payload =
hkdf.expand(b"key", &mut shared_key_data)?; shared_key.decrypt(&nonce, &payload[..payload[payload.len() - 1] as usize])?;
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[..payload[payload.len() - 1] as usize])?;
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 {

View File

@ -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.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" } keyfork-shard = { version = "0.1.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"] }