keyfork-shard: move code from bin file to library
This commit is contained in:
parent
f157a8c954
commit
d7f33874f6
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue