codesign_verify/lib.rs
1#[cfg(target_os = "macos")]
2mod macos;
3#[cfg(windows)]
4mod windows;
5
6#[cfg(target_os = "macos")]
7use macos::{Context, Verifier};
8#[cfg(windows)]
9use windows::{Context, Verifier};
10
11///
12/// Used to verify the validity of a code signature
13///
14pub struct CodeSignVerifier(Verifier);
15
16///
17/// Used to extract additional information from the signing leaf certificate
18///
19pub struct SignatureContext(Context);
20
21///
22/// Represents an Issuer or Subject name with the following fields:
23///
24/// # Fields
25///
26/// `common_name`: OID 2.5.4.3
27///
28/// `organization`: OID 2.5.4.10
29///
30/// `organization_unit`: OID 2.5.4.11
31///
32/// `country`: OID 2.5.4.6
33///
34#[derive(Debug)]
35pub struct Name {
36 pub common_name: Option<String>, // 2.5.4.3
37 pub organization: Option<String>, // 2.5.4.10
38 pub organization_unit: Option<String>, // 2.5.4.11
39 pub country: Option<String>, // 2.5.4.6
40}
41
42#[derive(Debug)]
43pub enum Error {
44 Unsigned, // The binary file didn't have any singature
45 OsError(i32), // Warps an inner provider error code
46 InvalidPath, // The provided path was malformed
47 LeafCertNotFound, // Unable to fetch certificate information
48 #[cfg(target_os = "macos")]
49 CFError(String),
50 #[cfg(windows)]
51 IoError(std::io::Error),
52}
53
54impl CodeSignVerifier {
55 /// Create a verifier for a binary at a given path.
56 /// On macOS it can be either a binary or an application package.
57 pub fn for_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
58 Verifier::for_file(path).map(CodeSignVerifier)
59 }
60
61 /// Create a verifier for a running application by PID.
62 /// On Windows it will get the full path to the running application first.
63 /// This can be used for e.g. verifying the app on the other end of a pipe.
64 pub fn for_pid(pid: i32) -> Result<Self, Error> {
65 Verifier::for_pid(pid).map(CodeSignVerifier)
66 }
67
68 /// Perform the verification itself.
69 /// On macOS the verification uses the Security framework with "anchor trusted" as the requirement.
70 /// On Windows the verification uses WinTrust and the `WINTRUST_ACTION_GENERIC_VERIFY_V2` action.
71 ///
72 /// # Examples
73 ///
74 /// ```no_run
75 /// use codesign_verify::CodeSignVerifier;
76 ///
77 /// CodeSignVerifier::for_file("C:/Windows/explorer.exe").unwrap().verify().unwrap();
78 /// ```
79 pub fn verify(self) -> Result<SignatureContext, Error> {
80 self.0.verify().map(SignatureContext)
81 }
82}
83
84impl SignatureContext {
85 /// Retrieve the subject name on the leaf certificate
86 ///
87 /// # Examples
88 ///
89 /// ```no_run
90 /// use codesign_verify::CodeSignVerifier;
91 ///
92 /// let ctx = CodeSignVerifier::for_file("C:/Windows/explorer.exe").unwrap().verify().unwrap();
93 /// assert_eq!(
94 /// ctx.subject_name().organization.as_deref(),
95 /// Some("Microsoft Corporation")
96 /// );
97 ///
98 /// ```
99 pub fn subject_name(&self) -> Name {
100 self.0.subject_name()
101 }
102
103 /// Retrieve the issuer name on the leaf certificate
104 pub fn issuer_name(&self) -> Name {
105 self.0.issuer_name()
106 }
107
108 /// Compute the sha1 thumbprint of the leaf certificate
109 pub fn sha1_thumbprint(&self) -> String {
110 self.0.sha1_thumbprint()
111 }
112
113 /// Compute the sha256 thumbprint of the leaf certificate
114 pub fn sha256_thumbprint(&self) -> String {
115 self.0.sha256_thumbprint()
116 }
117
118 /// Retrieve the leaf certificate serial number
119 pub fn serial(&self) -> Option<String> {
120 self.0.serial()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use crate::Error;
127
128 #[test]
129 #[cfg(target_os = "macos")]
130 fn test_signed() {
131 let verifier = super::CodeSignVerifier::for_file("/sbin/ping").unwrap(); // Should always be present on macOS
132 let ctx = verifier.verify().unwrap(); // Should always be signed
133
134 // If those values begin to fail, Apple probably changed their certficate
135 assert_eq!(
136 ctx.subject_name().organization.as_deref(),
137 Some("Apple Inc.")
138 );
139
140 assert_eq!(
141 ctx.issuer_name().organization_unit.as_deref(),
142 Some("Apple Certification Authority")
143 );
144
145 assert_eq!(
146 ctx.sha1_thumbprint(),
147 "efdbc9139dd98dbae5a9c7165a096511b15eaef9"
148 );
149 }
150
151 #[test]
152 #[cfg(windows)]
153 fn test_signed() {
154 let path = format!("{}/explorer.exe", std::env::var("windir").unwrap()); // Should always be present on Windows
155 let verifier = super::CodeSignVerifier::for_file(path).unwrap();
156 let ctx = verifier.verify().unwrap(); // Should always be signed
157
158 // If those values begin to fail, Microsoft probably changed their certficate
159 assert_eq!(
160 ctx.subject_name().organization.as_deref(),
161 Some("Microsoft Corporation")
162 );
163
164 assert_eq!(
165 ctx.issuer_name().common_name.as_deref(),
166 Some("Microsoft Windows Production PCA 2011")
167 );
168
169 assert_eq!(
170 ctx.sha1_thumbprint(),
171 "a4341b9fd50fb9964283220a36a1ef6f6faa7840"
172 );
173
174 assert_eq!(
175 ctx.serial().as_deref(),
176 Some("3300000266bd1580efa75cd6d3000000000266")
177 );
178 }
179
180 #[test]
181 fn test_unsigned() {
182 let path = std::env::args().next().unwrap(); // own path, always unsigned and present
183
184 assert!(matches!(
185 super::CodeSignVerifier::for_file(path).unwrap().verify(),
186 Err(Error::Unsigned)
187 ));
188 }
189}