keyfork-shard: refactor key discovery mechanisms
This commit is contained in:
parent
2541d49fb8
commit
b75d45876a
|
@ -7,7 +7,7 @@ use std::{
|
|||
process::ExitCode,
|
||||
};
|
||||
|
||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
||||
|
@ -32,7 +32,7 @@ fn run() -> Result<()> {
|
|||
};
|
||||
|
||||
let openpgp = OpenPGP;
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, messages_file)?;
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file)?;
|
||||
print!("{}", smex::encode(&bytes));
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -33,7 +33,7 @@ fn run() -> Result<()> {
|
|||
|
||||
let openpgp = OpenPGP;
|
||||
|
||||
openpgp.decrypt_one_shard_for_transport(key_discovery, messages_file)?;
|
||||
openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ fn run() -> Result<()> {
|
|||
|
||||
let openpgp = OpenPGP;
|
||||
|
||||
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery, std::io::stdout())?;
|
||||
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::{
|
||||
io::{stdin, stdout, Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
|
||||
use aes_gcm::{
|
||||
aead::{consts::U12, Aead, AeadCore, OsRng},
|
||||
|
@ -25,6 +22,27 @@ const ENC_LEN: u8 = 4 * 16;
|
|||
#[cfg(feature = "openpgp")]
|
||||
pub mod openpgp;
|
||||
|
||||
/// A trait to specify where keys can be discovered from, such as a Rust-native type or a path on
|
||||
/// the filesystem that keys may be read from.
|
||||
pub trait KeyDiscovery<F: Format + ?Sized> {
|
||||
/// Discover public keys for the associated format.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if public keys could not be loaded from the given discovery
|
||||
/// mechanism. A discovery mechanism _must_ be able to detect public keys.
|
||||
fn discover_public_keys(&self) -> Result<Vec<F::PublicKey>, F::Error>;
|
||||
|
||||
/// Discover private keys for the associated format.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if private keys could not be loaded from the given
|
||||
/// discovery mechanism. Keys may exist off-system (such as with smartcards), in which case the
|
||||
/// PrivateKeyData type of the asssociated format should be either `()` (if the keys may never
|
||||
/// exist on-system) or an empty container (such as an empty Vec); in either case, this method
|
||||
/// _must not_ return an error if keys are accessible but can't be transferred into memory.
|
||||
fn discover_private_keys(&self) -> Result<F::PrivateKeyData, F::Error>;
|
||||
}
|
||||
|
||||
/// A format to use for splitting and combining secrets.
|
||||
pub trait Format {
|
||||
/// The error type returned from any failed operations.
|
||||
|
@ -42,17 +60,6 @@ pub trait Format {
|
|||
/// A type representing the parsed, but encrypted, Shard data.
|
||||
type EncryptedData;
|
||||
|
||||
/// Parse the public key data from a readable type.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if private key data could not be properly parsed from the
|
||||
/// path.
|
||||
/// occurred while parsing the public key data.
|
||||
fn parse_public_key_data(
|
||||
&self,
|
||||
key_data_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<Self::PublicKey>, Self::Error>;
|
||||
|
||||
/// Derive a signer
|
||||
fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey;
|
||||
|
||||
|
@ -83,17 +90,6 @@ pub trait Format {
|
|||
signing_key: &mut Self::SigningKey,
|
||||
) -> Result<Self::EncryptedData, Self::Error>;
|
||||
|
||||
/// Parse the private key data from a readable type. The private key may not be accessible (it
|
||||
/// may be hardware only, such as a smartcard), for which this method may return None.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if private key data could not be properly parsed from the
|
||||
/// path.
|
||||
fn parse_private_key_data(
|
||||
&self,
|
||||
key_data_path: impl AsRef<Path>,
|
||||
) -> Result<Self::PrivateKeyData, Self::Error>;
|
||||
|
||||
/// Parse the Shard file into a processable type.
|
||||
///
|
||||
/// # Errors
|
||||
|
@ -148,11 +144,11 @@ pub trait Format {
|
|||
/// be combined into a secret.
|
||||
fn decrypt_all_shards_to_secret(
|
||||
&self,
|
||||
private_key_data_path: Option<impl AsRef<Path>>,
|
||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||
reader: impl Read + Send + Sync,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let private_keys = private_key_data_path
|
||||
.map(|p| self.parse_private_key_data(p))
|
||||
let private_keys = private_key_discovery
|
||||
.map(|p| p.discover_private_keys())
|
||||
.transpose()?;
|
||||
let encrypted_messages = self.parse_shard_file(reader)?;
|
||||
let (shares, threshold) = self.decrypt_all_shards(private_keys, &encrypted_messages)?;
|
||||
|
@ -173,15 +169,15 @@ pub trait Format {
|
|||
/// QR code; instead, a mnemonic prompt will be used.
|
||||
fn decrypt_one_shard_for_transport(
|
||||
&self,
|
||||
private_key_data_path: Option<impl AsRef<Path>>,
|
||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||
reader: impl Read + Send + Sync,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut pm = Terminal::new(stdin(), stdout())?;
|
||||
let wordlist = Wordlist::default();
|
||||
|
||||
// parse input
|
||||
let private_keys = private_key_data_path
|
||||
.map(|p| self.parse_private_key_data(p))
|
||||
let private_keys = private_key_discovery
|
||||
.map(|p| p.discover_private_keys())
|
||||
.transpose()?;
|
||||
let encrypted_messages = self.parse_shard_file(reader)?;
|
||||
|
||||
|
@ -321,7 +317,7 @@ pub trait Format {
|
|||
threshold: u8,
|
||||
max: u8,
|
||||
secret: &[u8],
|
||||
public_key_data_path: impl AsRef<Path>,
|
||||
public_key_discovery: impl KeyDiscovery<Self>,
|
||||
writer: impl Write + Send + Sync,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut signing_key = self.derive_signing_key(secret);
|
||||
|
@ -329,7 +325,7 @@ pub trait Format {
|
|||
let sharks = Sharks(threshold);
|
||||
let dealer = sharks.dealer(secret);
|
||||
|
||||
let public_keys = self.parse_public_key_data(public_key_data_path)?;
|
||||
let public_keys = public_key_discovery.discover_public_keys()?;
|
||||
assert!(
|
||||
public_keys.len() < u8::MAX as usize,
|
||||
"must have less than u8::MAX public keys"
|
||||
|
|
|
@ -58,7 +58,7 @@ const SHARD_METADATA_OFFSET: usize = 2;
|
|||
|
||||
use super::{
|
||||
Format, InvalidData, SharksError, HUNK_VERSION, QRCODE_COULDNT_READ, QRCODE_ERROR,
|
||||
QRCODE_PROMPT, QRCODE_TIMEOUT,
|
||||
QRCODE_PROMPT, QRCODE_TIMEOUT, KeyDiscovery
|
||||
};
|
||||
|
||||
// 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding
|
||||
|
@ -282,13 +282,6 @@ impl Format for OpenPGP {
|
|||
type SigningKey = Cert;
|
||||
type EncryptedData = EncryptedMessage;
|
||||
|
||||
fn parse_public_key_data(
|
||||
&self,
|
||||
key_data_path: impl AsRef<Path>,
|
||||
) -> std::result::Result<Vec<Self::PublicKey>, Self::Error> {
|
||||
Self::discover_certs(key_data_path)
|
||||
}
|
||||
|
||||
/// Derive an OpenPGP Shard certificate from the given seed.
|
||||
fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey {
|
||||
let seed = VariableLengthSeed::new(seed);
|
||||
|
@ -449,13 +442,6 @@ impl Format for OpenPGP {
|
|||
Ok(message)
|
||||
}
|
||||
|
||||
fn parse_private_key_data(
|
||||
&self,
|
||||
key_data_path: impl AsRef<Path>,
|
||||
) -> std::result::Result<Self::PrivateKeyData, Self::Error> {
|
||||
Self::discover_certs(key_data_path)
|
||||
}
|
||||
|
||||
fn parse_shard_file(
|
||||
&self,
|
||||
shard_file: impl Read + Send + Sync,
|
||||
|
@ -579,6 +565,26 @@ impl Format for OpenPGP {
|
|||
}
|
||||
}
|
||||
|
||||
impl KeyDiscovery<OpenPGP> for &Path {
|
||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
|
||||
OpenPGP::discover_certs(self)
|
||||
}
|
||||
|
||||
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDiscovery<OpenPGP> for &[Cert] {
|
||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
|
||||
Ok(self.to_vec())
|
||||
}
|
||||
|
||||
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
|
||||
Ok(self.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// Read all OpenPGP certificates in a path and return a [`Vec`] of them. Certificates are read
|
||||
/// from a file, or from files one level deep in a directory.
|
||||
///
|
||||
|
|
|
@ -111,12 +111,10 @@ impl DecryptionHelper for &mut Keyring {
|
|||
pkesk.recipient().is_wildcard()
|
||||
|| cert.keys().any(|k| &k.keyid() == pkesk.recipient())
|
||||
}) {
|
||||
#[allow(deprecated, clippy::map_flatten)]
|
||||
let name = cert
|
||||
.userids()
|
||||
.next()
|
||||
.map(|userid| userid.userid().name().transpose())
|
||||
.flatten()
|
||||
.and_then(|userid| userid.userid().name2().transpose())
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
|
|
|
@ -37,7 +37,7 @@ impl RecoverSubcommands {
|
|||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
// TODO: remove .clone() by making handle() consume self
|
||||
let seed = openpgp
|
||||
.decrypt_all_shards_to_secret(key_discovery.clone(), content.as_bytes())?;
|
||||
.decrypt_all_shards_to_secret(key_discovery.as_deref(), content.as_bytes())?;
|
||||
Ok(seed)
|
||||
} else {
|
||||
panic!("unknown format of shard file");
|
||||
|
|
|
@ -32,27 +32,23 @@ trait ShardExec {
|
|||
&self,
|
||||
threshold: u8,
|
||||
max: u8,
|
||||
key_discovery: impl AsRef<Path>,
|
||||
key_discovery: &Path,
|
||||
secret: &[u8],
|
||||
output: &mut (impl Write + Send + Sync),
|
||||
) -> Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
fn combine<T>(
|
||||
fn combine(
|
||||
&self,
|
||||
key_discovery: Option<T>,
|
||||
key_discovery: Option<&Path>,
|
||||
input: impl Read + Send + Sync,
|
||||
output: &mut impl Write,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
T: AsRef<Path>;
|
||||
) -> Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
fn decrypt<T>(
|
||||
fn decrypt(
|
||||
&self,
|
||||
key_discovery: Option<T>,
|
||||
key_discovery: Option<&Path>,
|
||||
input: impl Read + Send + Sync,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
T: AsRef<Path>;
|
||||
) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -63,7 +59,7 @@ impl ShardExec for OpenPGP {
|
|||
&self,
|
||||
threshold: u8,
|
||||
max: u8,
|
||||
key_discovery: impl AsRef<Path>,
|
||||
key_discovery: &Path,
|
||||
secret: &[u8],
|
||||
output: &mut (impl Write + Send + Sync),
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
@ -71,14 +67,12 @@ impl ShardExec for OpenPGP {
|
|||
opgp.shard_and_encrypt(threshold, max, secret, key_discovery, output)
|
||||
}
|
||||
|
||||
fn combine<T>(
|
||||
fn combine(
|
||||
&self,
|
||||
key_discovery: Option<T>,
|
||||
key_discovery: Option<&Path>,
|
||||
input: impl Read + Send + Sync,
|
||||
output: &mut impl Write,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input)?;
|
||||
|
@ -87,13 +81,11 @@ impl ShardExec for OpenPGP {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt<T>(
|
||||
fn decrypt(
|
||||
&self,
|
||||
key_discovery: Option<T>,
|
||||
key_discovery: Option<&Path>,
|
||||
input: impl Read + Send + Sync,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
openpgp.decrypt_one_shard_for_transport(key_discovery, input)?;
|
||||
|
@ -189,7 +181,7 @@ impl ShardSubcommands {
|
|||
|
||||
match format {
|
||||
Some(Format::OpenPGP(o)) => {
|
||||
o.decrypt(key_discovery.as_ref(), shard_content.as_bytes())
|
||||
o.decrypt(key_discovery.as_deref(), shard_content.as_bytes())
|
||||
}
|
||||
Some(Format::P256(_p)) => todo!(),
|
||||
None => panic!("{COULD_NOT_DETERMINE_FORMAT}"),
|
||||
|
@ -206,7 +198,7 @@ impl ShardSubcommands {
|
|||
|
||||
match format {
|
||||
Some(Format::OpenPGP(o)) => o.combine(
|
||||
key_discovery.as_ref(),
|
||||
key_discovery.as_deref(),
|
||||
shard_content.as_bytes(),
|
||||
&mut stdout,
|
||||
),
|
||||
|
|
|
@ -15,6 +15,8 @@ use keyfork_prompt::{
|
|||
Message, PromptHandler, Terminal,
|
||||
};
|
||||
|
||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("Invalid PIN length: {0}")]
|
||||
pub struct PinLength(usize);
|
||||
|
@ -163,13 +165,13 @@ fn generate_shard_secret(
|
|||
certs.push(cert);
|
||||
}
|
||||
|
||||
let opgp = OpenPGP;
|
||||
|
||||
if let Some(output_file) = output_file {
|
||||
let output = File::create(output_file)?;
|
||||
#[allow(deprecated)]
|
||||
keyfork_shard::openpgp::split(threshold, certs, &seed, output)?;
|
||||
opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], output)?;
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
keyfork_shard::openpgp::split(threshold, certs, &seed, std::io::stdout())?;
|
||||
opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], std::io::stdout())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue