apple_security_framework/os/macos/
certificate.rs

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