1pub(crate) mod macos;
8pub(crate) mod secret;
9pub(crate) mod windows;
10
11use std::io;
12use std::path::Path;
13use std::process::{Command, ExitStatus};
14
15use thiserror::Error;
16
17pub use macos::{
18 CodesignConfigError, CodesignError, KeychainSetupError, KeychainStep, MacOsSigner,
19 MacOsSigningSession, adhoc_sign,
20};
21pub use windows::{SigntoolConfigError, SigntoolError, WindowsSigner};
22
23#[derive(Debug, Error)]
25pub enum CommandError {
26 #[error("failed to spawn: {0}")]
27 Spawn(#[source] io::Error),
28 #[error("exited with {status}\nstdout: {stdout}\nstderr: {stderr}")]
29 Failed {
30 status: ExitStatus,
31 stdout: String,
32 stderr: String,
33 },
34}
35
36pub(crate) fn run_command(cmd: &mut Command) -> Result<(), CommandError> {
38 let output = cmd.output().map_err(CommandError::Spawn)?;
39 if !output.status.success() {
40 return Err(CommandError::Failed {
41 status: output.status,
42 stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
43 stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
44 });
45 }
46 Ok(())
47}
48
49#[derive(Debug, Error)]
50pub enum SignError {
51 #[error("codesign failed: {0}")]
52 Codesign(#[from] macos::CodesignError),
53 #[error("signtool failed: {0}")]
54 Signtool(#[from] windows::SigntoolError),
55}
56
57#[derive(Debug, Error)]
58pub enum SignConfigError {
59 #[error("invalid macOS signing configuration: {0}")]
60 MacOs(#[from] macos::CodesignConfigError),
61 #[error("invalid Windows signing configuration: {0}")]
62 Windows(#[from] windows::SigntoolConfigError),
63}
64
65#[derive(Debug)]
67pub enum Signer {
68 MacOsIdentity(MacOsSigner),
69 MacOsAdHoc,
70 Windows(WindowsSigner),
71}
72
73impl Signer {
74 pub fn from_env(target_triple: &str) -> Result<Option<Self>, SignConfigError> {
85 if target_triple.contains("apple") {
86 if let Some(signer) = MacOsSigner::from_env()? {
87 return Ok(Some(Self::MacOsIdentity(signer)));
88 }
89 if cfg!(target_os = "macos") {
92 return Ok(Some(Self::MacOsAdHoc));
93 }
94 tracing::warn!(
95 target = %target_triple,
96 "skipping Apple signing on non-macOS host"
97 );
98 return Ok(None);
99 }
100
101 if target_triple.contains("windows") {
102 if !cfg!(target_os = "windows") {
104 tracing::warn!(
105 target = %target_triple,
106 "skipping Windows signing on non-Windows host"
107 );
108 return Ok(None);
109 }
110
111 if let Some(signer) = WindowsSigner::from_env()? {
112 return Ok(Some(Self::Windows(signer)));
113 }
114 return Ok(None);
116 }
117
118 Ok(None)
120 }
121
122 pub fn begin_session(self) -> Result<SigningSession, SignError> {
133 match self {
134 Self::MacOsIdentity(s) => {
135 let session = s.begin_session()?;
136 Ok(SigningSession::MacOsIdentity(session))
137 }
138 Self::MacOsAdHoc => Ok(SigningSession::MacOsAdHoc),
139 Self::Windows(s) => Ok(SigningSession::Windows(s)),
140 }
141 }
142}
143
144#[derive(Debug)]
151pub enum SigningSession {
152 MacOsIdentity(MacOsSigningSession),
153 MacOsAdHoc,
154 Windows(WindowsSigner),
155}
156
157impl SigningSession {
158 pub fn sign(&self, path: &Path) -> Result<(), SignError> {
165 match self {
166 Self::MacOsIdentity(s) => s.sign(path).map_err(SignError::from),
167 Self::MacOsAdHoc => adhoc_sign(path).map_err(SignError::from),
168 Self::Windows(s) => s.sign(path).map_err(SignError::from),
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 fn assert_send<T: Send>() {}
178 fn assert_sync<T: Sync>() {}
179
180 #[test]
181 fn signer_is_send_and_sync() {
182 assert_send::<Signer>();
183 assert_sync::<Signer>();
184 }
185
186 #[test]
187 fn errors_are_send_and_sync() {
188 assert_send::<SignError>();
189 assert_sync::<SignError>();
190 assert_send::<SignConfigError>();
191 assert_sync::<SignConfigError>();
192 }
193}