Start with signing outgoing mails
- Add a scheme on how to discover Husk signing keys. Siging keys mut be certified by the `local trust root` with a signature having a notation with a name equal to `signing_keys@husk-projet.org`. - Fetch the key from the keystore. - Expand `authenticate()` so that notation can be added to the signature. - Add a test to create a signing key and fetch it from the keystore.
This commit is contained in:
@@ -37,6 +37,7 @@ pub async fn dispatch(cmd: LocalsAddCommand, config: HuskConfigContainer) -> Res
|
||||
&vc,
|
||||
crypto::Role::Local,
|
||||
true,
|
||||
None,
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ pub async fn dispatch(cmd: LocalsRemoveCommand, config: HuskConfigContainer) ->
|
||||
cert,
|
||||
crypto::Role::Local,
|
||||
false,
|
||||
None,
|
||||
None)
|
||||
} else {
|
||||
Err(CommandError::CertNotUsable(fpr).into())
|
||||
|
||||
+193
-1
@@ -3,15 +3,20 @@
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
|
||||
use anyhow;
|
||||
use sequoia_openpgp::cert::ValidCert;
|
||||
use sequoia_openpgp::packet::signature::subpacket::NotationDataFlags;
|
||||
use tokio::task::JoinSet;
|
||||
use bytes::Bytes;
|
||||
use sequoia_net::{KeyServer, wkd, dane};
|
||||
use sequoia_directories::Home;
|
||||
use sequoia_keystore::Keystore;
|
||||
use sequoia_keystore::Context;
|
||||
use sequoia_openpgp::packet::prelude::SignatureBuilder;
|
||||
use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse};
|
||||
use sequoia_openpgp::policy::StandardPolicy;
|
||||
@@ -437,7 +442,7 @@ pub enum Role {
|
||||
/// `domains`. Passing `None` or an empty list in this case is an error.
|
||||
/// If `activate` is false, create a certifiaction with minimal trust_roots
|
||||
/// amount and no depth. In this case `domains` is ignored.
|
||||
pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activate: bool, domains: Option<Vec<String>>)
|
||||
pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activate: bool, domains: Option<Vec<String>>, notation: Option<(String, String)>)
|
||||
-> anyhow::Result<()> {
|
||||
|
||||
let policy = &context.policy;
|
||||
@@ -502,6 +507,14 @@ pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activat
|
||||
builder = builder.set_trust_signature(0, 1)?;
|
||||
}
|
||||
|
||||
if let Some((name, value)) = notation {
|
||||
builder = builder.set_notation(
|
||||
name,
|
||||
value,
|
||||
NotationDataFlags::empty().set_human_readable(),
|
||||
false)?;
|
||||
}
|
||||
|
||||
// For a certification, user ids are needed. Build a list of
|
||||
// valid (non self revoked) user ids.
|
||||
let user_ids: Vec<_> = cert.userids()
|
||||
@@ -559,9 +572,107 @@ pub fn can_encrypt(cert: &ValidCert) -> bool {
|
||||
.count() > 0
|
||||
}
|
||||
|
||||
/// Signing keys used by Husk must have a certification with a
|
||||
/// notation of the name `HUSK_SIG_NOTATION`.
|
||||
pub const HUSK_SIG_NOTATION: &str = "signing_key@husk-project.org";
|
||||
|
||||
/// Get Husks signing key
|
||||
pub fn get_signing_key(sequoia_home: Home, cert_store: &CertStore<'_>, policy: &StandardPolicy<'_>, local_trust_root: &Cert) -> anyhow::Result<Option<Cert>> {
|
||||
|
||||
let local_trust_root = local_trust_root.with_policy(policy, None)?;
|
||||
let local_trust_fpr = local_trust_root.fingerprint();
|
||||
|
||||
let mut newest = None;
|
||||
let mut result = None;
|
||||
|
||||
if let Some(keys) = get_all_keys(sequoia_home) {
|
||||
for key in keys {
|
||||
if let Ok(cert) = cert_store.lookup_by_cert_fpr(&key) {
|
||||
if let Ok(vc) = cert.with_policy(policy, None) {
|
||||
if let Some(time) = contains_signing_key(&vc, &local_trust_fpr) {
|
||||
if let Some(othertime) = newest {
|
||||
if time > othertime {
|
||||
newest = Some(time);
|
||||
result = Some(vc.cert().clone());
|
||||
}
|
||||
} else { // first detected key
|
||||
newest = Some(time);
|
||||
result = Some(vc.cert().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// If `vc` is a certificate marked as a Husk signing key
|
||||
/// key, return the creation time of the signature - else return `None`.
|
||||
pub fn contains_signing_key(vc: &ValidCert, local_root_fpr: &Fingerprint) -> Option<SystemTime> {
|
||||
for uid in vc.userids().into_iter() {
|
||||
for sig in uid.certifications() {
|
||||
if sig.issuer_fingerprints().any(|i| i == local_root_fpr)
|
||||
&& sig.notation(HUSK_SIG_NOTATION).count() > 0 {
|
||||
|
||||
// ignore signatures without creation time
|
||||
if let Some(time) = sig.signature_creation_time() {
|
||||
return Some(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get all keys from all backends
|
||||
pub fn get_all_keys(sequoia_home: Home) -> Option<Vec<Fingerprint>> {
|
||||
|
||||
let mut result = Vec::new();
|
||||
std::thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
if let Ok(keystore_base) = Context::configure()
|
||||
.home(sequoia_home.data_dir(sequoia_directories::Component::Keystore))
|
||||
.build() {
|
||||
if let Ok(mut keystore) = Keystore::connect(&keystore_base) {
|
||||
|
||||
if let Ok(mut backends) = keystore.backends() {
|
||||
for backend in &mut backends {
|
||||
if let Ok(devices) = backend.devices() {
|
||||
for mut device in devices {
|
||||
if let Ok(keys) = device.keys() {
|
||||
keys.iter().for_each(|k| {
|
||||
result.push(k.fingerprint());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use sequoia_cert_store::store::CertD;
|
||||
use sequoia_cert_store::{CertStore, LazyCert, StoreUpdate};
|
||||
use sequoia_keystore::Context;
|
||||
use sequoia_keystore::Keystore;
|
||||
use sequoia_directories::Home;
|
||||
use sequoia_directories::Component;
|
||||
use sequoia_openpgp::{cert::CertBuilder, policy::StandardPolicy};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::common::crypto::{HUSK_SIG_NOTATION, get_all_keys};
|
||||
use crate::types::{husk_context::HuskContext, mail_context::MailContext};
|
||||
|
||||
use super::{authenticate, get_signing_key};
|
||||
|
||||
#[test]
|
||||
pub fn test_is_encrypted() {
|
||||
@@ -644,4 +755,85 @@ jk6lh01Ej9QTGQ==
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signing_key() -> anyhow::Result<()> {
|
||||
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let temp_path = temp_dir.path().to_path_buf();
|
||||
let home = Home::new(temp_path)?;
|
||||
|
||||
// build the keystore
|
||||
let keystore_home = home.data_dir(Component::Keystore);
|
||||
let c = Context::configure()
|
||||
.home(keystore_home)
|
||||
.build()?;
|
||||
let mut ks = Keystore::connect(&c)?;
|
||||
|
||||
// get the softkeys backend
|
||||
let mut softkeys = None;
|
||||
for mut backend in ks.backends()?.into_iter() {
|
||||
if backend.id().expect("backend id") == "softkeys" {
|
||||
softkeys = Some(backend);
|
||||
}
|
||||
}
|
||||
assert!(softkeys.is_some());
|
||||
let mut softkeys = softkeys.unwrap();
|
||||
|
||||
// build the certstore
|
||||
let cert_store_base = home.data_dir(sequoia_directories::Component::CertD);
|
||||
std::fs::create_dir_all(&cert_store_base)?;
|
||||
let _ = CertD::open(&cert_store_base)?;
|
||||
let store = CertStore::open(cert_store_base)?;
|
||||
|
||||
// build the trust root
|
||||
let (root, _) = CertBuilder::general_purpose(Some("local root"))
|
||||
.add_signing_subkey()
|
||||
.generate()?;
|
||||
|
||||
// create a skeleton HuskContext for later use.
|
||||
let context = HuskContext {
|
||||
cert_store: store,
|
||||
policy: StandardPolicy::new(),
|
||||
local_trust_root: root,
|
||||
signing_key: None,
|
||||
introducers: Vec::new(),
|
||||
keyservers: Vec::new(),
|
||||
mail: MailContext::new()
|
||||
};
|
||||
|
||||
// store the local trust root
|
||||
context.cert_store.update(Arc::new(LazyCert::from(context.local_trust_root.clone())))?;
|
||||
|
||||
// the siging key
|
||||
let (signing_key, _) = CertBuilder::general_purpose(Some("signing key"))
|
||||
.add_signing_subkey()
|
||||
.generate()?;
|
||||
let valid_signing_cert = signing_key.with_policy(&context.policy, None)?;
|
||||
let signing_fpr = signing_key.fingerprint();
|
||||
softkeys.import(&signing_key)?;
|
||||
|
||||
let keys= get_all_keys(home.clone());
|
||||
eprintln!("{:?}", keys);
|
||||
|
||||
authenticate(&context,
|
||||
&valid_signing_cert,
|
||||
super::Role::Local,
|
||||
true,
|
||||
None,
|
||||
Some((HUSK_SIG_NOTATION.to_string(), "dummy".to_string())))?;
|
||||
|
||||
// get the Husk signing key
|
||||
let key = get_signing_key(home,
|
||||
&context.cert_store,
|
||||
&context.policy,
|
||||
&context.local_trust_root)?;
|
||||
|
||||
assert!(key.is_some());
|
||||
let fpr = key.unwrap().fingerprint();
|
||||
|
||||
assert_eq!(signing_fpr, fpr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub struct HuskContext<'hc> {
|
||||
pub policy: StandardPolicy<'hc>,
|
||||
pub cert_store: CertStore<'hc>,
|
||||
pub local_trust_root: Cert,
|
||||
pub signing_key: Option<Cert>,
|
||||
pub introducers: Vec<Introducer>,
|
||||
pub keyservers: Vec<String>,
|
||||
pub mail: MailContext<'hc>,
|
||||
@@ -45,10 +46,19 @@ impl<'hc> HuskContext<'hc> {
|
||||
let local_trust_root = crypto::get_local_trust_root(&cert_store)?;
|
||||
let introducers = crypto::get_introducers(&cert_store, &policy, local_trust_root.fingerprint());
|
||||
|
||||
let signing_key = match crypto::get_signing_key(sequoia_home, &cert_store, &policy, &local_trust_root) {
|
||||
Ok(Some(cert)) => Some(cert),
|
||||
_ => {
|
||||
log::warn!("No signing key found");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(HuskContext {
|
||||
policy,
|
||||
cert_store,
|
||||
local_trust_root,
|
||||
signing_key,
|
||||
introducers,
|
||||
keyservers: config.get_keyservers(),
|
||||
mail: MailContext::new(),
|
||||
|
||||
@@ -83,7 +83,8 @@ impl Introducer {
|
||||
cert,
|
||||
crypto::Role::Introducer,
|
||||
true,
|
||||
Some(domains))
|
||||
Some(domains),
|
||||
None)
|
||||
}
|
||||
|
||||
/// Remove an introducer.
|
||||
@@ -101,6 +102,7 @@ impl Introducer {
|
||||
cert,
|
||||
crypto::Role::Introducer,
|
||||
false,
|
||||
None,
|
||||
None)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user