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