From cd1d1955d9375cb8b5dfe94972c601a24d51dee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20Ma=CC=88nnchen?= Date: Fri, 24 Nov 2017 16:19:34 +0100 Subject: [PATCH] PKCS7 Support (Rebased onto latest version) --- openssl/src/bio.rs | 4 + openssl/src/crypto/mod.rs | 8 + openssl/src/crypto/pkcs7/mod.rs | 1 + openssl/src/crypto/pkcs7/pk7_smime.rs | 257 ++++++++++++++++++++++++++ systest/build.rs | 2 + 5 files changed, 272 insertions(+) create mode 100644 openssl/src/crypto/mod.rs create mode 100644 openssl/src/crypto/pkcs7/mod.rs create mode 100644 openssl/src/crypto/pkcs7/pk7_smime.rs diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs index 51724e3f..15f6e066 100644 --- a/openssl/src/bio.rs +++ b/openssl/src/bio.rs @@ -66,6 +66,10 @@ impl MemBio { slice::from_raw_parts(ptr as *const _ as *const _, len as usize) } } + + pub fn from_ptr(bio: *mut ffi::BIO) -> MemBio { + MemBio(bio) + } } cfg_if! { diff --git a/openssl/src/crypto/mod.rs b/openssl/src/crypto/mod.rs new file mode 100644 index 00000000..1fb6e08c --- /dev/null +++ b/openssl/src/crypto/mod.rs @@ -0,0 +1,8 @@ +#![doc(hidden)] +#![deprecated(since = "0.9.20")] +use string::OpensslString; + +#[deprecated(note = "renamed to OpensslString", since = "0.9.7")] +pub type CryptoString = OpensslString; + +pub mod pkcs7; diff --git a/openssl/src/crypto/pkcs7/mod.rs b/openssl/src/crypto/pkcs7/mod.rs new file mode 100644 index 00000000..de7a3d2d --- /dev/null +++ b/openssl/src/crypto/pkcs7/mod.rs @@ -0,0 +1 @@ +pub mod pk7_smime; diff --git a/openssl/src/crypto/pkcs7/pk7_smime.rs b/openssl/src/crypto/pkcs7/pk7_smime.rs new file mode 100644 index 00000000..6cca691c --- /dev/null +++ b/openssl/src/crypto/pkcs7/pk7_smime.rs @@ -0,0 +1,257 @@ +use x509::{X509, X509Ref}; +use x509::store::X509Store; +use ffi; +use bio::{MemBio, MemBioSlice}; +use error::ErrorStack; +use stack::Stack; +use foreign_types::ForeignType; +use symm::Cipher; +use pkey::PKeyRef; +use libc::c_int; +use std::ptr::null_mut; +use foreign_types::ForeignTypeRef; + +pub struct PKCS7(*mut ffi::pkcs7_st); + +bitflags! { + pub struct PKCS7Flags: c_int { + const PKCS7_TEXT = ffi::PKCS7_TEXT; + const PKCS7_NOCERTS = ffi::PKCS7_NOCERTS; + const PKCS7_NOSIGS = ffi::PKCS7_NOSIGS; + const PKCS7_NOCHAIN = ffi::PKCS7_NOCHAIN; + const PKCS7_NOINTERN = ffi::PKCS7_NOINTERN; + const PKCS7_NOVERIFY = ffi::PKCS7_NOVERIFY; + const PKCS7_DETACHED = ffi::PKCS7_DETACHED; + const PKCS7_BINARY = ffi::PKCS7_BINARY; + const PKCS7_NOATTR = ffi::PKCS7_NOATTR; + const PKCS7_NOSMIMECAP = ffi::PKCS7_NOSMIMECAP; + const PKCS7_NOOLDMIMETYPE = ffi::PKCS7_NOOLDMIMETYPE; + const PKCS7_CRLFEOL = ffi::PKCS7_CRLFEOL; + const PKCS7_STREAM = ffi::PKCS7_STREAM; + const PKCS7_NOCRL = ffi::PKCS7_NOCRL; + const PKCS7_PARTIAL = ffi::PKCS7_PARTIAL; + const PKCS7_REUSE_DIGEST = ffi::PKCS7_REUSE_DIGEST; + #[cfg(not(any(ossl101, ossl102, libressl)))] + const PKCS7_NO_DUAL_CONTENT = ffi::PKCS7_NO_DUAL_CONTENT; + } +} + +impl PKCS7 { + pub fn smime_write(&self, input: &[u8], flags: PKCS7Flags) -> Result, ErrorStack> { + ffi::init(); + + unsafe { + let input_bio = MemBioSlice::new(input)?; + + let output = MemBio::new()?; + + if ffi::SMIME_write_PKCS7(output.as_ptr(), self.0, input_bio.as_ptr(), flags.bits) == 1 { + Ok(output.get_buf().to_owned()) + } else { + Err(ErrorStack::get()) + } + } + } + + pub fn smime_read(input: &[u8], bcount: &mut Vec) -> Result { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + + let mut bcount_bio = null_mut(); + + let pkcs7 = unsafe { ffi::SMIME_read_PKCS7(input_bio.as_ptr(), &mut bcount_bio) }; + + bcount.clear(); + + if !bcount_bio.is_null() { + let bcount_bio = MemBio::from_ptr(bcount_bio); + bcount.append(&mut bcount_bio.get_buf().to_vec()); + } + + if pkcs7.is_null() { + Err(ErrorStack::get()) + } else { + Ok(PKCS7(pkcs7)) + } + } + + pub fn decrypt(&self, pkey: &PKeyRef, cert: &X509Ref) -> Result, ErrorStack> { + ffi::init(); + + let output = MemBio::new()?; + + unsafe { + if ffi::PKCS7_decrypt(self.0, pkey.as_ptr(), cert.as_ptr(), output.as_ptr(), 0) == 1 { + Ok(output.get_buf().to_owned()) + } else { + Err(ErrorStack::get()) + } + } + } + + pub fn encrypt(certs: &Stack, input: &[u8], cypher: Cipher, flags: PKCS7Flags) -> Result { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + + let pkcs7 = unsafe { ffi::PKCS7_encrypt(certs.as_ptr(), input_bio.as_ptr(), cypher.as_ptr(), flags.bits) }; + + if pkcs7.is_null() { + Err(ErrorStack::get()) + } else { + Ok(PKCS7(pkcs7)) + } + } + + pub fn sign(signcert: &X509Ref, pkey: &PKeyRef, certs: &Stack, input: &[u8], flags: PKCS7Flags) -> Result { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + + let pkcs7 = unsafe { ffi::PKCS7_sign(signcert.as_ptr(), pkey.as_ptr(), certs.as_ptr(), input_bio.as_ptr(), flags.bits) }; + + if pkcs7.is_null() { + Err(ErrorStack::get()) + } else { + Ok(PKCS7(pkcs7)) + } + } + + pub fn verify(&self, certs: &Stack, store: &X509Store, indata: Option<&[u8]>, out: Option<&mut Vec>, flags: PKCS7Flags) -> Result { + ffi::init(); + + let out_bio = MemBio::new()?; + + let result = match indata { + Some(data) => { + let indata_bio = MemBioSlice::new(data)?; + unsafe { ffi::PKCS7_verify(self.0, certs.as_ptr(), store.as_ptr(), indata_bio.as_ptr(), out_bio.as_ptr(), flags.bits) } + }, + None => unsafe { ffi::PKCS7_verify(self.0, certs.as_ptr(), store.as_ptr(), null_mut(), out_bio.as_ptr(), flags.bits) } + }; + + if let Some(data) = out { + data.clear(); + data.append(&mut out_bio.get_buf().to_vec()); + } + + if result == 1 { + Ok(true) + } else { + Err(ErrorStack::get()) + } + } +} + +#[cfg(test)] +mod tests { + use x509::X509; + use x509::store::X509StoreBuilder; + use symm::Cipher; + use crypto::pkcs7::pk7_smime::PKCS7_STREAM; + use crypto::pkcs7::pk7_smime::PKCS7_DETACHED; + use crypto::pkcs7::pk7_smime::PKCS7; + use pkey::PKey; + use stack::Stack; + + #[test] + fn encrypt_decrypt_test() { + let cert = include_bytes!("../../../test/certs.pem"); + let cert = X509::from_pem(cert).unwrap(); + let mut certs = Stack::new().unwrap(); + certs.push(cert.clone()).unwrap(); + let message: String = String::from("foo"); + let cypher = Cipher::des_ede3_cbc(); + let flags = PKCS7_STREAM; + let pkey = include_bytes!("../../../test/key.pem"); + let pkey = PKey::private_key_from_pem(pkey).unwrap(); + + let pkcs7 = PKCS7::encrypt(&certs, message.as_bytes(), cypher, flags).expect("should succeed"); + + let encrypted = pkcs7.smime_write(message.as_bytes(), flags).expect("should succeed"); + + let mut bcount = Vec::new(); + let pkcs7_decoded = PKCS7::smime_read(encrypted.as_slice(), &mut bcount).expect("should succeed"); + + let decoded = pkcs7_decoded.decrypt(&pkey, &cert).expect("should succeed"); + + assert_eq!(decoded, message.into_bytes()); + } + + #[test] + fn sign_verify_test_detached() { + let cert = include_bytes!("../../../test/cert.pem"); + let cert = X509::from_pem(cert).unwrap(); + let certs = Stack::new().unwrap(); + let message: String = String::from("foo"); + let flags = PKCS7_STREAM | PKCS7_DETACHED; + let pkey = include_bytes!("../../../test/key.pem"); + let pkey = PKey::private_key_from_pem(pkey).unwrap(); + let mut store_builder = X509StoreBuilder::new().expect("should succeed"); + + let root_ca = include_bytes!("../../../test/root-ca.pem"); + let root_ca = X509::from_pem(root_ca).unwrap(); + store_builder.add_cert(root_ca).expect("should succeed"); + + let store = store_builder.build(); + + let pkcs7 = PKCS7::sign(&cert, &pkey, &certs, message.as_bytes(), flags).expect("should succeed"); + + let signed = pkcs7.smime_write(message.as_bytes(), flags).expect("should succeed"); + + let mut bcount = Vec::new(); + let pkcs7_decoded = PKCS7::smime_read(signed.as_slice(), &mut bcount).expect("should succeed"); + + let mut output = Vec::new(); + let result = pkcs7_decoded.verify(&certs, &store, Some(message.as_bytes()), Some(&mut output), flags) + .expect("should succeed"); + + assert!(result); + assert_eq!(message.clone().into_bytes(), output); + assert_eq!(message.clone().into_bytes(), bcount); + } + + #[test] + fn sign_verify_test_normal() { + let cert = include_bytes!("../../../test/cert.pem"); + let cert = X509::from_pem(cert).unwrap(); + let certs = Stack::new().unwrap(); + let message: String = String::from("foo"); + let flags = PKCS7_STREAM; + let pkey = include_bytes!("../../../test/key.pem"); + let pkey = PKey::private_key_from_pem(pkey).unwrap(); + let mut store_builder = X509StoreBuilder::new().expect("should succeed"); + + let root_ca = include_bytes!("../../../test/root-ca.pem"); + let root_ca = X509::from_pem(root_ca).unwrap(); + store_builder.add_cert(root_ca).expect("should succeed"); + + let store = store_builder.build(); + + let pkcs7 = PKCS7::sign(&cert, &pkey, &certs, message.as_bytes(), flags).expect("should succeed"); + + let signed = pkcs7.smime_write(message.as_bytes(), flags).expect("should succeed"); + + let mut bcount = Vec::new(); + let pkcs7_decoded = PKCS7::smime_read(signed.as_slice(), &mut bcount).expect("should succeed"); + + let mut output = Vec::new(); + let result = pkcs7_decoded.verify(&certs, &store, None, Some(&mut output), flags).expect("should succeed"); + + assert!(result); + assert_eq!(message.clone().into_bytes(), output); + let empty: Vec = Vec::new(); + assert_eq!(empty, bcount); + } + + #[test] + fn invalid_smime_read() { + let input = String::from("Invalid SMIME Message"); + let mut bcount = Vec::new(); + + let result = PKCS7::smime_read(input.as_bytes(), &mut bcount); + + assert_eq!(result.is_err(), true) + } +} diff --git a/systest/build.rs b/systest/build.rs index 84bdcbdb..fe76fae9 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -94,6 +94,8 @@ fn main() { s == "PasswordCallback" || s == "pem_password_cb" || s == "bio_info_cb" || s.starts_with("CRYPTO_EX_") }); cfg.skip_struct(|s| s == "ProbeResult"); + cfg.skip_field(|s, field| s == "pkcs7_st" && field == "d"); + cfg.skip_struct(|s| s == "pkcs7_st__d"); cfg.skip_fn(move |s| { s == "CRYPTO_memcmp" || // uses volatile