diff options
Diffstat (limited to 'openssl/src/pkcs7.rs')
-rw-r--r-- | openssl/src/pkcs7.rs | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/openssl/src/pkcs7.rs b/openssl/src/pkcs7.rs new file mode 100644 index 00000000..40569eaf --- /dev/null +++ b/openssl/src/pkcs7.rs @@ -0,0 +1,366 @@ +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::{HasPrivate, Public, PKeyRef}; +use libc::c_int; +use std::ptr::null_mut; +use foreign_types::ForeignTypeRef; +use {cvt, cvt_p}; + +generic_foreign_type_and_impl_send_sync! { + type CType = ffi::PKCS7; + fn drop = ffi::PKCS7_free; + + /// A PKCS#7 structure. + /// + /// Contains signed and/or encrypted data. + pub struct Pkcs7<T>; + + /// Reference to `Pkcs7` + pub struct Pkcs7Ref<T>; +} + +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<Public> { + /// Converts PKCS#7 structure to S/MIME format + /// + /// This corresponds to [`SMIME_write_PKCS7`]. + /// + /// [`SMIME_write_PKCS7`]: https://www.openssl.org/docs/man1.1.0/crypto/SMIME_write_PKCS7.html + pub fn to_smime( + &self, + input: &[u8], + flags: PKCS7Flags + ) -> Result<Vec<u8>, ErrorStack> + { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + let output = MemBio::new()?; + unsafe { + cvt( + ffi::SMIME_write_PKCS7( + output.as_ptr(), + self.0, + input_bio.as_ptr(), + flags.bits) + ).and( + Ok(output.get_buf().to_owned()) + ) + } + } + + /// Parses a message in S/MIME format. + /// + /// This corresponds to [`SMIME_read_PKCS7`]. + /// + /// [`SMIME_read_PKCS7`]: https://www.openssl.org/docs/man1.1.0/crypto/SMIME_read_PKCS7.html + pub fn from_smime(input: &[u8], bcont: &mut Vec<u8>) -> Result<Self, ErrorStack> { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + let mut bcount_bio = null_mut(); + let pkcs7 = unsafe { + cvt_p(ffi::SMIME_read_PKCS7(input_bio.as_ptr(), &mut bcount_bio))? + }; + bcont.clear(); + if !bcount_bio.is_null() { + let bcount_bio = MemBio::from_ptr(bcount_bio); + bcont.append(&mut bcount_bio.get_buf().to_vec()); + } + unsafe { + Ok(Pkcs7::from_ptr(pkcs7)) + } + } + + to_pem! { + /// Serializes the data into a PEM-encoded PKCS#7 structure. + /// + /// The output will have a header of `-----BEGIN PKCS7-----`. + /// + /// This corresponds to [`PEM_write_bio_PKCS7`]. + /// + /// [`PEM_write_bio_PKCS7`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_write_bio_PKCS7.html + to_pem, + ffi::PEM_write_bio_PKCS7 + } + + from_pem! { + /// Deserializes a PEM-encoded PKCS#7 signature + /// + /// The input should have a header of `-----BEGIN PKCS7-----`. + /// + /// This corresponds to [`PEM_read_bio_PKCS7`]. + /// + /// [`PEM_read_bio_PKCS7`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_read_bio_PKCS7.html + from_pem, + Pkcs7<Public>, + ffi::PEM_read_bio_PKCS7 + } + + /// Decrypts data using the provided private key. + /// + /// `pkey` is the recipient's private key, and `cert` is the recipient's + /// certificate. + /// + /// Returns the decrypted message. + /// + /// This corresponds to [`PKCS7_decrypt`]. + /// + /// [`PKCS7_decrypt`]: https://www.openssl.org/docs/man1.0.2/crypto/PKCS7_decrypt.html + pub fn decrypt<PT>(&self, pkey: &PKeyRef<PT>, cert: &X509Ref) -> Result<Vec<u8>, ErrorStack> + where + PT: HasPrivate + { + ffi::init(); + + let output = MemBio::new()?; + + unsafe { + cvt(ffi::PKCS7_decrypt(self.0, pkey.as_ptr(), cert.as_ptr(), output.as_ptr(), 0)) + .and(Ok(output.get_buf().to_owned())) + } + } + + /// Creates and returns a PKCS#7 `envelopedData` structure. + /// + /// `certs` is a list of recipient certificates. `input` is the content to be + /// encrypted. `cipher` is the symmetric cipher to use. `flags` is an optional + /// set of flags. + /// + /// This corresponds to [`PKCS7_encrypt`]. + /// + /// [`PKCS7_encrypt`]: https://www.openssl.org/docs/man1.0.2/crypto/PKCS7_encrypt.html + pub fn encrypt(certs: &Stack<X509>, input: &[u8], cipher: Cipher, flags: PKCS7Flags) -> Result<Self, ErrorStack> { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + + unsafe { + cvt_p(ffi::PKCS7_encrypt( + certs.as_ptr(), + input_bio.as_ptr(), + cipher.as_ptr(), + flags.bits) + ).map(|p| Pkcs7::from_ptr(p)) + } + } + + /// Creates and returns a PKCS#7 `signedData` structure. + /// + /// `signcert` is the certificate to sign with, `pkey` is the corresponding + /// private key. `certs` is an optional additional set of certificates to + /// include in the PKCS#7 structure (for example any intermediate CAs in the + /// chain). + /// + /// This corresponds to [`PKCS7_sign`]. + /// + /// [`PKCS7_sign`]: https://www.openssl.org/docs/man1.0.2/crypto/PKCS7_sign.html + pub fn sign<PT>( + signcert: &X509Ref, + pkey: &PKeyRef<PT>, + certs: &Stack<X509>, + input: &[u8], + flags: PKCS7Flags + ) -> Result<Self, ErrorStack> + where + PT: HasPrivate + { + ffi::init(); + + let input_bio = MemBioSlice::new(input)?; + unsafe { + cvt_p(ffi::PKCS7_sign( + signcert.as_ptr(), + pkey.as_ptr(), + certs.as_ptr(), + input_bio.as_ptr(), + flags.bits) + ).map(|p| Pkcs7::from_ptr(p)) + } + } + + /// Verifies the PKCS#7 `signedData` structure contained by `&self`. + /// + /// `certs` is a set of certificates in which to search for the signer's + /// certificate. `store` is a trusted certificate store (used for chain + /// verification). `indata` is the signed data if the content is not present + /// in `&self`. The content is written to `out` if it is not `None`. + /// + /// This corresponds to [`PKCS7_verify`]. + /// + /// [`PKCS7_verify`]: https://www.openssl.org/docs/man1.0.2/crypto/PKCS7_verify.html + pub fn verify( + &self, + certs: &Stack<X509>, + store: &X509Store, + indata: Option<&[u8]>, + out: Option<&mut Vec<u8>>, + flags: PKCS7Flags + ) -> Result<bool, ErrorStack> { + ffi::init(); + + let out_bio = MemBio::new()?; + + let indata_bio = match indata { + Some(data) => Some(MemBioSlice::new(data)?), + None => None, + }; + let indata_bio_ptr = indata_bio.as_ref().map_or(null_mut(), |p| p.as_ptr()); + + let result = unsafe { + cvt(ffi::PKCS7_verify( + self.0, + certs.as_ptr(), + store.as_ptr(), + indata_bio_ptr, + out_bio.as_ptr(), + flags.bits)) + .map(|r| r == 1) + }; + + if let Some(data) = out { + data.clear(); + data.append(&mut out_bio.get_buf().to_vec()); + } + + result + } +} + +#[cfg(test)] +mod tests { + use x509::X509; + use x509::store::X509StoreBuilder; + use symm::Cipher; + use pkcs7::{Pkcs7, PKCS7Flags}; + use pkey::{PKey, Public}; + 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 = PKCS7Flags::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.to_smime(message.as_bytes(), flags).expect("should succeed"); + + let mut bcount = Vec::new(); + let pkcs7_decoded = Pkcs7::from_smime(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 = PKCS7Flags::PKCS7_STREAM | PKCS7Flags::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.to_smime(message.as_bytes(), flags).expect("should succeed"); + println!("{:?}", String::from_utf8(signed.clone()).unwrap()); + let mut bcount = Vec::new(); + let pkcs7_decoded = Pkcs7::from_smime(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 = PKCS7Flags::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.to_smime(message.as_bytes(), flags).expect("should succeed"); + + let mut bcount = Vec::new(); + let pkcs7_decoded = Pkcs7::<Public>::from_smime(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<u8> = Vec::new(); + assert_eq!(empty, bcount); + } + + #[test] + fn invalid_from_smime() { + let input = String::from("Invalid SMIME Message"); + let mut bcount = Vec::new(); + + let result = Pkcs7::from_smime(input.as_bytes(), &mut bcount); + + assert_eq!(result.is_err(), true) + } +} |