keyfork-shard: generate nonce using hkdf
This commit is contained in:
parent
2bca0a1580
commit
9394500f2f
|
@ -1830,7 +1830,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork-shard"
|
name = "keyfork-shard"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keyfork-shard"
|
name = "keyfork-shard"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,17 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use aes_gcm::{
|
use aes_gcm::{
|
||||||
aead::{consts::U12, Aead, AeadCore, OsRng},
|
aead::{consts::U12, Aead},
|
||||||
Aes256Gcm, KeyInit, Nonce,
|
Aes256Gcm, KeyInit, Nonce,
|
||||||
};
|
};
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||||
use keyfork_mnemonic_util::{English, Mnemonic};
|
use keyfork_mnemonic_util::{English, Mnemonic};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
validators::{mnemonic::MnemonicSetValidator, Validator},
|
validators::{
|
||||||
|
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
||||||
|
Validator,
|
||||||
|
},
|
||||||
Message as PromptMessage, PromptHandler, Terminal,
|
Message as PromptMessage, PromptHandler, Terminal,
|
||||||
};
|
};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
@ -194,7 +197,6 @@ pub trait Format {
|
||||||
let encrypted_messages = self.parse_shard_file(reader)?;
|
let encrypted_messages = self.parse_shard_file(reader)?;
|
||||||
|
|
||||||
// establish AES-256-GCM key via ECDH
|
// establish AES-256-GCM key via ECDH
|
||||||
let mut nonce_data: Option<[u8; 12]> = None;
|
|
||||||
let mut pubkey_data: Option<[u8; 32]> = None;
|
let mut pubkey_data: Option<[u8; 32]> = None;
|
||||||
|
|
||||||
// receive remote data via scanning QR code from camera
|
// receive remote data via scanning QR code from camera
|
||||||
|
@ -207,9 +209,8 @@ pub trait Format {
|
||||||
if let Ok(Some(hex)) =
|
if let Ok(Some(hex)) =
|
||||||
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
|
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0)
|
||||||
{
|
{
|
||||||
let decoded_data = smex::decode(&hex)?;
|
let decoded_data = smex::decode(hex)?;
|
||||||
nonce_data = Some(decoded_data[..12].try_into().map_err(|_| InvalidData)?);
|
pubkey_data = Some(decoded_data.try_into().map_err(|_| InvalidData)?)
|
||||||
pubkey_data = Some(decoded_data[12..].try_into().map_err(|_| InvalidData)?)
|
|
||||||
} else {
|
} else {
|
||||||
prompt
|
prompt
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -219,30 +220,23 @@ pub trait Format {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if QR code scanning failed or was unavailable, read from a set of mnemonics
|
// if QR code scanning failed or was unavailable, read from a set of mnemonics
|
||||||
let (nonce, their_pubkey) = match (nonce_data, pubkey_data) {
|
let their_pubkey = match pubkey_data {
|
||||||
(Some(nonce), Some(pubkey)) => (nonce, pubkey),
|
Some(pubkey) => pubkey,
|
||||||
_ => {
|
None => {
|
||||||
let validator = MnemonicSetValidator {
|
let validator = MnemonicValidator {
|
||||||
word_lengths: [9, 24],
|
word_length: Some(WordLength::Count(24)),
|
||||||
};
|
};
|
||||||
let [nonce_mnemonic, pubkey_mnemonic] = prompt
|
prompt
|
||||||
.lock()
|
.lock()
|
||||||
.expect(bug!(POISONED_MUTEX))
|
.expect(bug!(POISONED_MUTEX))
|
||||||
.prompt_validated_wordlist::<English, _>(
|
.prompt_validated_wordlist::<English, _>(
|
||||||
QRCODE_COULDNT_READ,
|
QRCODE_COULDNT_READ,
|
||||||
3,
|
3,
|
||||||
validator.to_fn(),
|
validator.to_fn(),
|
||||||
)?;
|
)?
|
||||||
|
|
||||||
let nonce = nonce_mnemonic
|
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| InvalidData)?;
|
.map_err(|_| InvalidData)?
|
||||||
let pubkey = pubkey_mnemonic
|
|
||||||
.as_bytes()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| InvalidData)?;
|
|
||||||
(nonce, pubkey)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -253,9 +247,14 @@ pub trait Format {
|
||||||
.diffie_hellman(&PublicKey::from(their_pubkey))
|
.diffie_hellman(&PublicKey::from(their_pubkey))
|
||||||
.to_bytes();
|
.to_bytes();
|
||||||
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
||||||
let mut hkdf_output = [0u8; 256 / 8];
|
|
||||||
hkdf.expand(&[], &mut hkdf_output)?;
|
let mut shared_key_data = [0u8; 256 / 8];
|
||||||
let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?;
|
hkdf.expand(b"key", &mut shared_key_data)?;
|
||||||
|
let shared_key = Aes256Gcm::new_from_slice(&shared_key_data)?;
|
||||||
|
|
||||||
|
let mut nonce_data = [0u8; 12];
|
||||||
|
hkdf.expand(b"nonce", &mut nonce_data)?;
|
||||||
|
let nonce = Nonce::<U12>::from_slice(&nonce_data);
|
||||||
|
|
||||||
// decrypt a single shard and create the payload
|
// decrypt a single shard and create the payload
|
||||||
let (share, threshold) =
|
let (share, threshold) =
|
||||||
|
@ -269,7 +268,6 @@ pub trait Format {
|
||||||
);
|
);
|
||||||
|
|
||||||
// encrypt data
|
// encrypt data
|
||||||
let nonce = Nonce::<U12>::from_slice(&nonce);
|
|
||||||
let payload_bytes = shared_key.encrypt(nonce, payload.as_slice())?;
|
let payload_bytes = shared_key.encrypt(nonce, payload.as_slice())?;
|
||||||
|
|
||||||
// convert data to a static-size payload
|
// convert data to a static-size payload
|
||||||
|
@ -432,16 +430,13 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
|
|
||||||
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
|
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
|
||||||
iter += 1;
|
iter += 1;
|
||||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
|
||||||
let nonce_mnemonic = unsafe { Mnemonic::from_raw_bytes(nonce.as_slice()) };
|
|
||||||
let our_key = EphemeralSecret::random();
|
let our_key = EphemeralSecret::random();
|
||||||
let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
|
let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
|
||||||
|
|
||||||
#[cfg(feature = "qrcode")]
|
#[cfg(feature = "qrcode")]
|
||||||
{
|
{
|
||||||
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
use keyfork_qrcode::{qrencode, ErrorCorrection};
|
||||||
let mut qrcode_data = nonce_mnemonic.to_bytes();
|
let qrcode_data = key_mnemonic.to_bytes();
|
||||||
qrcode_data.extend(key_mnemonic.as_bytes());
|
|
||||||
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) {
|
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) {
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
concat!(
|
concat!(
|
||||||
|
@ -458,10 +453,9 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
concat!(
|
concat!(
|
||||||
"Upon request, these words should be sent to shardholder {iter}: ",
|
"Upon request, these words should be sent to shardholder {iter}: ",
|
||||||
"{nonce_mnemonic} {key_mnemonic}"
|
"{key_mnemonic}"
|
||||||
),
|
),
|
||||||
iter = iter,
|
iter = iter,
|
||||||
nonce_mnemonic = nonce_mnemonic,
|
|
||||||
key_mnemonic = key_mnemonic,
|
key_mnemonic = key_mnemonic,
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
|
@ -506,12 +500,16 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
|
|
||||||
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)).to_bytes();
|
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)).to_bytes();
|
||||||
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
|
||||||
let mut hkdf_output = [0u8; 256 / 8];
|
|
||||||
hkdf.expand(&[], &mut hkdf_output)?;
|
|
||||||
let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?;
|
|
||||||
|
|
||||||
let payload =
|
let mut shared_key_data = [0u8; 256 / 8];
|
||||||
shared_key.decrypt(&nonce, &payload[..payload[payload.len() - 1] as usize])?;
|
hkdf.expand(b"key", &mut shared_key_data)?;
|
||||||
|
let shared_key = Aes256Gcm::new_from_slice(&shared_key_data)?;
|
||||||
|
|
||||||
|
let mut nonce_data = [0u8; 12];
|
||||||
|
hkdf.expand(b"nonce", &mut nonce_data)?;
|
||||||
|
let nonce = Nonce::<U12>::from_slice(&nonce_data);
|
||||||
|
|
||||||
|
let payload = shared_key.decrypt(nonce, &payload[..payload[payload.len() - 1] as usize])?;
|
||||||
assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version");
|
assert_eq!(HUNK_VERSION, payload[0], "Incompatible hunk version");
|
||||||
|
|
||||||
match &mut iter_count {
|
match &mut iter_count {
|
||||||
|
|
|
@ -32,7 +32,7 @@ keyfork-entropy = { version = "0.1.0", path = "../util/keyfork-entropy", registr
|
||||||
keyfork-mnemonic-util = { version = "0.2.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" }
|
keyfork-mnemonic-util = { version = "0.2.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" }
|
||||||
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" }
|
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" }
|
||||||
keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" }
|
keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" }
|
||||||
keyfork-shard = { version = "0.1.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" }
|
keyfork-shard = { version = "0.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" }
|
||||||
smex = { version = "0.1.0", path = "../util/smex", registry = "distrust" }
|
smex = { version = "0.1.0", path = "../util/smex", registry = "distrust" }
|
||||||
|
|
||||||
clap = { version = "4.4.2", features = ["derive", "env", "wrap_help"] }
|
clap = { version = "4.4.2", features = ["derive", "env", "wrap_help"] }
|
||||||
|
|
Loading…
Reference in New Issue