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 #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 24 #[repr(transparent)] 25 pub struct CMSOptions : c_uint { 26 const TEXT = ffi::CMS_TEXT; 27 const CMS_NOCERTS = ffi::CMS_NOCERTS; 28 const NO_CONTENT_VERIFY = ffi::CMS_NO_CONTENT_VERIFY; 29 const NO_ATTR_VERIFY = ffi::CMS_NO_ATTR_VERIFY; 30 const NOSIGS = ffi::CMS_NOSIGS; 31 const NOINTERN = ffi::CMS_NOINTERN; 32 const NO_SIGNER_CERT_VERIFY = ffi::CMS_NO_SIGNER_CERT_VERIFY; 33 const NOVERIFY = ffi::CMS_NOVERIFY; 34 const DETACHED = ffi::CMS_DETACHED; 35 const BINARY = ffi::CMS_BINARY; 36 const NOATTR = ffi::CMS_NOATTR; 37 const NOSMIMECAP = ffi::CMS_NOSMIMECAP; 38 const NOOLDMIMETYPE = ffi::CMS_NOOLDMIMETYPE; 39 const CRLFEOL = ffi::CMS_CRLFEOL; 40 const STREAM = ffi::CMS_STREAM; 41 const NOCRL = ffi::CMS_NOCRL; 42 const PARTIAL = ffi::CMS_PARTIAL; 43 const REUSE_DIGEST = ffi::CMS_REUSE_DIGEST; 44 const USE_KEYID = ffi::CMS_USE_KEYID; 45 const DEBUG_DECRYPT = ffi::CMS_DEBUG_DECRYPT; 46 #[cfg(all(not(libressl), not(ossl101)))] 47 const KEY_PARAM = ffi::CMS_KEY_PARAM; 48 #[cfg(all(not(libressl), not(ossl101), not(ossl102)))] 49 const ASCIICRLF = ffi::CMS_ASCIICRLF; 50 } 51 } 52 53 foreign_type_and_impl_send_sync! { 54 type CType = ffi::CMS_ContentInfo; 55 fn drop = ffi::CMS_ContentInfo_free; 56 57 /// High level CMS wrapper 58 /// 59 /// CMS supports nesting various types of data, including signatures, certificates, 60 /// encrypted data, smime messages (encrypted email), and data digest. The ContentInfo 61 /// content type is the encapsulation of all those content types. [`RFC 5652`] describes 62 /// CMS and OpenSSL follows this RFC's implementation. 63 /// 64 /// [`RFC 5652`]: https://tools.ietf.org/html/rfc5652#page-6 65 pub struct CmsContentInfo; 66 /// Reference to [`CMSContentInfo`] 67 /// 68 /// [`CMSContentInfo`]:struct.CmsContentInfo.html 69 pub struct CmsContentInfoRef; 70 } 71 72 impl CmsContentInfoRef { 73 /// Given the sender's private key, `pkey` and the recipient's certificate, `cert`, 74 /// decrypt the data in `self`. 75 #[corresponds(CMS_decrypt)] 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 #[corresponds(CMS_decrypt)] 103 // FIXME merge into decrypt decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack> where T: HasPrivate,104 pub fn decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack> 105 where 106 T: HasPrivate, 107 { 108 unsafe { 109 let pkey = pkey.as_ptr(); 110 let out = MemBio::new()?; 111 112 cvt(ffi::CMS_decrypt( 113 self.as_ptr(), 114 pkey, 115 ptr::null_mut(), 116 ptr::null_mut(), 117 out.as_ptr(), 118 0, 119 ))?; 120 121 Ok(out.get_buf().to_owned()) 122 } 123 } 124 125 to_der! { 126 /// Serializes this CmsContentInfo using DER. 127 #[corresponds(i2d_CMS_ContentInfo)] 128 to_der, 129 ffi::i2d_CMS_ContentInfo 130 } 131 132 to_pem! { 133 /// Serializes this CmsContentInfo using DER. 134 #[corresponds(PEM_write_bio_CMS)] 135 to_pem, 136 ffi::PEM_write_bio_CMS 137 } 138 } 139 140 impl CmsContentInfo { 141 /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`. 142 #[corresponds(SMIME_read_CMS)] smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack>143 pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> { 144 unsafe { 145 let bio = MemBioSlice::new(smime)?; 146 147 let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?; 148 149 Ok(CmsContentInfo::from_ptr(cms)) 150 } 151 } 152 153 from_der! { 154 /// Deserializes a DER-encoded ContentInfo structure. 155 #[corresponds(d2i_CMS_ContentInfo)] 156 from_der, 157 CmsContentInfo, 158 ffi::d2i_CMS_ContentInfo 159 } 160 161 from_pem! { 162 /// Deserializes a PEM-encoded ContentInfo structure. 163 #[corresponds(PEM_read_bio_CMS)] 164 from_pem, 165 CmsContentInfo, 166 ffi::PEM_read_bio_CMS 167 } 168 169 /// Given a signing cert `signcert`, private key `pkey`, a certificate stack `certs`, 170 /// data `data` and flags `flags`, create a CmsContentInfo struct. 171 /// 172 /// All arguments are optional. 173 #[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,174 pub fn sign<T>( 175 signcert: Option<&X509Ref>, 176 pkey: Option<&PKeyRef<T>>, 177 certs: Option<&StackRef<X509>>, 178 data: Option<&[u8]>, 179 flags: CMSOptions, 180 ) -> Result<CmsContentInfo, ErrorStack> 181 where 182 T: HasPrivate, 183 { 184 unsafe { 185 let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr()); 186 let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr()); 187 let data_bio = match data { 188 Some(data) => Some(MemBioSlice::new(data)?), 189 None => None, 190 }; 191 let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr()); 192 let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr()); 193 194 let cms = cvt_p(ffi::CMS_sign( 195 signcert, 196 pkey, 197 certs, 198 data_bio_ptr, 199 flags.bits(), 200 ))?; 201 202 Ok(CmsContentInfo::from_ptr(cms)) 203 } 204 } 205 206 /// Given a certificate stack `certs`, data `data`, cipher `cipher` and flags `flags`, 207 /// create a CmsContentInfo struct. 208 /// 209 /// OpenSSL documentation at [`CMS_encrypt`] 210 /// 211 /// [`CMS_encrypt`]: https://www.openssl.org/docs/manmaster/man3/CMS_encrypt.html 212 #[corresponds(CMS_encrypt)] encrypt( certs: &StackRef<X509>, data: &[u8], cipher: Cipher, flags: CMSOptions, ) -> Result<CmsContentInfo, ErrorStack>213 pub fn encrypt( 214 certs: &StackRef<X509>, 215 data: &[u8], 216 cipher: Cipher, 217 flags: CMSOptions, 218 ) -> Result<CmsContentInfo, ErrorStack> { 219 unsafe { 220 let data_bio = MemBioSlice::new(data)?; 221 222 let cms = cvt_p(ffi::CMS_encrypt( 223 certs.as_ptr(), 224 data_bio.as_ptr(), 225 cipher.as_ptr(), 226 flags.bits(), 227 ))?; 228 229 Ok(CmsContentInfo::from_ptr(cms)) 230 } 231 } 232 233 /// Verify this CmsContentInfo's signature, 234 /// This will search the 'certs' list for the signing certificate. 235 /// Additional certificates, needed for building the certificate chain, may be 236 /// given in 'store' as well as additional CRLs. 237 /// A detached signature may be passed in `detached_data`. The signed content 238 /// without signature, will be copied into output_data if it is present. 239 /// 240 #[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>241 pub fn verify( 242 &mut self, 243 certs: Option<&StackRef<X509>>, 244 store: Option<&X509StoreRef>, 245 detached_data: Option<&[u8]>, 246 output_data: Option<&mut Vec<u8>>, 247 flags: CMSOptions, 248 ) -> Result<(), ErrorStack> { 249 unsafe { 250 let certs_ptr = certs.map_or(ptr::null_mut(), |p| p.as_ptr()); 251 let store_ptr = store.map_or(ptr::null_mut(), |p| p.as_ptr()); 252 let detached_data_bio = match detached_data { 253 Some(data) => Some(MemBioSlice::new(data)?), 254 None => None, 255 }; 256 let detached_data_bio_ptr = detached_data_bio 257 .as_ref() 258 .map_or(ptr::null_mut(), |p| p.as_ptr()); 259 let out_bio = MemBio::new()?; 260 261 cvt(ffi::CMS_verify( 262 self.as_ptr(), 263 certs_ptr, 264 store_ptr, 265 detached_data_bio_ptr, 266 out_bio.as_ptr(), 267 flags.bits(), 268 ))?; 269 270 if let Some(data) = output_data { 271 data.clear(); 272 data.extend_from_slice(out_bio.get_buf()); 273 }; 274 275 Ok(()) 276 } 277 } 278 } 279 280 #[cfg(test)] 281 mod test { 282 use super::*; 283 284 use crate::pkcs12::Pkcs12; 285 use crate::pkey::PKey; 286 use crate::stack::Stack; 287 use crate::x509::{ 288 store::{X509Store, X509StoreBuilder}, 289 X509, 290 }; 291 292 #[test] cms_encrypt_decrypt()293 fn cms_encrypt_decrypt() { 294 #[cfg(ossl300)] 295 let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap(); 296 297 // load cert with public key only 298 let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der"); 299 let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert"); 300 301 // load cert with private key 302 let priv_cert_bytes = include_bytes!("../test/cms.p12"); 303 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert"); 304 let priv_cert = priv_cert 305 .parse2("mypass") 306 .expect("failed to parse priv cert"); 307 308 // encrypt cms message using public key cert 309 let input = String::from("My Message"); 310 let mut cert_stack = Stack::new().expect("failed to create stack"); 311 cert_stack 312 .push(pub_cert) 313 .expect("failed to add pub cert to stack"); 314 315 let encrypt = CmsContentInfo::encrypt( 316 &cert_stack, 317 input.as_bytes(), 318 Cipher::des_ede3_cbc(), 319 CMSOptions::empty(), 320 ) 321 .expect("failed create encrypted cms"); 322 323 // decrypt cms message using private key cert (DER) 324 { 325 let encrypted_der = encrypt.to_der().expect("failed to create der from cms"); 326 let decrypt = 327 CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der"); 328 329 let decrypt_with_cert_check = decrypt 330 .decrypt( 331 priv_cert.pkey.as_ref().unwrap(), 332 priv_cert.cert.as_ref().unwrap(), 333 ) 334 .expect("failed to decrypt cms"); 335 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check) 336 .expect("failed to create string from cms content"); 337 338 let decrypt_without_cert_check = decrypt 339 .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap()) 340 .expect("failed to decrypt cms"); 341 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check) 342 .expect("failed to create string from cms content"); 343 344 assert_eq!(input, decrypt_with_cert_check); 345 assert_eq!(input, decrypt_without_cert_check); 346 } 347 348 // decrypt cms message using private key cert (PEM) 349 { 350 let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms"); 351 let decrypt = 352 CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem"); 353 354 let decrypt_with_cert_check = decrypt 355 .decrypt( 356 priv_cert.pkey.as_ref().unwrap(), 357 priv_cert.cert.as_ref().unwrap(), 358 ) 359 .expect("failed to decrypt cms"); 360 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check) 361 .expect("failed to create string from cms content"); 362 363 let decrypt_without_cert_check = decrypt 364 .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap()) 365 .expect("failed to decrypt cms"); 366 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check) 367 .expect("failed to create string from cms content"); 368 369 assert_eq!(input, decrypt_with_cert_check); 370 assert_eq!(input, decrypt_without_cert_check); 371 } 372 } 373 cms_sign_verify_generic_helper(is_detached: bool)374 fn cms_sign_verify_generic_helper(is_detached: bool) { 375 // load cert with private key 376 let cert_bytes = include_bytes!("../test/cert.pem"); 377 let cert = X509::from_pem(cert_bytes).expect("failed to load cert.pem"); 378 379 let key_bytes = include_bytes!("../test/key.pem"); 380 let key = PKey::private_key_from_pem(key_bytes).expect("failed to load key.pem"); 381 382 let root_bytes = include_bytes!("../test/root-ca.pem"); 383 let root = X509::from_pem(root_bytes).expect("failed to load root-ca.pem"); 384 385 // sign cms message using public key cert 386 let data = b"Hello world!"; 387 388 let (opt, ext_data): (CMSOptions, Option<&[u8]>) = if is_detached { 389 (CMSOptions::DETACHED | CMSOptions::BINARY, Some(data)) 390 } else { 391 (CMSOptions::empty(), None) 392 }; 393 394 let mut cms = CmsContentInfo::sign(Some(&cert), Some(&key), None, Some(data), opt) 395 .expect("failed to CMS sign a message"); 396 397 // check CMS signature length 398 let pem_cms = cms 399 .to_pem() 400 .expect("failed to pack CmsContentInfo into PEM"); 401 assert!(!pem_cms.is_empty()); 402 403 // verify CMS signature 404 let mut builder = X509StoreBuilder::new().expect("failed to create X509StoreBuilder"); 405 builder 406 .add_cert(root) 407 .expect("failed to add root-ca into X509StoreBuilder"); 408 let store: X509Store = builder.build(); 409 let mut out_data: Vec<u8> = Vec::new(); 410 let res = cms.verify( 411 None, 412 Some(&store), 413 ext_data, 414 Some(&mut out_data), 415 CMSOptions::empty(), 416 ); 417 418 // check verification result - valid signature 419 res.unwrap(); 420 assert_eq!(data.to_vec(), out_data); 421 } 422 423 #[test] cms_sign_verify_ok()424 fn cms_sign_verify_ok() { 425 cms_sign_verify_generic_helper(false); 426 } 427 428 #[test] cms_sign_verify_detached_ok()429 fn cms_sign_verify_detached_ok() { 430 cms_sign_verify_generic_helper(true); 431 } 432 433 #[test] cms_sign_verify_error()434 fn cms_sign_verify_error() { 435 #[cfg(ossl300)] 436 let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap(); 437 438 // load cert with private key 439 let priv_cert_bytes = include_bytes!("../test/cms.p12"); 440 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert"); 441 let priv_cert = priv_cert 442 .parse2("mypass") 443 .expect("failed to parse priv cert"); 444 445 // sign cms message using public key cert 446 let data = b"Hello world!"; 447 let mut cms = CmsContentInfo::sign( 448 Some(&priv_cert.cert.unwrap()), 449 Some(&priv_cert.pkey.unwrap()), 450 None, 451 Some(data), 452 CMSOptions::empty(), 453 ) 454 .expect("failed to CMS sign a message"); 455 456 // check CMS signature length 457 let pem_cms = cms 458 .to_pem() 459 .expect("failed to pack CmsContentInfo into PEM"); 460 assert!(!pem_cms.is_empty()); 461 462 let empty_store = X509StoreBuilder::new() 463 .expect("failed to create X509StoreBuilder") 464 .build(); 465 466 // verify CMS signature 467 let res = cms.verify( 468 None, 469 Some(&empty_store), 470 Some(data), 471 None, 472 CMSOptions::empty(), 473 ); 474 475 // check verification result - this is an invalid signature 476 // defined in openssl crypto/cms/cms.h 477 const CMS_R_CERTIFICATE_VERIFY_ERROR: i32 = 100; 478 let es = res.unwrap_err(); 479 let error_array = es.errors(); 480 assert_eq!(1, error_array.len()); 481 let code = error_array[0].reason_code(); 482 assert_eq!(code, CMS_R_CERTIFICATE_VERIFY_ERROR); 483 } 484 } 485