tugger_windows_codesign/
signtool.rs1use {
8 crate::signing::CodeSigningCertificate,
9 anyhow::{anyhow, Context, Result},
10 log::warn,
11 std::{
12 io::{BufRead, BufReader},
13 path::{Path, PathBuf},
14 },
15};
16
17#[cfg(target_family = "windows")]
18use tugger_windows::find_windows_sdk_current_arch_bin_path;
19
20#[derive(Clone, Debug)]
22pub enum TimestampServer {
23 Simple(String),
27
28 Rfc3161(String, String),
33}
34
35#[cfg(target_family = "windows")]
36pub fn find_signtool() -> Result<PathBuf> {
37 let bin_path = find_windows_sdk_current_arch_bin_path(None).context("finding Windows SDK")?;
38
39 let p = bin_path.join("signtool.exe");
40
41 if p.exists() {
42 Ok(p)
43 } else {
44 Err(anyhow!(
45 "unable to locate signtool.exe in Windows SDK at {}",
46 bin_path.display()
47 ))
48 }
49}
50
51#[cfg(target_family = "unix")]
52pub fn find_signtool() -> Result<PathBuf> {
53 Err(anyhow!("finding signtool.exe only supported on Windows"))
54}
55
56#[derive(Clone, Debug)]
58pub struct SigntoolSign {
59 certificate: CodeSigningCertificate,
60 verbose: bool,
61 debug: bool,
62 description: Option<String>,
63 file_digest_algorithm: String,
64 timestamp_server: Option<TimestampServer>,
65 extra_args: Vec<String>,
66 sign_files: Vec<PathBuf>,
67}
68
69impl SigntoolSign {
70 pub fn new(certificate: CodeSigningCertificate) -> Self {
72 Self {
73 certificate,
74 verbose: false,
75 debug: false,
76 description: None,
77 file_digest_algorithm: "SHA256".to_string(),
78 timestamp_server: None,
79 extra_args: vec![],
80 sign_files: vec![],
81 }
82 }
83
84 #[must_use]
86 pub fn clone_settings(&self) -> Self {
87 Self {
88 certificate: self.certificate.clone(),
89 verbose: self.verbose,
90 debug: self.debug,
91 description: self.description.clone(),
92 file_digest_algorithm: self.file_digest_algorithm.clone(),
93 timestamp_server: self.timestamp_server.clone(),
94 extra_args: self.extra_args.clone(),
95 sign_files: vec![],
96 }
97 }
98
99 pub fn verbose(&mut self) -> &mut Self {
103 self.verbose = true;
104 self
105 }
106
107 pub fn debug(&mut self) -> &mut Self {
111 self.debug = true;
112 self
113 }
114
115 pub fn description(&mut self, description: impl ToString) -> &mut Self {
119 self.description = Some(description.to_string());
120 self
121 }
122
123 pub fn file_digest_algorithm(&mut self, algorithm: impl ToString) -> &mut Self {
127 self.file_digest_algorithm = algorithm.to_string();
128 self
129 }
130
131 pub fn timestamp_server(&mut self, server: TimestampServer) -> &mut Self {
133 self.timestamp_server = Some(server);
134 self
135 }
136
137 pub fn extra_args(&mut self, extra_args: impl Iterator<Item = impl ToString>) -> &mut Self {
142 self.extra_args = extra_args.map(|x| x.to_string()).collect::<_>();
143 self
144 }
145
146 pub fn sign_file(&mut self, path: impl AsRef<Path>) -> &mut Self {
148 self.sign_files.push(path.as_ref().to_path_buf());
149 self
150 }
151
152 pub fn run(&self) -> Result<()> {
154 let signtool = find_signtool().context("locating signtool.exe")?;
155
156 let mut args = vec!["sign".to_string()];
157
158 if self.verbose {
159 args.push("/v".to_string());
160 }
161
162 if self.debug {
163 args.push("/debug".to_string());
164 }
165
166 match &self.certificate {
167 CodeSigningCertificate::Auto => {
168 args.push("/a".to_string());
169 }
170 CodeSigningCertificate::File(file) => {
171 args.push("/f".to_string());
172 args.push(file.path().display().to_string());
173 if let Some(password) = file.password() {
174 args.push("/p".to_string());
175 args.push(password.to_string());
176 }
177 }
178 CodeSigningCertificate::SubjectName(store, sn) => {
179 args.push("/s".to_string());
180 args.push(store.as_ref().to_string());
181 args.push("/n".to_string());
182 args.push(sn.to_string());
183 }
184 CodeSigningCertificate::Sha1Thumbprint(store, sha1) => {
185 args.push("/s".to_string());
186 args.push(store.as_ref().to_string());
187 args.push("/sha1".to_string());
188 args.push(sha1.to_string());
189 }
190 }
191
192 if let Some(description) = &self.description {
193 args.push("/d".to_string());
194 args.push(description.to_string());
195 }
196
197 args.push("/fd".to_string());
198 args.push(self.file_digest_algorithm.clone());
199
200 if let Some(server) = &self.timestamp_server {
201 match server {
202 TimestampServer::Simple(url) => {
203 args.push("/t".to_string());
204 args.push(url.to_string());
205 }
206 TimestampServer::Rfc3161(url, algorithm) => {
207 args.push("/tr".to_string());
208 args.push(url.to_string());
209 args.push("/td".to_string());
210 args.push(algorithm.to_string());
211 }
212 }
213 }
214
215 args.extend(self.extra_args.iter().cloned());
216
217 args.extend(self.sign_files.iter().map(|p| p.display().to_string()));
218
219 let command = duct::cmd(signtool, args)
220 .stderr_to_stdout()
221 .reader()
222 .context("running signtool")?;
223 {
224 let reader = BufReader::new(&command);
225 for line in reader.lines() {
226 warn!("{}", line?);
227 }
228 }
229
230 let output = command
231 .try_wait()?
232 .ok_or_else(|| anyhow!("unable to wait on command"))?;
233 if output.status.success() {
234 Ok(())
235 } else {
236 Err(anyhow!("error running signtool"))
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use {
244 super::*,
245 crate::{
246 create_self_signed_code_signing_certificate,
247 signing::{certificate_to_pfx, FileBasedCodeSigningCertificate},
248 },
249 tugger_common::testutil::*,
250 };
251
252 #[test]
253 fn test_find_signtool() -> Result<()> {
254 let res = find_signtool();
255
256 if cfg!(target_family = "windows") {
258 res?;
259 } else {
260 assert!(res.is_err());
261 }
262
263 Ok(())
264 }
265
266 #[test]
267 fn test_sign_executable() -> Result<()> {
268 if cfg!(target_family = "unix") {
269 eprintln!("skipping test because only works on Windows");
270 return Ok(());
271 }
272
273 let temp_path = DEFAULT_TEMP_DIR.path().join("test_sign_executable");
274 std::fs::create_dir(&temp_path)?;
275
276 let cert = create_self_signed_code_signing_certificate("tugger@example.com")?;
277 let pfx_data = certificate_to_pfx(&cert, "some_password", "cert_name")?;
278
279 let key_path = temp_path.join("signing.pfx");
280 std::fs::write(&key_path, pfx_data)?;
281
282 let sign_path = temp_path.join("test.exe");
284 let current_exe = std::env::current_exe()?;
285 std::fs::copy(current_exe, &sign_path)?;
286
287 let mut c = FileBasedCodeSigningCertificate::new(&key_path);
288 c.set_password("some_password");
289
290 SigntoolSign::new(c.into())
291 .verbose()
292 .debug()
293 .description("tugger test executable")
294 .file_digest_algorithm("sha256")
295 .sign_file(&sign_path)
296 .run()?;
297
298 Ok(())
299 }
300}