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}