Skip to main content

variant_ssl/
pkcs7.rs

1use bitflags::bitflags;
2use foreign_types::{ForeignType, ForeignTypeRef};
3use libc::c_int;
4use std::mem;
5use std::ptr;
6
7use crate::asn1::Asn1ObjectRef;
8use crate::bio::{MemBio, MemBioSlice};
9use crate::error::ErrorStack;
10#[cfg(not(awslc))]
11use crate::nid::Nid;
12use crate::pkey::{HasPrivate, PKeyRef};
13use crate::stack::{Stack, StackRef, Stackable};
14use crate::symm::Cipher;
15use crate::util::ForeignTypeRefExt;
16use crate::x509::store::X509StoreRef;
17use crate::x509::{X509Ref, X509};
18use crate::{cvt, cvt_p};
19use openssl_macros::corresponds;
20
21foreign_type_and_impl_send_sync! {
22    type CType = ffi::PKCS7_SIGNER_INFO;
23    fn drop = ffi::PKCS7_SIGNER_INFO_free;
24
25    pub struct Pkcs7SignerInfo;
26    pub struct Pkcs7SignerInfoRef;
27}
28
29impl Stackable for Pkcs7SignerInfo {
30    type StackType = ffi::stack_st_PKCS7_SIGNER_INFO;
31}
32
33foreign_type_and_impl_send_sync! {
34    type CType = ffi::PKCS7;
35    fn drop = ffi::PKCS7_free;
36
37    /// A PKCS#7 structure.
38    ///
39    /// Contains signed and/or encrypted data.
40    pub struct Pkcs7;
41
42    /// Reference to `Pkcs7`
43    pub struct Pkcs7Ref;
44}
45
46// AWS-LC does not expose `PKCS7_SIGNED_free`, which the owned type requires.
47#[cfg(not(awslc))]
48foreign_type_and_impl_send_sync! {
49    type CType = ffi::PKCS7_SIGNED;
50    fn drop = ffi::PKCS7_SIGNED_free;
51
52    /// A PKCS#7 signed data structure.
53    ///
54    /// Contains signed data.
55    pub struct Pkcs7Signed;
56
57    /// Reference to `Pkcs7Signed`
58    pub struct Pkcs7SignedRef;
59}
60
61bitflags! {
62    #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
63    #[repr(transparent)]
64    pub struct Pkcs7Flags: c_int {
65        const TEXT = ffi::PKCS7_TEXT;
66        const NOCERTS = ffi::PKCS7_NOCERTS;
67        const NOSIGS = ffi::PKCS7_NOSIGS;
68        const NOCHAIN = ffi::PKCS7_NOCHAIN;
69        const NOINTERN = ffi::PKCS7_NOINTERN;
70        const NOVERIFY = ffi::PKCS7_NOVERIFY;
71        const DETACHED = ffi::PKCS7_DETACHED;
72        const BINARY = ffi::PKCS7_BINARY;
73        const NOATTR = ffi::PKCS7_NOATTR;
74        const NOSMIMECAP = ffi::PKCS7_NOSMIMECAP;
75        #[cfg(not(awslc))]
76        const NOOLDMIMETYPE = ffi::PKCS7_NOOLDMIMETYPE;
77        #[cfg(not(awslc))]
78        const CRLFEOL = ffi::PKCS7_CRLFEOL;
79        const STREAM = ffi::PKCS7_STREAM;
80        #[cfg(not(awslc))]
81        const NOCRL = ffi::PKCS7_NOCRL;
82        const PARTIAL = ffi::PKCS7_PARTIAL;
83        #[cfg(not(awslc))]
84        const REUSE_DIGEST = ffi::PKCS7_REUSE_DIGEST;
85        #[cfg(ossl110)]
86        const NO_DUAL_CONTENT = ffi::PKCS7_NO_DUAL_CONTENT;
87    }
88}
89
90impl Pkcs7 {
91    from_pem! {
92        /// Deserializes a PEM-encoded PKCS#7 signature
93        ///
94        /// The input should have a header of `-----BEGIN PKCS7-----`.
95        #[corresponds(PEM_read_bio_PKCS7)]
96        from_pem,
97        Pkcs7,
98        ffi::PEM_read_bio_PKCS7
99    }
100
101    from_der! {
102        /// Deserializes a DER-encoded PKCS#7 signature
103        #[corresponds(d2i_PKCS7)]
104        from_der,
105        Pkcs7,
106        ffi::d2i_PKCS7
107    }
108
109    /// Parses a message in S/MIME format.
110    ///
111    /// Returns the loaded signature, along with the cleartext message (if
112    /// available).
113    #[corresponds(SMIME_read_PKCS7)]
114    #[cfg(not(awslc))]
115    pub fn from_smime(input: &[u8]) -> Result<(Pkcs7, Option<Vec<u8>>), ErrorStack> {
116        ffi::init();
117
118        let input_bio = MemBioSlice::new(input)?;
119        let mut bcont_bio = ptr::null_mut();
120        unsafe {
121            let pkcs7 =
122                cvt_p(ffi::SMIME_read_PKCS7(input_bio.as_ptr(), &mut bcont_bio)).map(Pkcs7)?;
123            let out = if !bcont_bio.is_null() {
124                let bcont_bio = MemBio::from_ptr(bcont_bio);
125                Some(bcont_bio.get_buf().to_vec())
126            } else {
127                None
128            };
129            Ok((pkcs7, out))
130        }
131    }
132
133    /// Creates and returns a PKCS#7 `envelopedData` structure.
134    ///
135    /// `certs` is a list of recipient certificates. `input` is the content to be
136    /// encrypted. `cipher` is the symmetric cipher to use. `flags` is an optional
137    /// set of flags.
138    #[corresponds(PKCS7_encrypt)]
139    pub fn encrypt(
140        certs: &StackRef<X509>,
141        input: &[u8],
142        cipher: Cipher,
143        flags: Pkcs7Flags,
144    ) -> Result<Pkcs7, ErrorStack> {
145        let input_bio = MemBioSlice::new(input)?;
146
147        unsafe {
148            cvt_p(ffi::PKCS7_encrypt(
149                certs.as_ptr(),
150                input_bio.as_ptr(),
151                cipher.as_ptr(),
152                flags.bits(),
153            ))
154            .map(Pkcs7)
155        }
156    }
157
158    /// Creates and returns a PKCS#7 `signedData` structure.
159    ///
160    /// `signcert` is the certificate to sign with, `pkey` is the corresponding
161    /// private key. `certs` is an optional additional set of certificates to
162    /// include in the PKCS#7 structure (for example any intermediate CAs in the
163    /// chain).
164    #[corresponds(PKCS7_sign)]
165    pub fn sign<PT>(
166        signcert: &X509Ref,
167        pkey: &PKeyRef<PT>,
168        certs: &StackRef<X509>,
169        input: &[u8],
170        flags: Pkcs7Flags,
171    ) -> Result<Pkcs7, ErrorStack>
172    where
173        PT: HasPrivate,
174    {
175        let input_bio = MemBioSlice::new(input)?;
176        unsafe {
177            cvt_p(ffi::PKCS7_sign(
178                signcert.as_ptr(),
179                pkey.as_ptr(),
180                certs.as_ptr(),
181                input_bio.as_ptr(),
182                flags.bits(),
183            ))
184            .map(Pkcs7)
185        }
186    }
187}
188
189impl Pkcs7Ref {
190    /// Converts PKCS#7 structure to S/MIME format
191    #[corresponds(SMIME_write_PKCS7)]
192    #[cfg(not(awslc))]
193    pub fn to_smime(&self, input: &[u8], flags: Pkcs7Flags) -> Result<Vec<u8>, ErrorStack> {
194        let input_bio = MemBioSlice::new(input)?;
195        let output = MemBio::new()?;
196        unsafe {
197            cvt(ffi::SMIME_write_PKCS7(
198                output.as_ptr(),
199                self.as_ptr(),
200                input_bio.as_ptr(),
201                flags.bits(),
202            ))
203            .map(|_| output.get_buf().to_owned())
204        }
205    }
206
207    to_pem! {
208        /// Serializes the data into a PEM-encoded PKCS#7 structure.
209        ///
210        /// The output will have a header of `-----BEGIN PKCS7-----`.
211        #[corresponds(PEM_write_bio_PKCS7)]
212        to_pem,
213        ffi::PEM_write_bio_PKCS7
214    }
215
216    to_der! {
217        /// Serializes the data into a DER-encoded PKCS#7 structure.
218        #[corresponds(i2d_PKCS7)]
219        to_der,
220        ffi::i2d_PKCS7
221    }
222
223    /// Decrypts data using the provided private key.
224    ///
225    /// `pkey` is the recipient's private key, and `cert` is the recipient's
226    /// certificate.
227    ///
228    /// Returns the decrypted message.
229    #[corresponds(PKCS7_decrypt)]
230    pub fn decrypt<PT>(
231        &self,
232        pkey: &PKeyRef<PT>,
233        cert: &X509Ref,
234        flags: Pkcs7Flags,
235    ) -> Result<Vec<u8>, ErrorStack>
236    where
237        PT: HasPrivate,
238    {
239        let output = MemBio::new()?;
240
241        unsafe {
242            cvt(ffi::PKCS7_decrypt(
243                self.as_ptr(),
244                pkey.as_ptr(),
245                cert.as_ptr(),
246                output.as_ptr(),
247                flags.bits(),
248            ))
249            .map(|_| output.get_buf().to_owned())
250        }
251    }
252
253    /// Verifies the PKCS#7 `signedData` structure contained by `&self`.
254    ///
255    /// `certs` is a set of certificates in which to search for the signer's
256    /// certificate. `store` is a trusted certificate store (used for chain
257    /// verification). `indata` is the signed data if the content is not present
258    /// in `&self`. The content is written to `out` if it is not `None`.
259    #[corresponds(PKCS7_verify)]
260    pub fn verify(
261        &self,
262        certs: &StackRef<X509>,
263        store: &X509StoreRef,
264        indata: Option<&[u8]>,
265        out: Option<&mut Vec<u8>>,
266        flags: Pkcs7Flags,
267    ) -> Result<(), ErrorStack> {
268        let out_bio = MemBio::new()?;
269
270        let indata_bio = match indata {
271            Some(data) => Some(MemBioSlice::new(data)?),
272            None => None,
273        };
274        let indata_bio_ptr = indata_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr());
275
276        unsafe {
277            cvt(ffi::PKCS7_verify(
278                self.as_ptr(),
279                certs.as_ptr(),
280                store.as_ptr(),
281                indata_bio_ptr,
282                out_bio.as_ptr(),
283                flags.bits(),
284            ))
285            .map(|_| ())?
286        }
287
288        if let Some(data) = out {
289            data.clear();
290            data.extend_from_slice(out_bio.get_buf());
291        }
292
293        Ok(())
294    }
295
296    /// Retrieve the signer's certificates from the PKCS#7 structure without verifying them.
297    #[corresponds(PKCS7_get0_signers)]
298    pub fn signers(
299        &self,
300        certs: &StackRef<X509>,
301        flags: Pkcs7Flags,
302    ) -> Result<Stack<X509>, ErrorStack> {
303        unsafe {
304            let ptr = cvt_p(ffi::PKCS7_get0_signers(
305                self.as_ptr(),
306                certs.as_ptr(),
307                flags.bits(),
308            ))?;
309
310            // The returned stack is owned by the caller, but the certs inside are not! Our stack interface can't deal
311            // with that, so instead we just manually bump the refcount of the certs so that the whole stack is properly
312            // owned.
313            let stack = Stack::<X509>::from_ptr(ptr);
314            for cert in &stack {
315                mem::forget(cert.to_owned());
316            }
317
318            Ok(stack)
319        }
320    }
321
322    /// Return the type of a PKCS#7 structure as an Asn1Object
323    pub fn type_(&self) -> Option<&Asn1ObjectRef> {
324        unsafe {
325            let ptr = (*self.as_ptr()).type_;
326            Asn1ObjectRef::from_const_ptr_opt(ptr)
327        }
328    }
329
330    /// Get the signed data of a PKCS#7 structure of type PKCS7_SIGNED
331    #[cfg(not(awslc))]
332    pub fn signed(&self) -> Option<&Pkcs7SignedRef> {
333        unsafe {
334            if self.type_().map(|x| x.nid()) != Some(Nid::PKCS7_SIGNED) {
335                return None;
336            }
337            let signed_data = (*self.as_ptr()).d.sign;
338            Pkcs7SignedRef::from_const_ptr_opt(signed_data)
339        }
340    }
341}
342
343#[cfg(not(awslc))]
344impl Pkcs7SignedRef {
345    /// Get the stack of certificates from the PKCS7_SIGNED object
346    pub fn certificates(&self) -> Option<&StackRef<X509>> {
347        unsafe {
348            self.as_ptr()
349                .as_ref()
350                .and_then(|x| x.cert.as_mut())
351                .and_then(|x| StackRef::<X509>::from_const_ptr_opt(x))
352        }
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use crate::hash::MessageDigest;
359    use crate::nid::Nid;
360    use crate::pkcs7::{Pkcs7, Pkcs7Flags};
361    use crate::pkey::PKey;
362    use crate::stack::Stack;
363    use crate::symm::Cipher;
364    use crate::x509::store::X509StoreBuilder;
365    use crate::x509::X509;
366
367    #[test]
368    fn encrypt_decrypt_test() {
369        let cert = include_bytes!("../test/certs.pem");
370        let cert = X509::from_pem(cert).unwrap();
371        let mut certs = Stack::new().unwrap();
372        certs.push(cert.clone()).unwrap();
373        let message: String = String::from("foo");
374        let cipher = Cipher::des_ede3_cbc();
375        let flags = Pkcs7Flags::STREAM;
376        let pkey = include_bytes!("../test/key.pem");
377        let pkey = PKey::private_key_from_pem(pkey).unwrap();
378
379        let pkcs7 =
380            Pkcs7::encrypt(&certs, message.as_bytes(), cipher, flags).expect("should succeed");
381        assert_eq!(
382            pkcs7.type_().expect("PKCS7 should have a type").nid(),
383            Nid::PKCS7_ENVELOPED
384        );
385
386        #[cfg(not(awslc))]
387        let pkcs7_decoded = {
388            let encrypted = pkcs7
389                .to_smime(message.as_bytes(), flags)
390                .expect("should succeed");
391
392            Pkcs7::from_smime(encrypted.as_slice())
393                .expect("should succeed")
394                .0
395        };
396        // AWS-LC's SMIME functions are no-ops, so roundtrip through PEM instead.
397        #[cfg(awslc)]
398        let pkcs7_decoded = {
399            let encrypted = pkcs7.to_pem().expect("should succeed");
400            Pkcs7::from_pem(encrypted.as_slice()).expect("should succeed")
401        };
402
403        let decoded = pkcs7_decoded
404            .decrypt(&pkey, &cert, Pkcs7Flags::empty())
405            .expect("should succeed");
406
407        assert_eq!(decoded, message.into_bytes());
408    }
409
410    #[test]
411    fn sign_verify_test_detached() {
412        let cert = include_bytes!("../test/cert.pem");
413        let cert = X509::from_pem(cert).unwrap();
414        let certs = Stack::new().unwrap();
415        let message = "foo";
416        let flags = Pkcs7Flags::STREAM | Pkcs7Flags::DETACHED;
417        let pkey = include_bytes!("../test/key.pem");
418        let pkey = PKey::private_key_from_pem(pkey).unwrap();
419        let mut store_builder = X509StoreBuilder::new().expect("should succeed");
420
421        let root_ca = include_bytes!("../test/root-ca.pem");
422        let root_ca = X509::from_pem(root_ca).unwrap();
423        store_builder.add_cert(root_ca).expect("should succeed");
424
425        let store = store_builder.build();
426
427        let pkcs7 =
428            Pkcs7::sign(&cert, &pkey, &certs, message.as_bytes(), flags).expect("should succeed");
429        assert_eq!(
430            pkcs7.type_().expect("PKCS7 should have a type").nid(),
431            Nid::PKCS7_SIGNED
432        );
433
434        #[cfg(not(awslc))]
435        let (pkcs7_decoded, content) = {
436            let signed = pkcs7
437                .to_smime(message.as_bytes(), flags)
438                .expect("should succeed");
439            println!("{:?}", String::from_utf8(signed.clone()).unwrap());
440            Pkcs7::from_smime(signed.as_slice()).expect("should succeed")
441        };
442        // AWS-LC's SMIME functions are no-ops, so roundtrip through PEM instead.
443        #[cfg(awslc)]
444        let pkcs7_decoded = {
445            let signed = pkcs7.to_pem().expect("should succeed");
446            Pkcs7::from_pem(signed.as_slice()).expect("should succeed")
447        };
448
449        let mut output = Vec::new();
450        pkcs7_decoded
451            .verify(
452                &certs,
453                &store,
454                Some(message.as_bytes()),
455                Some(&mut output),
456                flags,
457            )
458            .expect("should succeed");
459
460        assert_eq!(output, message.as_bytes());
461        #[cfg(not(awslc))]
462        assert_eq!(content.expect("should be non-empty"), message.as_bytes());
463    }
464
465    /// https://marc.info/?l=openbsd-cvs&m=166602943014106&w=2
466    #[test]
467    #[cfg_attr(all(libressl360, not(libressl361)), ignore)]
468    fn sign_verify_test_normal() {
469        let cert = include_bytes!("../test/cert.pem");
470        let cert = X509::from_pem(cert).unwrap();
471        let certs = Stack::new().unwrap();
472        let message = "foo";
473        let flags = Pkcs7Flags::STREAM;
474        let pkey = include_bytes!("../test/key.pem");
475        let pkey = PKey::private_key_from_pem(pkey).unwrap();
476        let mut store_builder = X509StoreBuilder::new().expect("should succeed");
477
478        let root_ca = include_bytes!("../test/root-ca.pem");
479        let root_ca = X509::from_pem(root_ca).unwrap();
480        store_builder.add_cert(root_ca).expect("should succeed");
481
482        let store = store_builder.build();
483
484        let pkcs7 =
485            Pkcs7::sign(&cert, &pkey, &certs, message.as_bytes(), flags).expect("should succeed");
486        assert_eq!(
487            pkcs7.type_().expect("PKCS7 should have a type").nid(),
488            Nid::PKCS7_SIGNED
489        );
490
491        #[cfg(not(awslc))]
492        let (pkcs7_decoded, content) = {
493            let signed = pkcs7
494                .to_smime(message.as_bytes(), flags)
495                .expect("should succeed");
496
497            Pkcs7::from_smime(signed.as_slice()).expect("should succeed")
498        };
499        // AWS-LC's SMIME functions are no-ops, so roundtrip through PEM instead.
500        #[cfg(awslc)]
501        let pkcs7_decoded = {
502            let signed = pkcs7.to_pem().expect("should succeed");
503            Pkcs7::from_pem(signed.as_slice()).expect("should succeed")
504        };
505
506        let mut output = Vec::new();
507        pkcs7_decoded
508            .verify(&certs, &store, None, Some(&mut output), flags)
509            .expect("should succeed");
510
511        assert_eq!(output, message.as_bytes());
512        #[cfg(not(awslc))]
513        assert!(content.is_none());
514    }
515
516    /// https://marc.info/?l=openbsd-cvs&m=166602943014106&w=2
517    #[test]
518    #[cfg_attr(all(libressl360, not(libressl361)), ignore)]
519    fn signers() {
520        let cert = include_bytes!("../test/cert.pem");
521        let cert = X509::from_pem(cert).unwrap();
522        let cert_digest = cert.digest(MessageDigest::sha256()).unwrap();
523        let certs = Stack::new().unwrap();
524        let message = "foo";
525        let flags = Pkcs7Flags::STREAM;
526        let pkey = include_bytes!("../test/key.pem");
527        let pkey = PKey::private_key_from_pem(pkey).unwrap();
528        let mut store_builder = X509StoreBuilder::new().expect("should succeed");
529
530        let root_ca = include_bytes!("../test/root-ca.pem");
531        let root_ca = X509::from_pem(root_ca).unwrap();
532        store_builder.add_cert(root_ca).expect("should succeed");
533
534        let pkcs7 =
535            Pkcs7::sign(&cert, &pkey, &certs, message.as_bytes(), flags).expect("should succeed");
536        assert_eq!(
537            pkcs7.type_().expect("PKCS7 should have a type").nid(),
538            Nid::PKCS7_SIGNED
539        );
540
541        #[cfg(not(awslc))]
542        let pkcs7_decoded = {
543            let signed = pkcs7
544                .to_smime(message.as_bytes(), flags)
545                .expect("should succeed");
546
547            Pkcs7::from_smime(signed.as_slice())
548                .expect("should succeed")
549                .0
550        };
551        // AWS-LC's SMIME functions are no-ops, so roundtrip through PEM instead.
552        #[cfg(awslc)]
553        let pkcs7_decoded = {
554            let signed = pkcs7.to_pem().expect("should succeed");
555            Pkcs7::from_pem(signed.as_slice()).expect("should succeed")
556        };
557
558        let empty_certs = Stack::new().unwrap();
559        let signer_certs = pkcs7_decoded
560            .signers(&empty_certs, flags)
561            .expect("should succeed");
562        assert_eq!(empty_certs.len(), 0);
563        assert_eq!(signer_certs.len(), 1);
564        let signer_digest = signer_certs[0].digest(MessageDigest::sha256()).unwrap();
565        assert_eq!(*cert_digest, *signer_digest);
566    }
567
568    #[test]
569    #[cfg(not(awslc))]
570    fn invalid_from_smime() {
571        let input = String::from("Invalid SMIME Message");
572        let result = Pkcs7::from_smime(input.as_bytes());
573
574        assert!(result.is_err());
575    }
576
577    #[test]
578    #[cfg(not(awslc))]
579    fn signed_data_certificates() {
580        let cert = include_bytes!("../test/cert.pem");
581        let cert = X509::from_pem(cert).unwrap();
582        let mut extra_certs = Stack::<X509>::new().unwrap();
583        for cert in
584            X509::stack_from_pem(include_bytes!("../test/certs.pem")).expect("should succeed")
585        {
586            extra_certs.push(cert).expect("should succeed");
587        }
588
589        let message = "foo";
590        let flags = Pkcs7Flags::STREAM;
591        let pkey = include_bytes!("../test/key.pem");
592        let pkey = PKey::private_key_from_pem(pkey).unwrap();
593
594        let pkcs7 = Pkcs7::sign(&cert, &pkey, &extra_certs, message.as_bytes(), flags)
595            .expect("should succeed");
596        assert_eq!(
597            pkcs7.type_().expect("PKCS7 should have a type").nid(),
598            Nid::PKCS7_SIGNED
599        );
600        let signed_data_certs = pkcs7.signed().and_then(|x| x.certificates());
601        assert_eq!(signed_data_certs.expect("should succeed").len(), 3);
602    }
603
604    #[test]
605    #[cfg(not(awslc))]
606    fn signed_data_certificates_no_signed_data() {
607        let cert = include_bytes!("../test/certs.pem");
608        let cert = X509::from_pem(cert).unwrap();
609        let mut certs = Stack::new().unwrap();
610        certs.push(cert).unwrap();
611        let message: String = String::from("foo");
612        let cipher = Cipher::des_ede3_cbc();
613        let flags = Pkcs7Flags::STREAM;
614
615        // Use `Pkcs7::encrypt` since it populates the PKCS7_ENVELOPE struct rather than
616        // PKCS7_SIGNED
617        let pkcs7 =
618            Pkcs7::encrypt(&certs, message.as_bytes(), cipher, flags).expect("should succeed");
619        assert_eq!(
620            pkcs7.type_().expect("PKCS7 should have a type").nid(),
621            Nid::PKCS7_ENVELOPED
622        );
623
624        let signed_data_certs = pkcs7.signed().and_then(|x| x.certificates());
625        assert!(signed_data_certs.is_none())
626    }
627}