keyfork-shard: add new methods to trait to support split()
This commit is contained in:
parent
3c84a1ba9b
commit
92fa68fa3b
|
@ -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.
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue