diff --git a/Cargo.lock b/Cargo.lock index 756d91d..547846c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,6 +893,7 @@ dependencies = [ name = "keyfork-derive-openpgp" version = "0.1.0" dependencies = [ + "anyhow", "ed25519-dalek", "keyfork-derive-util", "keyforkd-client", diff --git a/keyfork-derive-openpgp/Cargo.toml b/keyfork-derive-openpgp/Cargo.toml index ac73cc4..f5a865a 100644 --- a/keyfork-derive-openpgp/Cargo.toml +++ b/keyfork-derive-openpgp/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.75" ed25519-dalek = "2.0.0" keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", default-features = false, features = ["ed25519"] } keyforkd-client = { version = "0.1.0", path = "../keyforkd-client", default-features = false, features = ["ed25519"] } diff --git a/keyfork-derive-openpgp/src/lib.rs b/keyfork-derive-openpgp/src/lib.rs new file mode 100644 index 0000000..f2d0590 --- /dev/null +++ b/keyfork-derive-openpgp/src/lib.rs @@ -0,0 +1,121 @@ +use std::time::{Duration, SystemTime, SystemTimeError}; + +use ed25519_dalek::SigningKey; +use keyfork_derive_util::{ + request::{DerivationResponse, TryFromDerivationResponseError}, + DerivationIndex, ExtendedPrivateKey, PrivateKey, +}; +use sequoia_openpgp::{ + packet::{ + key::{Key4, PrimaryRole, SubordinateRole}, + signature::SignatureBuilder, + Key, UserID, + }, + types::{KeyFlags, SignatureType}, + Cert, Packet, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Anyhow(#[from] anyhow::Error), + + #[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 subkey = Key::from( + Key4::<_, SubordinateRole>::import_secret_ed25519( + &PrivateKey::to_bytes(derived_key.private_key()), + epoch, + ) + .unwrap(), + ); + + // 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) +} diff --git a/keyfork-derive-openpgp/src/main.rs b/keyfork-derive-openpgp/src/main.rs index e25bda1..0fa9d03 100644 --- a/keyfork-derive-openpgp/src/main.rs +++ b/keyfork-derive-openpgp/src/main.rs @@ -121,75 +121,9 @@ fn main() -> Result<(), Box> { let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); let derived_data = Client::discover_socket()?.request(&request)?; - let xprv = ExtendedPrivateKey::::try_from(&derived_data)?; + let subkeys = subkey_format.iter().map(|kt| kt.inner().clone()).collect::>(); - let derived_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?; - let epoch = SystemTime::UNIX_EPOCH + Duration::from_secs(1); - let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - let primary_key = Key::from( - Key4::<_, PrimaryRole>::import_secret_ed25519( - &PrivateKey::to_bytes(derived_key.private_key()), - epoch, - ) - .unwrap(), - ); - - let cert = - Cert::from_packets(vec![Packet::SecretKey(primary_key.clone())].into_iter()).unwrap(); - let builder = SignatureBuilder::new(SignatureType::PositiveCertification) - .set_key_validity_period(one_day.duration_since(epoch)?)? - .set_signature_creation_time(epoch)? - .set_key_flags(subkey_format[0].inner().clone())?; - let binding = default_userid.bind(&mut primary_key.clone().into_keypair()?, &cert, builder)?; - let cert = cert.insert_packets(vec![Packet::from(default_userid), binding.into()])?; - - let policy = sequoia_openpgp::policy::StandardPolicy::new(); - let mut keypair = cert - .primary_key() - .key() - .clone() - .parts_into_secret()? - .into_keypair()?; - let signatures = cert.set_expiration_time(&policy, None, &mut keypair, Some(one_day))?; - let mut cert = cert.insert_packets(signatures)?; - - for (index, subkey_flags) in subkey_format.iter().enumerate().skip(1) { - let index = u32::try_from(index)?; - let derived_key = xprv.derive_child(&DerivationIndex::new(index, true)?)?; - let subkey = Key::from( - Key4::<_, SubordinateRole>::import_secret_ed25519( - &PrivateKey::to_bytes(derived_key.private_key()), - epoch, - ) - .unwrap(), - ); - let builder = if subkey_flags.inner().for_signing() { - SignatureBuilder::new(SignatureType::SubkeyBinding) - .set_key_flags(subkey_flags.inner().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.inner().clone())? - .set_signature_creation_time(epoch)? - .set_key_validity_period(one_day.duration_since(epoch)?)? - }; - 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), - ])?; - } + let cert = keyfork_derive_openpgp::derive(derived_data, subkeys.as_slice(), default_userid)?; use sequoia_openpgp::{ armor::{Kind, Writer},