summaryrefslogtreecommitdiffstats
path: root/vendor/openssl/src/cms.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/openssl/src/cms.rs')
-rw-r--r--vendor/openssl/src/cms.rs488
1 files changed, 488 insertions, 0 deletions
diff --git a/vendor/openssl/src/cms.rs b/vendor/openssl/src/cms.rs
new file mode 100644
index 0000000..d11443b
--- /dev/null
+++ b/vendor/openssl/src/cms.rs
@@ -0,0 +1,488 @@
+//! SMIME implementation using CMS
+//!
+//! CMS (PKCS#7) is an encryption standard. It allows signing and encrypting data using
+//! X.509 certificates. The OpenSSL implementation of CMS is used in email encryption
+//! generated from a `Vec` of bytes. This `Vec` follows the smime protocol standards.
+//! Data accepted by this module will be smime type `enveloped-data`.
+
+use bitflags::bitflags;
+use foreign_types::{ForeignType, ForeignTypeRef};
+use libc::c_uint;
+use std::ptr;
+
+use crate::bio::{MemBio, MemBioSlice};
+use crate::error::ErrorStack;
+use crate::pkey::{HasPrivate, PKeyRef};
+use crate::stack::StackRef;
+use crate::symm::Cipher;
+use crate::x509::{store::X509StoreRef, X509Ref, X509};
+use crate::{cvt, cvt_p};
+use openssl_macros::corresponds;
+
+bitflags! {
+ #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+ #[repr(transparent)]
+ pub struct CMSOptions : c_uint {
+ const TEXT = ffi::CMS_TEXT;
+ const CMS_NOCERTS = ffi::CMS_NOCERTS;
+ const NO_CONTENT_VERIFY = ffi::CMS_NO_CONTENT_VERIFY;
+ const NO_ATTR_VERIFY = ffi::CMS_NO_ATTR_VERIFY;
+ const NOSIGS = ffi::CMS_NOSIGS;
+ const NOINTERN = ffi::CMS_NOINTERN;
+ const NO_SIGNER_CERT_VERIFY = ffi::CMS_NO_SIGNER_CERT_VERIFY;
+ const NOVERIFY = ffi::CMS_NOVERIFY;
+ const DETACHED = ffi::CMS_DETACHED;
+ const BINARY = ffi::CMS_BINARY;
+ const NOATTR = ffi::CMS_NOATTR;
+ const NOSMIMECAP = ffi::CMS_NOSMIMECAP;
+ const NOOLDMIMETYPE = ffi::CMS_NOOLDMIMETYPE;
+ const CRLFEOL = ffi::CMS_CRLFEOL;
+ const STREAM = ffi::CMS_STREAM;
+ const NOCRL = ffi::CMS_NOCRL;
+ const PARTIAL = ffi::CMS_PARTIAL;
+ const REUSE_DIGEST = ffi::CMS_REUSE_DIGEST;
+ const USE_KEYID = ffi::CMS_USE_KEYID;
+ const DEBUG_DECRYPT = ffi::CMS_DEBUG_DECRYPT;
+ #[cfg(all(not(libressl), not(ossl101)))]
+ const KEY_PARAM = ffi::CMS_KEY_PARAM;
+ #[cfg(all(not(libressl), not(ossl101), not(ossl102)))]
+ const ASCIICRLF = ffi::CMS_ASCIICRLF;
+ }
+}
+
+foreign_type_and_impl_send_sync! {
+ type CType = ffi::CMS_ContentInfo;
+ fn drop = ffi::CMS_ContentInfo_free;
+
+ /// High level CMS wrapper
+ ///
+ /// CMS supports nesting various types of data, including signatures, certificates,
+ /// encrypted data, smime messages (encrypted email), and data digest. The ContentInfo
+ /// content type is the encapsulation of all those content types. [`RFC 5652`] describes
+ /// CMS and OpenSSL follows this RFC's implementation.
+ ///
+ /// [`RFC 5652`]: https://tools.ietf.org/html/rfc5652#page-6
+ pub struct CmsContentInfo;
+ /// Reference to [`CMSContentInfo`]
+ ///
+ /// [`CMSContentInfo`]:struct.CmsContentInfo.html
+ pub struct CmsContentInfoRef;
+}
+
+impl CmsContentInfoRef {
+ /// Given the sender's private key, `pkey` and the recipient's certificate, `cert`,
+ /// decrypt the data in `self`.
+ #[corresponds(CMS_decrypt)]
+ pub fn decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack>
+ where
+ T: HasPrivate,
+ {
+ unsafe {
+ let pkey = pkey.as_ptr();
+ let cert = cert.as_ptr();
+ let out = MemBio::new()?;
+
+ cvt(ffi::CMS_decrypt(
+ self.as_ptr(),
+ pkey,
+ cert,
+ ptr::null_mut(),
+ out.as_ptr(),
+ 0,
+ ))?;
+
+ Ok(out.get_buf().to_owned())
+ }
+ }
+
+ /// Given the sender's private key, `pkey`,
+ /// decrypt the data in `self` without validating the recipient certificate.
+ ///
+ /// *Warning*: Not checking the recipient certificate may leave you vulnerable to Bleichenbacher's attack on PKCS#1 v1.5 RSA padding.
+ #[corresponds(CMS_decrypt)]
+ // FIXME merge into decrypt
+ pub fn decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack>
+ where
+ T: HasPrivate,
+ {
+ unsafe {
+ let pkey = pkey.as_ptr();
+ let out = MemBio::new()?;
+
+ cvt(ffi::CMS_decrypt(
+ self.as_ptr(),
+ pkey,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ out.as_ptr(),
+ 0,
+ ))?;
+
+ Ok(out.get_buf().to_owned())
+ }
+ }
+
+ to_der! {
+ /// Serializes this CmsContentInfo using DER.
+ #[corresponds(i2d_CMS_ContentInfo)]
+ to_der,
+ ffi::i2d_CMS_ContentInfo
+ }
+
+ to_pem! {
+ /// Serializes this CmsContentInfo using DER.
+ #[corresponds(PEM_write_bio_CMS)]
+ to_pem,
+ ffi::PEM_write_bio_CMS
+ }
+}
+
+impl CmsContentInfo {
+ /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`.
+ #[corresponds(SMIME_read_CMS)]
+ pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> {
+ unsafe {
+ let bio = MemBioSlice::new(smime)?;
+
+ let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?;
+
+ Ok(CmsContentInfo::from_ptr(cms))
+ }
+ }
+
+ from_der! {
+ /// Deserializes a DER-encoded ContentInfo structure.
+ #[corresponds(d2i_CMS_ContentInfo)]
+ from_der,
+ CmsContentInfo,
+ ffi::d2i_CMS_ContentInfo
+ }
+
+ from_pem! {
+ /// Deserializes a PEM-encoded ContentInfo structure.
+ #[corresponds(PEM_read_bio_CMS)]
+ from_pem,
+ CmsContentInfo,
+ ffi::PEM_read_bio_CMS
+ }
+
+ /// Given a signing cert `signcert`, private key `pkey`, a certificate stack `certs`,
+ /// data `data` and flags `flags`, create a CmsContentInfo struct.
+ ///
+ /// All arguments are optional.
+ #[corresponds(CMS_sign)]
+ pub fn sign<T>(
+ signcert: Option<&X509Ref>,
+ pkey: Option<&PKeyRef<T>>,
+ certs: Option<&StackRef<X509>>,
+ data: Option<&[u8]>,
+ flags: CMSOptions,
+ ) -> Result<CmsContentInfo, ErrorStack>
+ where
+ T: HasPrivate,
+ {
+ unsafe {
+ let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr());
+ let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr());
+ let data_bio = match data {
+ Some(data) => Some(MemBioSlice::new(data)?),
+ None => None,
+ };
+ let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr());
+ let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
+
+ let cms = cvt_p(ffi::CMS_sign(
+ signcert,
+ pkey,
+ certs,
+ data_bio_ptr,
+ flags.bits(),
+ ))?;
+
+ Ok(CmsContentInfo::from_ptr(cms))
+ }
+ }
+
+ /// Given a certificate stack `certs`, data `data`, cipher `cipher` and flags `flags`,
+ /// create a CmsContentInfo struct.
+ ///
+ /// OpenSSL documentation at [`CMS_encrypt`]
+ ///
+ /// [`CMS_encrypt`]: https://www.openssl.org/docs/manmaster/man3/CMS_encrypt.html
+ #[corresponds(CMS_encrypt)]
+ pub fn encrypt(
+ certs: &StackRef<X509>,
+ data: &[u8],
+ cipher: Cipher,
+ flags: CMSOptions,
+ ) -> Result<CmsContentInfo, ErrorStack> {
+ unsafe {
+ let data_bio = MemBioSlice::new(data)?;
+
+ let cms = cvt_p(ffi::CMS_encrypt(
+ certs.as_ptr(),
+ data_bio.as_ptr(),
+ cipher.as_ptr(),
+ flags.bits(),
+ ))?;
+
+ Ok(CmsContentInfo::from_ptr(cms))
+ }
+ }
+
+ /// Verify this CmsContentInfo's signature,
+ /// This will search the 'certs' list for the signing certificate.
+ /// Additional certificates, needed for building the certificate chain, may be
+ /// given in 'store' as well as additional CRLs.
+ /// A detached signature may be passed in `detached_data`. The signed content
+ /// without signature, will be copied into output_data if it is present.
+ ///
+ #[corresponds(CMS_verify)]
+ pub fn verify(
+ &mut self,
+ certs: Option<&StackRef<X509>>,
+ store: Option<&X509StoreRef>,
+ detached_data: Option<&[u8]>,
+ output_data: Option<&mut Vec<u8>>,
+ flags: CMSOptions,
+ ) -> Result<(), ErrorStack> {
+ unsafe {
+ let certs_ptr = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
+ let store_ptr = store.map_or(ptr::null_mut(), |p| p.as_ptr());
+ let detached_data_bio = match detached_data {
+ Some(data) => Some(MemBioSlice::new(data)?),
+ None => None,
+ };
+ let detached_data_bio_ptr = detached_data_bio
+ .as_ref()
+ .map_or(ptr::null_mut(), |p| p.as_ptr());
+ let out_bio = MemBio::new()?;
+
+ cvt(ffi::CMS_verify(
+ self.as_ptr(),
+ certs_ptr,
+ store_ptr,
+ detached_data_bio_ptr,
+ out_bio.as_ptr(),
+ flags.bits(),
+ ))?;
+
+ if let Some(data) = output_data {
+ data.clear();
+ data.extend_from_slice(out_bio.get_buf());
+ };
+
+ Ok(())
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use crate::pkcs12::Pkcs12;
+ use crate::pkey::PKey;
+ use crate::stack::Stack;
+ use crate::x509::{
+ store::{X509Store, X509StoreBuilder},
+ X509,
+ };
+
+ #[test]
+ fn cms_encrypt_decrypt() {
+ #[cfg(ossl300)]
+ let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
+
+ // load cert with public key only
+ let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der");
+ let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert");
+
+ // load cert with private key
+ let priv_cert_bytes = include_bytes!("../test/cms.p12");
+ let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
+ let priv_cert = priv_cert
+ .parse2("mypass")
+ .expect("failed to parse priv cert");
+
+ // encrypt cms message using public key cert
+ let input = String::from("My Message");
+ let mut cert_stack = Stack::new().expect("failed to create stack");
+ cert_stack
+ .push(pub_cert)
+ .expect("failed to add pub cert to stack");
+
+ let encrypt = CmsContentInfo::encrypt(
+ &cert_stack,
+ input.as_bytes(),
+ Cipher::des_ede3_cbc(),
+ CMSOptions::empty(),
+ )
+ .expect("failed create encrypted cms");
+
+ // decrypt cms message using private key cert (DER)
+ {
+ let encrypted_der = encrypt.to_der().expect("failed to create der from cms");
+ let decrypt =
+ CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der");
+
+ let decrypt_with_cert_check = decrypt
+ .decrypt(
+ priv_cert.pkey.as_ref().unwrap(),
+ priv_cert.cert.as_ref().unwrap(),
+ )
+ .expect("failed to decrypt cms");
+ let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
+ .expect("failed to create string from cms content");
+
+ let decrypt_without_cert_check = decrypt
+ .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
+ .expect("failed to decrypt cms");
+ let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
+ .expect("failed to create string from cms content");
+
+ assert_eq!(input, decrypt_with_cert_check);
+ assert_eq!(input, decrypt_without_cert_check);
+ }
+
+ // decrypt cms message using private key cert (PEM)
+ {
+ let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms");
+ let decrypt =
+ CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem");
+
+ let decrypt_with_cert_check = decrypt
+ .decrypt(
+ priv_cert.pkey.as_ref().unwrap(),
+ priv_cert.cert.as_ref().unwrap(),
+ )
+ .expect("failed to decrypt cms");
+ let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
+ .expect("failed to create string from cms content");
+
+ let decrypt_without_cert_check = decrypt
+ .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
+ .expect("failed to decrypt cms");
+ let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
+ .expect("failed to create string from cms content");
+
+ assert_eq!(input, decrypt_with_cert_check);
+ assert_eq!(input, decrypt_without_cert_check);
+ }
+ }
+
+ fn cms_sign_verify_generic_helper(is_detached: bool) {
+ // load cert with private key
+ let cert_bytes = include_bytes!("../test/cert.pem");
+ let cert = X509::from_pem(cert_bytes).expect("failed to load cert.pem");
+
+ let key_bytes = include_bytes!("../test/key.pem");
+ let key = PKey::private_key_from_pem(key_bytes).expect("failed to load key.pem");
+
+ let root_bytes = include_bytes!("../test/root-ca.pem");
+ let root = X509::from_pem(root_bytes).expect("failed to load root-ca.pem");
+
+ // sign cms message using public key cert
+ let data = b"Hello world!";
+
+ let (opt, ext_data): (CMSOptions, Option<&[u8]>) = if is_detached {
+ (CMSOptions::DETACHED | CMSOptions::BINARY, Some(data))
+ } else {
+ (CMSOptions::empty(), None)
+ };
+
+ let mut cms = CmsContentInfo::sign(Some(&cert), Some(&key), None, Some(data), opt)
+ .expect("failed to CMS sign a message");
+
+ // check CMS signature length
+ let pem_cms = cms
+ .to_pem()
+ .expect("failed to pack CmsContentInfo into PEM");
+ assert!(!pem_cms.is_empty());
+
+ // verify CMS signature
+ let mut builder = X509StoreBuilder::new().expect("failed to create X509StoreBuilder");
+ builder
+ .add_cert(root)
+ .expect("failed to add root-ca into X509StoreBuilder");
+ let store: X509Store = builder.build();
+ let mut out_data: Vec<u8> = Vec::new();
+ let res = cms.verify(
+ None,
+ Some(&store),
+ ext_data,
+ Some(&mut out_data),
+ CMSOptions::empty(),
+ );
+
+ // check verification result - valid signature
+ res.unwrap();
+ assert_eq!(data.to_vec(), out_data);
+ }
+
+ #[test]
+ fn cms_sign_verify_ok() {
+ cms_sign_verify_generic_helper(false);
+ }
+
+ #[test]
+ fn cms_sign_verify_detached_ok() {
+ cms_sign_verify_generic_helper(true);
+ }
+
+ #[test]
+ fn cms_sign_verify_error() {
+ #[cfg(ossl300)]
+ let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
+
+ // load cert with private key
+ let priv_cert_bytes = include_bytes!("../test/cms.p12");
+ let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
+ let priv_cert = priv_cert
+ .parse2("mypass")
+ .expect("failed to parse priv cert");
+
+ // sign cms message using public key cert
+ let data = b"Hello world!";
+ let mut cms = CmsContentInfo::sign(
+ Some(&priv_cert.cert.unwrap()),
+ Some(&priv_cert.pkey.unwrap()),
+ None,
+ Some(data),
+ CMSOptions::empty(),
+ )
+ .expect("failed to CMS sign a message");
+
+ // check CMS signature length
+ let pem_cms = cms
+ .to_pem()
+ .expect("failed to pack CmsContentInfo into PEM");
+ assert!(!pem_cms.is_empty());
+
+ let empty_store = X509StoreBuilder::new()
+ .expect("failed to create X509StoreBuilder")
+ .build();
+
+ // verify CMS signature
+ let res = cms.verify(
+ None,
+ Some(&empty_store),
+ Some(data),
+ None,
+ CMSOptions::empty(),
+ );
+
+ // check verification result - this is an invalid signature
+ // defined in openssl crypto/cms/cms.h
+ const CMS_R_CERTIFICATE_VERIFY_ERROR: i32 = 100;
+ match res {
+ Err(es) => {
+ let error_array = es.errors();
+ assert_eq!(1, error_array.len());
+ let code = error_array[0].code();
+ assert_eq!(ffi::ERR_GET_REASON(code), CMS_R_CERTIFICATE_VERIFY_ERROR);
+ }
+ _ => panic!("expected CMS verification error, got Ok()"),
+ }
+ }
+}