use std::time::{Duration, SystemTime, SystemTimeError}; use derive_util::{ request::{DerivationResponse, TryFromDerivationResponseError}, DerivationIndex, ExtendedPrivateKey, PrivateKey, }; use ed25519_dalek::SigningKey; pub use keyfork_derive_util as derive_util; use sequoia_openpgp::{ packet::{ key::{Key4, PrimaryRole, SubordinateRole}, signature::SignatureBuilder, Key, UserID, }, types::{KeyFlags, SignatureType}, Cert, Packet, }; pub use sequoia_openpgp as openpgp; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] Anyhow(#[from] anyhow::Error), #[error("Key configured with both encryption and non-encryption key flags: {0:?}")] InvalidKeyFlags(KeyFlags), #[error("Incorrect derived data: {0}")] IncorrectDerivedData(#[from] TryFromDerivationResponseError), #[error("Could not create derivation index: {0}")] Index(#[from] keyfork_derive_util::index::Error), #[error("Could not perform operation against private key: {0}")] PrivateKey(#[from] keyfork_derive_util::extended_key::private_key::Error), #[error("Invalid system time: {0}")] SystemTime(#[from] SystemTimeError), #[error("First key in certificate must have certify capability")] NotCert, #[error("Index out of bounds: {0}")] IndexOutOfBounds(#[from] std::num::TryFromIntError), } pub type Result = std::result::Result; pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: &UserID) -> Result { let primary_key_flags = match keys.get(0) { Some(kf) if kf.for_certification() => kf, _ => return Err(Error::NotCert), }; let epoch = SystemTime::UNIX_EPOCH + Duration::from_secs(1); let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24); // Create certificate with initial key and signature let xprv = ExtendedPrivateKey::::try_from(data)?; let derived_primary_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?; let primary_key = Key::from(Key4::<_, PrimaryRole>::import_secret_ed25519( &PrivateKey::to_bytes(derived_primary_key.private_key()), epoch, )?); let cert = Cert::from_packets(vec![Packet::SecretKey(primary_key.clone())].into_iter())?; // Sign and attach primary key and primary userid let builder = SignatureBuilder::new(SignatureType::PositiveCertification) .set_key_validity_period(one_day.duration_since(epoch)?)? .set_signature_creation_time(epoch)? .set_key_flags(primary_key_flags.clone())?; let binding = userid.bind(&mut primary_key.clone().into_keypair()?, &cert, builder)?; let cert = cert.insert_packets(vec![Packet::from(userid.clone()), binding.into()])?; let policy = sequoia_openpgp::policy::StandardPolicy::new(); // Set certificate expiration to one day let mut keypair = primary_key.clone().into_keypair()?; let signatures = cert.set_expiration_time(&policy, None, &mut keypair, Some(one_day))?; let cert = cert.insert_packets(signatures)?; let mut cert = cert; for (index, subkey_flags) in keys.iter().enumerate().skip(1) { // Generate subkey let index = u32::try_from(index)?; let derived_key = xprv.derive_child(&DerivationIndex::new(index, true)?)?; let is_enc = subkey_flags.for_transport_encryption() || subkey_flags.for_storage_encryption(); let is_non_enc = subkey_flags.for_certification() || subkey_flags.for_signing() || subkey_flags.for_authentication(); let subkey = if is_enc && is_non_enc { return Err(Error::InvalidKeyFlags(subkey_flags.clone())); } else if is_enc { Key::from( Key4::<_, SubordinateRole>::import_secret_cv25519( &PrivateKey::to_bytes(derived_key.private_key()), None, None, epoch, )? ) } else { Key::from( Key4::<_, SubordinateRole>::import_secret_ed25519( &PrivateKey::to_bytes(derived_key.private_key()), epoch, )? ) }; // As per OpenPGP spec, signing keys must backsig the primary key let builder = if subkey_flags.for_signing() { SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags(subkey_flags.clone())? .set_signature_creation_time(epoch)? .set_key_validity_period(one_day.duration_since(epoch)?)? .set_embedded_signature( SignatureBuilder::new(SignatureType::PrimaryKeyBinding) .set_signature_creation_time(epoch)? .sign_primary_key_binding( &mut subkey.clone().into_keypair()?, &primary_key, &subkey, )?, )? } else { SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags(subkey_flags.clone())? .set_signature_creation_time(epoch)? .set_key_validity_period(one_day.duration_since(epoch)?)? }; // Sign subkey with primary key and attach to cert let binding = builder.sign_subkey_binding(&mut primary_key.clone().into_keypair()?, None, &subkey)?; cert = cert.insert_packets(vec![ Packet::SecretSubkey(subkey.clone()), Packet::Signature(binding), ])?; } Ok(cert) }