Skip to main content

cargo_codesign/platform/
windows.rs

1use std::path::{Path, PathBuf};
2
3#[derive(Debug, thiserror::Error)]
4pub enum WindowsSignError {
5    #[error("subprocess failed: {0}")]
6    Subprocess(#[from] crate::subprocess::SubprocessError),
7    #[error("signing failed for {path}: {detail}")]
8    SigningFailed { path: PathBuf, detail: String },
9    #[error("verification failed for {path}: {detail}")]
10    VerificationFailed { path: PathBuf, detail: String },
11    #[error("tool not found: {0}")]
12    ToolNotFound(String),
13    #[error("io error: {0}")]
14    Io(#[from] std::io::Error),
15}
16
17pub struct SignOpts<'a> {
18    pub endpoint: &'a str,
19    pub account_name: &'a str,
20    pub cert_profile: &'a str,
21    pub timestamp_server: &'a str,
22    pub dlib_path: &'a Path,
23    pub verbose: bool,
24}
25
26/// Generate the `metadata.json` file required by Azure Trusted Signing.
27pub fn generate_metadata_json(endpoint: &str, account_name: &str, cert_profile: &str) -> String {
28    serde_json::json!({
29        "Endpoint": endpoint,
30        "CodeSigningAccountName": account_name,
31        "CertificateProfileName": cert_profile,
32    })
33    .to_string()
34}
35
36/// Sign a Windows executable using `signtool.exe` with Azure Trusted Signing.
37#[cfg(target_os = "windows")]
38pub fn sign_exe(exe_path: &Path, opts: &SignOpts<'_>) -> Result<(), WindowsSignError> {
39    use crate::subprocess::run;
40
41    let exe_str = exe_path.to_string_lossy().to_string();
42    let dlib_str = opts.dlib_path.to_string_lossy().to_string();
43
44    let metadata = generate_metadata_json(opts.endpoint, opts.account_name, opts.cert_profile);
45    let metadata_path = exe_path.with_extension("metadata.json");
46    std::fs::write(&metadata_path, &metadata)?;
47    let metadata_str = metadata_path.to_string_lossy().to_string();
48
49    let output = run(
50        "signtool",
51        &[
52            "sign",
53            "/fd",
54            "SHA256",
55            "/tr",
56            opts.timestamp_server,
57            "/td",
58            "SHA256",
59            "/dlib",
60            &dlib_str,
61            "/dmdf",
62            &metadata_str,
63            &exe_str,
64        ],
65        opts.verbose,
66    )?;
67
68    // Clean up metadata file
69    let _ = std::fs::remove_file(&metadata_path);
70
71    if !output.success {
72        return Err(WindowsSignError::SigningFailed {
73            path: exe_path.to_path_buf(),
74            detail: output.stderr,
75        });
76    }
77    Ok(())
78}
79
80/// Verify a signed Windows executable.
81#[cfg(target_os = "windows")]
82pub fn verify_exe(exe_path: &Path, verbose: bool) -> Result<(), WindowsSignError> {
83    use crate::subprocess::run;
84
85    let exe_str = exe_path.to_string_lossy().to_string();
86    let output = run("signtool", &["verify", "/pa", "/v", &exe_str], verbose)?;
87    if !output.success {
88        return Err(WindowsSignError::VerificationFailed {
89            path: exe_path.to_path_buf(),
90            detail: output.stderr,
91        });
92    }
93    Ok(())
94}
95
96/// Install Azure Trusted Signing tools via nuget.
97#[cfg(target_os = "windows")]
98pub fn install_tools(verbose: bool) -> Result<PathBuf, WindowsSignError> {
99    use crate::subprocess::run;
100
101    let output = run(
102        "nuget",
103        &[
104            "install",
105            "Microsoft.Trusted.Signing.Client",
106            "-OutputDirectory",
107            ".signing-tools",
108        ],
109        verbose,
110    )?;
111    if !output.success {
112        return Err(WindowsSignError::ToolNotFound(
113            "nuget install Microsoft.Trusted.Signing.Client failed".to_string(),
114        ));
115    }
116
117    // Find the DLL in the installed package
118    let tools_dir = PathBuf::from(".signing-tools");
119    for entry in std::fs::read_dir(&tools_dir)? {
120        let entry = entry?;
121        let dll_path = entry
122            .path()
123            .join("bin")
124            .join("x64")
125            .join("Azure.CodeSigning.Dlib.dll");
126        if dll_path.exists() {
127            return Ok(dll_path);
128        }
129    }
130
131    Err(WindowsSignError::ToolNotFound(
132        "Azure.CodeSigning.Dlib.dll not found after install".to_string(),
133    ))
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn metadata_json_has_expected_fields() {
142        let json = generate_metadata_json(
143            "https://wus2.codesigning.azure.net",
144            "my-account",
145            "my-profile",
146        );
147        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
148        assert_eq!(parsed["Endpoint"], "https://wus2.codesigning.azure.net");
149        assert_eq!(parsed["CodeSigningAccountName"], "my-account");
150        assert_eq!(parsed["CertificateProfileName"], "my-profile");
151    }
152
153    #[test]
154    fn metadata_json_is_valid_json() {
155        let json = generate_metadata_json("e", "a", "p");
156        assert!(serde_json::from_str::<serde_json::Value>(&json).is_ok());
157    }
158}