Compare commits
No commits in common. "31d1992e163c197dd10ffc0a9ea9835dd2c40bfe" and "fa5d5ede1d59b6b63964a39931191639997623d8" have entirely different histories.
31d1992e16
...
fa5d5ede1d
|
@ -17,12 +17,6 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -747,15 +741,6 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
|
@ -812,7 +797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.1",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -963,19 +948,6 @@ dependencies = [
|
||||||
"smex",
|
"smex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "keyfork-shard"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"bincode",
|
|
||||||
"keyfork-derive-openpgp",
|
|
||||||
"sequoia-openpgp",
|
|
||||||
"serde",
|
|
||||||
"sharks",
|
|
||||||
"smex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork-slip10-test-data"
|
name = "keyfork-slip10-test-data"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1404,22 +1376,11 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.1.16",
|
"getrandom 0.1.16",
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.2.2",
|
"rand_chacha",
|
||||||
"rand_core 0.5.1",
|
"rand_core 0.5.1",
|
||||||
"rand_hc",
|
"rand_hc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"rand_chacha 0.3.1",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -1430,16 +1391,6 @@ dependencies = [
|
||||||
"rand_core 0.5.1",
|
"rand_core 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -1746,17 +1697,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sharks"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "902b1e955f8a2e429fb1bad49f83fb952e6195d3c360ac547ff00fb826388753"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.9.1",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -2392,17 +2332,3 @@ name = "zeroize"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||||
dependencies = [
|
|
||||||
"zeroize_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zeroize_derive"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.29",
|
|
||||||
]
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ members = [
|
||||||
"keyfork-frame",
|
"keyfork-frame",
|
||||||
"keyfork-mnemonic-util",
|
"keyfork-mnemonic-util",
|
||||||
"keyfork-plumbing",
|
"keyfork-plumbing",
|
||||||
"keyfork-shard",
|
|
||||||
"keyfork-slip10-test-data",
|
"keyfork-slip10-test-data",
|
||||||
"keyforkd",
|
"keyforkd",
|
||||||
"keyforkd-client",
|
"keyforkd-client",
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::time::{Duration, SystemTime, SystemTimeError};
|
use std::time::{Duration, SystemTime, SystemTimeError};
|
||||||
|
|
||||||
use derive_util::{
|
use ed25519_dalek::SigningKey;
|
||||||
|
use keyfork_derive_util::{
|
||||||
request::{DerivationResponse, TryFromDerivationResponseError},
|
request::{DerivationResponse, TryFromDerivationResponseError},
|
||||||
DerivationIndex, ExtendedPrivateKey, PrivateKey,
|
DerivationIndex, ExtendedPrivateKey, PrivateKey,
|
||||||
};
|
};
|
||||||
use ed25519_dalek::SigningKey;
|
|
||||||
pub use keyfork_derive_util as derive_util;
|
|
||||||
use sequoia_openpgp::{
|
use sequoia_openpgp::{
|
||||||
packet::{
|
packet::{
|
||||||
key::{Key4, PrimaryRole, SubordinateRole},
|
key::{Key4, PrimaryRole, SubordinateRole},
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "keyfork-shard"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.75"
|
|
||||||
bincode = "1.3.3"
|
|
||||||
keyfork-derive-openpgp = { version = "0.1.0", path = "../keyfork-derive-openpgp" }
|
|
||||||
sequoia-openpgp = "1.16.1"
|
|
||||||
serde = "1.0.188"
|
|
||||||
sharks = "0.5.0"
|
|
||||||
smex = { version = "0.1.0", path = "../smex" }
|
|
|
@ -1,98 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
env,
|
|
||||||
io::{stdin, stdout},
|
|
||||||
path::PathBuf,
|
|
||||||
process::ExitCode,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use keyfork_shard::{combine, discover_certs, openpgp::Cert, EncryptedMessage};
|
|
||||||
use openpgp::{
|
|
||||||
packet::Packet,
|
|
||||||
parse::Parse,
|
|
||||||
PacketPile,
|
|
||||||
};
|
|
||||||
use sequoia_openpgp as openpgp;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum Error {
|
|
||||||
Usage(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::Usage(program_name) => {
|
|
||||||
write!(f, "Usage: {program_name} threshold key_discovery")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
|
|
||||||
let threshold = u8::from_str(threshold)?;
|
|
||||||
let key_discovery = PathBuf::from(key_discovery);
|
|
||||||
|
|
||||||
// Verify path exists
|
|
||||||
std::fs::metadata(&key_discovery)?;
|
|
||||||
|
|
||||||
// Load certs from path
|
|
||||||
let certs = discover_certs(key_discovery)?;
|
|
||||||
|
|
||||||
Ok((threshold, certs))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
|
||||||
let mut args = env::args();
|
|
||||||
let program_name = args.next().expect("program name");
|
|
||||||
let args = args.collect::<Vec<_>>();
|
|
||||||
let (threshold, cert_list) = match args.as_slice() {
|
|
||||||
[threshold, key_discovery] => validate(threshold, key_discovery)?,
|
|
||||||
_ => return Err(Error::Usage(program_name).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let stdin = stdin();
|
|
||||||
|
|
||||||
let mut pkesks = Vec::new();
|
|
||||||
let mut encrypted_messages = VecDeque::new();
|
|
||||||
|
|
||||||
for packet in PacketPile::from_reader(stdin)?.into_children() {
|
|
||||||
match packet {
|
|
||||||
Packet::PKESK(p) => pkesks.push(p),
|
|
||||||
Packet::SEIP(s) => {
|
|
||||||
encrypted_messages.push_back(EncryptedMessage::with_swap(&mut pkesks, s));
|
|
||||||
}
|
|
||||||
s => {
|
|
||||||
panic!("Invalid variant found: {}", s.tag());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let encrypted_metadata = encrypted_messages
|
|
||||||
.pop_front()
|
|
||||||
.expect("any pgp encrypted message");
|
|
||||||
|
|
||||||
combine(
|
|
||||||
threshold,
|
|
||||||
cert_list,
|
|
||||||
encrypted_metadata,
|
|
||||||
encrypted_messages.into(),
|
|
||||||
stdout(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
|
||||||
let result = run();
|
|
||||||
if let Err(e) = result {
|
|
||||||
eprintln!("Error: {e}");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
ExitCode::SUCCESS
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
|
|
||||||
|
|
||||||
use keyfork_shard::{discover_certs, openpgp::Cert, split};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum Error {
|
|
||||||
Usage(String),
|
|
||||||
Input,
|
|
||||||
Threshold(u8, u8),
|
|
||||||
InvalidCertCount(usize, u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::Usage(program_name) => {
|
|
||||||
write!(f, "Usage: {program_name} threshold max key_discovery")
|
|
||||||
}
|
|
||||||
Error::Input => f.write_str("Expected hex encoded input"),
|
|
||||||
Error::Threshold(threshold, max) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Invalid threshold: 0 < threshold {threshold} <= max {max} < 256"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Error::InvalidCertCount(count, max) => {
|
|
||||||
write!(f, "Invalid cert count: count {count} != max {max}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
fn validate(threshold: &str, max: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
|
|
||||||
let threshold = u8::from_str(threshold)?;
|
|
||||||
let max = u8::from_str(max)?;
|
|
||||||
let key_discovery = PathBuf::from(key_discovery);
|
|
||||||
if threshold > max {
|
|
||||||
return Err(Error::Threshold(threshold, max).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify path exists
|
|
||||||
std::fs::metadata(&key_discovery)?;
|
|
||||||
|
|
||||||
// Load certs from path
|
|
||||||
let certs = discover_certs(key_discovery)?;
|
|
||||||
if certs.len() != max.into() {
|
|
||||||
return Err(Error::InvalidCertCount(certs.len(), max).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((threshold, certs))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
|
||||||
let mut args = env::args();
|
|
||||||
let program_name = args.next().expect("program name");
|
|
||||||
let args = args.collect::<Vec<_>>();
|
|
||||||
let (threshold, cert_list) = match args.as_slice() {
|
|
||||||
[threshold, max, key_discovery] => validate(threshold, max, key_discovery)?,
|
|
||||||
_ => return Err(Error::Usage(program_name).into()),
|
|
||||||
};
|
|
||||||
let input = {
|
|
||||||
use std::io::stdin;
|
|
||||||
let Some(line) = stdin().lines().next() else {
|
|
||||||
return Err(Error::Input.into());
|
|
||||||
};
|
|
||||||
smex::decode(&line?)?
|
|
||||||
};
|
|
||||||
|
|
||||||
split(threshold, cert_list, &input, std::io::stdout())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
|
||||||
let result = run();
|
|
||||||
if let Err(e) = result {
|
|
||||||
eprintln!("Error: {e}");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
ExitCode::SUCCESS
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
use crate::openpgp::{
|
|
||||||
self,
|
|
||||||
cert::Cert,
|
|
||||||
packet::{PKESK, SKESK},
|
|
||||||
parse::stream::{DecryptionHelper, VerificationHelper, MessageStructure},
|
|
||||||
KeyHandle, KeyID,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum KeyringFailure {
|
|
||||||
SecretKeyNotFound,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
SmartcardDecrypt,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for KeyringFailure {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
KeyringFailure::SecretKeyNotFound => f.write_str("Secret key was not found"),
|
|
||||||
KeyringFailure::SmartcardDecrypt => {
|
|
||||||
f.write_str("Smartcard could not decrypt any PKESKs")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for KeyringFailure {}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Keyring {
|
|
||||||
full_certs: Vec<Cert>,
|
|
||||||
root: Option<Cert>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Keyring {
|
|
||||||
pub fn new(certs: impl AsRef<[Cert]>) -> Self {
|
|
||||||
Self {
|
|
||||||
full_certs: certs.as_ref().to_vec(),
|
|
||||||
root: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the root cert, returning the old cert
|
|
||||||
pub fn set_root_cert(&mut self, cert: impl Into<Option<Cert>>) -> Option<Cert> {
|
|
||||||
let mut cert = cert.into();
|
|
||||||
std::mem::swap(&mut self.root, &mut cert);
|
|
||||||
cert
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_cert_for_primary_keyid<'a>(&'a self, keyid: &KeyID) -> Option<&'a Cert> {
|
|
||||||
self
|
|
||||||
.full_certs
|
|
||||||
.iter()
|
|
||||||
.find(|cert| &cert.keyid() == keyid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This can't return an iterator because iterators are all different types
|
|
||||||
// and returning different types is naughty
|
|
||||||
fn get_certs_for_pkesk<'a>(&'a self, pkesk: &'a PKESK) -> impl Iterator<Item = &Cert> + 'a {
|
|
||||||
self.full_certs.iter().filter(move |cert| {
|
|
||||||
pkesk.recipient().is_wildcard()
|
|
||||||
|| cert.keys().any(|k| {
|
|
||||||
&k.keyid() == pkesk.recipient()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerificationHelper for &mut Keyring {
|
|
||||||
fn get_certs(&mut self, _ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
|
||||||
// TODO: no verification logic until we mark a cert as "root"
|
|
||||||
// this is the first cert in the metadata list
|
|
||||||
Ok(Vec::new())
|
|
||||||
}
|
|
||||||
fn check(&mut self, _structure: MessageStructure) -> openpgp::Result<()> {
|
|
||||||
// TODO: ensure that we have a "root" cert and assign it
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DecryptionHelper for &mut Keyring {
|
|
||||||
fn decrypt<D>(
|
|
||||||
&mut self,
|
|
||||||
pkesks: &[PKESK],
|
|
||||||
_skesks: &[SKESK],
|
|
||||||
sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
|
|
||||||
mut decrypt: D,
|
|
||||||
) -> openpgp::Result<Option<openpgp::Fingerprint>>
|
|
||||||
where
|
|
||||||
D: FnMut(
|
|
||||||
openpgp::types::SymmetricAlgorithm,
|
|
||||||
&openpgp::crypto::SessionKey,
|
|
||||||
) -> bool,
|
|
||||||
{
|
|
||||||
// optimized route: use all locally stored certs
|
|
||||||
for pkesk in pkesks {
|
|
||||||
for cert in self.get_certs_for_pkesk(pkesk) {
|
|
||||||
for key in cert.keys().secret() {
|
|
||||||
let secret_key = key.key().clone();
|
|
||||||
// NOTE: Returns an error if using an encrypted secret key.
|
|
||||||
// TODO: support skipping or validating encrypted secret keys.
|
|
||||||
let mut keypair = secret_key.into_keypair()?;
|
|
||||||
if pkesk
|
|
||||||
.decrypt(&mut keypair, sym_algo)
|
|
||||||
.map(|(algo, sk)| decrypt(algo, &sk))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Ok(Some(key.fingerprint()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// smartcard route: plug in smartcard, attempt decrypt, fail and bail
|
|
||||||
|
|
||||||
Err(KeyringFailure::SecretKeyNotFound.into())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,337 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
io::{Read, Write},
|
|
||||||
path::Path,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use keyfork_derive_openpgp::derive_util::{
|
|
||||||
request::{DerivationAlgorithm, DerivationRequest},
|
|
||||||
DerivationPath,
|
|
||||||
};
|
|
||||||
use openpgp::{
|
|
||||||
armor::{Kind, Writer},
|
|
||||||
cert::{Cert, CertParser, ValidCert},
|
|
||||||
packet::{Tag, UserID, PKESK, SEIP},
|
|
||||||
parse::{stream::DecryptorBuilder, Parse},
|
|
||||||
policy::{NullPolicy, Policy, StandardPolicy},
|
|
||||||
serialize::{
|
|
||||||
stream::{ArbitraryWriter, Encryptor, LiteralWriter, Message, Recipient, Signer},
|
|
||||||
Marshal,
|
|
||||||
},
|
|
||||||
types::KeyFlags,
|
|
||||||
KeyID,
|
|
||||||
};
|
|
||||||
pub use sequoia_openpgp as openpgp;
|
|
||||||
use sharks::{Share, Sharks};
|
|
||||||
|
|
||||||
mod keyring;
|
|
||||||
use keyring::Keyring;
|
|
||||||
|
|
||||||
// TODO: better error handling
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct WrappedError(String);
|
|
||||||
|
|
||||||
impl std::fmt::Display for WrappedError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for WrappedError {}
|
|
||||||
|
|
||||||
pub type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EncryptedMessage {
|
|
||||||
pkesks: Vec<PKESK>,
|
|
||||||
message: SEIP,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EncryptedMessage {
|
|
||||||
pub fn with_swap(pkesks: &mut Vec<PKESK>, seip: SEIP) -> Self {
|
|
||||||
Self {
|
|
||||||
pkesks: std::mem::take(pkesks),
|
|
||||||
message: seip,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decrypt_with(&self, policy: &'_ dyn Policy, keyring: &mut Keyring) -> Result<Vec<u8>> {
|
|
||||||
let mut packets = vec![];
|
|
||||||
|
|
||||||
for pkesk in &self.pkesks {
|
|
||||||
let mut packet = vec![];
|
|
||||||
pkesk.serialize(&mut packet)?;
|
|
||||||
let message = Message::new(&mut packets);
|
|
||||||
let mut message = ArbitraryWriter::new(message, Tag::PKESK)?;
|
|
||||||
message.write_all(&packet)?;
|
|
||||||
message.finalize()?;
|
|
||||||
}
|
|
||||||
let mut packet = vec![];
|
|
||||||
self.message.serialize(&mut packet)?;
|
|
||||||
let message = Message::new(&mut packets);
|
|
||||||
let mut message = ArbitraryWriter::new(message, Tag::SEIP)?;
|
|
||||||
message.write_all(&packet)?;
|
|
||||||
message.finalize()?;
|
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: only serialize the message and use provided PKESK vec
|
|
||||||
let mut pkesks = vec![];
|
|
||||||
let ppr = PacketParser::from_reader(&packets[..])?;
|
|
||||||
while let PacketParserResult::Some(mut pp) = ppr {
|
|
||||||
match pp.packet {
|
|
||||||
openpgp::Packet::PKESK(pkesk) => pkesks.push(pkesk),
|
|
||||||
openpgp::Packet::SEIP(_) => {
|
|
||||||
keyring.decrypt(&pkesks, &[], None, |algo, sk| {
|
|
||||||
// pushes packet to stack
|
|
||||||
pp.decrypt(algo, sk).is_ok()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
p => panic!("Unexpected packet type: {}", p.tag()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
let mut decryptor =
|
|
||||||
DecryptorBuilder::from_bytes(&packets)?.with_policy(policy, None, keyring)?;
|
|
||||||
|
|
||||||
let mut content = vec![];
|
|
||||||
decryptor.read_to_end(&mut content)?;
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn discover_certs(path: impl AsRef<Path>) -> Result<Vec<Cert>> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
|
|
||||||
if path.is_file() {
|
|
||||||
let mut vec = vec![];
|
|
||||||
for cert in CertParser::from_file(path)? {
|
|
||||||
vec.push(cert?);
|
|
||||||
}
|
|
||||||
Ok(vec)
|
|
||||||
} else {
|
|
||||||
let mut vec = vec![];
|
|
||||||
for entry in path
|
|
||||||
.read_dir()?
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.filter(|p| p.path().is_file())
|
|
||||||
{
|
|
||||||
vec.push(Cert::from_file(entry.path())?);
|
|
||||||
}
|
|
||||||
Ok(vec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_encryption_keys<'a>(
|
|
||||||
cert: &'a ValidCert,
|
|
||||||
) -> openpgp::cert::prelude::ValidKeyAmalgamationIter<
|
|
||||||
'a,
|
|
||||||
openpgp::packet::key::PublicParts,
|
|
||||||
openpgp::packet::key::UnspecifiedRole,
|
|
||||||
> {
|
|
||||||
cert.keys()
|
|
||||||
.alive()
|
|
||||||
.revoked(false)
|
|
||||||
.supported()
|
|
||||||
.for_storage_encryption()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_decryption_keys<'a>(
|
|
||||||
cert: &'a ValidCert,
|
|
||||||
) -> openpgp::cert::prelude::ValidKeyAmalgamationIter<
|
|
||||||
'a,
|
|
||||||
openpgp::packet::key::SecretParts,
|
|
||||||
openpgp::packet::key::UnspecifiedRole,
|
|
||||||
> {
|
|
||||||
cert.keys()
|
|
||||||
/*
|
|
||||||
.alive()
|
|
||||||
.revoked(false)
|
|
||||||
.supported()
|
|
||||||
*/
|
|
||||||
.for_storage_encryption()
|
|
||||||
.secret()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn combine(
|
|
||||||
threshold: u8,
|
|
||||||
certs: Vec<Cert>,
|
|
||||||
metadata: EncryptedMessage,
|
|
||||||
messages: Vec<EncryptedMessage>,
|
|
||||||
mut output: impl Write,
|
|
||||||
) -> Result<()> {
|
|
||||||
// Be as liberal as possible when decrypting.
|
|
||||||
// We don't want to invalidate someone's keys just because the old sig expired.
|
|
||||||
let policy = NullPolicy::new();
|
|
||||||
|
|
||||||
let mut keyring = Keyring::new(certs);
|
|
||||||
let content = metadata.decrypt_with(&policy, &mut keyring)?;
|
|
||||||
|
|
||||||
let mut cert_parser = CertParser::from_bytes(&content)?;
|
|
||||||
let root_cert = match cert_parser.next() {
|
|
||||||
Some(Ok(c)) => c,
|
|
||||||
Some(Err(e)) => panic!("Could not find root (first) certificate: {e}"),
|
|
||||||
None => panic!("No certs found in cert parser"),
|
|
||||||
};
|
|
||||||
let certs = cert_parser.collect::<openpgp::Result<Vec<_>>>()?;
|
|
||||||
keyring.set_root_cert(root_cert);
|
|
||||||
let mut messages: HashMap<KeyID, EncryptedMessage> =
|
|
||||||
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
|
|
||||||
let mut decrypted_messages: HashMap<KeyID, Vec<u8>> = HashMap::new();
|
|
||||||
|
|
||||||
// NOTE: This is ONLY stable because we control the generation of PKESK packets and
|
|
||||||
// encode the policy to ourselves.
|
|
||||||
for valid_cert in certs.iter().map(|cert| cert.with_policy(&policy, None)) {
|
|
||||||
let valid_cert = valid_cert?;
|
|
||||||
// get keys from keyring for cert
|
|
||||||
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let secret_cert = secret_cert.with_policy(&policy, None)?;
|
|
||||||
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
|
|
||||||
if !keys.is_empty() {
|
|
||||||
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
|
|
||||||
for (pkesk, key) in message.pkesks.iter_mut().zip(keys) {
|
|
||||||
pkesk.set_recipient(key.keyid());
|
|
||||||
}
|
|
||||||
// we have a pkesk, decrypt via keyring
|
|
||||||
let result = message.decrypt_with(&policy, &mut keyring);
|
|
||||||
match result {
|
|
||||||
Ok(message) => {
|
|
||||||
decrypted_messages.insert(valid_cert.keyid(), message);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!(
|
|
||||||
"Could not decrypt with fingerprint {}: {}",
|
|
||||||
valid_cert.keyid(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
// do nothing, key will be retained
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean decrypted messages from encrypted messages
|
|
||||||
messages.retain(|k, _v| !decrypted_messages.contains_key(k));
|
|
||||||
|
|
||||||
let left_from_threshold = threshold as usize - decrypted_messages.len();
|
|
||||||
if left_from_threshold > 0 {
|
|
||||||
eprintln!("remaining keys: {left_from_threshold}, prompting yubikeys");
|
|
||||||
}
|
|
||||||
for _ in 0..left_from_threshold {
|
|
||||||
todo!("prompt for Yubikeys")
|
|
||||||
}
|
|
||||||
|
|
||||||
let shares = decrypted_messages
|
|
||||||
.values()
|
|
||||||
.map(|message| Share::try_from(message.as_slice()))
|
|
||||||
.collect::<Result<Vec<_>, &str>>()
|
|
||||||
.map_err(|e| WrappedError(e.to_string()))?;
|
|
||||||
let secret = Sharks(threshold).recover(&shares)?;
|
|
||||||
|
|
||||||
output.write_all(smex::encode(&secret).as_bytes())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write) -> Result<()> {
|
|
||||||
// build cert to sign encrypted shares
|
|
||||||
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,
|
|
||||||
)?;
|
|
||||||
let signing_key = derived_cert
|
|
||||||
.primary_key()
|
|
||||||
.parts_into_secret()?
|
|
||||||
.key()
|
|
||||||
.clone()
|
|
||||||
.into_keypair()?;
|
|
||||||
|
|
||||||
let sharks = Sharks(threshold);
|
|
||||||
let dealer = sharks.dealer(secret);
|
|
||||||
let shares = dealer.map(|s| Vec::from(&s)).collect::<Vec<_>>();
|
|
||||||
let policy = StandardPolicy::new();
|
|
||||||
let mut writer = Writer::new(output, Kind::Message)?;
|
|
||||||
|
|
||||||
let mut total_recipients = vec![];
|
|
||||||
let mut messages = vec![];
|
|
||||||
|
|
||||||
for (share, cert) in shares.iter().zip(certs) {
|
|
||||||
total_recipients.push(cert.clone());
|
|
||||||
let valid_cert = cert.with_policy(&policy, None)?;
|
|
||||||
let encryption_keys = get_encryption_keys(&valid_cert).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut message_output = vec![];
|
|
||||||
let message = Message::new(&mut message_output);
|
|
||||||
let message = Encryptor::for_recipients(
|
|
||||||
message,
|
|
||||||
encryption_keys
|
|
||||||
.iter()
|
|
||||||
.map(|k| Recipient::new(KeyID::wildcard(), k.key())),
|
|
||||||
)
|
|
||||||
.build()?;
|
|
||||||
let message = Signer::new(message, signing_key.clone()).build()?;
|
|
||||||
let mut message = LiteralWriter::new(message).build()?;
|
|
||||||
message.write_all(share)?;
|
|
||||||
message.finalize()?;
|
|
||||||
|
|
||||||
messages.push(message_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pp = vec![];
|
|
||||||
// store derived cert to verify provided shares
|
|
||||||
derived_cert.serialize(&mut pp)?;
|
|
||||||
for recipient in &total_recipients {
|
|
||||||
recipient.serialize(&mut pp)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify packet pile
|
|
||||||
for (packet_cert, cert) in openpgp::cert::CertParser::from_bytes(&pp)?
|
|
||||||
.skip(1)
|
|
||||||
.zip(total_recipients.iter())
|
|
||||||
{
|
|
||||||
if packet_cert? != *cert {
|
|
||||||
panic!(
|
|
||||||
"packet pile could not recreate cert: {}",
|
|
||||||
cert.fingerprint()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid_certs = total_recipients
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.with_policy(&policy, None))
|
|
||||||
.collect::<openpgp::Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let total_recipients = valid_certs.iter().flat_map(|vc| {
|
|
||||||
get_encryption_keys(vc).map(|key| Recipient::new(KeyID::wildcard(), key.key()))
|
|
||||||
});
|
|
||||||
|
|
||||||
// metadata
|
|
||||||
let mut message_output = vec![];
|
|
||||||
let message = Message::new(&mut message_output);
|
|
||||||
let message = Encryptor::for_recipients(message, total_recipients).build()?;
|
|
||||||
let mut message = LiteralWriter::new(message).build()?;
|
|
||||||
message.write_all(&pp)?;
|
|
||||||
message.finalize()?;
|
|
||||||
writer.write_all(&message_output)?;
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
writer.write_all(&message)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.finalize()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Loading…
Reference in New Issue