diff --git a/src/main.rs b/src/main.rs index 3206667..3854dbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,12 @@ use anyhow::{Context, Result}; use sequoia_openpgp::{ cert::CertBuilder, - packet::signature::SignatureBuilder, + packet::{signature::SignatureBuilder, Signature}, types::{KeyFlags, SignatureType}, + Cert, }; -use spec::KeyFlag; +use spec::{KeyFlag, Spec}; mod paths; mod setup; @@ -14,6 +15,14 @@ mod spec; fn main() -> Result<()> { let (paths, spec) = crate::setup::setup().context("Failed to setup application")?; + let (cert, rev) = paths.load()?.ok_or(()).or_else(|()| generate_new(spec))?; + paths + .write(cert, rev) + .context("Failed to store certificate!")?; + Ok(()) +} + +fn generate_new(spec: Spec) -> Result<(Cert, Signature)> { let mut builder = CertBuilder::new() .set_primary_key_flags( spec.primary @@ -57,11 +66,7 @@ fn main() -> Result<()> { ))?; } - let (cert, rev) = builder + builder .generate() - .context("Failed to generate certificate!")?; - paths - .write(cert, rev) - .context("Failed to store certificate!")?; - Ok(()) + .context("Failed to generate certificate!") } diff --git a/src/paths.rs b/src/paths.rs index 0c29e54..ea9b7a4 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,7 +1,17 @@ -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + time::SystemTime, +}; -use anyhow::{Context, Result}; -use sequoia_openpgp::{packet::Signature, serialize::SerializeInto, Cert}; +use anyhow::{bail, Context, Result}; +use sequoia_openpgp::{ + cert::CertRevocationBuilder, + packet::Signature, + parse::Parse, + serialize::SerializeInto, + types::{ReasonForRevocation, SignatureType}, + Cert, Packet, +}; pub(crate) struct Paths { pub(crate) spec: PathBuf, @@ -27,6 +37,102 @@ impl Paths { rev, } } + + pub(crate) fn load(&self) -> Result> { + let mut merged = if self + .secret + .try_exists() + .context("Couldn't check if secret part of certificate exists")? + { + Cert::from_file(&self.secret) + .context("Couldn't load secret part of certificate from file")? + } else { + return Ok(None); + }; + + if let (_, Some(secret_material)) = merged.primary_key().key().clone().take_secret() { + if secret_material.is_encrypted() { + bail!("Secret material for primary key is encrypted!"); + } + } else { + bail!("Primary key has no secret material!") + } + + if self + .public + .try_exists() + .context("Couldn't check if public part of certificate exists")? + { + merged = merged + .merge_public_and_secret( + Cert::from_file(&self.public) + .context("Couldn't load public part from certificate from file")?, + ) + .context("Couldn't merge public part onto secret part of certificate")? + } + if self.rev.try_exists()? { + merged = merged + .merge_public_and_secret( + Cert::from_file(&self.rev) + .context("Couldn't load revocation for certificate from file")?, + ) + .context("Couldn't merge revocation onto secret part of certificate")? + } + + let fingerprint = merged.fingerprint(); + let mut filtered = vec![]; + + let mut revocation = None; + + // strip revocation for primary key + for packet in merged.into_packets() { + match packet { + Packet::Signature(Signature::V4(sig)) => { + if sig.typ() == SignatureType::KeyRevocation + && sig + .issuer_fingerprints() + .any(|issuer| issuer.eq(&fingerprint)) + { + revocation = Some(Signature::V4(sig)); + } else { + filtered.push(Packet::Signature(Signature::V4(sig))); + } + } + packet => filtered.push(packet), + } + } + + let filtered = Cert::from_packets(filtered.into_iter()) + .context("Could't parse cert from packets after removing primary key revocations")?; + + let revocation = revocation + .ok_or(()) + .or_else(|()| { + let now = SystemTime::now(); + CertRevocationBuilder::new() + .set_signature_creation_time(now) + .context("Failed to set signature creation time")? + .set_reason_for_revocation(ReasonForRevocation::Unspecified, b"Unspecified") + .context("Failed to set revocation reason")? + .build( + &mut filtered + .primary_key() + .key() + .clone() + .parts_into_secret() + .context("Failed to get secret parts of certificate primary key")? + .into_keypair() + .context("Failed to convert primary key into keypair")?, + &filtered, + None, + ) + .context("Failed to generate revocation for certificate primary key") + }) + .context("Failed to generate new revocation after not finding an existing one")?; + + Ok(Some((filtered, revocation))) + } + pub(crate) fn write(&self, cert: Cert, rev: Signature) -> Result<()> { self.write_part(Self::public, cert.armored()) .context("Failed to write public certificate to file")?;