diff --git a/Cargo.lock b/Cargo.lock index 2e707bd..03f9ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1830,7 +1830,7 @@ dependencies = [ [[package]] name = "keyfork-shard" -version = "0.1.0" +version = "0.2.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/crates/keyfork-shard/Cargo.toml b/crates/keyfork-shard/Cargo.toml index 6cdd0c0..8066277 100644 --- a/crates/keyfork-shard/Cargo.toml +++ b/crates/keyfork-shard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keyfork-shard" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "AGPL-3.0-only" diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index 7aeb2a3..338aaea 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -7,14 +7,17 @@ use std::{ }; use aes_gcm::{ - aead::{consts::U12, Aead, AeadCore, OsRng}, + aead::{consts::U12, Aead}, 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}, + validators::{ + mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength}, + Validator, + }, Message as PromptMessage, PromptHandler, Terminal, }; use sha2::Sha256; @@ -194,7 +197,6 @@ pub trait Format { let encrypted_messages = self.parse_shard_file(reader)?; // establish AES-256-GCM key via ECDH - let mut nonce_data: Option<[u8; 12]> = None; let mut pubkey_data: Option<[u8; 32]> = None; // receive remote data via scanning QR code from camera @@ -207,9 +209,8 @@ pub trait Format { if let Ok(Some(hex)) = keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0) { - let decoded_data = smex::decode(&hex)?; - nonce_data = Some(decoded_data[..12].try_into().map_err(|_| InvalidData)?); - pubkey_data = Some(decoded_data[12..].try_into().map_err(|_| InvalidData)?) + let decoded_data = smex::decode(hex)?; + pubkey_data = Some(decoded_data.try_into().map_err(|_| InvalidData)?) } else { prompt .lock() @@ -219,30 +220,23 @@ pub trait Format { } // if QR code scanning failed or was unavailable, read from a set of mnemonics - let (nonce, their_pubkey) = match (nonce_data, pubkey_data) { - (Some(nonce), Some(pubkey)) => (nonce, pubkey), - _ => { - let validator = MnemonicSetValidator { - word_lengths: [9, 24], + let their_pubkey = match pubkey_data { + Some(pubkey) => pubkey, + None => { + let validator = MnemonicValidator { + word_length: Some(WordLength::Count(24)), }; - let [nonce_mnemonic, pubkey_mnemonic] = prompt + prompt .lock() .expect(bug!(POISONED_MUTEX)) .prompt_validated_wordlist::( QRCODE_COULDNT_READ, 3, validator.to_fn(), - )?; - - let nonce = nonce_mnemonic + )? .as_bytes() .try_into() - .map_err(|_| InvalidData)?; - let pubkey = pubkey_mnemonic - .as_bytes() - .try_into() - .map_err(|_| InvalidData)?; - (nonce, pubkey) + .map_err(|_| InvalidData)? } }; @@ -253,9 +247,14 @@ pub trait Format { .diffie_hellman(&PublicKey::from(their_pubkey)) .to_bytes(); let hkdf = Hkdf::::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]; + 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::::from_slice(&nonce_data); // decrypt a single shard and create the payload let (share, threshold) = @@ -269,7 +268,6 @@ pub trait Format { ); // encrypt data - let nonce = Nonce::::from_slice(&nonce); let payload_bytes = shared_key.encrypt(nonce, payload.as_slice())?; // convert data to a static-size payload @@ -432,16 +430,13 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box 0) { 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 key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; #[cfg(feature = "qrcode")] { use keyfork_qrcode::{qrencode, ErrorCorrection}; - let mut qrcode_data = nonce_mnemonic.to_bytes(); - qrcode_data.extend(key_mnemonic.as_bytes()); + let qrcode_data = key_mnemonic.to_bytes(); if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) { pm.prompt_message(PromptMessage::Text(format!( concat!( @@ -458,10 +453,9 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box Result<(), Box::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 = - shared_key.decrypt(&nonce, &payload[..payload[payload.len() - 1] as usize])?; + let mut shared_key_data = [0u8; 256 / 8]; + 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::::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"); match &mut iter_count { diff --git a/crates/keyfork/Cargo.toml b/crates/keyfork/Cargo.toml index bc3bda2..d54db2e 100644 --- a/crates/keyfork/Cargo.toml +++ b/crates/keyfork/Cargo.toml @@ -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-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-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" } clap = { version = "4.4.2", features = ["derive", "env", "wrap_help"] }