cargo_codesign/platform/
windows.rs1use 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
26pub 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#[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 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#[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#[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 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}