2024-01-20 06:17:32 +00:00
|
|
|
#![doc = include_str!("../README.md")]
|
2024-01-16 02:44:48 +00:00
|
|
|
|
2024-01-10 20:34:29 +00:00
|
|
|
use std::io::{stdin, stdout, Write};
|
2024-01-05 04:05:30 +00:00
|
|
|
|
|
|
|
use aes_gcm::{
|
|
|
|
aead::{Aead, AeadCore, OsRng},
|
|
|
|
Aes256Gcm, KeyInit,
|
|
|
|
};
|
2024-01-08 19:00:31 +00:00
|
|
|
use hkdf::Hkdf;
|
2024-01-05 04:05:30 +00:00
|
|
|
use keyfork_mnemonic_util::{Mnemonic, Wordlist};
|
2024-01-10 20:34:29 +00:00
|
|
|
use keyfork_prompt::{
|
|
|
|
validators::{mnemonic::MnemonicSetValidator, Validator},
|
2024-01-12 00:49:56 +00:00
|
|
|
Message as PromptMessage, PromptHandler, Terminal,
|
2024-01-10 20:34:29 +00:00
|
|
|
};
|
|
|
|
use sha2::Sha256;
|
2024-01-05 04:05:30 +00:00
|
|
|
use sharks::{Share, Sharks};
|
|
|
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
|
|
|
|
2023-10-19 22:06:34 +00:00
|
|
|
#[cfg(feature = "openpgp")]
|
|
|
|
pub mod openpgp;
|
2024-01-05 04:05:30 +00:00
|
|
|
|
2024-01-16 02:44:48 +00:00
|
|
|
/// Errors encountered while creating or combining shares using Shamir's Secret Sharing.
|
2024-01-05 04:11:15 +00:00
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum SharksError {
|
2024-01-16 02:44:48 +00:00
|
|
|
/// A Shamir Share could not be created.
|
2024-01-05 04:11:15 +00:00
|
|
|
#[error("Error creating share: {0}")]
|
|
|
|
Share(String),
|
|
|
|
|
2024-01-16 02:44:48 +00:00
|
|
|
/// The Shamir shares could not be combined.
|
2024-01-05 04:11:15 +00:00
|
|
|
#[error("Error combining shares: {0}")]
|
|
|
|
CombineShare(String),
|
|
|
|
}
|
|
|
|
|
2024-01-16 02:44:48 +00:00
|
|
|
/// The mnemonic or QR code used to transport an encrypted shard did not store the correct amount
|
|
|
|
/// of data.
|
2024-01-07 04:23:03 +00:00
|
|
|
#[derive(thiserror::Error, Debug)]
|
2024-01-12 00:49:56 +00:00
|
|
|
#[error("Mnemonic or QR code did not store enough data")]
|
|
|
|
pub struct InvalidData;
|
2024-01-07 04:23:03 +00:00
|
|
|
|
2024-01-05 04:05:30 +00:00
|
|
|
/// Decrypt hunk version 1:
|
|
|
|
/// 1 byte: Version
|
|
|
|
/// 1 byte: Threshold
|
|
|
|
/// Data: &[u8]
|
|
|
|
pub(crate) const HUNK_VERSION: u8 = 1;
|
|
|
|
pub(crate) const HUNK_OFFSET: usize = 2;
|
|
|
|
|
2024-01-16 02:44:48 +00:00
|
|
|
/// Establish ECDH transport for remote operators, receive transport-encrypted shares, decrypt the
|
|
|
|
/// shares, and combine them.
|
2024-01-07 04:23:03 +00:00
|
|
|
///
|
2024-01-16 02:44:48 +00:00
|
|
|
/// # Errors
|
|
|
|
/// The function may error if:
|
|
|
|
/// * Prompting for transport-encrypted shards fails.
|
|
|
|
/// * Decrypting shards fails.
|
|
|
|
/// * Combining shards fails.
|
|
|
|
///
|
|
|
|
/// # Panics
|
2024-01-07 04:23:03 +00:00
|
|
|
/// The function may panic if it is given payloads generated using a version of Keyfork that is
|
|
|
|
/// incompatible with the currently running version.
|
2024-01-07 05:44:59 +00:00
|
|
|
pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
2024-01-11 03:35:31 +00:00
|
|
|
let mut pm = Terminal::new(stdin(), stdout())?;
|
2024-01-05 04:05:30 +00:00
|
|
|
let wordlist = Wordlist::default();
|
|
|
|
|
|
|
|
let mut iter_count = None;
|
|
|
|
let mut shares = vec![];
|
|
|
|
|
|
|
|
let mut threshold = 0;
|
|
|
|
|
|
|
|
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
|
|
|
|
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
|
|
|
let nonce_mnemonic =
|
|
|
|
unsafe { Mnemonic::from_raw_entropy(nonce.as_slice(), Default::default()) };
|
|
|
|
let our_key = EphemeralSecret::random();
|
|
|
|
let key_mnemonic =
|
|
|
|
Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?;
|
2024-01-12 00:49:56 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "qrcode")]
|
|
|
|
{
|
|
|
|
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
2024-01-19 04:50:23 +00:00
|
|
|
let mut qrcode_data = nonce_mnemonic.to_bytes();
|
|
|
|
qrcode_data.extend(key_mnemonic.as_bytes());
|
2024-01-12 00:49:56 +00:00
|
|
|
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Medium) {
|
|
|
|
pm.prompt_message(PromptMessage::Data(qrcode))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-11 04:28:56 +00:00
|
|
|
pm.prompt_message(PromptMessage::Text(format!(
|
2024-01-12 00:49:56 +00:00
|
|
|
"Our words: {nonce_mnemonic} {key_mnemonic}"
|
2024-01-05 04:05:30 +00:00
|
|
|
)))?;
|
|
|
|
|
2024-01-12 00:49:56 +00:00
|
|
|
let mut pubkey_data: Option<[u8; 32]> = None;
|
|
|
|
let mut payload_data = None;
|
|
|
|
|
|
|
|
#[cfg(feature = "qrcode")]
|
|
|
|
{
|
|
|
|
pm.prompt_message(PromptMessage::Text(
|
|
|
|
"Press enter, then present QR code to camera".to_string(),
|
|
|
|
))?;
|
|
|
|
if let Ok(Some(hex)) =
|
|
|
|
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
|
|
|
|
{
|
|
|
|
let decoded_data = smex::decode(&hex)?;
|
|
|
|
let _ = pubkey_data.insert(decoded_data[..32].try_into().map_err(|_| InvalidData)?);
|
|
|
|
let _ = payload_data.insert(decoded_data[32..].to_vec());
|
|
|
|
} else {
|
|
|
|
pm.prompt_message(PromptMessage::Text(
|
|
|
|
"Unable to detect QR code, falling back to text".to_string(),
|
|
|
|
))?;
|
|
|
|
};
|
2024-01-06 05:58:18 +00:00
|
|
|
}
|
|
|
|
|
2024-01-12 00:49:56 +00:00
|
|
|
let (pubkey, payload) = match (pubkey_data, payload_data) {
|
|
|
|
(Some(pubkey), Some(payload)) => (pubkey, payload),
|
|
|
|
_ => {
|
|
|
|
let validator = MnemonicSetValidator {
|
|
|
|
word_lengths: [24, 48],
|
|
|
|
};
|
|
|
|
|
|
|
|
let [pubkey_mnemonic, payload_mnemonic] =
|
|
|
|
pm.prompt_validated_wordlist("Their words: ", &wordlist, 3, validator.to_fn())?;
|
|
|
|
let pubkey = pubkey_mnemonic
|
2024-01-19 04:50:23 +00:00
|
|
|
.as_bytes()
|
2024-01-12 00:49:56 +00:00
|
|
|
.try_into()
|
|
|
|
.map_err(|_| InvalidData)?;
|
2024-01-19 04:50:23 +00:00
|
|
|
let payload = payload_mnemonic.to_bytes();
|
2024-01-12 00:49:56 +00:00
|
|
|
(pubkey, payload)
|
|
|
|
}
|
2024-01-10 20:34:29 +00:00
|
|
|
};
|
2024-01-05 04:05:30 +00:00
|
|
|
|
2024-01-12 00:49:56 +00:00
|
|
|
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)).to_bytes();
|
2024-01-08 19:00:31 +00:00
|
|
|
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
|
|
|
let mut hkdf_output = [0u8; 256 / 8];
|
|
|
|
hkdf.expand(&[], &mut hkdf_output)?;
|
2024-01-10 20:34:29 +00:00
|
|
|
let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?;
|
2024-01-05 04:05:30 +00:00
|
|
|
|
|
|
|
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 {
|
|
|
|
Some(n) => {
|
|
|
|
// Must be > 0 to start loop, can't go lower
|
|
|
|
*n -= 1;
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
// NOTE: Should always be >= 1, < 256 due to Shamir constraints
|
|
|
|
threshold = payload[1];
|
|
|
|
let _ = iter_count.insert(threshold - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
shares.push(payload[HUNK_OFFSET..].to_vec());
|
|
|
|
}
|
|
|
|
|
|
|
|
let shares = shares
|
|
|
|
.into_iter()
|
|
|
|
.map(|s| Share::try_from(s.as_slice()))
|
|
|
|
.collect::<Result<Vec<_>, &str>>()
|
2024-01-05 04:11:15 +00:00
|
|
|
.map_err(|e| SharksError::Share(e.to_string()))?;
|
2024-01-05 04:05:30 +00:00
|
|
|
let secret = Sharks(threshold)
|
|
|
|
.recover(&shares)
|
2024-01-05 04:11:15 +00:00
|
|
|
.map_err(|e| SharksError::CombineShare(e.to_string()))?;
|
2024-01-05 04:05:30 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Verification would take up too much size, mnemonic would be very large
|
|
|
|
let userid = UserID::from("keyfork-sss");
|
|
|
|
let kdr = DerivationRequest::new(
|
|
|
|
DerivationAlgorithm::Ed25519,
|
|
|
|
&DerivationPath::from_str("m/7366512'/0'")?,
|
|
|
|
)
|
|
|
|
.derive_with_master_seed(secret.to_vec())?;
|
|
|
|
let derived_cert = keyfork_derive_openpgp::derive(
|
|
|
|
kdr,
|
|
|
|
&[KeyFlags::empty().set_certification().set_signing()],
|
|
|
|
userid,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// NOTE: Signatures on certs will be different. Compare fingerprints instead.
|
|
|
|
let derived_fp = derived_cert.fingerprint();
|
|
|
|
let expected_fp = root_cert.fingerprint();
|
|
|
|
if derived_fp != expected_fp {
|
|
|
|
return Err(Error::InvalidSecret(derived_fp, expected_fp));
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2024-01-07 05:44:59 +00:00
|
|
|
w.write_all(&secret)?;
|
2024-01-05 04:05:30 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|