1use core_foundation::array::{CFArray, CFArrayRef};
3use core_foundation::base::{TCFType, ToVoid};
4use core_foundation::data::CFData;
5use core_foundation::dictionary::CFMutableDictionary;
6use core_foundation::string::CFString;
7use core_foundation::{declare_TCFType, impl_TCFType};
8use core_foundation_sys::base::kCFAllocatorDefault;
9#[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
10use security_framework_sys::base::{errSecNotTrusted, errSecSuccess};
11use security_framework_sys::base::{errSecParam, SecCertificateRef};
12use security_framework_sys::certificate::*;
13use security_framework_sys::keychain_item::SecItemDelete;
14use std::fmt;
15use std::ptr;
16
17use crate::base::{Error, Result};
18use crate::cvt;
19#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
20use crate::key;
21#[cfg(target_os = "macos")]
22use crate::os::macos::keychain::SecKeychain;
23#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
24use core_foundation::base::FromVoid;
25#[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
26use core_foundation::error::{CFError, CFErrorRef};
27#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
28use core_foundation::number::CFNumber;
29use security_framework_sys::item::kSecValueRef;
30
31declare_TCFType! {
32 SecCertificate, SecCertificateRef
34}
35impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID);
36
37unsafe impl Sync for SecCertificate {}
38unsafe impl Send for SecCertificate {}
39
40impl fmt::Debug for SecCertificate {
41 #[cold]
42 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
43 fmt.debug_struct("SecCertificate")
44 .field("subject", &self.subject_summary())
45 .finish()
46 }
47}
48
49impl SecCertificate {
50 pub fn from_der(der_data: &[u8]) -> Result<Self> {
52 let der_data = CFData::from_buffer(der_data);
53 unsafe {
54 let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, der_data.as_concrete_TypeRef());
55 if certificate.is_null() {
56 Err(Error::from_code(errSecParam))
57 } else {
58 Ok(Self::wrap_under_create_rule(certificate))
59 }
60 }
61 }
62
63 #[must_use]
65 pub fn to_der(&self) -> Vec<u8> {
66 unsafe {
67 let der_data = SecCertificateCopyData(self.0);
68 CFData::wrap_under_create_rule(der_data).to_vec()
69 }
70 }
71
72 #[cfg(target_os = "macos")]
74 pub fn add_to_keychain(&self, keychain: Option<SecKeychain>) -> Result<()> {
75 let kch = match keychain {
76 Some(kch) => kch,
77 _ => SecKeychain::default()?,
78 };
79 cvt(unsafe {
80 SecCertificateAddToKeychain(self.as_CFTypeRef() as *mut _, kch.as_CFTypeRef() as *mut _)
81 })
82 }
83
84 #[must_use]
86 pub fn subject_summary(&self) -> String {
87 unsafe {
88 let summary = SecCertificateCopySubjectSummary(self.0);
89 CFString::wrap_under_create_rule(summary).to_string()
90 }
91 }
92
93 pub fn email_addresses(&self) -> Result<Vec<String>, Error> {
95 let mut array: CFArrayRef = ptr::null();
96 unsafe {
97 cvt(SecCertificateCopyEmailAddresses(
98 self.as_concrete_TypeRef(),
99 &mut array,
100 ))?;
101
102 let array = CFArray::<CFString>::wrap_under_create_rule(array);
103 Ok(array.into_iter().map(|p| p.to_string()).collect())
104 }
105 }
106
107 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
108 #[must_use]
110 pub fn issuer(&self) -> Vec<u8> {
111 unsafe {
112 let issuer = SecCertificateCopyNormalizedIssuerSequence(self.0);
113 CFData::wrap_under_create_rule(issuer).to_vec()
114 }
115 }
116
117 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
118 #[must_use]
120 pub fn subject(&self) -> Vec<u8> {
121 unsafe {
122 let subject = SecCertificateCopyNormalizedSubjectSequence(self.0);
123 CFData::wrap_under_create_rule(subject).to_vec()
124 }
125 }
126
127 #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
128 pub fn serial_number_bytes(&self) -> Result<Vec<u8>, CFError> {
130 unsafe {
131 let mut error: CFErrorRef = ptr::null_mut();
132 let serial_number = SecCertificateCopySerialNumberData(self.0, &mut error);
133 if error.is_null() {
134 Ok(CFData::wrap_under_create_rule(serial_number).to_vec())
135 } else {
136 Err(CFError::wrap_under_create_rule(error))
137 }
138 }
139 }
140
141 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
142 pub fn public_key_info_der(&self) -> Result<Option<Vec<u8>>> {
145 let public_key = self.public_key()?;
148 Ok(self.pk_to_der(public_key))
149 }
150
151 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
152 #[must_use]
153 #[allow(clippy::unused_self)]
154 #[allow(clippy::needless_pass_by_value)]
155 fn pk_to_der(&self, public_key: key::SecKey) -> Option<Vec<u8>> {
156 use security_framework_sys::item::{kSecAttrKeySizeInBits, kSecAttrKeyType};
157
158 let public_key_attributes = public_key.attributes();
159 let public_key_type = public_key_attributes
160 .find(unsafe { kSecAttrKeyType }.cast::<std::os::raw::c_void>())?;
161 let public_keysize = public_key_attributes
162 .find(unsafe { kSecAttrKeySizeInBits }.cast::<std::os::raw::c_void>())?;
163 let public_keysize = unsafe { CFNumber::from_void(*public_keysize) };
164 let public_keysize_val = public_keysize.to_i64()? as u32;
165 let hdr_bytes = get_asn1_header_bytes(
166 unsafe { CFString::wrap_under_get_rule((*public_key_type).cast()) },
167 public_keysize_val,
168 )?;
169 let public_key_data = public_key.external_representation()?;
170 let mut out = Vec::with_capacity(hdr_bytes.len() + public_key_data.len() as usize);
171 out.extend_from_slice(hdr_bytes);
172 out.extend_from_slice(public_key_data.bytes());
173 Some(out)
174 }
175
176 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
177 pub fn public_key(&self) -> Result<key::SecKey> {
179 use crate::policy::SecPolicy;
180 use crate::trust::SecTrust;
181 use std::slice::from_ref;
182
183 let policy = SecPolicy::create_x509();
184 let mut trust = SecTrust::create_with_certificates(from_ref(self), from_ref(&policy))?;
185 #[allow(deprecated)]
186 #[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos")))]
187 trust.evaluate()?;
188 #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
189 cvt(match trust.evaluate_with_error() {
190 Ok(_) => errSecSuccess,
191 Err(_) => errSecNotTrusted,
192 })?;
193 trust.copy_public_key()
194 }
195
196 pub fn delete(&self) -> Result<(), Error> {
198 let query = CFMutableDictionary::from_CFType_pairs(&[(
199 unsafe { kSecValueRef }.to_void(),
200 self.to_void(),
201 )]);
202
203 cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
204 }
205}
206
207#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
208fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> {
209 use security_framework_sys::item::{kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyTypeRSA};
210
211 if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 2048 {
212 return Some(&RSA_2048_ASN1_HEADER);
213 }
214 if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 4096 {
215 return Some(&RSA_4096_ASN1_HEADER);
216 }
217 if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 256 {
218 return Some(&EC_DSA_SECP_256_R1_ASN1_HEADER);
219 }
220 if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 384 {
221 return Some(&EC_DSA_SECP_384_R1_ASN1_HEADER);
222 }
223 None
224}
225
226#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
227const RSA_2048_ASN1_HEADER: [u8; 24] = [
228 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
229 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
230];
231
232#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
233const RSA_4096_ASN1_HEADER: [u8; 24] = [
234 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
235 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00,
236];
237
238#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
239const EC_DSA_SECP_256_R1_ASN1_HEADER: [u8; 26] = [
240 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
241 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
242];
243
244#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
245const EC_DSA_SECP_384_R1_ASN1_HEADER: [u8; 23] = [
246 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b,
247 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00,
248];
249
250#[cfg(test)]
251mod test {
252 use crate::test::certificate;
253 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
254 use x509_parser::prelude::*;
255
256 #[test]
257 fn subject_summary() {
258 let cert = certificate();
259 assert_eq!("foobar.com", cert.subject_summary());
260 }
261
262 #[test]
263 fn email_addresses() {
264 let cert = certificate();
265 assert_eq!(Vec::<String>::new(), cert.email_addresses().unwrap());
266 }
267
268 #[test]
269 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
270 fn issuer() {
271 let cert = certificate();
272 let issuer = cert.issuer();
273 let (_, name) = X509Name::from_der(&issuer).unwrap();
274 let name_str = name.to_string_with_registry(oid_registry()).unwrap();
275 assert_eq!(
276 "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM",
277 name_str
278 );
279 }
280
281 #[test]
282 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
283 fn subject() {
284 let cert = certificate();
285 let subject = cert.subject();
286 let (_, name) = X509Name::from_der(&subject).unwrap();
287 let name_str = name.to_string_with_registry(oid_registry()).unwrap();
288 assert_eq!(
289 "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM",
290 name_str
291 );
292 }
293}