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 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::{store::X509StoreRef, X509Ref, X509}; 19 use crate::{cvt, cvt_p}; 20 use openssl_macros::corresponds; 21 22 bitflags! { 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 51 foreign_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 70 impl 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)] decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack> where T: HasPrivate,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 decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack> where T: HasPrivate,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 138 impl CmsContentInfo { 139 /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`. 140 #[corresponds(SMIME_read_CMS)] smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack>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)] sign<T>( signcert: Option<&X509Ref>, pkey: Option<&PKeyRef<T>>, certs: Option<&StackRef<X509>>, data: Option<&[u8]>, flags: CMSOptions, ) -> Result<CmsContentInfo, ErrorStack> where T: HasPrivate,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)] encrypt( certs: &StackRef<X509>, data: &[u8], cipher: Cipher, flags: CMSOptions, ) -> Result<CmsContentInfo, ErrorStack>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)] verify( &mut self, certs: Option<&StackRef<X509>>, store: Option<&X509StoreRef>, detached_data: Option<&[u8]>, output_data: Option<&mut Vec<u8>>, flags: CMSOptions, ) -> Result<(), ErrorStack>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)] 279 mod 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] cms_encrypt_decrypt()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 cms_sign_verify_generic_helper(is_detached: bool)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] cms_sign_verify_ok()422 fn cms_sign_verify_ok() { 423 cms_sign_verify_generic_helper(false); 424 } 425 426 #[test] cms_sign_verify_detached_ok()427 fn cms_sign_verify_detached_ok() { 428 cms_sign_verify_generic_helper(true); 429 } 430 431 #[test] cms_sign_verify_error()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