keyfork-shard: move code from bin file to library

This commit is contained in:
Ryan Heywood 2023-12-29 16:23:04 -05:00
parent f157a8c954
commit d7f33874f6
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
2 changed files with 90 additions and 72 deletions

View File

@ -1,18 +1,11 @@
use std::{ use std::{
env, env,
fs::File, fs::File,
io::{stdin, stdout},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::ExitCode, process::ExitCode,
str::FromStr,
}; };
use aes_gcm::{aead::Aead, aes::cipher::consts::U12, Aes256Gcm, KeyInit, Nonce}; use keyfork_shard::openpgp::{decrypt, discover_certs, openpgp::Cert, parse_messages};
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};
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>; type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -48,68 +41,11 @@ fn run() -> Result<()> {
.pop_front() .pop_front()
.expect("any pgp encrypted message"); .expect("any pgp encrypted message");
// Receive their key decrypt(
&cert_list,
let mut pm = PromptManager::new(stdin(), stdout())?; encrypted_metadata,
let wordlist = Wordlist::default(); encrypted_messages.make_contiguous(),
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::<U12>::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}")))?;
Ok(()) Ok(())
} }

View File

@ -1,14 +1,20 @@
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
io::{Read, Write}, io::{stdin, stdout, Read, Write},
path::Path, path::Path,
str::FromStr, str::FromStr,
}; };
use aes_gcm::{
aead::{consts::U12, Aead},
Aes256Gcm, KeyInit, Nonce,
};
use keyfork_derive_openpgp::derive_util::{ use keyfork_derive_openpgp::derive_util::{
request::{DerivationAlgorithm, DerivationRequest}, request::{DerivationAlgorithm, DerivationRequest},
DerivationPath, DerivationPath,
}; };
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist};
use keyfork_prompt::{Error as PromptError, Message as PromptMessage, PromptManager};
use openpgp::{ use openpgp::{
armor::{Kind, Writer}, armor::{Kind, Writer},
cert::{Cert, CertParser, ValidCert}, cert::{Cert, CertParser, ValidCert},
@ -33,6 +39,7 @@ use keyring::Keyring;
mod smartcard; mod smartcard;
use smartcard::SmartcardManager; use smartcard::SmartcardManager;
use x25519_dalek::{EphemeralSecret, PublicKey};
/// Shard metadata verson 1: /// Shard metadata verson 1:
/// 1 byte: Version /// 1 byte: Version
@ -64,6 +71,15 @@ pub enum Error {
#[error("Smartcard error: {0}")] #[error("Smartcard error: {0}")]
Smartcard(#[from] smartcard::Error), 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}")] #[error("IO error: {0}")]
Io(#[source] std::io::Error), Io(#[source] std::io::Error),
@ -323,7 +339,7 @@ fn decrypt_metadata(
}) })
} }
pub fn decrypt_one( fn decrypt_one(
messages: Vec<EncryptedMessage>, messages: Vec<EncryptedMessage>,
certs: &[Cert], certs: &[Cert],
metadata: EncryptedMessage, metadata: EncryptedMessage,
@ -358,6 +374,72 @@ pub fn decrypt_one(
unreachable!("smartcard manager should always decrypt") 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::<U12>::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( pub fn combine(
certs: Vec<Cert>, certs: Vec<Cert>,
metadata: EncryptedMessage, metadata: EncryptedMessage,