From d7f33874f69f8a4b5f377e02be77d5c7272a083d Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 29 Dec 2023 16:23:04 -0500 Subject: [PATCH] keyfork-shard: move code from bin file to library --- .../src/bin/keyfork-shard-decrypt-openpgp.rs | 76 ++-------------- keyfork-shard/src/openpgp.rs | 86 ++++++++++++++++++- 2 files changed, 90 insertions(+), 72 deletions(-) diff --git a/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs b/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs index 9211186..015dcd1 100644 --- a/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs +++ b/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs @@ -1,18 +1,11 @@ use std::{ env, fs::File, - io::{stdin, stdout}, path::{Path, PathBuf}, process::ExitCode, - str::FromStr, }; -use aes_gcm::{aead::Aead, aes::cipher::consts::U12, Aes256Gcm, KeyInit, Nonce}; -use x25519_dalek::{EphemeralSecret, PublicKey}; - -use keyfork_mnemonic_util::{Mnemonic, Wordlist}; -use keyfork_prompt::{PromptManager, Message}; -use keyfork_shard::openpgp::{decrypt_one, discover_certs, openpgp::Cert, parse_messages}; +use keyfork_shard::openpgp::{decrypt, discover_certs, openpgp::Cert, parse_messages}; type Result> = std::result::Result; @@ -48,68 +41,11 @@ fn run() -> Result<()> { .pop_front() .expect("any pgp encrypted message"); - // Receive their key - - let mut pm = PromptManager::new(stdin(), stdout())?; - let wordlist = Wordlist::default(); - let their_words = pm.prompt_wordlist("Their words: ", &wordlist)?; - let mut nonce_words = their_words - .split_whitespace() - .take(9) - .peekable(); - let mut pubkey_words = their_words - .split_whitespace() - .skip(9) - .take(24) - .peekable(); - let mut nonce_mnemonic = String::new(); - let mut pubkey_mnemonic = String::new(); - while let Some(word) = nonce_words.next() { - nonce_mnemonic.push_str(word); - if nonce_words.peek().is_some() { - nonce_mnemonic.push(' '); - } - } - while let Some(word) = pubkey_words.next() { - pubkey_mnemonic.push_str(word); - if pubkey_words.peek().is_some() { - pubkey_mnemonic.push(' '); - } - } - let their_key = Mnemonic::from_str(&pubkey_mnemonic)?.entropy(); - let their_key: [u8; 32] = their_key.try_into().expect("24 words"); - let their_nonce = Mnemonic::from_str(&nonce_mnemonic)?.entropy(); - let their_nonce = Nonce::::from_slice(&their_nonce); - - let our_key = EphemeralSecret::random(); - let our_mnemonic = - Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?; - // TODO: Encode using `qrencode -t ansiutf8 -m 2` - pm.prompt_message(Message::Text(format!("Our words: {our_mnemonic}")))?; - - let shared_secret = our_key - .diffie_hellman(&PublicKey::from(their_key)) - .to_bytes(); - - let share = decrypt_one(encrypted_messages.into(), &cert_list, encrypted_metadata)?; - assert!(share.len() <= 65, "invalid share length (too long)"); - const LEN: u8 = 24 * 3; - let mut encrypted_payload = [(LEN - share.len() as u8); LEN as usize]; - encrypted_payload[..share.len()].copy_from_slice(&share); - - let shared_key = Aes256Gcm::new_from_slice(&shared_secret)?; - let bytes = shared_key.encrypt(their_nonce, share.as_slice()).unwrap(); - - // NOTE: Padding length is less than u8::MAX because 24 * 4 < u8::MAX - const ENC_LEN: u8 = 24 * 4; - let mut out_bytes = [(ENC_LEN - bytes.len() as u8); ENC_LEN as usize]; - assert!(bytes.len() < out_bytes.len(), "encrypted payload larger than acceptable limit"); - out_bytes[..bytes.len()].clone_from_slice(&bytes); - - // safety: size of out_bytes is constant and always % 4 == 0 - let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) }; - - pm.prompt_message(Message::Text(format!("Our payload: {mnemonic}")))?; + decrypt( + &cert_list, + encrypted_metadata, + encrypted_messages.make_contiguous(), + )?; Ok(()) } diff --git a/keyfork-shard/src/openpgp.rs b/keyfork-shard/src/openpgp.rs index 020a29c..dea5ade 100644 --- a/keyfork-shard/src/openpgp.rs +++ b/keyfork-shard/src/openpgp.rs @@ -1,14 +1,20 @@ use std::{ collections::{HashMap, VecDeque}, - io::{Read, Write}, + io::{stdin, stdout, Read, Write}, path::Path, str::FromStr, }; +use aes_gcm::{ + aead::{consts::U12, Aead}, + Aes256Gcm, KeyInit, Nonce, +}; use keyfork_derive_openpgp::derive_util::{ request::{DerivationAlgorithm, DerivationRequest}, DerivationPath, }; +use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist}; +use keyfork_prompt::{Error as PromptError, Message as PromptMessage, PromptManager}; use openpgp::{ armor::{Kind, Writer}, cert::{Cert, CertParser, ValidCert}, @@ -33,6 +39,7 @@ use keyring::Keyring; mod smartcard; use smartcard::SmartcardManager; +use x25519_dalek::{EphemeralSecret, PublicKey}; /// Shard metadata verson 1: /// 1 byte: Version @@ -64,6 +71,15 @@ pub enum Error { #[error("Smartcard error: {0}")] Smartcard(#[from] smartcard::Error), + #[error("Prompt error: {0}")] + Prompt(#[from] PromptError), + + #[error("Mnemonic generation error: {0}")] + MnemonicGeneration(#[from] MnemonicGenerationError), + + #[error("Mnemonic parse error: {0}")] + MnemonicFromStr(#[from] MnemonicFromStrError), + #[error("IO error: {0}")] Io(#[source] std::io::Error), @@ -323,7 +339,7 @@ fn decrypt_metadata( }) } -pub fn decrypt_one( +fn decrypt_one( messages: Vec, certs: &[Cert], metadata: EncryptedMessage, @@ -358,6 +374,72 @@ pub fn decrypt_one( unreachable!("smartcard manager should always decrypt") } +pub fn decrypt( + certs: &[Cert], + metadata: EncryptedMessage, + encrypted_messages: &[EncryptedMessage], +) -> Result<()> { + let mut pm = PromptManager::new(stdin(), stdout())?; + let wordlist = Wordlist::default(); + let their_words = pm.prompt_wordlist("Their words: ", &wordlist)?; + let mut nonce_words = their_words.split_whitespace().take(9).peekable(); + let mut pubkey_words = their_words.split_whitespace().skip(9).take(24).peekable(); + let mut nonce_mnemonic = String::new(); + let mut pubkey_mnemonic = String::new(); + while let Some(word) = nonce_words.next() { + nonce_mnemonic.push_str(word); + if nonce_words.peek().is_some() { + nonce_mnemonic.push(' '); + } + } + while let Some(word) = pubkey_words.next() { + pubkey_mnemonic.push_str(word); + if pubkey_words.peek().is_some() { + pubkey_mnemonic.push(' '); + } + } + let their_key = Mnemonic::from_str(&pubkey_mnemonic)?.entropy(); + let their_key: [u8; 32] = their_key.try_into().expect("24 words"); + let their_nonce = Mnemonic::from_str(&nonce_mnemonic)?.entropy(); + let their_nonce = Nonce::::from_slice(&their_nonce); + + let our_key = EphemeralSecret::random(); + let our_mnemonic = + Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?; + // TODO: Encode using `qrencode -t ansiutf8 -m 2` + pm.prompt_message(PromptMessage::Text(format!("Our words: {our_mnemonic}")))?; + + let shared_secret = our_key + .diffie_hellman(&PublicKey::from(their_key)) + .to_bytes(); + + let share = decrypt_one(encrypted_messages.to_vec(), &certs, metadata)?; + assert!(share.len() <= 65, "invalid share length (too long)"); + const LEN: u8 = 24 * 3; + let mut encrypted_payload = [(LEN - share.len() as u8); LEN as usize]; + encrypted_payload[..share.len()].copy_from_slice(&share); + + let shared_key = + Aes256Gcm::new_from_slice(&shared_secret).expect("Invalid length of constant key size"); + let bytes = shared_key.encrypt(their_nonce, share.as_slice()).unwrap(); + + // NOTE: Padding length is less than u8::MAX because 24 * 4 < u8::MAX + const ENC_LEN: u8 = 24 * 4; + let mut out_bytes = [(ENC_LEN - bytes.len() as u8); ENC_LEN as usize]; + assert!( + bytes.len() < out_bytes.len(), + "encrypted payload larger than acceptable limit" + ); + out_bytes[..bytes.len()].clone_from_slice(&bytes); + + // safety: size of out_bytes is constant and always % 4 == 0 + let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) }; + + pm.prompt_message(PromptMessage::Text(format!("Our payload: {mnemonic}")))?; + + Ok(()) +} + pub fn combine( certs: Vec, metadata: EncryptedMessage,