diff --git a/Cargo.toml b/Cargo.toml index ac112a3..13c6044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ serde_json = "1.0.145" directories = "6.0.0" ssh2 = "0.9.5" tempfile = "3.10.1" +gpgme = "0.11" [dev-dependencies] test-log = "0.2.19" diff --git a/src/build.rs b/src/build.rs index c0733d9..6d72ee2 100644 --- a/src/build.rs +++ b/src/build.rs @@ -2,19 +2,59 @@ use std::error::Error; use std::path::Path; use std::process::Command; +use crate::changelog::parse_changelog_footer; +use crate::utils::gpg; + /// Build a Debian source package (to a .dsc) pub fn build_source_package(cwd: Option<&Path>) -> Result<(), Box> { let cwd = cwd.unwrap_or_else(|| Path::new(".")); + // Parse changelog to get maintainer information from the last modification entry + let changelog_path = cwd.join("debian/changelog"); + let (maintainer_name, maintainer_email) = parse_changelog_footer(&changelog_path)?; + + // Check if a GPG key matching the maintainer's email exists + let signing_key = match gpg::find_signing_key_for_email(&maintainer_email) { + Ok(key) => key, + Err(e) => { + // If GPG is not available or there's an error, continue without signing + log::warn!("Failed to check for GPG key: {}", e); + None + } + }; + + // Build arguments + let mut args = vec!["-S", "-I", "-i", "-nc", "-d"]; + + // If a signing key is found, use it for signing + if let Some(key_id) = &signing_key { + args.push("-sa"); // Sign the source package + args.push("-k"); + args.push(key_id); + log::info!("Using GPG key {} for signing", key_id); + } else { + log::info!( + "No GPG key found for {} ({}), building without signing", + maintainer_name, + maintainer_email + ); + } + let status = Command::new("dpkg-buildpackage") .current_dir(cwd) - .args(["-S", "-I", "-i", "-nc", "-d"]) + .args(&args) .status()?; if !status.success() { return Err(format!("dpkg-buildpackage failed with status: {}", status).into()); } + if signing_key.is_some() { + println!("Package built and signed successfully!"); + } else { + println!("Package built successfully (unsigned)."); + } + Ok(()) } diff --git a/src/changelog.rs b/src/changelog.rs index ab7a990..71481f0 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -114,7 +114,8 @@ fn increment_suffix(version: &str, suffix: &str) -> String { } } -/// Parse a changelog file first entry header, to obtain (package, version, series) +/// Parse a changelog file first entry header +/// Returns (package, version, series) tuple from the last modification entry pub fn parse_changelog_header( path: &Path, ) -> Result<(String, String, String), Box> { @@ -135,6 +136,33 @@ pub fn parse_changelog_header( } } +/// Parse a changelog file footer to extract maintainer information +/// Returns (name, email) tuple from the last modification entry +pub fn parse_changelog_footer(path: &Path) -> Result<(String, String), Box> { + let mut file = File::open(path)?; + let mut content = String::new(); + file.read_to_string(&mut content)?; + + // Find the last maintainer line (format: -- Name Date) + let re = Regex::new(r"--\s*([^<]+?)\s*<([^>]+)>\s*")?; + + if let Some(last_match) = re.captures_iter(&content).last() { + let name = last_match + .get(1) + .map_or("", |m| m.as_str()) + .trim() + .to_string(); + let email = last_match + .get(2) + .map_or("", |m| m.as_str()) + .trim() + .to_string(); + Ok((name, email)) + } else { + Err(format!("No maintainer information found in {}", path.display()).into()) + } +} + /* * Obtain all commit messages as a list since a tagged version in a git repository */ diff --git a/src/lib.rs b/src/lib.rs index e3fbba1..738d985 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,9 @@ pub mod pull; /// Handle context for .deb building: locally, over ssh, in a chroot... pub mod context; +/// Utility functions +pub(crate) mod utils; + /// Optional callback function (taking 4 arguments) /// - Name of the current main operation (e.g. pulling package) /// - Name of the current nested operation (e.g. cloning git repo) diff --git a/src/utils/gpg.rs b/src/utils/gpg.rs new file mode 100644 index 0000000..4efe81f --- /dev/null +++ b/src/utils/gpg.rs @@ -0,0 +1,32 @@ +use gpgme::{Context, Protocol}; + +/// Check if a GPG key matching 'email' exists +/// Returns the key ID if found, None otherwise +pub fn find_signing_key_for_email( + email: &str, +) -> Result, Box> { + // Create a new GPG context + let mut ctx = Context::from_protocol(Protocol::OpenPgp)?; + + // List all secret keys + let keys = ctx.secret_keys()?; + + // Find a key that matches the email and can sign + for key_result in keys { + let key = key_result?; + // Check if the key has signing capability + if key.can_sign() { + // Check user IDs for email match + for user_id in key.user_ids() { + if let Ok(userid_email) = user_id.email() + && userid_email.eq_ignore_ascii_case(email) + && let Ok(fingerprint) = key.fingerprint() + { + return Ok(Some(fingerprint.to_string())); + } + } + } + } + + Ok(None) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..0538510 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod gpg;