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