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 core_foundation::{declare_TCFType, impl_TCFType};
34 use crate::identity::SecIdentity;
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 Ok(())
132 }
133
134 pub fn get_recipients(&self) -> Result<Vec<SecCertificate>> {
136 let mut out: CFArrayRef = ptr::null_mut();
137 cvt(unsafe { CMSEncoderCopyRecipients(self.0, &mut out) })?;
138
139 if out.is_null() {
140 Ok(Vec::new())
141 } else {
142 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
143 Ok(array.into_iter().map(|c| c.clone()).collect())
144 }
145 }
146
147 pub fn set_has_detached_content(&self, has_detached_content: bool) -> Result<()> {
149 cvt(unsafe { CMSEncoderSetHasDetachedContent(self.0, has_detached_content.into()) })?;
150 Ok(())
151 }
152
153 pub fn get_has_detached_content(&self) -> Result<bool> {
155 let mut has_detached_content = 0;
156 cvt(unsafe { CMSEncoderGetHasDetachedContent(self.0, &mut has_detached_content) })?;
157 Ok(has_detached_content != 0)
158 }
159
160 pub fn set_encapsulated_content_type_oid(&self, oid: &str) -> Result<()> {
162 let oid = CFString::new(oid);
163 cvt(unsafe { CMSEncoderSetEncapsulatedContentTypeOID(self.0, oid.as_CFTypeRef()) })?;
164 Ok(())
165 }
166
167 pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> {
169 let mut out: CFDataRef = ptr::null_mut();
170 cvt(unsafe { CMSEncoderCopyEncapsulatedContentType(self.0, &mut out) })?;
171 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
172 }
173
174 pub fn add_supporting_certs(&self, certs: &[SecCertificate]) -> Result<()> {
176 let certs = CFArray::from_CFTypes(certs);
177 cvt(unsafe {
178 CMSEncoderAddSupportingCerts(
179 self.0,
180 if !certs.is_empty() { certs.as_CFTypeRef() } else { ptr::null() })
181 })?;
182 Ok(())
183 }
184
185 pub fn get_supporting_certs(&self) -> Result<Vec<SecCertificate>> {
187 let mut out: CFArrayRef = ptr::null_mut();
188 cvt(unsafe { CMSEncoderCopySupportingCerts(self.0, &mut out) })?;
189
190 if out.is_null() {
191 Ok(Vec::new())
192 } else {
193 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
194 Ok(array.into_iter().map(|c| c.clone()).collect())
195 }
196 }
197
198 pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> {
200 cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?;
201 Ok(())
202 }
203
204 pub fn set_certificate_chain_mode(&self, certificate_chain_mode: CMSCertificateChainMode) -> Result<()> {
206 cvt(unsafe { CMSEncoderSetCertificateChainMode(self.0, certificate_chain_mode) })?;
207 Ok(())
208 }
209
210 pub fn get_certificate_chain_mode(&self) -> Result<CMSCertificateChainMode> {
212 let mut out = CMSCertificateChainMode::kCMSCertificateNone;
213 cvt(unsafe { CMSEncoderGetCertificateChainMode(self.0, &mut out) })?;
214 Ok(out)
215 }
216
217 pub fn update_content(&self, content: &[u8]) -> Result<()> {
219 cvt(unsafe { CMSEncoderUpdateContent(self.0, content.as_ptr().cast(), content.len()) })?;
220 Ok(())
221 }
222
223 pub fn get_encoded_content(&self) -> Result<Vec<u8>> {
225 let mut out: CFDataRef = ptr::null_mut();
226 cvt(unsafe { CMSEncoderCopyEncodedContent(self.0, &mut out) })?;
227 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
228 }
229
230 pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
232 let mut out = CFAbsoluteTime::default();
233 cvt(unsafe { CMSEncoderCopySignerTimestamp(self.0, signer_index, &mut out) })?;
234 Ok(out)
235 }
236
237 pub fn get_signer_timestamp_with_policy(
239 &self,
240 timestamp_policy: Option<CFStringRef>,
241 signer_index: usize,
242 ) -> Result<CFAbsoluteTime> {
243 let mut out = CFAbsoluteTime::default();
244 cvt(unsafe {
245 CMSEncoderCopySignerTimestampWithPolicy(
246 self.0,
247 timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()),
248 signer_index,
249 &mut out)
250 })?;
251
252 Ok(out)
253 }
254 }
255
256 pub fn cms_encode_content(
258 signers: &[SecIdentity],
259 recipients: &[SecCertificate],
260 content_type_oid: Option<&str>,
261 detached_content: bool,
262 signed_attributes: SignedAttributes,
263 content: &[u8],
264 ) -> Result<Vec<u8>> {
265 let mut out: CFDataRef = ptr::null_mut();
266 let signers = CFArray::from_CFTypes(signers);
267 let recipients = CFArray::from_CFTypes(recipients);
268 let content_type_oid = content_type_oid.map(CFString::new);
269
270 cvt(unsafe {
271 CMSEncodeContent(
272 if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() },
273 if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() },
274 content_type_oid.as_ref().map(|oid| oid.as_CFTypeRef()).unwrap_or(ptr::null()),
275 detached_content.into(),
276 signed_attributes.bits(),
277 content.as_ptr().cast(),
278 content.len(),
279 &mut out,
280 )
281 })?;
282
283 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
284 }
285}
286
287mod decoder {
288 use core_foundation::{declare_TCFType, impl_TCFType};
289 use super::*;
290
291 pub struct SignerStatus {
293 pub signer_status: CMSSignerStatus,
295 pub sec_trust: SecTrust,
297 pub cert_verify_result: Result<()>,
299 }
300
301 declare_TCFType! {
302 CMSDecoder, CMSDecoderRef
304 }
305 impl_TCFType!(CMSDecoder, CMSDecoderRef, CMSDecoderGetTypeID);
306
307 unsafe impl Sync for CMSDecoder {}
308 unsafe impl Send for CMSDecoder {}
309
310 impl fmt::Debug for CMSDecoder {
311 #[cold]
312 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
313 fmt.debug_struct("CMSDecoder").finish()
314 }
315 }
316
317 impl CMSDecoder {
318 pub fn create() -> Result<Self> {
320 let mut inner: CMSDecoderRef = ptr::null_mut();
321 cvt(unsafe { CMSDecoderCreate(&mut inner) })?;
322 Ok(Self(inner))
323 }
324
325 pub fn update_message(&self, message: &[u8]) -> Result<()> {
327 cvt(unsafe { CMSDecoderUpdateMessage(self.0, message.as_ptr().cast(), message.len()) })?;
328 Ok(())
329 }
330
331 pub fn finalize_message(&self) -> Result<()> {
333 cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?;
334 Ok(())
335 }
336
337 pub fn set_detached_content(&self, detached_content: &[u8]) -> Result<()> {
339 let data = CFData::from_buffer(detached_content);
340 cvt(unsafe { CMSDecoderSetDetachedContent(self.0, data.as_concrete_TypeRef()) })?;
341 Ok(())
342 }
343
344 pub fn get_detached_content(&self) -> Result<Vec<u8>> {
346 unsafe {
347 let mut out: CFDataRef = ptr::null_mut();
348 cvt(CMSDecoderCopyDetachedContent(self.0, &mut out))?;
349 if out.is_null() {
350 Ok(Vec::new())
351 } else {
352 Ok(CFData::wrap_under_create_rule(out).to_vec())
353 }
354 }
355 }
356
357 pub fn get_num_signers(&self) -> Result<usize> {
359 let mut out = 0;
360 cvt(unsafe { CMSDecoderGetNumSigners(self.0, &mut out) })?;
361 Ok(out)
362 }
363
364 pub fn get_signer_status(
366 &self,
367 signer_index: usize,
368 policies: &[SecPolicy],
369 ) -> Result<SignerStatus> {
370 let policies = CFArray::from_CFTypes(policies);
371
372 let mut signer_status = CMSSignerStatus::kCMSSignerUnsigned;
373 let mut sec_trust: SecTrustRef = ptr::null_mut();
374 let mut verify_result = OSStatus::default();
375
376 cvt(unsafe {
377 CMSDecoderCopySignerStatus(
378 self.0,
379 signer_index,
380 if policies.is_empty() { ptr::null() } else { policies.as_CFTypeRef() },
381 true.into(),
382 &mut signer_status,
383 &mut sec_trust,
384 &mut verify_result,
385 )
386 })?;
387
388 Ok(SignerStatus {
389 signer_status,
390 sec_trust: unsafe { SecTrust::wrap_under_create_rule(sec_trust) },
391 cert_verify_result: cvt(verify_result),
392 })
393 }
394
395 pub fn get_signer_email_address(&self, signer_index: usize) -> Result<String> {
397 let mut out: CFStringRef = ptr::null_mut();
398 cvt(unsafe { CMSDecoderCopySignerEmailAddress(self.0, signer_index, &mut out) })?;
399 Ok(unsafe { CFString::wrap_under_create_rule(out).to_string() })
400 }
401
402 pub fn is_content_encrypted(&self) -> Result<bool> {
404 let mut out = 0;
405 cvt(unsafe { CMSDecoderIsContentEncrypted(self.0, &mut out) })?;
406 Ok(out != 0)
407 }
408
409 pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> {
411 let mut out: CFDataRef = ptr::null_mut();
412 if out.is_null() {
413 Ok(Vec::new())
414 } else {
415 cvt(unsafe { CMSDecoderCopyEncapsulatedContentType(self.0, &mut out) })?;
416 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
417 }
418 }
419
420 pub fn get_all_certs(&self) -> Result<Vec<SecCertificate>> {
422 let mut out: CFArrayRef = ptr::null_mut();
423 cvt(unsafe { CMSDecoderCopyAllCerts(self.0, &mut out) })?;
424
425 if out.is_null() {
426 Ok(Vec::new())
427 } else {
428 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
429 Ok(array.into_iter().map(|c| c.clone()).collect())
430 }
431 }
432
433 pub fn get_content(&self) -> Result<Vec<u8>> {
435 let mut out: CFDataRef = ptr::null_mut();
436
437 cvt(unsafe { CMSDecoderCopyContent(self.0, &mut out) })?;
438
439 if out.is_null() {
440 Ok(Vec::new())
441 } else {
442 Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
443 }
444 }
445
446 pub fn get_signer_signing_time(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
448 let mut out = CFAbsoluteTime::default();
449 cvt(unsafe { CMSDecoderCopySignerSigningTime(self.0, signer_index, &mut out) })?;
450 Ok(out)
451 }
452
453 pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
455 let mut out = CFAbsoluteTime::default();
456 cvt(unsafe { CMSDecoderCopySignerTimestamp(self.0, signer_index, &mut out) })?;
457 Ok(out)
458 }
459
460 pub fn get_signer_timestamp_with_policy(
462 &self,
463 timestamp_policy: Option<CFStringRef>,
464 signer_index: usize,
465 ) -> Result<CFAbsoluteTime> {
466 let mut out = CFAbsoluteTime::default();
467 cvt(unsafe {
468 CMSDecoderCopySignerTimestampWithPolicy(
469 self.0,
470 timestamp_policy
471 .map(|p| p.as_void_ptr())
472 .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(
483 &self,
484 signer_index: usize,
485 ) -> Result<Vec<SecCertificate>> {
486 let mut out: CFArrayRef = ptr::null_mut();
487 cvt(unsafe {
488 CMSDecoderCopySignerTimestampCertificates(self.0, signer_index, &mut out)
489 })?;
490
491 if out.is_null() {
492 Ok(Vec::new())
493 } else {
494 let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
495 Ok(array.into_iter().map(|c| c.clone()).collect())
496 }
497 }
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use crate::cms::{cms_encode_content, CMSDecoder, SignedAttributes};
504 use crate::import_export::{ImportedIdentity, Pkcs12ImportOptions};
505 use crate::policy::SecPolicy;
506 use security_framework_sys::cms::CMSSignerStatus;
507
508 const KEYSTORE: &[u8] = include_bytes!("../test/cms/keystore.p12");
509 const ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/encrypted.p7m");
510 const SIGNED_ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/signed-encrypted.p7m");
511
512 fn import_keystore() -> Vec<ImportedIdentity> {
513 let mut import_opts = Pkcs12ImportOptions::new();
514 import_opts.passphrase("cms").import(KEYSTORE).unwrap()
515 }
516
517 #[test]
518 fn test_decode_encrypted() {
519 let _identities = import_keystore();
520
521 let decoder = CMSDecoder::create().unwrap();
522 decoder.update_message(ENCRYPTED_CMS).unwrap();
523 decoder.finalize_message().unwrap();
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 _identities = 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 identities = import_keystore();
558
559 let chain = identities
560 .iter()
561 .filter_map(|id| id.cert_chain.as_ref())
562 .next()
563 .unwrap();
564
565 let message = cms_encode_content(
566 &[],
567 &chain[0..1],
568 None,
569 false,
570 SignedAttributes::empty(),
571 b"encrypted message\n",
572 ).unwrap();
573
574 let decoder = CMSDecoder::create().unwrap();
575 decoder.update_message(&message).unwrap();
576 decoder.finalize_message().unwrap();
577 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
578 }
579
580 #[test]
581 fn test_encode_signed_encrypted() {
582 let identities = import_keystore();
583
584 let chain = identities
585 .iter()
586 .filter_map(|id| id.cert_chain.as_ref())
587 .next()
588 .unwrap();
589
590 let identity = identities
591 .iter()
592 .filter_map(|id| id.identity.as_ref())
593 .next()
594 .unwrap();
595
596 let message = cms_encode_content(
597 &[identity.clone()],
598 &chain[0..1],
599 None,
600 false,
601 SignedAttributes::empty(),
602 b"encrypted message\n",
603 ).unwrap();
604
605 let decoder = CMSDecoder::create().unwrap();
606 decoder.update_message(&message).unwrap();
607 decoder.finalize_message().unwrap();
608 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
609 assert_eq!(decoder.get_num_signers().unwrap(), 1);
610 }
611
612 #[test]
613 fn test_encode_with_cms_encoder() {
614 let identities = import_keystore();
615
616 let chain = identities
617 .iter()
618 .filter_map(|id| id.cert_chain.as_ref())
619 .next()
620 .unwrap();
621
622 let identity = identities
623 .iter()
624 .filter_map(|id| id.identity.as_ref())
625 .next()
626 .unwrap();
627
628 let message = cms_encode_content(
629 &[identity.clone()],
630 &chain[0..1],
631 None,
632 false,
633 SignedAttributes::empty(),
634 b"encrypted message\n",
635 ).unwrap();
636
637 let decoder = CMSDecoder::create().unwrap();
638 decoder.update_message(&message).unwrap();
639 decoder.finalize_message().unwrap();
640 assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
641 assert_eq!(decoder.get_num_signers().unwrap(), 1);
642 }
643}