Skip to main content

native_ossl/
cms.rs

1//! CMS (RFC 5652) — Cryptographic Message Syntax.
2//!
3//! [`CmsContentInfo`] wraps `CMS_ContentInfo*` and covers the `SignedData`
4//! content type used in modern PKI: signing a payload with an X.509 certificate,
5//! verifying a signed message against a trust store, and extracting the embedded
6//! certificates and CRLs.
7//!
8//! # Quick example
9//!
10//! ```rust,no_run
11//! # use native_ossl::cms::{CmsContentInfo, CmsSignFlags, CmsVerifyFlags};
12//! # use native_ossl::x509::{X509, X509Store};
13//! # use native_ossl::pkey::Pkey;
14//! # use native_ossl::pkey::Private;
15//! # fn example(cert: &X509, key: &Pkey<Private>, store: &X509Store) -> Result<(), native_ossl::error::ErrorStack> {
16//! let payload = b"hello, PKINIT";
17//! let signed = CmsContentInfo::sign(cert, key, &[], payload, CmsSignFlags::NONE)?;
18//! let der = signed.to_der()?;
19//!
20//! let parsed = CmsContentInfo::from_der(&der)?;
21//! let content = parsed.verify(store, &[], CmsVerifyFlags::NONE)?;
22//! assert_eq!(content, payload);
23//! # Ok(()) }
24//! ```
25
26use crate::bio::{MemBio, MemBioBuf};
27use crate::error::ErrorStack;
28use crate::obj::Oid;
29use crate::pkey::{Pkey, Private};
30use crate::x509::{X509Crl, X509Store, X509};
31use native_ossl_sys as sys;
32use std::marker::PhantomData;
33use std::sync::Arc;
34
35// ── CmsVerifyFlags ────────────────────────────────────────────────────────────
36
37/// Flags that control [`CmsContentInfo::verify`].
38///
39/// Combine multiple flags with `|`.
40#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
41pub struct CmsVerifyFlags(pub u32);
42
43impl CmsVerifyFlags {
44    /// No flags — full verification (signature + chain).
45    pub const NONE: Self = Self(0);
46    /// Skip signer certificate verification (equivalent to `CMS_NOVERIFY`).
47    pub const NO_SIGNER_CERT_VERIFY: Self = Self(0x20);
48    /// Skip all verification — only decode the structure.
49    pub const NOVERIFY: Self = Self(0x20);
50    /// Do not search for signers in the embedded certificate set.
51    pub const NOINTERN: Self = Self(0x10);
52    /// Do not consult embedded CRLs for revocation checks.
53    pub const NOCRL: Self = Self(0x2000);
54}
55
56impl std::ops::BitOr for CmsVerifyFlags {
57    type Output = Self;
58    fn bitor(self, rhs: Self) -> Self {
59        Self(self.0 | rhs.0)
60    }
61}
62
63// ── CmsSignFlags ──────────────────────────────────────────────────────────────
64
65/// Flags that control [`CmsContentInfo::sign`].
66///
67/// Combine multiple flags with `|`.
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub struct CmsSignFlags(pub u32);
70
71impl CmsSignFlags {
72    /// No flags — produce a full `SignedData` structure with embedded content.
73    pub const NONE: Self = Self(0);
74    /// Produce detached content — the signed bytes are not embedded.
75    pub const DETACHED: Self = Self(0x40);
76    /// Treat content as binary (do not convert line endings).
77    pub const BINARY: Self = Self(0x80);
78    /// Omit S/MIME capabilities from signed attributes.
79    pub const NOSMIMECAP: Self = Self(0x200);
80    /// Do not embed the signer certificate in the `SignedData`.
81    pub const NOCERTS: Self = Self(0x2);
82    /// Omit signed attributes entirely.
83    pub const NOATTR: Self = Self(0x100);
84}
85
86impl std::ops::BitOr for CmsSignFlags {
87    type Output = Self;
88    fn bitor(self, rhs: Self) -> Self {
89        Self(self.0 | rhs.0)
90    }
91}
92
93// ── CmsSignerInfo ─────────────────────────────────────────────────────────────
94
95/// A borrowed reference to a `CMS_SignerInfo` within a [`CmsContentInfo`].
96///
97/// The lifetime is tied to the [`CmsContentInfo`] that owns the signer info
98/// stack; the pointer is valid only while that parent is alive.
99pub struct CmsSignerInfo<'a> {
100    ptr: *mut sys::CMS_SignerInfo,
101    _marker: PhantomData<&'a CmsContentInfo>,
102}
103
104impl CmsSignerInfo<'_> {
105    /// Return the signer certificate embedded in this signer info, if any.
106    ///
107    /// Returns `None` if the certificate was not embedded (e.g. `CmsSignFlags::NOCERTS`
108    /// was set when signing).
109    #[must_use]
110    pub fn signer_cert(&self) -> Option<X509> {
111        let mut signer: *mut sys::X509 = std::ptr::null_mut();
112        // SAFETY: self.ptr is valid for the lifetime of the parent CmsContentInfo;
113        // all out-params we don't need are passed as null.
114        unsafe {
115            sys::CMS_SignerInfo_get0_algs(
116                self.ptr,
117                std::ptr::null_mut(), // pk
118                std::ptr::addr_of_mut!(signer),
119                std::ptr::null_mut(), // pdig
120                std::ptr::null_mut(), // psig
121            );
122        }
123        if signer.is_null() {
124            return None;
125        }
126        // The returned pointer is borrowed from the SignedData structure; call
127        // up_ref to take an independent reference for the returned X509.
128        unsafe { sys::X509_up_ref(signer) };
129        Some(unsafe { X509::from_ptr(signer) })
130    }
131}
132
133// ── CmsContentInfo ────────────────────────────────────────────────────────────
134
135/// An owned `CMS_ContentInfo*` — the top-level CMS container.
136pub struct CmsContentInfo {
137    ptr: *mut sys::CMS_ContentInfo,
138}
139
140// SAFETY: CMS_ContentInfo* has no thread-local state; read-only methods
141// access only the allocated data through a shared reference.
142unsafe impl Send for CmsContentInfo {}
143unsafe impl Sync for CmsContentInfo {}
144
145impl Drop for CmsContentInfo {
146    fn drop(&mut self) {
147        // SAFETY: ptr is a valid, non-null, owned CMS_ContentInfo*.
148        unsafe { sys::CMS_ContentInfo_free(self.ptr) };
149    }
150}
151
152impl CmsContentInfo {
153    // ── Constructors ─────────────────────────────────────────────────────────
154
155    /// Decode a DER-encoded `CMS_ContentInfo` structure.
156    ///
157    /// # Errors
158    ///
159    /// Returns `Err` if `data` is not valid CMS DER.
160    pub fn from_der(data: &[u8]) -> Result<Self, ErrorStack> {
161        let bio = MemBioBuf::new(data)?;
162        // SAFETY: bio.as_ptr() is valid; second arg NULL means allocate fresh.
163        let ptr = unsafe { sys::d2i_CMS_bio(bio.as_ptr(), std::ptr::null_mut()) };
164        if ptr.is_null() {
165            Err(ErrorStack::drain())
166        } else {
167            Ok(CmsContentInfo { ptr })
168        }
169    }
170
171    /// Decode a DER-encoded `CMS_ContentInfo` using an explicit library context.
172    ///
173    /// Allocates the container with `CMS_ContentInfo_new_ex(ctx)` first, then
174    /// decodes into it so that any internal algorithm fetches use `ctx`'s
175    /// provider set.
176    ///
177    /// # Errors
178    pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, data: &[u8]) -> Result<Self, ErrorStack> {
179        let shell = unsafe { sys::CMS_ContentInfo_new_ex(ctx.as_ptr(), std::ptr::null()) };
180        if shell.is_null() {
181            return Err(ErrorStack::drain());
182        }
183        let mut ptr = data.as_ptr();
184        let len = i64::try_from(data.len()).unwrap_or(i64::MAX);
185        // d2i_CMS_ContentInfo with a non-null `a` pointer will free the old
186        // value and write the newly decoded one.
187        let mut shell_mut = shell;
188        let result = unsafe {
189            sys::d2i_CMS_ContentInfo(
190                std::ptr::addr_of_mut!(shell_mut),
191                std::ptr::addr_of_mut!(ptr),
192                len,
193            )
194        };
195        if result.is_null() {
196            // d2i freed `shell` on failure.
197            return Err(ErrorStack::drain());
198        }
199        Ok(CmsContentInfo { ptr: result })
200    }
201
202    // ── Serialisation ────────────────────────────────────────────────────────
203
204    /// DER-encode this `CMS_ContentInfo`.
205    ///
206    /// # Errors
207    pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
208        let mut bio = MemBio::new()?;
209        // SAFETY: self.ptr is valid; bio.as_ptr() is valid.
210        let rc = unsafe { sys::i2d_CMS_bio(bio.as_ptr(), self.ptr) };
211        if rc != 1 {
212            return Err(ErrorStack::drain());
213        }
214        Ok(bio.into_vec())
215    }
216
217    // ── Accessors ────────────────────────────────────────────────────────────
218
219    /// Return the content-type OID (the outer `contentType` field).
220    ///
221    /// For `SignedData` this is OID `1.2.840.113549.1.7.2`.
222    ///
223    /// # Panics
224    ///
225    /// Panics if OpenSSL fails to allocate memory for the OID copy.
226    #[must_use]
227    pub fn content_type_oid(&self) -> Oid {
228        // SAFETY: CMS_get0_type returns a const pointer borrowed from self.ptr;
229        // OBJ_dup copies it into a new heap allocation for us to own.
230        let obj = unsafe { sys::CMS_get0_type(self.ptr) };
231        let dup = unsafe { sys::OBJ_dup(obj.cast_mut()) };
232        assert!(!dup.is_null(), "OBJ_dup: allocation failed");
233        unsafe { Oid::from_ptr(dup) }
234    }
235
236    /// Return the encapsulated content type OID (`eContentType`).
237    ///
238    /// For ordinary `SignedData` carrying arbitrary data this is typically
239    /// `data` (OID `1.2.840.113549.1.7.1`); PKINIT uses its own OIDs
240    /// (`id-pkinit-authData`, `id-pkinit-DHKeyData`, etc.).
241    ///
242    /// # Panics
243    ///
244    /// Panics if OpenSSL fails to allocate memory for the OID copy.
245    #[must_use]
246    pub fn econtent_type_oid(&self) -> Oid {
247        let obj = unsafe { sys::CMS_get0_eContentType(self.ptr) };
248        let dup = unsafe { sys::OBJ_dup(obj.cast_mut()) };
249        assert!(!dup.is_null(), "OBJ_dup: allocation failed");
250        unsafe { Oid::from_ptr(dup) }
251    }
252
253    /// Return `true` if the content is detached (not embedded in the structure).
254    #[must_use]
255    pub fn is_detached(&self) -> bool {
256        unsafe { sys::CMS_is_detached(self.ptr) == 1 }
257    }
258
259    /// Return the raw encapsulated content bytes, or `None` if absent or detached.
260    #[must_use]
261    pub fn content(&self) -> Option<Vec<u8>> {
262        // CMS_get0_content returns ASN1_OCTET_STRING** — a pointer to the
263        // pointer stored inside the structure.  We dereference twice.
264        // SAFETY: self.ptr is valid; the returned pointer lives as long as self.
265        let pp = unsafe { sys::CMS_get0_content(self.ptr) };
266        if pp.is_null() {
267            return None;
268        }
269        let ostr = unsafe { *pp };
270        if ostr.is_null() {
271            return None;
272        }
273        // SAFETY: ostr is a valid, non-null ASN1_OCTET_STRING*.
274        let data_ptr = unsafe { sys::ASN1_STRING_get0_data(ostr.cast()) };
275        let data_len = usize::try_from(unsafe { sys::ASN1_STRING_length(ostr.cast()) }).ok()?;
276        if data_ptr.is_null() || data_len == 0 {
277            return None;
278        }
279        let slice = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
280        Some(slice.to_vec())
281    }
282
283    /// Return the list of signer infos (borrowed from this `CmsContentInfo`).
284    #[must_use]
285    pub fn signers(&self) -> Vec<CmsSignerInfo<'_>> {
286        // SAFETY: self.ptr is valid; the returned stack is borrowed from self.
287        let stack = unsafe { sys::CMS_get0_SignerInfos(self.ptr) };
288        if stack.is_null() {
289            return Vec::new();
290        }
291        // OPENSSL_sk_num returns c_int; loop with i32 to avoid any cast.
292        let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
293        let count = usize::try_from(n.max(0)).unwrap_or(0);
294        let mut out = Vec::with_capacity(count);
295        for i in 0..n {
296            let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
297            if !raw.is_null() {
298                out.push(CmsSignerInfo {
299                    ptr: raw.cast::<sys::CMS_SignerInfo>(),
300                    _marker: PhantomData,
301                });
302            }
303        }
304        out
305    }
306
307    /// Return the certificates embedded in this `CMS_ContentInfo`.
308    ///
309    /// `CMS_get1_certs` increments the reference count of each returned
310    /// certificate; each `X509` in the returned `Vec` owns an independent ref.
311    #[must_use]
312    pub fn certs(&self) -> Vec<X509> {
313        let stack = unsafe { sys::CMS_get1_certs(self.ptr) };
314        collect_x509_stack(stack)
315    }
316
317    /// Return the CRLs embedded in this `CMS_ContentInfo`.
318    #[must_use]
319    pub fn crls(&self) -> Vec<X509Crl> {
320        let stack = unsafe { sys::CMS_get1_crls(self.ptr) };
321        collect_x509_crl_stack(stack)
322    }
323
324    // ── Signature operations ──────────────────────────────────────────────────
325
326    /// Verify the CMS `SignedData`, returning the encapsulated content on success.
327    ///
328    /// - `store` — trust anchor store.
329    /// - `certs` — additional untrusted certificates to include in chain building.
330    /// - `flags` — verification control flags (see [`CmsVerifyFlags`]).
331    ///
332    /// # Errors
333    ///
334    /// Returns `Err` if signature verification fails, the chain cannot be built,
335    /// or the content is missing or malformed.
336    pub fn verify(
337        &self,
338        store: &X509Store,
339        certs: &[X509],
340        flags: CmsVerifyFlags,
341    ) -> Result<Vec<u8>, ErrorStack> {
342        let certs_stack = build_x509_stack(certs)?;
343        let mut out_bio = MemBio::new()?;
344
345        // SAFETY: all pointers are valid for the duration of the call;
346        // certs_stack is freed below.  dcont=NULL means use the embedded content.
347        let rc = unsafe {
348            sys::CMS_verify(
349                self.ptr,
350                certs_stack,
351                store.as_ptr(),
352                std::ptr::null_mut(), // dcont
353                out_bio.as_ptr(),
354                flags.0,
355            )
356        };
357
358        // Free the temporary certs stack (elements are borrowed; not pop_free).
359        if !certs_stack.is_null() {
360            unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
361        }
362
363        if rc != 1 {
364            return Err(ErrorStack::drain());
365        }
366        Ok(out_bio.into_vec())
367    }
368
369    /// Create a CMS `SignedData` wrapping `content`.
370    ///
371    /// - `cert` — signer certificate.
372    /// - `key` — corresponding private key.
373    /// - `extra_certs` — additional certificates to embed in the `SignedData`
374    ///   (e.g. intermediate CA certificates for chain building).
375    /// - `content` — the payload to sign.
376    /// - `flags` — sign control flags (see [`CmsSignFlags`]).
377    ///
378    /// # Errors
379    pub fn sign(
380        cert: &X509,
381        key: &Pkey<Private>,
382        extra_certs: &[X509],
383        content: &[u8],
384        flags: CmsSignFlags,
385    ) -> Result<Self, ErrorStack> {
386        let data_bio = MemBioBuf::new(content)?;
387        let certs_stack = build_x509_stack(extra_certs)?;
388
389        // SAFETY: all pointers are valid; certs_stack is freed below.
390        let ptr = unsafe {
391            sys::CMS_sign(
392                cert.as_ptr(),
393                key.as_ptr(),
394                certs_stack,
395                data_bio.as_ptr(),
396                flags.0,
397            )
398        };
399
400        if !certs_stack.is_null() {
401            unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
402        }
403
404        if ptr.is_null() {
405            Err(ErrorStack::drain())
406        } else {
407            Ok(CmsContentInfo { ptr })
408        }
409    }
410
411    /// Create a CMS `SignedData` using an explicit library context.
412    ///
413    /// Calls `CMS_sign_ex` so all algorithm fetches use `ctx`'s provider set.
414    ///
415    /// # Errors
416    pub fn sign_in(
417        ctx: &Arc<crate::lib_ctx::LibCtx>,
418        cert: &X509,
419        key: &Pkey<Private>,
420        extra_certs: &[X509],
421        content: &[u8],
422        flags: CmsSignFlags,
423    ) -> Result<Self, ErrorStack> {
424        let data_bio = MemBioBuf::new(content)?;
425        let certs_stack = build_x509_stack(extra_certs)?;
426
427        let ptr = unsafe {
428            sys::CMS_sign_ex(
429                cert.as_ptr(),
430                key.as_ptr(),
431                certs_stack,
432                data_bio.as_ptr(),
433                flags.0,
434                ctx.as_ptr(),
435                std::ptr::null(),
436            )
437        };
438
439        if !certs_stack.is_null() {
440            unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
441        }
442
443        if ptr.is_null() {
444            Err(ErrorStack::drain())
445        } else {
446            Ok(CmsContentInfo { ptr })
447        }
448    }
449}
450
451// ── Internal helpers ──────────────────────────────────────────────────────────
452
453/// Build a temporary `STACK_OF(X509)*` from a borrowed `&[X509]` slice.
454///
455/// The stack is a *shallow* owner — elements are not ref-counted up; the
456/// caller must free it with `OPENSSL_sk_free` (not `pop_free`) once done.
457/// Returns `null` when `certs` is empty (acceptable as NULL to OpenSSL).
458fn build_x509_stack(certs: &[X509]) -> Result<*mut sys::stack_st_X509, ErrorStack> {
459    if certs.is_empty() {
460        return Ok(std::ptr::null_mut());
461    }
462    let stack = unsafe { sys::OPENSSL_sk_new_null() };
463    if stack.is_null() {
464        return Err(ErrorStack::drain());
465    }
466    for cert in certs {
467        unsafe { sys::OPENSSL_sk_push(stack, cert.as_ptr().cast()) };
468    }
469    Ok(stack.cast())
470}
471
472/// Consume an owned `STACK_OF(X509)*` returned by `CMS_get1_certs` and
473/// convert it to a `Vec<X509>`.
474///
475/// `CMS_get1_certs` increments the ref count of each element; each `X509`
476/// we create takes ownership of that extra ref.  The stack *structure* is freed
477/// with `OPENSSL_sk_free` (not `pop_free`) to avoid double-freeing.
478fn collect_x509_stack(stack: *mut sys::stack_st_X509) -> Vec<X509> {
479    if stack.is_null() {
480        return Vec::new();
481    }
482    // OPENSSL_sk_num returns c_int; loop with i32 to avoid casts.
483    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
484    let count = usize::try_from(n.max(0)).unwrap_or(0);
485    let mut out = Vec::with_capacity(count);
486    for i in 0..n {
487        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
488        if !raw.is_null() {
489            // SAFETY: raw is a valid X509* with an extra ref from CMS_get1_certs.
490            out.push(unsafe { X509::from_ptr(raw.cast()) });
491        }
492    }
493    // Free the stack structure; elements are now owned by the Rust X509 values.
494    unsafe { sys::OPENSSL_sk_free(stack.cast()) };
495    out
496}
497
498/// Consume an owned `STACK_OF(X509_CRL)*` returned by `CMS_get1_crls` and
499/// convert it to a `Vec<X509Crl>`.
500fn collect_x509_crl_stack(stack: *mut sys::stack_st_X509_CRL) -> Vec<X509Crl> {
501    if stack.is_null() {
502        return Vec::new();
503    }
504    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
505    let count = usize::try_from(n.max(0)).unwrap_or(0);
506    let mut out = Vec::with_capacity(count);
507    for i in 0..n {
508        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
509        if !raw.is_null() {
510            // SAFETY: raw is a valid X509_CRL* with an extra ref from CMS_get1_crls.
511            out.push(unsafe { X509Crl::from_ptr(raw.cast()) });
512        }
513    }
514    unsafe { sys::OPENSSL_sk_free(stack.cast()) };
515    out
516}
517
518// ── Tests ─────────────────────────────────────────────────────────────────────
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523    use crate::obj::Oid;
524    use crate::params::ParamBuilder;
525    use crate::pkey::KeygenCtx;
526    use crate::x509::{X509Builder, X509NameOwned};
527
528    fn make_cert_and_key() -> (X509, Pkey<Private>) {
529        // Use EC P-256 — fast keygen and has a default digest for CMS_sign.
530        let priv_key = (|| -> Result<Pkey<Private>, ErrorStack> {
531            let mut kg = KeygenCtx::new(c"EC")?;
532            let params = ParamBuilder::new()?
533                .push_utf8_string(c"group", c"P-256")?
534                .build()?;
535            kg.set_params(&params)?;
536            kg.generate()
537        })()
538        .expect("EC P-256 keygen");
539
540        let pub_key: crate::pkey::Pkey<crate::pkey::Public> = priv_key.clone().into();
541        let mut name = X509NameOwned::new().expect("X509NameOwned::new");
542        name.add_entry_by_txt(c"CN", b"cms-test").expect("CN");
543
544        let cert = (|| -> Result<X509, ErrorStack> {
545            Ok(X509Builder::new()?
546                .set_version(2)?
547                .set_serial_number(1)?
548                .set_not_before_offset(0)?
549                .set_not_after_offset(365 * 86400)?
550                .set_subject_name(&name)?
551                .set_issuer_name(&name)?
552                .set_public_key(&pub_key)?
553                .sign(&priv_key, None)?
554                .build())
555        })()
556        .expect("X509Builder");
557
558        (cert, priv_key)
559    }
560
561    #[test]
562    fn sign_and_verify_roundtrip() {
563        let (cert, key) = make_cert_and_key();
564        let payload = b"native-ossl cms test payload";
565
566        let signed = CmsContentInfo::sign(
567            &cert,
568            &key,
569            &[],
570            payload,
571            CmsSignFlags::BINARY | CmsSignFlags::NOSMIMECAP,
572        )
573        .expect("CMS_sign");
574
575        let der = signed.to_der().expect("to_der");
576        assert!(!der.is_empty());
577
578        let parsed = CmsContentInfo::from_der(&der).expect("from_der");
579
580        let mut store = X509Store::new().expect("X509Store");
581        store.add_cert(&cert).expect("add_cert");
582
583        let content = parsed
584            .verify(&store, &[], CmsVerifyFlags::NONE)
585            .expect("verify");
586        assert_eq!(content, payload);
587    }
588
589    #[test]
590    fn content_type_oid_is_signed_data() {
591        let (cert, key) = make_cert_and_key();
592        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
593        assert_eq!(cms.content_type_oid().to_string(), "1.2.840.113549.1.7.2");
594    }
595
596    #[test]
597    fn econtent_type_oid_is_data() {
598        let (cert, key) = make_cert_and_key();
599        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
600        assert_eq!(cms.econtent_type_oid().to_string(), "1.2.840.113549.1.7.1");
601    }
602
603    #[test]
604    fn embedded_signer_cert() {
605        let (cert, key) = make_cert_and_key();
606        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
607        let signers = cms.signers();
608        assert_eq!(signers.len(), 1);
609        let signer_cert = signers[0].signer_cert().expect("signer cert present");
610        assert_eq!(cert.to_der().unwrap(), signer_cert.to_der().unwrap());
611    }
612
613    #[test]
614    fn certs_list_contains_embedded_cert() {
615        let (cert, key) = make_cert_and_key();
616        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
617        assert!(!cms.certs().is_empty());
618    }
619
620    #[test]
621    fn nocerts_flag_omits_embedded_certs() {
622        let (cert, key) = make_cert_and_key();
623        let cms =
624            CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NOCERTS).expect("sign");
625        assert!(
626            cms.certs().is_empty(),
627            "NOCERTS should produce no embedded certs"
628        );
629    }
630
631    #[test]
632    fn verify_fails_with_wrong_trust_anchor() {
633        let (cert, key) = make_cert_and_key();
634        let (other_cert, _) = make_cert_and_key();
635
636        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::BINARY).expect("sign");
637        let der = cms.to_der().expect("to_der");
638        let parsed = CmsContentInfo::from_der(&der).expect("from_der");
639
640        let mut store = X509Store::new().expect("store");
641        store.add_cert(&other_cert).expect("add_cert");
642
643        assert!(
644            parsed.verify(&store, &[], CmsVerifyFlags::NONE).is_err(),
645            "verify with wrong trust anchor must fail"
646        );
647    }
648
649    #[test]
650    fn content_accessor_returns_payload() {
651        let (cert, key) = make_cert_and_key();
652        let payload = b"detectable content";
653        let cms =
654            CmsContentInfo::sign(&cert, &key, &[], payload, CmsSignFlags::BINARY).expect("sign");
655        let content = cms.content().expect("content present");
656        assert_eq!(content, payload);
657    }
658
659    #[test]
660    fn is_detached_flag() {
661        let (cert, key) = make_cert_and_key();
662        let embedded =
663            CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
664        let detached =
665            CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::DETACHED).expect("sign");
666        assert!(!embedded.is_detached());
667        assert!(detached.is_detached());
668    }
669
670    #[test]
671    fn oid_comparison_econtent() {
672        let (cert, key) = make_cert_and_key();
673        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
674        let data_oid = Oid::from_text("1.2.840.113549.1.7.1").expect("data oid");
675        assert_eq!(cms.econtent_type_oid(), data_oid);
676    }
677}