Skip to main content

security_framework/os/macos/
certificate.rs

1//! OSX specific extensions to certificate functionality.
2
3use core_foundation::array::{CFArray, CFArrayIterator};
4use core_foundation::base::{TCFType, ToVoid};
5use core_foundation::data::CFData;
6use core_foundation::dictionary::CFDictionary;
7use core_foundation::error::CFError;
8use core_foundation::string::CFString;
9use security_framework_sys::certificate::*;
10use std::ptr;
11
12use crate::base::Error;
13use crate::certificate::SecCertificate;
14use crate::cvt;
15use crate::key::SecKey;
16use crate::os::macos::certificate_oids::CertificateOid;
17use crate::os::macos::digest_transform::{Builder, DigestType};
18
19/// An extension trait adding OSX specific functionality to `SecCertificate`.
20pub trait SecCertificateExt {
21    /// Returns the common name associated with the certificate.
22    fn common_name(&self) -> Result<String, Error>;
23
24    /// Returns the public key associated with the certificate.
25    fn public_key(&self) -> Result<SecKey, Error>;
26
27    /// Returns the set of properties associated with the certificate.
28    ///
29    /// The `keys` argument can optionally be used to filter the properties loaded to an explicit
30    /// subset.
31    fn properties(&self, keys: Option<&[CertificateOid]>) -> Result<CertificateProperties, CFError>;
32
33    /// Returns the SHA-256 fingerprint of the certificate.
34    fn fingerprint(&self) -> Result<[u8; 32], CFError> { unimplemented!() }
35}
36
37impl SecCertificateExt for SecCertificate {
38    fn common_name(&self) -> Result<String, Error> {
39        unsafe {
40            let mut string = ptr::null();
41            cvt(SecCertificateCopyCommonName(self.as_concrete_TypeRef(), &mut string))?;
42            Ok(CFString::wrap_under_create_rule(string).to_string())
43        }
44    }
45
46    fn public_key(&self) -> Result<SecKey, Error> {
47        unsafe {
48            let key = SecCertificateCopyKey(self.as_concrete_TypeRef());
49            if key.is_null() {
50                return Err(Error::from_code(-26275));
51            }
52            Ok(SecKey::wrap_under_create_rule(key))
53        }
54    }
55
56    fn properties(&self, keys: Option<&[CertificateOid]>) -> Result<CertificateProperties, CFError> {
57        unsafe {
58            let keys = keys.map(|oids| {
59                let oids = oids.iter().map(|oid| oid.to_str()).collect::<Vec<_>>();
60                CFArray::from_CFTypes(&oids)
61            });
62
63            let keys = match &keys {
64                Some(keys) => keys.as_concrete_TypeRef(),
65                None => ptr::null_mut(),
66            };
67
68            let mut error = ptr::null_mut();
69
70            let dictionary = SecCertificateCopyValues(self.as_concrete_TypeRef(), keys, &mut error);
71
72            if error.is_null() {
73                Ok(CertificateProperties(CFDictionary::wrap_under_create_rule(dictionary)))
74            } else {
75                Err(CFError::wrap_under_create_rule(error))
76            }
77        }
78    }
79
80    /// Returns the SHA-256 fingerprint of the certificate.
81    fn fingerprint(&self) -> Result<[u8; 32], CFError> {
82        let data = CFData::from_buffer(&self.to_der());
83        let hash = Builder::new()
84            .type_(DigestType::sha2())
85            .length(256)
86            .execute(&data)?;
87        Ok(hash.bytes().try_into().unwrap())
88    }
89}
90
91/// Properties associated with a certificate.
92pub struct CertificateProperties(CFDictionary);
93
94impl CertificateProperties {
95    /// Retrieves a specific property identified by its OID.
96    #[must_use]
97    pub fn get(&self, oid: CertificateOid) -> Option<CertificateProperty> {
98        unsafe {
99            self.0
100                .find(oid.as_ptr().to_void())
101                .map(|value| CertificateProperty(CFDictionary::wrap_under_get_rule(*value as *mut _)))
102        }
103    }
104}
105
106/// A property associated with a certificate.
107pub struct CertificateProperty(CFDictionary);
108
109impl CertificateProperty {
110    /// Returns the label of this property.
111    #[must_use]
112    pub fn label(&self) -> CFString {
113        unsafe {
114            CFString::wrap_under_get_rule((*self.0.get(kSecPropertyKeyLabel.to_void())).cast())
115        }
116    }
117
118    /// Returns an enum of the underlying data for this property.
119    #[must_use]
120    pub fn get(&self) -> PropertyType {
121        unsafe {
122            let type_ = CFString::wrap_under_get_rule(*self.0.get(kSecPropertyKeyType.to_void()) as *mut _);
123            let value = self.0.get(kSecPropertyKeyValue.to_void());
124
125            if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeSection) {
126                PropertyType::Section(PropertySection(CFArray::wrap_under_get_rule((*value).cast())))
127            } else if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeString) {
128                PropertyType::String(CFString::wrap_under_get_rule((*value).cast()))
129            } else {
130                PropertyType::__Unknown
131            }
132        }
133    }
134}
135
136/// A "section" property.
137///
138/// Sections are sequences of other properties.
139pub struct PropertySection(CFArray<CFDictionary>);
140
141impl PropertySection {
142    /// Returns an iterator over the properties in this section.
143    #[inline(always)]
144    #[must_use]
145    pub fn iter(&self) -> PropertySectionIter<'_> {
146        PropertySectionIter(self.0.iter())
147    }
148}
149
150impl<'a> IntoIterator for &'a PropertySection {
151    type IntoIter = PropertySectionIter<'a>;
152    type Item = CertificateProperty;
153
154    #[inline(always)]
155    fn into_iter(self) -> PropertySectionIter<'a> {
156        self.iter()
157    }
158}
159
160/// An iterator over the properties in a section.
161pub struct PropertySectionIter<'a>(CFArrayIterator<'a, CFDictionary>);
162
163impl Iterator for PropertySectionIter<'_> {
164    type Item = CertificateProperty;
165
166    #[inline]
167    fn next(&mut self) -> Option<CertificateProperty> {
168        self.0.next().map(|t| CertificateProperty(t.clone()))
169    }
170
171    #[inline(always)]
172    fn size_hint(&self) -> (usize, Option<usize>) {
173        self.0.size_hint()
174    }
175}
176
177/// An enum of the various types of properties.
178pub enum PropertyType {
179    /// A section.
180    Section(PropertySection),
181    /// A string.
182    String(CFString),
183    #[doc(hidden)]
184    __Unknown,
185}
186
187#[cfg(test)]
188mod test {
189    use super::*;
190    use crate::test::certificate;
191    use std::collections::HashMap;
192
193    #[test]
194    fn common_name() {
195        let certificate = certificate();
196        assert_eq!("foobar.com", p!(certificate.common_name()));
197    }
198
199    #[test]
200    fn public_key() {
201        let certificate = certificate();
202        p!(certificate.public_key());
203    }
204
205    #[test]
206    fn fingerprint() {
207        let certificate = certificate();
208        let fingerprint = p!(certificate.fingerprint());
209        assert_eq!(fingerprint.len(), 32);
210    }
211
212    #[test]
213    fn signature_algorithm() {
214        let certificate = certificate();
215        let properties = certificate
216            .properties(Some(&[CertificateOid::x509_v1_signature_algorithm()]))
217            .unwrap();
218        let value = properties
219            .get(CertificateOid::x509_v1_signature_algorithm())
220            .unwrap();
221        let PropertyType::Section(section) = value.get() else {
222            panic!()
223        };
224        let properties = section
225            .iter()
226            .map(|p| (p.label().to_string(), p.get()))
227            .collect::<HashMap<_, _>>();
228        let algorithm = match properties["Algorithm"] {
229            PropertyType::String(ref s) => s.to_string(),
230            _ => panic!(),
231        };
232        // 1.2.840.113549.1.1.11 = sha256WithRSAEncryption
233        assert_eq!(algorithm, "1.2.840.113549.1.1.11");
234    }
235}