1 //! SMIME implementation using CMS 2 //! 3 //! CMS (PKCS#7) is an encyption standard. It allows signing and ecrypting 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 ffi; 9 use foreign_types::{ForeignType, ForeignTypeRef}; 10 use std::ptr; 11 12 use bio::{MemBio, MemBioSlice}; 13 use error::ErrorStack; 14 use libc::c_uint; 15 use pkey::{HasPrivate, PKeyRef}; 16 use stack::StackRef; 17 use symm::Cipher; 18 use x509::{X509Ref, X509}; 19 use {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 to_der! { 99 /// Serializes this CmsContentInfo using DER. 100 /// 101 /// OpenSSL documentation at [`i2d_CMS_ContentInfo`] 102 /// 103 /// [`i2d_CMS_ContentInfo`]: https://www.openssl.org/docs/man1.0.2/crypto/i2d_CMS_ContentInfo.html 104 to_der, 105 ffi::i2d_CMS_ContentInfo 106 } 107 108 to_pem! { 109 /// Serializes this CmsContentInfo using DER. 110 /// 111 /// OpenSSL documentation at [`PEM_write_bio_CMS`] 112 /// 113 /// [`PEM_write_bio_CMS`]: https://www.openssl.org/docs/man1.1.0/man3/PEM_write_bio_CMS.html 114 to_pem, 115 ffi::PEM_write_bio_CMS 116 } 117 } 118 119 impl CmsContentInfo { 120 /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`. 121 /// 122 /// OpenSSL documentation at [`SMIME_read_CMS`] 123 /// 124 /// [`SMIME_read_CMS`]: https://www.openssl.org/docs/man1.0.2/crypto/SMIME_read_CMS.html smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack>125 pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> { 126 unsafe { 127 let bio = MemBioSlice::new(smime)?; 128 129 let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?; 130 131 Ok(CmsContentInfo::from_ptr(cms)) 132 } 133 } 134 135 from_der! { 136 /// Deserializes a DER-encoded ContentInfo structure. 137 /// 138 /// This corresponds to [`d2i_CMS_ContentInfo`]. 139 /// 140 /// [`d2i_CMS_ContentInfo`]: https://www.openssl.org/docs/manmaster/man3/d2i_X509.html 141 from_der, 142 CmsContentInfo, 143 ffi::d2i_CMS_ContentInfo 144 } 145 146 from_pem! { 147 /// Deserializes a PEM-encoded ContentInfo structure. 148 /// 149 /// This corresponds to [`PEM_read_bio_CMS`]. 150 /// 151 /// [`PEM_read_bio_CMS`]: https://www.openssl.org/docs/man1.1.0/man3/PEM_read_bio_CMS.html 152 from_pem, 153 CmsContentInfo, 154 ffi::PEM_read_bio_CMS 155 } 156 157 /// Given a signing cert `signcert`, private key `pkey`, a certificate stack `certs`, 158 /// data `data` and flags `flags`, create a CmsContentInfo struct. 159 /// 160 /// All arguments are optional. 161 /// 162 /// OpenSSL documentation at [`CMS_sign`] 163 /// 164 /// [`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,165 pub fn sign<T>( 166 signcert: Option<&X509Ref>, 167 pkey: Option<&PKeyRef<T>>, 168 certs: Option<&StackRef<X509>>, 169 data: Option<&[u8]>, 170 flags: CMSOptions, 171 ) -> Result<CmsContentInfo, ErrorStack> 172 where 173 T: HasPrivate, 174 { 175 unsafe { 176 let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr()); 177 let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr()); 178 let data_bio = match data { 179 Some(data) => Some(MemBioSlice::new(data)?), 180 None => None, 181 }; 182 let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr()); 183 let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr()); 184 185 let cms = cvt_p(ffi::CMS_sign( 186 signcert, 187 pkey, 188 certs, 189 data_bio_ptr, 190 flags.bits(), 191 ))?; 192 193 Ok(CmsContentInfo::from_ptr(cms)) 194 } 195 } 196 197 /// Given a certificate stack `certs`, data `data`, cipher `cipher` and flags `flags`, 198 /// create a CmsContentInfo struct. 199 /// 200 /// OpenSSL documentation at [`CMS_encrypt`] 201 /// 202 /// [`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>203 pub fn encrypt( 204 certs: &StackRef<X509>, 205 data: &[u8], 206 cipher: Cipher, 207 flags: CMSOptions, 208 ) -> Result<CmsContentInfo, ErrorStack> { 209 unsafe { 210 let data_bio = MemBioSlice::new(data)?; 211 212 let cms = cvt_p(ffi::CMS_encrypt( 213 certs.as_ptr(), 214 data_bio.as_ptr(), 215 cipher.as_ptr(), 216 flags.bits(), 217 ))?; 218 219 Ok(CmsContentInfo::from_ptr(cms)) 220 } 221 } 222 } 223 224 #[cfg(test)] 225 mod test { 226 use super::*; 227 use pkcs12::Pkcs12; 228 use stack::Stack; 229 use x509::X509; 230 231 #[test] cms_encrypt_decrypt()232 fn cms_encrypt_decrypt() { 233 // load cert with public key only 234 let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der"); 235 let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert"); 236 237 // load cert with private key 238 let priv_cert_bytes = include_bytes!("../test/cms.p12"); 239 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert"); 240 let priv_cert = priv_cert 241 .parse("mypass") 242 .expect("failed to parse priv cert"); 243 244 // encrypt cms message using public key cert 245 let input = String::from("My Message"); 246 let mut cert_stack = Stack::new().expect("failed to create stack"); 247 cert_stack 248 .push(pub_cert) 249 .expect("failed to add pub cert to stack"); 250 251 let encrypt = CmsContentInfo::encrypt( 252 &cert_stack, 253 &input.as_bytes(), 254 Cipher::des_ede3_cbc(), 255 CMSOptions::empty(), 256 ) 257 .expect("failed create encrypted cms"); 258 259 // decrypt cms message using private key cert (DER) 260 { 261 let encrypted_der = encrypt.to_der().expect("failed to create der from cms"); 262 let decrypt = 263 CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der"); 264 let decrypt = decrypt 265 .decrypt(&priv_cert.pkey, &priv_cert.cert) 266 .expect("failed to decrypt cms"); 267 let decrypt = 268 String::from_utf8(decrypt).expect("failed to create string from cms content"); 269 assert_eq!(input, decrypt); 270 } 271 272 // decrypt cms message using private key cert (PEM) 273 { 274 let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms"); 275 let decrypt = 276 CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem"); 277 let decrypt = decrypt 278 .decrypt(&priv_cert.pkey, &priv_cert.cert) 279 .expect("failed to decrypt cms"); 280 let decrypt = 281 String::from_utf8(decrypt).expect("failed to create string from cms content"); 282 assert_eq!(input, decrypt); 283 } 284 } 285 } 286