security_framework/
cms.rs

1//! Cryptographic Message Syntax support
2
3use 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    /// SHA1 digest algorithm
37    pub const CMS_DIGEST_ALGORITHM_SHA1: &str = "sha1";
38    /// SHA256 digest algorithm
39    pub const CMS_DIGEST_ALGORITHM_SHA256: &str = "sha256";
40
41    bitflags::bitflags! {
42        /// Optional attributes you can add to a signed message
43        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44        pub struct SignedAttributes: CMSSignedAttributes {
45            /// Identify signature, encryption, and digest algorithms supported by the encoder
46            const SMIME_CAPABILITIES = kCMSAttrSmimeCapabilities;
47            /// Indicate that the signing certificate included with the message is the preferred one for S/MIME encryption
48            const SMIME_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeEncryptionKeyPrefs;
49            /// Indicate that the signing certificate included with the message is the preferred one for S/MIME encryption,
50            /// but using an attribute object identifier (OID) preferred by Microsoft
51            const SMIME_MS_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeMSEncryptionKeyPrefs;
52            /// Include the signing time
53            const SIGNING_TIME =  kCMSAttrSigningTime;
54            /// Include Apple codesigning hash agility
55            const APPLE_CODESIGNING_HASH_AGILITY = kCMSAttrAppleCodesigningHashAgility;
56            /// Include Apple codesigning hash agility, version 2
57            const APPLE_CODESIGNING_HASH_AGILITY_V2 = kCMSAttrAppleCodesigningHashAgilityV2;
58            /// Include the expiration time
59            const APPLE_EXPIRATION_TIME = kCMSAttrAppleExpirationTime;
60        }
61    }
62
63    declare_TCFType! {
64        /// A type representing CMS encoder
65        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        /// Create a new instance of `CMSEncoder`
81        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        /// Sets the digest algorithm to use for the signer.
88        /// Can be one of the predefined constants:
89        ///
90        /// * `CMS_DIGEST_ALGORITHM_SHA1`
91        /// * `CMS_DIGEST_ALGORITHM_SHA256`
92        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        /// Specify signers of the CMS message; implies that the message will be signed
100        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        /// Obtains the array of signers specified with the `add_signers` function
111        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        /// Specifies a message is to be encrypted and specifies the recipients of the message
124        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        /// Obtains the array of recipients specified with the `add_recipients` function
136        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        /// Specifies whether the signed data is to be separate from the message
149        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        /// Indicates whether the message is to have detached content
155        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        /// Specifies an object identifier for the encapsulated data of a signed message
162        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        /// Obtains the object identifier for the encapsulated data of a signed message
169        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        /// Adds certificates to a message
176        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        /// Obtains the certificates added to a message with `add_supporting_certs`
187        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        /// Specifies attributes for a signed message
200        pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> {
201            cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?;
202            Ok(())
203        }
204
205        /// Specifies which certificates to include in a signed CMS message
206        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        /// Obtains a constant that indicates which certificates are to be included in a signed CMS message
212        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        /// Feeds content bytes into the encoder
219        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        /// Finishes encoding the message and obtains the encoded result
225        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        /// Returns the timestamp of a signer of a CMS message, if present
232        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        /// Returns the timestamp of a signer of a CMS message using a particular policy, if present
239        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    /// Encodes a message and obtains the result in one high-level function call
259    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    /// Holds a result of the `CMSDecoder::get_signer_status` function
294    pub struct SignerStatus {
295        /// Signature status
296        pub signer_status: CMSSignerStatus,
297        /// Trust instance that was used to verify the signer’s certificate
298        pub sec_trust: SecTrust,
299        /// Result of the certificate verification
300        pub cert_verify_result: Result<()>,
301    }
302
303    declare_TCFType! {
304        /// A type representing CMS Decoder
305        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        /// Create a new instance of `CMSDecoder`
321        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        /// Feeds raw bytes of the message to be decoded into the decoder
328        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        /// Indicates that there is no more data to decode
334        pub fn finalize_message(&self) -> Result<()> {
335            cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?;
336            Ok(())
337        }
338
339        /// Specifies the message’s detached content, if any
340        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        /// Obtains the detached content specified with the `set_detached_content` function
347        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        /// Obtains the number of signers of a message
360        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        /// Obtains the status of a CMS message’s signature
367        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        /// Obtains the email address of the specified signer of a CMS message
398        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        /// Determines whether a CMS message was encrypted
405        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        /// Obtains the object identifier for the encapsulated data of a signed message
412        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        /// Obtains an array of all of the certificates in a message
423        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        /// Obtains the message content, if any
436        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        /// Obtains the signing time of a CMS message, if present
449        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        /// Returns the timestamp of a signer of a CMS message, if present
456        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        /// Returns the timestamp of a signer of a CMS message using a given policy, if present
463        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        /// Returns an array containing the certificates from a timestamp response
482        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}