1use std::{fmt, ptr};
4
5use core_foundation::array::CFArray;
6use core_foundation::base::TCFType;
7use core_foundation::data::CFData;
8use core_foundation::string::CFString;
9use core_foundation_sys::array::CFArrayRef;
10use core_foundation_sys::base::{OSStatus, TCFTypeRef};
11use core_foundation_sys::data::CFDataRef;
12use core_foundation_sys::date::CFAbsoluteTime;
13use core_foundation_sys::string::CFStringRef;
14use security_framework_sys::cms::*;
15use security_framework_sys::trust::SecTrustRef;
16
17use crate::base::Result;
18use crate::certificate::SecCertificate;
19use crate::cvt;
20use crate::policy::SecPolicy;
21use crate::trust::SecTrust;
22
23pub use decoder::CMSDecoder;
24
25pub use encoder::cms_encode_content;
26pub use encoder::CMSEncoder;
27pub use encoder::SignedAttributes;
28pub use encoder::CMS_DIGEST_ALGORITHM_SHA1;
29pub use encoder::CMS_DIGEST_ALGORITHM_SHA256;
30
31mod encoder {
32 use super::*;
33 use crate::identity::SecIdentity;
34 use core_foundation::{declare_TCFType, impl_TCFType};
35
36 pub const CMS_DIGEST_ALGORITHM_SHA1: &str = "sha1";
38 pub const CMS_DIGEST_ALGORITHM_SHA256: &str = "sha256";
40
41 bitflags::bitflags! {
42 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44 pub struct SignedAttributes: CMSSignedAttributes {
45 const SMIME_CAPABILITIES = kCMSAttrSmimeCapabilities;
47 const SMIME_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeEncryptionKeyPrefs;
49 const SMIME_MS_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeMSEncryptionKeyPrefs;
52 const SIGNING_TIME = kCMSAttrSigningTime;
54 const APPLE_CODESIGNING_HASH_AGILITY = kCMSAttrAppleCodesigningHashAgility;
56 const APPLE_CODESIGNING_HASH_AGILITY_V2 = kCMSAttrAppleCodesigningHashAgilityV2;
58 const APPLE_EXPIRATION_TIME = kCMSAttrAppleExpirationTime;
60 }
61 }
62
63 declare_TCFType! {
64 CMSEncoder, CMSEncoderRef
66 }
67 impl_TCFType!(CMSEncoder, CMSEncoderRef, CMSEncoderGetTypeID);
68
69 unsafe impl Sync for CMSEncoder {}
70 unsafe impl Send for CMSEncoder {}
71
72 impl fmt::Debug for CMSEncoder {
73 #[cold]
74 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
75 fmt.debug_struct("CMSEncoder").finish()
76 }
77 }
78
79 impl CMSEncoder {
80 pub fn create() -> Result<Self> {
82 let mut inner: CMSEncoderRef = ptr::null_mut();
83 cvt(unsafe { CMSEncoderCreate(&mut inner) })?;
84 Ok(Self(inner))
85 }
86
87 pub fn set_signer_algorithm(&self, digest_algorithm: &str) -> Result<()> {
93 let alg = CFString::new(digest_algorithm);
94
95 cvt(unsafe { CMSEncoderSetSignerAlgorithm(self.0, alg.as_concrete_TypeRef()) })?;
96 Ok(())
97 }
98
99 pub fn add_signers(&self, signers: &[SecIdentity]) -> Result<()> {
101 let signers = CFArray::from_CFTypes(signers);
102 cvt(unsafe {
103 CMSEncoderAddSigners(
104 self.0,
105 if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() })
106 })?;
107 Ok(())
108 }
109
110 pub fn get_signers(&self) -> Result<Vec<SecIdentity>> {
112 let mut out: CFArrayRef = ptr::null_mut();
113 cvt(unsafe { CMSEncoderCopySigners(self.0, &mut out) })?;
114
115 if out.is_null() {
116 Ok(Vec::new())
117 } else {
118 let array = unsafe { CFArray::<SecIdentity>::wrap_under_create_rule(out) };
119 Ok(array.into_iter().map(|c| c.clone()).collect())
120 }
121 }
122
123 pub fn add_recipients(&self, recipients: &[SecCertificate]) -> Result<()> {
125 let recipients = CFArray::from_CFTypes(recipients);
126 cvt(unsafe {
127 CMSEncoderAddRecipients(
128 self.0,
129 if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() },
130 )
131 })?;
132 Ok(())
133 }
134
135 pub fn get_recipients(&self) -> Result<Vec<SecCertificate>> {
137 let mut out: CFArrayRef = ptr::null_mut();
138 cvt(unsafe { CMSEncoderCopyRecipients(self.0, &mut out) })?;
139
140 if out.is_null() {
141 Ok(Vec::new())
142 } else {
143 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
144 Ok(array.into_iter().map(|c| c.clone()).collect())
145 }
146 }
147
148 pub fn set_has_detached_content(&self, has_detached_content: bool) -> Result<()> {
150 cvt(unsafe { CMSEncoderSetHasDetachedContent(self.0, has_detached_content.into()) })?;
151 Ok(())
152 }
153
154 pub fn get_has_detached_content(&self) -> Result<bool> {
156 let mut has_detached_content = 0;
157 cvt(unsafe { CMSEncoderGetHasDetachedContent(self.0, &mut has_detached_content) })?;
158 Ok(has_detached_content != 0)
159 }
160
161 pub fn set_encapsulated_content_type_oid(&self, oid: &str) -> Result<()> {
163 let oid = CFString::new(oid);
164 cvt(unsafe { CMSEncoderSetEncapsulatedContentTypeOID(self.0, oid.as_CFTypeRef()) })?;
165 Ok(())
166 }
167
168 pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> {
170 let mut out: CFDataRef = ptr::null_mut();
171 cvt(unsafe { CMSEncoderCopyEncapsulatedContentType(self.0, &mut out) })?;
172 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
173 }
174
175 pub fn add_supporting_certs(&self, certs: &[SecCertificate]) -> Result<()> {
177 let certs = CFArray::from_CFTypes(certs);
178 cvt(unsafe {
179 CMSEncoderAddSupportingCerts(
180 self.0,
181 if !certs.is_empty() { certs.as_CFTypeRef() } else { ptr::null() })
182 })?;
183 Ok(())
184 }
185
186 pub fn get_supporting_certs(&self) -> Result<Vec<SecCertificate>> {
188 let mut out: CFArrayRef = ptr::null_mut();
189 cvt(unsafe { CMSEncoderCopySupportingCerts(self.0, &mut out) })?;
190
191 if out.is_null() {
192 Ok(Vec::new())
193 } else {
194 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
195 Ok(array.into_iter().map(|c| c.clone()).collect())
196 }
197 }
198
199 pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> {
201 cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?;
202 Ok(())
203 }
204
205 pub fn set_certificate_chain_mode(&self, certificate_chain_mode: CMSCertificateChainMode) -> Result<()> {
207 cvt(unsafe { CMSEncoderSetCertificateChainMode(self.0, certificate_chain_mode) })?;
208 Ok(())
209 }
210
211 pub fn get_certificate_chain_mode(&self) -> Result<CMSCertificateChainMode> {
213 let mut out = CMSCertificateChainMode::kCMSCertificateNone;
214 cvt(unsafe { CMSEncoderGetCertificateChainMode(self.0, &mut out) })?;
215 Ok(out)
216 }
217
218 pub fn update_content(&self, content: &[u8]) -> Result<()> {
220 cvt(unsafe { CMSEncoderUpdateContent(self.0, content.as_ptr().cast(), content.len()) })?;
221 Ok(())
222 }
223
224 pub fn get_encoded_content(&self) -> Result<Vec<u8>> {
226 let mut out: CFDataRef = ptr::null_mut();
227 cvt(unsafe { CMSEncoderCopyEncodedContent(self.0, &mut out) })?;
228 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
229 }
230
231 pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
233 let mut out = CFAbsoluteTime::default();
234 cvt(unsafe { CMSEncoderCopySignerTimestamp(self.0, signer_index, &mut out) })?;
235 Ok(out)
236 }
237
238 pub fn get_signer_timestamp_with_policy(
240 &self,
241 timestamp_policy: Option<CFStringRef>,
242 signer_index: usize,
243 ) -> Result<CFAbsoluteTime> {
244 let mut out = CFAbsoluteTime::default();
245 cvt(unsafe {
246 CMSEncoderCopySignerTimestampWithPolicy(
247 self.0,
248 timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()),
249 signer_index,
250 &mut out,
251 )
252 })?;
253
254 Ok(out)
255 }
256 }
257
258 pub fn cms_encode_content(
260 signers: &[SecIdentity],
261 recipients: &[SecCertificate],
262 content_type_oid: Option<&str>,
263 detached_content: bool,
264 signed_attributes: SignedAttributes,
265 content: &[u8],
266 ) -> Result<Vec<u8>> {
267 let mut out: CFDataRef = ptr::null_mut();
268 let signers = CFArray::from_CFTypes(signers);
269 let recipients = CFArray::from_CFTypes(recipients);
270 let content_type_oid = content_type_oid.map(CFString::new);
271
272 cvt(unsafe {
273 CMSEncodeContent(
274 if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() },
275 if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() },
276 content_type_oid.as_ref().map(|oid| oid.as_CFTypeRef()).unwrap_or(ptr::null()),
277 detached_content.into(),
278 signed_attributes.bits(),
279 content.as_ptr().cast(),
280 content.len(),
281 &mut out,
282 )
283 })?;
284
285 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
286 }
287}
288
289mod decoder {
290 use super::*;
291 use core_foundation::{declare_TCFType, impl_TCFType};
292
293 pub struct SignerStatus {
295 pub signer_status: CMSSignerStatus,
297 pub sec_trust: SecTrust,
299 pub cert_verify_result: Result<()>,
301 }
302
303 declare_TCFType! {
304 CMSDecoder, CMSDecoderRef
306 }
307 impl_TCFType!(CMSDecoder, CMSDecoderRef, CMSDecoderGetTypeID);
308
309 unsafe impl Sync for CMSDecoder {}
310 unsafe impl Send for CMSDecoder {}
311
312 impl fmt::Debug for CMSDecoder {
313 #[cold]
314 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
315 fmt.debug_struct("CMSDecoder").finish()
316 }
317 }
318
319 impl CMSDecoder {
320 pub fn create() -> Result<Self> {
322 let mut inner: CMSDecoderRef = ptr::null_mut();
323 cvt(unsafe { CMSDecoderCreate(&mut inner) })?;
324 Ok(Self(inner))
325 }
326
327 pub fn update_message(&self, message: &[u8]) -> Result<()> {
329 cvt(unsafe { CMSDecoderUpdateMessage(self.0, message.as_ptr().cast(), message.len()) })?;
330 Ok(())
331 }
332
333 pub fn finalize_message(&self) -> Result<()> {
335 cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?;
336 Ok(())
337 }
338
339 pub fn set_detached_content(&self, detached_content: &[u8]) -> Result<()> {
341 let data = CFData::from_buffer(detached_content);
342 cvt(unsafe { CMSDecoderSetDetachedContent(self.0, data.as_concrete_TypeRef()) })?;
343 Ok(())
344 }
345
346 pub fn get_detached_content(&self) -> Result<Vec<u8>> {
348 unsafe {
349 let mut out: CFDataRef = ptr::null_mut();
350 cvt(CMSDecoderCopyDetachedContent(self.0, &mut out))?;
351 if out.is_null() {
352 Ok(Vec::new())
353 } else {
354 Ok(CFData::wrap_under_create_rule(out).to_vec())
355 }
356 }
357 }
358
359 pub fn get_num_signers(&self) -> Result<usize> {
361 let mut out = 0;
362 cvt(unsafe { CMSDecoderGetNumSigners(self.0, &mut out) })?;
363 Ok(out)
364 }
365
366 pub fn get_signer_status(
368 &self,
369 signer_index: usize,
370 policies: &[SecPolicy],
371 ) -> Result<SignerStatus> {
372 let policies = CFArray::from_CFTypes(policies);
373
374 let mut signer_status = CMSSignerStatus::kCMSSignerUnsigned;
375 let mut sec_trust: SecTrustRef = ptr::null_mut();
376 let mut verify_result = OSStatus::default();
377
378 cvt(unsafe {
379 CMSDecoderCopySignerStatus(
380 self.0,
381 signer_index,
382 if policies.is_empty() { ptr::null() } else { policies.as_CFTypeRef() },
383 true.into(),
384 &mut signer_status,
385 &mut sec_trust,
386 &mut verify_result,
387 )
388 })?;
389
390 Ok(SignerStatus {
391 signer_status,
392 sec_trust: unsafe { SecTrust::wrap_under_create_rule(sec_trust) },
393 cert_verify_result: cvt(verify_result),
394 })
395 }
396
397 pub fn get_signer_email_address(&self, signer_index: usize) -> Result<String> {
399 let mut out: CFStringRef = ptr::null_mut();
400 cvt(unsafe { CMSDecoderCopySignerEmailAddress(self.0, signer_index, &mut out) })?;
401 Ok(unsafe { CFString::wrap_under_create_rule(out).to_string() })
402 }
403
404 pub fn is_content_encrypted(&self) -> Result<bool> {
406 let mut out = 0;
407 cvt(unsafe { CMSDecoderIsContentEncrypted(self.0, &mut out) })?;
408 Ok(out != 0)
409 }
410
411 pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> {
413 let mut out: CFDataRef = ptr::null_mut();
414 if out.is_null() {
415 Ok(Vec::new())
416 } else {
417 cvt(unsafe { CMSDecoderCopyEncapsulatedContentType(self.0, &mut out) })?;
418 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
419 }
420 }
421
422 pub fn get_all_certs(&self) -> Result<Vec<SecCertificate>> {
424 let mut out: CFArrayRef = ptr::null_mut();
425 cvt(unsafe { CMSDecoderCopyAllCerts(self.0, &mut out) })?;
426
427 if out.is_null() {
428 Ok(Vec::new())
429 } else {
430 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
431 Ok(array.into_iter().map(|c| c.clone()).collect())
432 }
433 }
434
435 pub fn get_content(&self) -> Result<Vec<u8>> {
437 let mut out: CFDataRef = ptr::null_mut();
438
439 cvt(unsafe { CMSDecoderCopyContent(self.0, &mut out) })?;
440
441 if out.is_null() {
442 Ok(Vec::new())
443 } else {
444 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
445 }
446 }
447
448 pub fn get_signer_signing_time(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
450 let mut out = CFAbsoluteTime::default();
451 cvt(unsafe { CMSDecoderCopySignerSigningTime(self.0, signer_index, &mut out) })?;
452 Ok(out)
453 }
454
455 pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
457 let mut out = CFAbsoluteTime::default();
458 cvt(unsafe { CMSDecoderCopySignerTimestamp(self.0, signer_index, &mut out) })?;
459 Ok(out)
460 }
461
462 pub fn get_signer_timestamp_with_policy(
464 &self,
465 timestamp_policy: Option<CFStringRef>,
466 signer_index: usize,
467 ) -> Result<CFAbsoluteTime> {
468 let mut out = CFAbsoluteTime::default();
469 cvt(unsafe {
470 CMSDecoderCopySignerTimestampWithPolicy(
471 self.0,
472 timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()),
473 signer_index,
474 &mut out,
475 )
476 })?;
477
478 Ok(out)
479 }
480
481 pub fn get_signer_timestamp_certificates(&self, signer_index: usize) -> Result<Vec<SecCertificate>> {
483 let mut out: CFArrayRef = ptr::null_mut();
484 cvt(unsafe { CMSDecoderCopySignerTimestampCertificates(self.0, signer_index, &mut out) })?;
485
486 if out.is_null() {
487 Ok(Vec::new())
488 } else {
489 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
490 Ok(array.into_iter().map(|c| c.clone()).collect())
491 }
492 }
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use crate::cms::{cms_encode_content, CMSDecoder, SignedAttributes};
499 use crate::import_export::{ImportedIdentity, Pkcs12ImportOptions};
500 use crate::policy::SecPolicy;
501 use security_framework_sys::cms::CMSSignerStatus;
502 use std::sync::{Mutex, MutexGuard};
503
504 const KEYSTORE: &[u8] = include_bytes!("../test/cms/keystore.p12");
505 const ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/encrypted.p7m");
506 const SIGNED_ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/signed-encrypted.p7m");
507
508 static SHARED_KEYCHAIN: Mutex<()> = Mutex::new(());
509
510 fn import_keystore() -> (MutexGuard<'static, ()>, Vec<ImportedIdentity>) {
511 let lock = SHARED_KEYCHAIN.lock().unwrap();
512 let mut import_opts = Pkcs12ImportOptions::new();
513 let id = import_opts.passphrase("cms").import(KEYSTORE).expect("import keystore.p12");
514 (lock, id)
515 }
516
517 #[test]
518 fn test_decode_encrypted() {
519 let _lock = import_keystore();
520
521 let decoder = CMSDecoder::create().expect("create");
522 decoder.update_message(ENCRYPTED_CMS).expect("update");
523 decoder.finalize_message().expect("finalize");
524
525 assert!(decoder.is_content_encrypted().unwrap());
526 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
527 assert_eq!(decoder.get_all_certs().unwrap().len(), 0);
528 assert_eq!(decoder.get_num_signers().unwrap(), 0);
529 }
530
531 #[test]
532 fn test_decode_signed_and_encrypted() {
533 let _lock = import_keystore();
534
535 let decoder = CMSDecoder::create().unwrap();
536 decoder.update_message(SIGNED_ENCRYPTED_CMS).unwrap();
537 decoder.finalize_message().unwrap();
538
539 assert!(decoder.is_content_encrypted().unwrap());
540
541 let signed_content = decoder.get_content().unwrap();
542
543 let decoder2 = CMSDecoder::create().unwrap();
544 decoder2.update_message(&signed_content).unwrap();
545 decoder2.finalize_message().unwrap();
546 assert_eq!(decoder2.get_content().unwrap(), b"encrypted message\n");
547 assert_eq!(decoder2.get_num_signers().unwrap(), 1);
548
549 let policies = vec![SecPolicy::create_x509()];
550 let status = decoder2.get_signer_status(0, &policies).unwrap();
551 assert!(status.cert_verify_result.is_err());
552 assert_eq!(status.signer_status, CMSSignerStatus::kCMSSignerInvalidCert);
553 }
554
555 #[test]
556 fn test_encode_encrypted() {
557 let (_lock, identities) = import_keystore();
558
559 let chain = identities
560 .iter().find_map(|id| id.cert_chain.as_ref())
561 .unwrap();
562
563 let message = cms_encode_content(
564 &[],
565 &chain[0..1],
566 None,
567 false,
568 SignedAttributes::empty(),
569 b"encrypted message\n",
570 ).unwrap();
571
572 let decoder = CMSDecoder::create().unwrap();
573 decoder.update_message(&message).unwrap();
574 decoder.finalize_message().unwrap();
575 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
576 }
577
578 #[test]
579 fn test_encode_signed_encrypted() {
580 let (_lock, identities) = import_keystore();
581
582 let chain = identities
583 .iter().find_map(|id| id.cert_chain.as_ref())
584 .unwrap();
585
586 let identity = identities
587 .iter().find_map(|id| id.identity.as_ref())
588 .unwrap();
589
590 let message = cms_encode_content(
591 std::slice::from_ref(identity),
592 &chain[0..1],
593 None,
594 false,
595 SignedAttributes::empty(),
596 b"encrypted message\n",
597 ).unwrap();
598
599 let decoder = CMSDecoder::create().unwrap();
600 decoder.update_message(&message).unwrap();
601 decoder.finalize_message().unwrap();
602 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
603 assert_eq!(decoder.get_num_signers().unwrap(), 1);
604 }
605
606 #[test]
607 fn test_encode_with_cms_encoder() {
608 let (_lock, identities) = import_keystore();
609
610 let chain = identities
611 .iter().find_map(|id| id.cert_chain.as_ref())
612 .unwrap();
613
614 let identity = identities
615 .iter().find_map(|id| id.identity.as_ref())
616 .unwrap();
617
618 let message = cms_encode_content(
619 std::slice::from_ref(identity),
620 &chain[0..1],
621 None,
622 false,
623 SignedAttributes::empty(),
624 b"encrypted message\n",
625 ).unwrap();
626
627 let decoder = CMSDecoder::create().unwrap();
628 decoder.update_message(&message).unwrap();
629 decoder.finalize_message().unwrap();
630 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
631 assert_eq!(decoder.get_num_signers().unwrap(), 1);
632 }
633}