Compare commits

..

2 Commits

5 changed files with 198 additions and 157 deletions

View File

@ -32,9 +32,7 @@ fn run() -> Result<()> {
}; };
let openpgp = OpenPGP; 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, messages_file)?;
print!("{}", smex::encode(&bytes)); print!("{}", smex::encode(&bytes));
Ok(()) Ok(())

View File

@ -31,17 +31,20 @@ pub trait Format {
type Error: std::error::Error + 'static; type Error: std::error::Error + 'static;
/// A type encapsulating the public key recipients of shards. /// A type encapsulating the public key recipients of shards.
type PublicKeyData; type PublicKeyData: IntoIterator<Item = Self::PublicKey>;
/// A type encapsulating a single public key recipient.
type PublicKey;
/// A type encapsulating the private key recipients of shards. /// A type encapsulating the private key recipients of shards.
type PrivateKeyData; type PrivateKeyData;
/// A type representing a Signer derived from the secret.
type SigningKey;
/// A type representing the parsed, but encrypted, Shard data. /// A type representing the parsed, but encrypted, Shard data.
type ShardData; type ShardData;
/// A type representing a Signer derived from the secret.
type Signer;
/// Parse the public key data from a readable type. /// Parse the public key data from a readable type.
/// ///
/// # Errors /// # Errors
@ -53,6 +56,36 @@ pub trait Format {
key_data_path: impl AsRef<Path>, key_data_path: impl AsRef<Path>,
) -> Result<Self::PublicKeyData, Self::Error>; ) -> Result<Self::PublicKeyData, Self::Error>;
/// Derive a signer
fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey;
/// Format a header containing necessary metadata. Such metadata contains a version byte, a
/// threshold byte, a public version of the [`Format::SigningKey`], and the public keys used to
/// encrypt shards. The public keys must be kept _in order_ to the encrypted shards. Keyfork
/// will use the same key_data for both, ensuring an iteration of this method will match with
/// iterations in methods called later.
///
/// # Errors
/// The method may return an error if encryption to any of the public keys fails.
fn format_encrypted_header(
&self,
signing_key: &Self::SigningKey,
key_data: &Self::PublicKeyData,
threshold: u8,
) -> Result<Vec<u8>, Self::Error>;
/// Format a shard encrypted to the given public key, signing with the private key.
///
/// # Errors
/// The method may return an error if the public key used to encrypt the shard is unsuitable
/// for encryption, or if an error occurs while encrypting.
fn encrypt_shard(
&self,
shard: &[u8],
public_key: &Self::PublicKey,
signing_key: &mut Self::SigningKey,
) -> Result<Vec<u8>, Self::Error>;
/// Parse the private key data from a readable type. The private key may not be accessible (it /// 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. /// may be hardware only, such as a smartcard), for which this method may return None.
/// ///
@ -85,24 +118,6 @@ pub trait Format {
shard_file: impl Write, shard_file: impl Write,
) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>;
/// Derive a Signer from the secret.
///
/// # Errors
/// This function may return an error if a Signer could not be properly created.
fn derive_signer(&self, secret: &[u8]) -> Result<Self::Signer, Self::Error>;
/// Encrypt multiple shares to public keys.
///
/// # Errors
/// The method may return an error if the share could not be encrypted to a public key or if
/// the ShardData could not be created.
fn generate_shard_data(
&self,
shares: &[Share],
signer: &Self::Signer,
public_keys: Self::PublicKeyData,
) -> Result<Self::ShardData, Self::Error>;
/// Decrypt shares and associated metadata from a readable input. For the current version of /// Decrypt shares and associated metadata from a readable input. For the current version of
/// Keyfork, the only associated metadata is a u8 representing the threshold to combine /// Keyfork, the only associated metadata is a u8 representing the threshold to combine
/// secrets. /// secrets.

View File

@ -57,9 +57,8 @@ const SHARD_METADATA_VERSION: u8 = 1;
const SHARD_METADATA_OFFSET: usize = 2; const SHARD_METADATA_OFFSET: usize = 2;
use super::{ use super::{
InvalidData, SharksError, HUNK_VERSION, QRCODE_COULDNT_READ, QRCODE_ERROR, QRCODE_PROMPT, Format, InvalidData, SharksError, HUNK_VERSION, QRCODE_COULDNT_READ, QRCODE_ERROR,
QRCODE_TIMEOUT, QRCODE_PROMPT, QRCODE_TIMEOUT,
Format,
}; };
// 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding // 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding
@ -256,10 +255,11 @@ impl OpenPGP {
impl Format for OpenPGP { impl Format for OpenPGP {
type Error = Error; type Error = Error;
type PublicKey = Cert;
type PublicKeyData = Vec<Cert>; type PublicKeyData = Vec<Cert>;
type PrivateKeyData = Vec<Cert>; type PrivateKeyData = Vec<Cert>;
type SigningKey = Cert;
type ShardData = Vec<EncryptedMessage>; type ShardData = Vec<EncryptedMessage>;
type Signer = openpgp::crypto::KeyPair;
fn parse_public_key_data( fn parse_public_key_data(
&self, &self,
@ -268,6 +268,145 @@ impl Format for OpenPGP {
Self::discover_certs(key_data_path) 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);
// build cert to sign encrypted shares
let userid = UserID::from("keyfork-sss");
let path = DerivationPath::from_str("m/7366512'/0'").expect("valid derivation path");
let xprv = XPrv::new(seed)
.derive_path(&path)
.expect("valid derivation");
keyfork_derive_openpgp::derive(
xprv,
&[KeyFlags::empty().set_certification().set_signing()],
&userid,
)
.expect("valid cert creation")
}
fn format_encrypted_header(
&self,
signing_key: &Self::SigningKey,
key_data: &Self::PublicKeyData,
threshold: u8,
) -> Result<Vec<u8>, Self::Error> {
let policy = StandardPolicy::new();
let mut pp = vec![SHARD_METADATA_VERSION, threshold];
// Note: Sequoia does not export private keys on a Cert, only on a TSK
signing_key
.serialize(&mut pp)
.expect("serialize cert into bytes");
for cert in key_data {
cert.serialize(&mut pp)
.expect("serialize pubkey into bytes");
}
// verify packet pile
let mut iter = openpgp::cert::CertParser::from_bytes(&pp[SHARD_METADATA_OFFSET..])
.expect("should have certs");
let first_cert = iter.next().transpose().ok().flatten().expect("first cert");
assert_eq!(signing_key, &first_cert);
for (packet_cert, cert) in iter.zip(key_data) {
assert_eq!(
&packet_cert.expect("parsed packet cert"),
cert,
"packet pile could not recreate cert: {}",
cert.fingerprint(),
);
}
let valid_certs = key_data
.iter()
.map(|c| c.with_policy(&policy, None))
.collect::<openpgp::Result<Vec<_>>>()
.map_err(Error::Sequoia)?;
let recipients = valid_certs.iter().flat_map(|vc| {
get_encryption_keys(vc).map(|key| Recipient::new(KeyID::wildcard(), key.key()))
});
// Process is as follows:
// * Any OpenPGP message
// * An encrypted message
// * A literal message
// * The packet pile
//
// When decrypting, OpenPGP will see:
// * A message, and parse it
// * An encrypted message, and decrypt it
// * A literal message, and extract it
// * The packet pile
let mut output = vec![];
let message = Message::new(&mut output);
let encrypted_message = Encryptor2::for_recipients(message, recipients)
.build()
.map_err(Error::Sequoia)?;
let mut literal_message = LiteralWriter::new(encrypted_message)
.build()
.map_err(Error::Sequoia)?;
literal_message.write_all(&pp).map_err(Error::SequoiaIo)?;
literal_message.finalize().map_err(Error::Sequoia)?;
Ok(output)
}
fn encrypt_shard(
&self,
shard: &[u8],
public_key: &Cert,
signing_key: &mut Self::SigningKey,
) -> Result<Vec<u8>> {
let policy = StandardPolicy::new();
let valid_cert = public_key
.with_policy(&policy, None)
.map_err(Error::Sequoia)?;
let encryption_keys = get_encryption_keys(&valid_cert).collect::<Vec<_>>();
let signing_key = signing_key
.primary_key()
.parts_into_secret()
.map_err(Error::Sequoia)?
.key()
.clone()
.into_keypair()
.map_err(Error::Sequoia)?;
// Process is as follows:
// * Any OpenPGP message
// * An encrypted message
// * A signed message
// * A literal message
// * The shard itself
//
// When decrypting, OpenPGP will see:
// * A message, and parse it
// * An encrypted message, and decrypt it
// * A signed message, and verify it
// * A literal message, and extract it
// * The shard itself
let mut message_output = vec![];
let message = Message::new(&mut message_output);
let encrypted_message = Encryptor2::for_recipients(
message,
encryption_keys
.iter()
.map(|k| Recipient::new(KeyID::wildcard(), k.key())),
)
.build()
.map_err(Error::Sequoia)?;
let signed_message = Signer::new(encrypted_message, signing_key)
.build()
.map_err(Error::Sequoia)?;
let mut message = LiteralWriter::new(signed_message)
.build()
.map_err(Error::Sequoia)?;
message.write_all(shard).map_err(Error::SequoiaIo)?;
message.finalize().map_err(Error::Sequoia)?;
Ok(message_output)
}
fn parse_private_key_data( fn parse_private_key_data(
&self, &self,
key_data_path: impl AsRef<Path>, key_data_path: impl AsRef<Path>,
@ -300,29 +439,6 @@ impl Format for OpenPGP {
Ok(encrypted_messages) Ok(encrypted_messages)
} }
fn derive_signer(&self, secret: &[u8]) -> Result<Self::Signer, Self::Error> {
let userid = UserID::from("keyfork-sss");
let path = DerivationPath::from_str("m/7366512'/0'")?;
let seed = VariableLengthSeed::new(secret);
let xprv = XPrv::new(seed).derive_path(&path)?;
let derived_cert = keyfork_derive_openpgp::derive(
xprv,
&[KeyFlags::empty().set_certification().set_signing()],
&userid,
)?;
let signing_key = derived_cert
.primary_key()
.parts_into_secret()
.map_err(Error::Sequoia)?
.key()
.clone()
.into_keypair()
.map_err(Error::Sequoia)?;
Ok(signing_key)
}
fn format_shard_file( fn format_shard_file(
&self, &self,
shard_data: Self::ShardData, shard_data: Self::ShardData,
@ -336,53 +452,6 @@ impl Format for OpenPGP {
Ok(()) Ok(())
} }
fn generate_shard_data(
&self,
shares: &[Share],
signer: &Self::Signer,
public_keys: Self::PublicKeyData,
) -> std::result::Result<Self::ShardData, Self::Error> {
let policy = StandardPolicy::new();
let mut total_recipients = vec![];
let mut messages = vec![];
for (share, cert) in shares.iter().zip(public_keys) {
total_recipients.push(cert.clone());
let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?;
let encryption_keys = get_encryption_keys(&valid_cert).collect::<Vec<_>>();
let mut message_output = vec![];
let message = Message::new(&mut message_output);
let message = Encryptor2::for_recipients(
message,
encryption_keys
.iter()
.map(|k| Recipient::new(KeyID::wildcard(), k.key())),
)
.build()
.map_err(Error::Sequoia)?;
let message = Signer::new(message, signer.clone())
.build()
.map_err(Error::Sequoia)?;
let mut message = LiteralWriter::new(message)
.build()
.map_err(Error::Sequoia)?;
// NOTE: This shouldn't be an alloc, but it's a minor alloc, so it's fine.
message
.write_all(&Vec::from(share))
.map_err(Error::SequoiaIo)?;
message.finalize().map_err(Error::Sequoia)?;
messages.push(message_output);
}
// A little bit of back and forth, we're going to parse the messages just to serialize them
// later.
let message = messages.into_iter().flatten().collect::<Vec<_>>();
let data = self.parse_shard_file(message.as_slice())?;
Ok(data)
}
fn decrypt_all_shards( fn decrypt_all_shards(
&self, &self,
private_keys: Option<Self::PrivateKeyData>, private_keys: Option<Self::PrivateKeyData>,
@ -406,11 +475,8 @@ impl Format for OpenPGP {
// because we control the order packets are encrypted and certificates are stored. // because we control the order packets are encrypted and certificates are stored.
// TODO: remove alloc, convert EncryptedMessage to &EncryptedMessage // TODO: remove alloc, convert EncryptedMessage to &EncryptedMessage
let mut messages: HashMap<KeyID, EncryptedMessage> = certs let mut messages: HashMap<KeyID, EncryptedMessage> =
.iter() certs.iter().map(Cert::keyid).zip(shard_data).collect();
.map(Cert::keyid)
.zip(shard_data)
.collect();
let mut decrypted_messages = let mut decrypted_messages =
decrypt_with_keyring(&mut messages, &certs, &policy, &mut keyring)?; decrypt_with_keyring(&mut messages, &certs, &policy, &mut keyring)?;
@ -454,17 +520,15 @@ impl Format for OpenPGP {
keyring.set_root_cert(root_cert.clone()); keyring.set_root_cert(root_cert.clone());
manager.set_root_cert(root_cert.clone()); manager.set_root_cert(root_cert.clone());
let mut messages: HashMap<KeyID, EncryptedMessage> = certs let mut messages: HashMap<KeyID, EncryptedMessage> =
.iter() certs.iter().map(Cert::keyid).zip(shard_data).collect();
.map(Cert::keyid)
.zip(shard_data)
.collect();
let decrypted_messages = let decrypted_messages =
decrypt_with_keyring(&mut messages, &certs, &policy, &mut keyring)?; decrypt_with_keyring(&mut messages, &certs, &policy, &mut keyring)?;
if let Some(message) = decrypted_messages.into_values().next() { if let Some(message) = decrypted_messages.into_values().next() {
let share = Share::try_from(message.as_slice()).map_err(|e| SharksError::Share(e.to_string()))?; let share = Share::try_from(message.as_slice())
.map_err(|e| SharksError::Share(e.to_string()))?;
return Ok((share, threshold)); return Ok((share, threshold));
} }
@ -472,7 +536,8 @@ impl Format for OpenPGP {
decrypt_with_manager(1, &mut messages, &certs, &policy, &mut manager)?; decrypt_with_manager(1, &mut messages, &certs, &policy, &mut manager)?;
if let Some(message) = decrypted_messages.into_values().next() { if let Some(message) = decrypted_messages.into_values().next() {
let share = Share::try_from(message.as_slice()).map_err(|e| SharksError::Share(e.to_string()))?; let share = Share::try_from(message.as_slice())
.map_err(|e| SharksError::Share(e.to_string()))?;
return Ok((share, threshold)); return Ok((share, threshold));
} }

View File

@ -3,10 +3,7 @@ use clap::{Parser, Subcommand};
use std::path::PathBuf; use std::path::PathBuf;
use keyfork_mnemonic_util::Mnemonic; use keyfork_mnemonic_util::Mnemonic;
use keyfork_shard::{ use keyfork_shard::{remote_decrypt, Format};
openpgp::{combine, discover_certs, parse_messages},
remote_decrypt,
};
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>;
@ -37,15 +34,10 @@ impl RecoverSubcommands {
} => { } => {
let content = std::fs::read_to_string(shard_file)?; let content = std::fs::read_to_string(shard_file)?;
if content.contains("BEGIN PGP MESSAGE") { if content.contains("BEGIN PGP MESSAGE") {
let certs = key_discovery let openpgp = keyfork_shard::openpgp::OpenPGP;
.as_ref() // TODO: remove .clone() by making handle() consume self
.map(discover_certs) let seed = openpgp
.transpose()? .decrypt_all_shards_to_secret(key_discovery.clone(), content.as_bytes())?;
.unwrap_or(vec![]);
let mut messages = parse_messages(content.as_bytes())?;
let metadata = messages.pop_front().expect("any pgp encrypted message");
let mut seed = vec![];
combine(certs, &metadata, messages.into(), &mut seed)?;
Ok(seed) Ok(seed)
} else { } else {
panic!("unknown format of shard file"); panic!("unknown format of shard file");

View File

@ -1,5 +1,6 @@
use super::Keyfork; use super::Keyfork;
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum}; use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
use keyfork_shard::Format as _;
use std::{ use std::{
io::{stdin, stdout, Read, Write}, io::{stdin, stdout, Read, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -86,25 +87,8 @@ impl ShardExec for OpenPGP {
where where
T: AsRef<Path>, T: AsRef<Path>,
{ {
let certs = key_discovery let openpgp = keyfork_shard::openpgp::OpenPGP;
.map(|kd| keyfork_shard::openpgp::discover_certs(kd.as_ref())) let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input)?;
.transpose()?
.unwrap_or(vec![]);
let mut encrypted_messages = keyfork_shard::openpgp::parse_messages(input)?;
let encrypted_metadata = encrypted_messages
.pop_front()
.expect("any pgp encrypted message");
let mut bytes = vec![];
keyfork_shard::openpgp::combine(
certs,
&encrypted_metadata,
encrypted_messages.into(),
&mut bytes,
)?;
write!(output, "{}", smex::encode(&bytes))?; write!(output, "{}", smex::encode(&bytes))?;
Ok(()) Ok(())
@ -118,21 +102,8 @@ impl ShardExec for OpenPGP {
where where
T: AsRef<Path>, T: AsRef<Path>,
{ {
let certs = key_discovery let openpgp = keyfork_shard::openpgp::OpenPGP;
.map(|kd| keyfork_shard::openpgp::discover_certs(kd.as_ref())) openpgp.decrypt_one_shard_for_transport(key_discovery, input)?;
.transpose()?
.unwrap_or(vec![]);
let mut encrypted_messages = keyfork_shard::openpgp::parse_messages(input)?;
let encrypted_metadata = encrypted_messages
.pop_front()
.expect("any pgp encrypted message");
keyfork_shard::openpgp::decrypt(
&certs,
&encrypted_metadata,
encrypted_messages.make_contiguous(),
)?;
Ok(()) Ok(())
} }
} }