nginx_src/
verifier.rs

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        // We do not want to mess with the default gpg data for the running user,
28        // so we store all gpg data within our build directory.
29        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    /// Imports all the required GPG keys into a temporary directory in order to
44    /// validate the integrity of the downloaded tarballs.
45    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    /// Validates the integrity of a file against the cryptographic signature associated with
77    /// the file.
78    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        // Set directory permissions to 700
107        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        // Set file permissions to 600
117        fs::set_permissions(path, Permissions::from_mode(file_mode))?;
118    }
119
120    Ok(())
121}