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 core_foundation::{declare_TCFType, impl_TCFType};
34    use crate::identity::SecIdentity;
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            Ok(())
132        }
133
134        /// Obtains the array of recipients specified with the `add_recipients` function
135        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        /// Specifies whether the signed data is to be separate from the message
148        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        /// Indicates whether the message is to have detached content
154        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        /// Specifies an object identifier for the encapsulated data of a signed message
161        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        /// Obtains the object identifier for the encapsulated data of a signed message
168        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        /// Adds certificates to a message
175        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        /// Obtains the certificates added to a message with `add_supporting_certs`
186        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        /// Specifies attributes for a signed message
199        pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> {
200            cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?;
201            Ok(())
202        }
203
204        /// Specifies which certificates to include in a signed CMS message
205        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        /// Obtains a constant that indicates which certificates are to be included in a signed CMS message
211        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        /// Feeds content bytes into the encoder
218        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        /// Finishes encoding the message and obtains the encoded result
224        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        /// Returns the timestamp of a signer of a CMS message, if present
231        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        /// Returns the timestamp of a signer of a CMS message using a particular policy, if present
238        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    /// Encodes a message and obtains the result in one high-level function call
257    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    /// Holds a result of the `CMSDecoder::get_signer_status` function
292    pub struct SignerStatus {
293        /// Signature status
294        pub signer_status: CMSSignerStatus,
295        /// Trust instance that was used to verify the signer’s certificate
296        pub sec_trust: SecTrust,
297        /// Result of the certificate verification
298        pub cert_verify_result: Result<()>,
299    }
300
301    declare_TCFType! {
302        /// A type representing CMS Decoder
303        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        /// Create a new instance of `CMSDecoder`
319        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        /// Feeds raw bytes of the message to be decoded into the decoder
326        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        /// Indicates that there is no more data to decode
332        pub fn finalize_message(&self) -> Result<()> {
333            cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?;
334            Ok(())
335        }
336
337        /// Specifies the message’s detached content, if any
338        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        /// Obtains the detached content specified with the `set_detached_content` function
345        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        /// Obtains the number of signers of a message
358        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        /// Obtains the status of a CMS message’s signature
365        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        /// Obtains the email address of the specified signer of a CMS message
396        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        /// Determines whether a CMS message was encrypted
403        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        /// Obtains the object identifier for the encapsulated data of a signed message
410        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        /// Obtains an array of all of the certificates in a message
421        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        /// Obtains the message content, if any
434        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        /// Obtains the signing time of a CMS message, if present
447        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        /// Returns the timestamp of a signer of a CMS message, if present
454        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        /// Returns the timestamp of a signer of a CMS message using a given policy, if present
461        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        /// Returns an array containing the certificates from a timestamp response
482        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}