Compare commits
No commits in common. "7eeb494819ea84e3a35970097b9870f7cd244901" and "6c25cb8f31886697640eeb6b629c28aa5e2d6678" have entirely different histories.
7eeb494819
...
6c25cb8f31
|
@ -17,41 +17,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.4.7"
|
||||
|
@ -376,16 +341,6 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
|
@ -526,19 +481,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.0"
|
||||
|
@ -838,16 +783,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.0"
|
||||
|
@ -968,15 +903,6 @@ dependencies = [
|
|||
"hashbrown 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.31.0"
|
||||
|
@ -1160,13 +1086,11 @@ dependencies = [
|
|||
name = "keyfork-shard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"card-backend",
|
||||
"card-backend-pcsc",
|
||||
"keyfork-derive-openpgp",
|
||||
"keyfork-mnemonic-util",
|
||||
"keyfork-prompt",
|
||||
"openpgp-card",
|
||||
"openpgp-card-sequoia",
|
||||
|
@ -1175,7 +1099,6 @@ dependencies = [
|
|||
"sharks",
|
||||
"smex",
|
||||
"thiserror",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1503,12 +1426,6 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openpgp-card"
|
||||
version = "0.4.0"
|
||||
|
@ -1707,18 +1624,6 @@ version = "3.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
|
@ -2535,16 +2440,6 @@ version = "0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
@ -2798,18 +2693,6 @@ version = "0.48.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.7"
|
||||
|
|
|
@ -149,8 +149,7 @@ impl FromStr for Mnemonic {
|
|||
None => return Err(MnemonicFromStrError::InvalidWord(index)),
|
||||
}
|
||||
}
|
||||
// 3 words for every 32 bits
|
||||
if words.len() % 3 != 0 {
|
||||
if ![12, 24].contains(&words.len()) {
|
||||
return Err(MnemonicFromStrError::InvalidWordCount(words.len()));
|
||||
}
|
||||
Ok(Mnemonic { words, wordlist })
|
||||
|
|
|
@ -6,7 +6,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
input.read_line(&mut line)?;
|
||||
let decoded = smex::decode(line.trim())?;
|
||||
|
||||
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&decoded, Default::default()) };
|
||||
let mnemonic = Mnemonic::from_entropy(&decoded, Default::default())?;
|
||||
|
||||
println!("{mnemonic}");
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use keyfork_mnemonic_util::Wordlist;
|
|||
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{read, Event, KeyCode, KeyModifiers},
|
||||
event::{read, Event, KeyCode},
|
||||
style::{Print, PrintStyledContent, Stylize},
|
||||
terminal,
|
||||
tty::IsTty,
|
||||
|
@ -110,9 +110,6 @@ where
|
|||
KeyCode::Backspace => {
|
||||
input.pop();
|
||||
}
|
||||
KeyCode::Char('w') if k.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
while input.pop().is_some_and(|c| !c.is_whitespace()) {}
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
if !input.chars().rev().next().is_some_and(char::is_whitespace) {
|
||||
input.push(' ');
|
||||
|
@ -238,8 +235,6 @@ where
|
|||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||
let mut terminal = RawMode::new(&mut terminal)?;
|
||||
|
||||
loop {
|
||||
// TODO: split on word boundaries
|
||||
terminal
|
||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||
.queue(cursor::MoveTo(0, 0))?;
|
||||
|
@ -259,6 +254,7 @@ where
|
|||
.queue(PrintStyledContent(" OK ".negative()))?
|
||||
.flush()?;
|
||||
|
||||
loop {
|
||||
match read()? {
|
||||
Event::Key(k) => match k.code {
|
||||
KeyCode::Enter | KeyCode::Char(' ') => break,
|
||||
|
@ -266,8 +262,6 @@ where
|
|||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
terminal.queue(cursor::EnableBlinking)?.flush()?;
|
||||
Ok(())
|
||||
|
|
|
@ -26,6 +26,3 @@ sharks = "0.5.0"
|
|||
smex = { version = "0.1.0", path = "../smex" }
|
||||
thiserror = "1.0.50"
|
||||
keyfork-prompt = { version = "0.1.0", path = "../keyfork-prompt", optional = true }
|
||||
x25519-dalek = { version = "2.0.0", features = ["getrandom"] }
|
||||
keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util" }
|
||||
aes-gcm = "0.10.3"
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
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;
|
||||
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>;
|
||||
|
||||
fn validate<'a>(
|
||||
messages_file: impl AsRef<Path>,
|
||||
key_discovery: impl Into<Option<&'a str>>,
|
||||
) -> Result<(File, Vec<Cert>)> {
|
||||
let key_discovery = key_discovery.into().map(PathBuf::from);
|
||||
key_discovery.as_ref().map(std::fs::metadata).transpose()?;
|
||||
|
||||
// Load certs from path
|
||||
let certs = key_discovery
|
||||
.map(discover_certs)
|
||||
.transpose()?
|
||||
.unwrap_or(vec![]);
|
||||
|
||||
Ok((File::open(messages_file)?, certs))
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let mut args = env::args();
|
||||
let program_name = args.next().expect("program name");
|
||||
let args = args.collect::<Vec<_>>();
|
||||
let (messages_file, cert_list) = match args.as_slice() {
|
||||
[messages_file, key_discovery] => validate(messages_file, key_discovery.as_str())?,
|
||||
[messages_file] => validate(messages_file, None)?,
|
||||
_ => panic!("Usage: {program_name} messages_file [key_discovery]"),
|
||||
};
|
||||
|
||||
let mut encrypted_messages = parse_messages(messages_file)?;
|
||||
|
||||
let encrypted_metadata = encrypted_messages
|
||||
.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::<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(&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_eq!(share.len(), 65, "non-constant share length");
|
||||
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();
|
||||
|
||||
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 immutable and always % 32 == 0
|
||||
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) };
|
||||
|
||||
pm.prompt_message(&format!("Our payload: {mnemonic}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let result = run();
|
||||
if let Err(e) = result {
|
||||
eprintln!("Error: {e}");
|
||||
let mut source = e.source();
|
||||
while let Some(new_error) = source.take() {
|
||||
eprintln!("Source: {new_error}");
|
||||
source = new_error.source();
|
||||
}
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
ExitCode::SUCCESS
|
||||
}
|
|
@ -205,159 +205,6 @@ fn get_decryption_keys<'a>(
|
|||
.secret()
|
||||
}
|
||||
|
||||
fn decode_metadata_v1(buf: &[u8]) -> Result<(u8, Cert, Vec<Cert>)> {
|
||||
assert_eq!(
|
||||
SHARD_METADATA_VERSION, buf[0],
|
||||
"Incompatible metadata version"
|
||||
);
|
||||
let threshold = buf[1];
|
||||
|
||||
let mut cert_parser =
|
||||
CertParser::from_bytes(&buf[SHARD_METADATA_OFFSET..]).map_err(Error::Sequoia)?;
|
||||
|
||||
let root_cert = match cert_parser.next() {
|
||||
Some(Ok(c)) => c,
|
||||
Some(Err(e)) => return Err(Error::Sequoia(e)),
|
||||
None => panic!("No data found"),
|
||||
};
|
||||
|
||||
let certs = cert_parser
|
||||
.collect::<openpgp::Result<Vec<_>>>()
|
||||
.map_err(Error::Sequoia)?;
|
||||
|
||||
Ok((threshold, root_cert, certs))
|
||||
}
|
||||
|
||||
// NOTE: When using single-decryptor mechanism, use this method with `threshold = 1` to return a
|
||||
// single message.
|
||||
fn decrypt_with_manager(
|
||||
threshold: u8,
|
||||
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
||||
certs: &[Cert],
|
||||
policy: NullPolicy,
|
||||
manager: &mut SmartcardManager,
|
||||
) -> Result<HashMap<KeyID, Vec<u8>>> {
|
||||
let mut decrypted_messages = HashMap::new();
|
||||
|
||||
while threshold as usize - decrypted_messages.len() > 0 {
|
||||
// Build list of fingerprints that haven't yet been used for decrypting
|
||||
let mut cert_by_fingerprint = HashMap::new();
|
||||
let mut unused_fingerprints = vec![];
|
||||
for valid_cert in certs
|
||||
.iter()
|
||||
.filter(|cert| !decrypted_messages.contains_key(&cert.keyid()))
|
||||
.map(|cert| cert.with_policy(&policy, None))
|
||||
{
|
||||
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
|
||||
let fp = valid_cert
|
||||
.keys()
|
||||
.for_storage_encryption()
|
||||
.map(|k| k.fingerprint())
|
||||
.collect::<Vec<_>>();
|
||||
for fp in &fp {
|
||||
cert_by_fingerprint.insert(fp.clone(), valid_cert.keyid());
|
||||
}
|
||||
unused_fingerprints.extend(fp.into_iter());
|
||||
}
|
||||
|
||||
// Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
|
||||
if let Some(fp) = manager.load_any_fingerprint(unused_fingerprints)? {
|
||||
let cert_keyid = cert_by_fingerprint.get(&fp).unwrap().clone();
|
||||
if let Some(message) = messages.remove(&cert_keyid) {
|
||||
let message = message.decrypt_with(&policy, &mut *manager)?;
|
||||
decrypted_messages.insert(cert_keyid, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(decrypted_messages)
|
||||
}
|
||||
|
||||
// NOTE: When using single-decryptor mechanism, only a single key should be provided in Keyring to
|
||||
// decrypt messages with.
|
||||
fn decrypt_with_keyring(
|
||||
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
||||
certs: &[Cert],
|
||||
policy: &NullPolicy,
|
||||
keyring: &mut Keyring,
|
||||
) -> Result<HashMap<KeyID, Vec<u8>>, Error> {
|
||||
let mut decrypted_messages = HashMap::new();
|
||||
|
||||
for valid_cert in certs.iter().map(|cert| cert.with_policy(policy, None)) {
|
||||
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
|
||||
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
|
||||
continue;
|
||||
};
|
||||
let secret_cert = secret_cert
|
||||
.with_policy(policy, None)
|
||||
.map_err(Error::Sequoia)?;
|
||||
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
|
||||
if !keys.is_empty() {
|
||||
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
|
||||
for (pkesk, key) in message.pkesks.iter_mut().zip(keys) {
|
||||
pkesk.set_recipient(key.keyid());
|
||||
}
|
||||
// we have a pkesk, decrypt via keyring
|
||||
decrypted_messages.insert(
|
||||
valid_cert.keyid(),
|
||||
message.decrypt_with(policy, &mut *keyring)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(decrypted_messages)
|
||||
}
|
||||
|
||||
fn decrypt_metadata(
|
||||
message: &EncryptedMessage,
|
||||
policy: &NullPolicy,
|
||||
keyring: &mut Keyring,
|
||||
manager: &mut SmartcardManager,
|
||||
) -> Result<Vec<u8>> {
|
||||
Ok(if keyring.is_empty() {
|
||||
manager.load_any_card()?;
|
||||
message.decrypt_with(policy, manager)?
|
||||
} else {
|
||||
message.decrypt_with(policy, keyring)?
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt_one(
|
||||
messages: Vec<EncryptedMessage>,
|
||||
certs: &[Cert],
|
||||
metadata: EncryptedMessage,
|
||||
) -> Result<Vec<u8>> {
|
||||
let policy = NullPolicy::new();
|
||||
|
||||
let mut keyring = Keyring::new(certs)?;
|
||||
let mut manager = SmartcardManager::new()?;
|
||||
|
||||
let content = decrypt_metadata(&metadata, &policy, &mut keyring, &mut manager)?;
|
||||
|
||||
let (_threshold, root_cert, certs) = decode_metadata_v1(&content)?;
|
||||
|
||||
keyring.set_root_cert(root_cert.clone());
|
||||
manager.set_root_cert(root_cert);
|
||||
|
||||
let mut messages: HashMap<KeyID, EncryptedMessage> =
|
||||
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
|
||||
|
||||
let decrypted_messages = decrypt_with_keyring(&mut messages, &certs, &policy, &mut keyring)?;
|
||||
|
||||
if let Some(message) = decrypted_messages.into_values().next() {
|
||||
return Ok(message);
|
||||
}
|
||||
|
||||
let decrypted_messages = decrypt_with_manager(1, &mut messages, &certs, policy, &mut manager)?;
|
||||
|
||||
if let Some(message) = decrypted_messages.into_values().next() {
|
||||
return Ok(message);
|
||||
}
|
||||
|
||||
unreachable!("smartcard manager should always decrypt")
|
||||
}
|
||||
|
||||
pub fn combine(
|
||||
certs: Vec<Cert>,
|
||||
metadata: EncryptedMessage,
|
||||
|
@ -370,35 +217,106 @@ pub fn combine(
|
|||
|
||||
let mut keyring = Keyring::new(certs)?;
|
||||
let mut manager = SmartcardManager::new()?;
|
||||
let content = decrypt_metadata(&metadata, &policy, &mut keyring, &mut manager)?;
|
||||
let content = if keyring.is_empty() {
|
||||
// NOTE: Any card plugged in that can't decrypt, will raise issues.
|
||||
// This should not be used on a system where OpenPGP cards are available that shouldn't be
|
||||
// used, due to the nature of how wildcard decryption works.
|
||||
manager.load_any_card()?;
|
||||
metadata.decrypt_with(&policy, &mut manager)?
|
||||
} else {
|
||||
metadata.decrypt_with(&policy, &mut keyring)?
|
||||
};
|
||||
|
||||
let (threshold, root_cert, certs) = decode_metadata_v1(&content)?;
|
||||
assert_eq!(
|
||||
SHARD_METADATA_VERSION, content[0],
|
||||
"incompatible metadata version"
|
||||
);
|
||||
let threshold = content[1];
|
||||
|
||||
let mut cert_parser =
|
||||
CertParser::from_bytes(&content[SHARD_METADATA_OFFSET..]).map_err(Error::Sequoia)?;
|
||||
let root_cert = match cert_parser.next() {
|
||||
Some(Ok(c)) => c,
|
||||
Some(Err(e)) => panic!("Could not find root (first) certificate: {e}"),
|
||||
None => panic!("No certs found in cert parser"),
|
||||
};
|
||||
let certs = cert_parser
|
||||
.collect::<openpgp::Result<Vec<_>>>()
|
||||
.map_err(Error::Sequoia)?;
|
||||
keyring.set_root_cert(root_cert.clone());
|
||||
manager.set_root_cert(root_cert);
|
||||
|
||||
// Generate a controlled binding from certificates to encrypted messages. This is stable
|
||||
// because we control the order packets are encrypted and certificates are stored.
|
||||
|
||||
let mut messages: HashMap<KeyID, EncryptedMessage> =
|
||||
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
|
||||
let mut decrypted_messages: HashMap<KeyID, Vec<u8>> = HashMap::new();
|
||||
|
||||
let mut decrypted_messages =
|
||||
decrypt_with_keyring(&mut messages, &certs, &policy, &mut keyring)?;
|
||||
// NOTE: This is ONLY stable because we control the generation of PKESK packets and
|
||||
// encode the policy to ourselves.
|
||||
for valid_cert in certs.iter().map(|cert| cert.with_policy(&policy, None)) {
|
||||
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
|
||||
// get keys from keyring for cert
|
||||
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
|
||||
continue;
|
||||
};
|
||||
let secret_cert = secret_cert
|
||||
.with_policy(&policy, None)
|
||||
.map_err(Error::Sequoia)?;
|
||||
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
|
||||
if !keys.is_empty() {
|
||||
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
|
||||
for (pkesk, key) in message.pkesks.iter_mut().zip(keys) {
|
||||
pkesk.set_recipient(key.keyid());
|
||||
}
|
||||
// we have a pkesk, decrypt via keyring
|
||||
decrypted_messages.insert(
|
||||
valid_cert.keyid(),
|
||||
message.decrypt_with(&policy, &mut keyring)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clean decrypted messages from encrypted messages
|
||||
messages.retain(|k, _v| !decrypted_messages.contains_key(k));
|
||||
|
||||
let left_from_threshold = threshold as usize - decrypted_messages.len();
|
||||
if left_from_threshold > 0 {
|
||||
let new_messages = decrypt_with_manager(
|
||||
left_from_threshold as u8,
|
||||
&mut messages,
|
||||
&certs,
|
||||
policy,
|
||||
&mut manager,
|
||||
)?;
|
||||
decrypted_messages.extend(new_messages.into_iter());
|
||||
let mut remaining_usable_certs = certs
|
||||
.iter()
|
||||
.filter(|cert| messages.contains_key(&cert.keyid()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
while threshold as usize - decrypted_messages.len() > 0 {
|
||||
remaining_usable_certs.retain(|cert| messages.contains_key(&cert.keyid()));
|
||||
let mut key_by_fingerprints = HashMap::new();
|
||||
let mut total_fingerprints = vec![];
|
||||
for valid_cert in remaining_usable_certs
|
||||
.iter()
|
||||
.map(|cert| cert.with_policy(&policy, None))
|
||||
{
|
||||
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
|
||||
let fp = valid_cert
|
||||
.keys()
|
||||
.for_storage_encryption()
|
||||
.map(|k| k.fingerprint())
|
||||
.collect::<Vec<_>>();
|
||||
for fp in &fp {
|
||||
key_by_fingerprints.insert(fp.clone(), valid_cert.keyid());
|
||||
}
|
||||
total_fingerprints.extend(fp.iter().cloned());
|
||||
}
|
||||
|
||||
// Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
|
||||
if let Some(fp) = manager.load_any_fingerprint(total_fingerprints)? {
|
||||
// soundness: `key_by_fingerprints` is extended by the same fps that are then
|
||||
// inserted into `total_fingerprints`
|
||||
let cert_keyid = key_by_fingerprints.get(&fp).unwrap().clone();
|
||||
let message = messages.remove(&cert_keyid);
|
||||
if let Some(message) = message {
|
||||
let message = message.decrypt_with(&policy, &mut manager)?;
|
||||
decrypted_messages.insert(cert_keyid, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shares = decrypted_messages
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::{collections::HashSet};
|
||||
|
||||
use keyfork_prompt::{default_prompt_manager, DefaultPromptManager, Error as PromptError};
|
||||
use keyfork_prompt::{Error as PromptError, DefaultPromptManager, default_prompt_manager};
|
||||
|
||||
use super::openpgp::{
|
||||
self,
|
||||
|
@ -65,7 +65,6 @@ pub struct SmartcardManager {
|
|||
current_card: Option<Card<Open>>,
|
||||
root: Option<Cert>,
|
||||
pm: DefaultPromptManager,
|
||||
pin_cache: HashMap<Fingerprint, String>,
|
||||
}
|
||||
|
||||
impl SmartcardManager {
|
||||
|
@ -74,7 +73,6 @@ impl SmartcardManager {
|
|||
current_card: None,
|
||||
root: None,
|
||||
pm: default_prompt_manager()?,
|
||||
pin_cache: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -219,13 +217,12 @@ impl DecryptionHelper for &mut SmartcardManager {
|
|||
.application_identifier()
|
||||
.context("Could not load application identifier")?
|
||||
.ident();
|
||||
let mut pin = self.pin_cache.get(&fp).cloned();
|
||||
let mut pin = None;
|
||||
while transaction
|
||||
.pw_status_bytes()
|
||||
.map_err(Error::PwStatusBytes)?
|
||||
.err_count_pw1()
|
||||
> 0
|
||||
&& pin.is_none()
|
||||
{
|
||||
transaction.reload_ard()?;
|
||||
let attempts = transaction
|
||||
|
@ -239,11 +236,12 @@ impl DecryptionHelper for &mut SmartcardManager {
|
|||
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
|
||||
};
|
||||
let temp_pin = self.pm.prompt_passphrase(&message)?;
|
||||
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||
let verification_status =
|
||||
transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||
match verification_status {
|
||||
Ok(_) => {
|
||||
self.pin_cache.insert(fp.clone(), temp_pin.clone());
|
||||
pin.replace(temp_pin);
|
||||
break;
|
||||
}
|
||||
Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => {
|
||||
self.pm.prompt_message("Invalid PIN length entered.")?;
|
||||
|
|
Loading…
Reference in New Issue