1use std::ffi::OsString;
2use std::fs::{self, Permissions};
3use std::os::unix::fs::PermissionsExt;
4use std::path::{Path, PathBuf};
5use std::{env, io};
6
7static GNUPG_COMMAND: &str = "gpg";
8
9pub struct SignatureVerifier {
10 gnupghome: PathBuf,
11}
12
13impl SignatureVerifier {
14 pub fn new() -> io::Result<Self> {
15 if env::var("NGX_NO_SIGNATURE_CHECK").is_ok() {
16 return Err(io::Error::other(
17 "signature check disabled by user".to_string(),
18 ));
19 };
20
21 if let Err(x) = duct::cmd!(GNUPG_COMMAND, "--version").stdout_null().run() {
22 return Err(io::Error::other(format!(
23 "signature check disabled: \"{GNUPG_COMMAND}\" not found ({x})"
24 )));
25 }
26
27 let gnupghome = env::var("OUT_DIR")
30 .map(PathBuf::from)
31 .map_err(io::Error::other)?
32 .join(".gnupg");
33
34 if !fs::exists(&gnupghome)? {
35 fs::create_dir_all(&gnupghome)?;
36 }
37
38 change_permissions_recursively(gnupghome.as_path(), 0o700, 0o600)?;
39
40 Ok(Self { gnupghome })
41 }
42
43 pub fn import_keys(&self, server: &str, key_ids: &[&str]) -> io::Result<()> {
46 println!(
47 "Importing {} GPG keys for key server: {}",
48 key_ids.len(),
49 server
50 );
51
52 let mut args = vec![
53 OsString::from("--homedir"),
54 self.gnupghome.clone().into(),
55 OsString::from("--keyserver"),
56 server.into(),
57 OsString::from("--recv-keys"),
58 ];
59 args.extend(key_ids.iter().map(OsString::from));
60
61 let cmd = duct::cmd(GNUPG_COMMAND, &args);
62 let output = cmd.stderr_to_stdout().stdout_capture().unchecked().run()?;
63
64 if !output.status.success() {
65 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
66 return Err(io::Error::other(format!(
67 "Command: {:?}\nFailed to import GPG keys: {}",
68 cmd,
69 key_ids.join(" ")
70 )));
71 }
72
73 Ok(())
74 }
75
76 pub fn verify_signature(&self, path: &Path, signature: &Path) -> io::Result<()> {
79 let cmd = duct::cmd!(
80 GNUPG_COMMAND,
81 "--homedir",
82 &self.gnupghome,
83 "--verify",
84 signature,
85 path
86 );
87 let output = cmd.stderr_to_stdout().stdout_capture().unchecked().run()?;
88 if !output.status.success() {
89 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
90 return Err(io::Error::other(format!(
91 "Command: {:?}\nGPG signature verification of archive failed [{}]",
92 cmd,
93 path.display()
94 )));
95 }
96 Ok(())
97 }
98}
99
100fn change_permissions_recursively(
101 path: &Path,
102 dir_mode: u32,
103 file_mode: u32,
104) -> std::io::Result<()> {
105 if path.is_dir() {
106 fs::set_permissions(path, Permissions::from_mode(dir_mode))?;
108
109 for entry in fs::read_dir(path)? {
110 let entry = entry?;
111 let path = entry.path();
112
113 change_permissions_recursively(&path, dir_mode, file_mode)?;
114 }
115 } else {
116 fs::set_permissions(path, Permissions::from_mode(file_mode))?;
118 }
119
120 Ok(())
121}