1//! SMIME implementation using CMS
2//!
3//! CMS (PKCS#7) is an encryption standard.  It allows signing and encrypting data using
4//! X.509 certificates.  The OpenSSL implementation of CMS is used in email encryption
5//! generated from a `Vec` of bytes.  This `Vec` follows the smime protocol standards.
6//! Data accepted by this module will be smime type `enveloped-data`.
7
8use bitflags::bitflags;
9use foreign_types::{ForeignType, ForeignTypeRef};
10use libc::c_uint;
11use std::ptr;
12
13use crate::bio::{MemBio, MemBioSlice};
14use crate::error::ErrorStack;
15use crate::pkey::{HasPrivate, PKeyRef};
16use crate::stack::StackRef;
17use crate::symm::Cipher;
18use crate::x509::{store::X509StoreRef, X509Ref, X509};
19use crate::{cvt, cvt_p};
20use openssl_macros::corresponds;
21
22bitflags! {
23    pub struct CMSOptions : c_uint {
24        const TEXT = ffi::CMS_TEXT;
25        const CMS_NOCERTS = ffi::CMS_NOCERTS;
26        const NO_CONTENT_VERIFY = ffi::CMS_NO_CONTENT_VERIFY;
27        const NO_ATTR_VERIFY = ffi::CMS_NO_ATTR_VERIFY;
28        const NOSIGS = ffi::CMS_NOSIGS;
29        const NOINTERN = ffi::CMS_NOINTERN;
30        const NO_SIGNER_CERT_VERIFY = ffi::CMS_NO_SIGNER_CERT_VERIFY;
31        const NOVERIFY = ffi::CMS_NOVERIFY;
32        const DETACHED = ffi::CMS_DETACHED;
33        const BINARY = ffi::CMS_BINARY;
34        const NOATTR = ffi::CMS_NOATTR;
35        const NOSMIMECAP = ffi::CMS_NOSMIMECAP;
36        const NOOLDMIMETYPE = ffi::CMS_NOOLDMIMETYPE;
37        const CRLFEOL = ffi::CMS_CRLFEOL;
38        const STREAM = ffi::CMS_STREAM;
39        const NOCRL = ffi::CMS_NOCRL;
40        const PARTIAL = ffi::CMS_PARTIAL;
41        const REUSE_DIGEST = ffi::CMS_REUSE_DIGEST;
42        const USE_KEYID = ffi::CMS_USE_KEYID;
43        const DEBUG_DECRYPT = ffi::CMS_DEBUG_DECRYPT;
44        #[cfg(all(not(libressl), not(ossl101)))]
45        const KEY_PARAM = ffi::CMS_KEY_PARAM;
46        #[cfg(all(not(libressl), not(ossl101), not(ossl102)))]
47        const ASCIICRLF = ffi::CMS_ASCIICRLF;
48    }
49}
50
51foreign_type_and_impl_send_sync! {
52    type CType = ffi::CMS_ContentInfo;
53    fn drop = ffi::CMS_ContentInfo_free;
54
55    /// High level CMS wrapper
56    ///
57    /// CMS supports nesting various types of data, including signatures, certificates,
58    /// encrypted data, smime messages (encrypted email), and data digest.  The ContentInfo
59    /// content type is the encapsulation of all those content types.  [`RFC 5652`] describes
60    /// CMS and OpenSSL follows this RFC's implementation.
61    ///
62    /// [`RFC 5652`]: https://tools.ietf.org/html/rfc5652#page-6
63    pub struct CmsContentInfo;
64    /// Reference to [`CMSContentInfo`]
65    ///
66    /// [`CMSContentInfo`]:struct.CmsContentInfo.html
67    pub struct CmsContentInfoRef;
68}
69
70impl CmsContentInfoRef {
71    /// Given the sender's private key, `pkey` and the recipient's certificate, `cert`,
72    /// decrypt the data in `self`.
73    #[corresponds(CMS_decrypt)]
74    pub fn decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack>
75    where
76        T: HasPrivate,
77    {
78        unsafe {
79            let pkey = pkey.as_ptr();
80            let cert = cert.as_ptr();
81            let out = MemBio::new()?;
82
83            cvt(ffi::CMS_decrypt(
84                self.as_ptr(),
85                pkey,
86                cert,
87                ptr::null_mut(),
88                out.as_ptr(),
89                0,
90            ))?;
91
92            Ok(out.get_buf().to_owned())
93        }
94    }
95
96    /// Given the sender's private key, `pkey`,
97    /// decrypt the data in `self` without validating the recipient certificate.
98    ///
99    /// *Warning*: Not checking the recipient certificate may leave you vulnerable to Bleichenbacher's attack on PKCS#1 v1.5 RSA padding.
100    #[corresponds(CMS_decrypt)]
101    // FIXME merge into decrypt
102    pub fn decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack>
103    where
104        T: HasPrivate,
105    {
106        unsafe {
107            let pkey = pkey.as_ptr();
108            let out = MemBio::new()?;
109
110            cvt(ffi::CMS_decrypt(
111                self.as_ptr(),
112                pkey,
113                ptr::null_mut(),
114                ptr::null_mut(),
115                out.as_ptr(),
116                0,
117            ))?;
118
119            Ok(out.get_buf().to_owned())
120        }
121    }
122
123    to_der! {
124        /// Serializes this CmsContentInfo using DER.
125        #[corresponds(i2d_CMS_ContentInfo)]
126        to_der,
127        ffi::i2d_CMS_ContentInfo
128    }
129
130    to_pem! {
131        /// Serializes this CmsContentInfo using DER.
132        #[corresponds(PEM_write_bio_CMS)]
133        to_pem,
134        ffi::PEM_write_bio_CMS
135    }
136}
137
138impl CmsContentInfo {
139    /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`.
140    #[corresponds(SMIME_read_CMS)]
141    pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> {
142        unsafe {
143            let bio = MemBioSlice::new(smime)?;
144
145            let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?;
146
147            Ok(CmsContentInfo::from_ptr(cms))
148        }
149    }
150
151    from_der! {
152        /// Deserializes a DER-encoded ContentInfo structure.
153        #[corresponds(d2i_CMS_ContentInfo)]
154        from_der,
155        CmsContentInfo,
156        ffi::d2i_CMS_ContentInfo
157    }
158
159    from_pem! {
160        /// Deserializes a PEM-encoded ContentInfo structure.
161        #[corresponds(PEM_read_bio_CMS)]
162        from_pem,
163        CmsContentInfo,
164        ffi::PEM_read_bio_CMS
165    }
166
167    /// Given a signing cert `signcert`, private key `pkey`, a certificate stack `certs`,
168    /// data `data` and flags `flags`, create a CmsContentInfo struct.
169    ///
170    /// All arguments are optional.
171    #[corresponds(CMS_sign)]
172    pub fn sign<T>(
173        signcert: Option<&X509Ref>,
174        pkey: Option<&PKeyRef<T>>,
175        certs: Option<&StackRef<X509>>,
176        data: Option<&[u8]>,
177        flags: CMSOptions,
178    ) -> Result<CmsContentInfo, ErrorStack>
179    where
180        T: HasPrivate,
181    {
182        unsafe {
183            let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr());
184            let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr());
185            let data_bio = match data {
186                Some(data) => Some(MemBioSlice::new(data)?),
187                None => None,
188            };
189            let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr());
190            let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
191
192            let cms = cvt_p(ffi::CMS_sign(
193                signcert,
194                pkey,
195                certs,
196                data_bio_ptr,
197                flags.bits(),
198            ))?;
199
200            Ok(CmsContentInfo::from_ptr(cms))
201        }
202    }
203
204    /// Given a certificate stack `certs`, data `data`, cipher `cipher` and flags `flags`,
205    /// create a CmsContentInfo struct.
206    ///
207    /// OpenSSL documentation at [`CMS_encrypt`]
208    ///
209    /// [`CMS_encrypt`]: https://www.openssl.org/docs/manmaster/man3/CMS_encrypt.html
210    #[corresponds(CMS_encrypt)]
211    pub fn encrypt(
212        certs: &StackRef<X509>,
213        data: &[u8],
214        cipher: Cipher,
215        flags: CMSOptions,
216    ) -> Result<CmsContentInfo, ErrorStack> {
217        unsafe {
218            let data_bio = MemBioSlice::new(data)?;
219
220            let cms = cvt_p(ffi::CMS_encrypt(
221                certs.as_ptr(),
222                data_bio.as_ptr(),
223                cipher.as_ptr(),
224                flags.bits(),
225            ))?;
226
227            Ok(CmsContentInfo::from_ptr(cms))
228        }
229    }
230
231    /// Verify this CmsContentInfo's signature,
232    /// This will search the 'certs' list for the signing certificate.
233    /// Additional certificates, needed for building the certificate chain, may be
234    /// given in 'store' as well as additional CRLs.
235    /// A detached signature may be passed in `detached_data`. The signed content
236    /// without signature, will be copied into output_data if it is present.
237    ///
238    #[corresponds(CMS_verify)]
239    pub fn verify(
240        &mut self,
241        certs: Option<&StackRef<X509>>,
242        store: Option<&X509StoreRef>,
243        detached_data: Option<&[u8]>,
244        output_data: Option<&mut Vec<u8>>,
245        flags: CMSOptions,
246    ) -> Result<(), ErrorStack> {
247        unsafe {
248            let certs_ptr = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
249            let store_ptr = store.map_or(ptr::null_mut(), |p| p.as_ptr());
250            let detached_data_bio = match detached_data {
251                Some(data) => Some(MemBioSlice::new(data)?),
252                None => None,
253            };
254            let detached_data_bio_ptr = detached_data_bio
255                .as_ref()
256                .map_or(ptr::null_mut(), |p| p.as_ptr());
257            let out_bio = MemBio::new()?;
258
259            cvt(ffi::CMS_verify(
260                self.as_ptr(),
261                certs_ptr,
262                store_ptr,
263                detached_data_bio_ptr,
264                out_bio.as_ptr(),
265                flags.bits(),
266            ))?;
267
268            if let Some(data) = output_data {
269                data.clear();
270                data.extend_from_slice(out_bio.get_buf());
271            };
272
273            Ok(())
274        }
275    }
276}
277
278#[cfg(test)]
279mod test {
280    use super::*;
281
282    use crate::pkcs12::Pkcs12;
283    use crate::pkey::PKey;
284    use crate::stack::Stack;
285    use crate::x509::{
286        store::{X509Store, X509StoreBuilder},
287        X509,
288    };
289
290    #[test]
291    fn cms_encrypt_decrypt() {
292        #[cfg(ossl300)]
293        let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
294
295        // load cert with public key only
296        let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der");
297        let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert");
298
299        // load cert with private key
300        let priv_cert_bytes = include_bytes!("../test/cms.p12");
301        let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
302        let priv_cert = priv_cert
303            .parse2("mypass")
304            .expect("failed to parse priv cert");
305
306        // encrypt cms message using public key cert
307        let input = String::from("My Message");
308        let mut cert_stack = Stack::new().expect("failed to create stack");
309        cert_stack
310            .push(pub_cert)
311            .expect("failed to add pub cert to stack");
312
313        let encrypt = CmsContentInfo::encrypt(
314            &cert_stack,
315            input.as_bytes(),
316            Cipher::des_ede3_cbc(),
317            CMSOptions::empty(),
318        )
319        .expect("failed create encrypted cms");
320
321        // decrypt cms message using private key cert (DER)
322        {
323            let encrypted_der = encrypt.to_der().expect("failed to create der from cms");
324            let decrypt =
325                CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der");
326
327            let decrypt_with_cert_check = decrypt
328                .decrypt(
329                    priv_cert.pkey.as_ref().unwrap(),
330                    priv_cert.cert.as_ref().unwrap(),
331                )
332                .expect("failed to decrypt cms");
333            let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
334                .expect("failed to create string from cms content");
335
336            let decrypt_without_cert_check = decrypt
337                .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
338                .expect("failed to decrypt cms");
339            let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
340                .expect("failed to create string from cms content");
341
342            assert_eq!(input, decrypt_with_cert_check);
343            assert_eq!(input, decrypt_without_cert_check);
344        }
345
346        // decrypt cms message using private key cert (PEM)
347        {
348            let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms");
349            let decrypt =
350                CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem");
351
352            let decrypt_with_cert_check = decrypt
353                .decrypt(
354                    priv_cert.pkey.as_ref().unwrap(),
355                    priv_cert.cert.as_ref().unwrap(),
356                )
357                .expect("failed to decrypt cms");
358            let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
359                .expect("failed to create string from cms content");
360
361            let decrypt_without_cert_check = decrypt
362                .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
363                .expect("failed to decrypt cms");
364            let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
365                .expect("failed to create string from cms content");
366
367            assert_eq!(input, decrypt_with_cert_check);
368            assert_eq!(input, decrypt_without_cert_check);
369        }
370    }
371
372    fn cms_sign_verify_generic_helper(is_detached: bool) {
373        // load cert with private key
374        let cert_bytes = include_bytes!("../test/cert.pem");
375        let cert = X509::from_pem(cert_bytes).expect("failed to load cert.pem");
376
377        let key_bytes = include_bytes!("../test/key.pem");
378        let key = PKey::private_key_from_pem(key_bytes).expect("failed to load key.pem");
379
380        let root_bytes = include_bytes!("../test/root-ca.pem");
381        let root = X509::from_pem(root_bytes).expect("failed to load root-ca.pem");
382
383        // sign cms message using public key cert
384        let data = b"Hello world!";
385
386        let (opt, ext_data): (CMSOptions, Option<&[u8]>) = if is_detached {
387            (CMSOptions::DETACHED | CMSOptions::BINARY, Some(data))
388        } else {
389            (CMSOptions::empty(), None)
390        };
391
392        let mut cms = CmsContentInfo::sign(Some(&cert), Some(&key), None, Some(data), opt)
393            .expect("failed to CMS sign a message");
394
395        // check CMS signature length
396        let pem_cms = cms
397            .to_pem()
398            .expect("failed to pack CmsContentInfo into PEM");
399        assert!(!pem_cms.is_empty());
400
401        // verify CMS signature
402        let mut builder = X509StoreBuilder::new().expect("failed to create X509StoreBuilder");
403        builder
404            .add_cert(root)
405            .expect("failed to add root-ca into X509StoreBuilder");
406        let store: X509Store = builder.build();
407        let mut out_data: Vec<u8> = Vec::new();
408        let res = cms.verify(
409            None,
410            Some(&store),
411            ext_data,
412            Some(&mut out_data),
413            CMSOptions::empty(),
414        );
415
416        // check verification result -  valid signature
417        res.unwrap();
418        assert_eq!(data.to_vec(), out_data);
419    }
420
421    #[test]
422    fn cms_sign_verify_ok() {
423        cms_sign_verify_generic_helper(false);
424    }
425
426    #[test]
427    fn cms_sign_verify_detached_ok() {
428        cms_sign_verify_generic_helper(true);
429    }
430
431    #[test]
432    fn cms_sign_verify_error() {
433        #[cfg(ossl300)]
434        let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
435
436        // load cert with private key
437        let priv_cert_bytes = include_bytes!("../test/cms.p12");
438        let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
439        let priv_cert = priv_cert
440            .parse2("mypass")
441            .expect("failed to parse priv cert");
442
443        // sign cms message using public key cert
444        let data = b"Hello world!";
445        let mut cms = CmsContentInfo::sign(
446            Some(&priv_cert.cert.unwrap()),
447            Some(&priv_cert.pkey.unwrap()),
448            None,
449            Some(data),
450            CMSOptions::empty(),
451        )
452        .expect("failed to CMS sign a message");
453
454        // check CMS signature length
455        let pem_cms = cms
456            .to_pem()
457            .expect("failed to pack CmsContentInfo into PEM");
458        assert!(!pem_cms.is_empty());
459
460        let empty_store = X509StoreBuilder::new()
461            .expect("failed to create X509StoreBuilder")
462            .build();
463
464        // verify CMS signature
465        let res = cms.verify(
466            None,
467            Some(&empty_store),
468            Some(data),
469            None,
470            CMSOptions::empty(),
471        );
472
473        // check verification result - this is an invalid signature
474        // defined in openssl crypto/cms/cms.h
475        const CMS_R_CERTIFICATE_VERIFY_ERROR: i32 = 100;
476        match res {
477            Err(es) => {
478                let error_array = es.errors();
479                assert_eq!(1, error_array.len());
480                let code = error_array[0].code();
481                assert_eq!(ffi::ERR_GET_REASON(code), CMS_R_CERTIFICATE_VERIFY_ERROR);
482            }
483            _ => panic!("expected CMS verification error, got Ok()"),
484        }
485    }
486}
487