1 //! SMIME implementation using CMS 2 //! 3 //! CMS (PKCS#7) is an encyption 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 8 use bitflags::bitflags; 9 use foreign_types::{ForeignType, ForeignTypeRef}; 10 use libc::c_uint; 11 use std::ptr; 12 13 use crate::bio::{MemBio, MemBioSlice}; 14 use crate::error::ErrorStack; 15 use crate::pkey::{HasPrivate, PKeyRef}; 16 use crate::stack::StackRef; 17 use crate::symm::Cipher; 18 use crate::x509::{X509Ref, X509}; 19 use crate::{cvt, cvt_p}; 20 21 bitflags! { 22 pub struct CMSOptions : c_uint { 23 const TEXT = ffi::CMS_TEXT; 24 const CMS_NOCERTS = ffi::CMS_NOCERTS; 25 const NO_CONTENT_VERIFY = ffi::CMS_NO_CONTENT_VERIFY; 26 const NO_ATTR_VERIFY = ffi::CMS_NO_ATTR_VERIFY; 27 const NOSIGS = ffi::CMS_NOSIGS; 28 const NOINTERN = ffi::CMS_NOINTERN; 29 const NO_SIGNER_CERT_VERIFY = ffi::CMS_NO_SIGNER_CERT_VERIFY; 30 const NOVERIFY = ffi::CMS_NOVERIFY; 31 const DETACHED = ffi::CMS_DETACHED; 32 const BINARY = ffi::CMS_BINARY; 33 const NOATTR = ffi::CMS_NOATTR; 34 const NOSMIMECAP = ffi::CMS_NOSMIMECAP; 35 const NOOLDMIMETYPE = ffi::CMS_NOOLDMIMETYPE; 36 const CRLFEOL = ffi::CMS_CRLFEOL; 37 const STREAM = ffi::CMS_STREAM; 38 const NOCRL = ffi::CMS_NOCRL; 39 const PARTIAL = ffi::CMS_PARTIAL; 40 const REUSE_DIGEST = ffi::CMS_REUSE_DIGEST; 41 const USE_KEYID = ffi::CMS_USE_KEYID; 42 const DEBUG_DECRYPT = ffi::CMS_DEBUG_DECRYPT; 43 #[cfg(all(not(libressl), not(ossl101)))] 44 const KEY_PARAM = ffi::CMS_KEY_PARAM; 45 #[cfg(all(not(libressl), not(ossl101), not(ossl102)))] 46 const ASCIICRLF = ffi::CMS_ASCIICRLF; 47 } 48 } 49 50 foreign_type_and_impl_send_sync! { 51 type CType = ffi::CMS_ContentInfo; 52 fn drop = ffi::CMS_ContentInfo_free; 53 54 /// High level CMS wrapper 55 /// 56 /// CMS supports nesting various types of data, including signatures, certificates, 57 /// encrypted data, smime messages (encrypted email), and data digest. The ContentInfo 58 /// content type is the encapsulation of all those content types. [`RFC 5652`] describes 59 /// CMS and OpenSSL follows this RFC's implmentation. 60 /// 61 /// [`RFC 5652`]: https://tools.ietf.org/html/rfc5652#page-6 62 pub struct CmsContentInfo; 63 /// Reference to [`CMSContentInfo`] 64 /// 65 /// [`CMSContentInfo`]:struct.CmsContentInfo.html 66 pub struct CmsContentInfoRef; 67 } 68 69 impl CmsContentInfoRef { 70 /// Given the sender's private key, `pkey` and the recipient's certificiate, `cert`, 71 /// decrypt the data in `self`. 72 /// 73 /// OpenSSL documentation at [`CMS_decrypt`] 74 /// 75 /// [`CMS_decrypt`]: https://www.openssl.org/docs/man1.1.0/crypto/CMS_decrypt.html decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack> where T: HasPrivate,76 pub fn decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack> 77 where 78 T: HasPrivate, 79 { 80 unsafe { 81 let pkey = pkey.as_ptr(); 82 let cert = cert.as_ptr(); 83 let out = MemBio::new()?; 84 85 cvt(ffi::CMS_decrypt( 86 self.as_ptr(), 87 pkey, 88 cert, 89 ptr::null_mut(), 90 out.as_ptr(), 91 0, 92 ))?; 93 94 Ok(out.get_buf().to_owned()) 95 } 96 } 97 98 /// Given the sender's private key, `pkey`, 99 /// decrypt the data in `self` without validating the recipient certificate. 100 /// 101 /// *Warning*: Not checking the recipient certificate may leave you vulnerable to Bleichenbacher's attack on PKCS#1 v1.5 RSA padding. 102 /// See [`CMS_decrypt`] for more information. 103 /// 104 /// [`CMS_decrypt`]: https://www.openssl.org/docs/man1.1.0/crypto/CMS_decrypt.html 105 // FIXME merge into decrypt decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack> where T: HasPrivate,106 pub fn decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack> 107 where 108 T: HasPrivate, 109 { 110 unsafe { 111 let pkey = pkey.as_ptr(); 112 let out = MemBio::new()?; 113 114 cvt(ffi::CMS_decrypt( 115 self.as_ptr(), 116 pkey, 117 ptr::null_mut(), 118 ptr::null_mut(), 119 out.as_ptr(), 120 0, 121 ))?; 122 123 Ok(out.get_buf().to_owned()) 124 } 125 } 126 127 to_der! { 128 /// Serializes this CmsContentInfo using DER. 129 /// 130 /// OpenSSL documentation at [`i2d_CMS_ContentInfo`] 131 /// 132 /// [`i2d_CMS_ContentInfo`]: https://www.openssl.org/docs/man1.0.2/crypto/i2d_CMS_ContentInfo.html 133 to_der, 134 ffi::i2d_CMS_ContentInfo 135 } 136 137 to_pem! { 138 /// Serializes this CmsContentInfo using DER. 139 /// 140 /// OpenSSL documentation at [`PEM_write_bio_CMS`] 141 /// 142 /// [`PEM_write_bio_CMS`]: https://www.openssl.org/docs/man1.1.0/man3/PEM_write_bio_CMS.html 143 to_pem, 144 ffi::PEM_write_bio_CMS 145 } 146 } 147 148 impl CmsContentInfo { 149 /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`. 150 /// 151 /// OpenSSL documentation at [`SMIME_read_CMS`] 152 /// 153 /// [`SMIME_read_CMS`]: https://www.openssl.org/docs/man1.0.2/crypto/SMIME_read_CMS.html smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack>154 pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> { 155 unsafe { 156 let bio = MemBioSlice::new(smime)?; 157 158 let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?; 159 160 Ok(CmsContentInfo::from_ptr(cms)) 161 } 162 } 163 164 from_der! { 165 /// Deserializes a DER-encoded ContentInfo structure. 166 /// 167 /// This corresponds to [`d2i_CMS_ContentInfo`]. 168 /// 169 /// [`d2i_CMS_ContentInfo`]: https://www.openssl.org/docs/manmaster/man3/d2i_X509.html 170 from_der, 171 CmsContentInfo, 172 ffi::d2i_CMS_ContentInfo 173 } 174 175 from_pem! { 176 /// Deserializes a PEM-encoded ContentInfo structure. 177 /// 178 /// This corresponds to [`PEM_read_bio_CMS`]. 179 /// 180 /// [`PEM_read_bio_CMS`]: https://www.openssl.org/docs/man1.1.0/man3/PEM_read_bio_CMS.html 181 from_pem, 182 CmsContentInfo, 183 ffi::PEM_read_bio_CMS 184 } 185 186 /// Given a signing cert `signcert`, private key `pkey`, a certificate stack `certs`, 187 /// data `data` and flags `flags`, create a CmsContentInfo struct. 188 /// 189 /// All arguments are optional. 190 /// 191 /// OpenSSL documentation at [`CMS_sign`] 192 /// 193 /// [`CMS_sign`]: https://www.openssl.org/docs/manmaster/man3/CMS_sign.html sign<T>( signcert: Option<&X509Ref>, pkey: Option<&PKeyRef<T>>, certs: Option<&StackRef<X509>>, data: Option<&[u8]>, flags: CMSOptions, ) -> Result<CmsContentInfo, ErrorStack> where T: HasPrivate,194 pub fn sign<T>( 195 signcert: Option<&X509Ref>, 196 pkey: Option<&PKeyRef<T>>, 197 certs: Option<&StackRef<X509>>, 198 data: Option<&[u8]>, 199 flags: CMSOptions, 200 ) -> Result<CmsContentInfo, ErrorStack> 201 where 202 T: HasPrivate, 203 { 204 unsafe { 205 let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr()); 206 let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr()); 207 let data_bio = match data { 208 Some(data) => Some(MemBioSlice::new(data)?), 209 None => None, 210 }; 211 let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr()); 212 let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr()); 213 214 let cms = cvt_p(ffi::CMS_sign( 215 signcert, 216 pkey, 217 certs, 218 data_bio_ptr, 219 flags.bits(), 220 ))?; 221 222 Ok(CmsContentInfo::from_ptr(cms)) 223 } 224 } 225 226 /// Given a certificate stack `certs`, data `data`, cipher `cipher` and flags `flags`, 227 /// create a CmsContentInfo struct. 228 /// 229 /// OpenSSL documentation at [`CMS_encrypt`] 230 /// 231 /// [`CMS_encrypt`]: https://www.openssl.org/docs/manmaster/man3/CMS_encrypt.html encrypt( certs: &StackRef<X509>, data: &[u8], cipher: Cipher, flags: CMSOptions, ) -> Result<CmsContentInfo, ErrorStack>232 pub fn encrypt( 233 certs: &StackRef<X509>, 234 data: &[u8], 235 cipher: Cipher, 236 flags: CMSOptions, 237 ) -> Result<CmsContentInfo, ErrorStack> { 238 unsafe { 239 let data_bio = MemBioSlice::new(data)?; 240 241 let cms = cvt_p(ffi::CMS_encrypt( 242 certs.as_ptr(), 243 data_bio.as_ptr(), 244 cipher.as_ptr(), 245 flags.bits(), 246 ))?; 247 248 Ok(CmsContentInfo::from_ptr(cms)) 249 } 250 } 251 } 252 253 #[cfg(test)] 254 mod test { 255 use super::*; 256 use crate::pkcs12::Pkcs12; 257 use crate::stack::Stack; 258 use crate::x509::X509; 259 260 #[test] 261 #[cfg_attr(ossl300, ignore)] // 3.0.0 can't load RC2-40-CBC cms_encrypt_decrypt()262 fn cms_encrypt_decrypt() { 263 // load cert with public key only 264 let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der"); 265 let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert"); 266 267 // load cert with private key 268 let priv_cert_bytes = include_bytes!("../test/cms.p12"); 269 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert"); 270 let priv_cert = priv_cert 271 .parse("mypass") 272 .expect("failed to parse priv cert"); 273 274 // encrypt cms message using public key cert 275 let input = String::from("My Message"); 276 let mut cert_stack = Stack::new().expect("failed to create stack"); 277 cert_stack 278 .push(pub_cert) 279 .expect("failed to add pub cert to stack"); 280 281 let encrypt = CmsContentInfo::encrypt( 282 &cert_stack, 283 &input.as_bytes(), 284 Cipher::des_ede3_cbc(), 285 CMSOptions::empty(), 286 ) 287 .expect("failed create encrypted cms"); 288 289 // decrypt cms message using private key cert (DER) 290 { 291 let encrypted_der = encrypt.to_der().expect("failed to create der from cms"); 292 let decrypt = 293 CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der"); 294 295 let decrypt_with_cert_check = decrypt 296 .decrypt(&priv_cert.pkey, &priv_cert.cert) 297 .expect("failed to decrypt cms"); 298 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check) 299 .expect("failed to create string from cms content"); 300 301 let decrypt_without_cert_check = decrypt 302 .decrypt_without_cert_check(&priv_cert.pkey) 303 .expect("failed to decrypt cms"); 304 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check) 305 .expect("failed to create string from cms content"); 306 307 assert_eq!(input, decrypt_with_cert_check); 308 assert_eq!(input, decrypt_without_cert_check); 309 } 310 311 // decrypt cms message using private key cert (PEM) 312 { 313 let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms"); 314 let decrypt = 315 CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem"); 316 317 let decrypt_with_cert_check = decrypt 318 .decrypt(&priv_cert.pkey, &priv_cert.cert) 319 .expect("failed to decrypt cms"); 320 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check) 321 .expect("failed to create string from cms content"); 322 323 let decrypt_without_cert_check = decrypt 324 .decrypt_without_cert_check(&priv_cert.pkey) 325 .expect("failed to decrypt cms"); 326 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check) 327 .expect("failed to create string from cms content"); 328 329 assert_eq!(input, decrypt_with_cert_check); 330 assert_eq!(input, decrypt_without_cert_check); 331 } 332 } 333 } 334