Skip to content

Add CMS_verify() method. #1393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions openssl-sys/src/cms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ extern "C" {
data: *mut ::BIO,
flags: c_uint,
) -> *mut ::CMS_ContentInfo;
#[cfg(ossl101)]
pub fn CMS_verify(
cms: *mut ::CMS_ContentInfo,
certs: *mut ::stack_st_X509,
store: *mut ::X509_STORE,
indata: *mut ::BIO,
out: *mut ::BIO,
flags: c_uint,
) -> c_int;

#[cfg(ossl101)]
pub fn CMS_encrypt(
Expand Down
157 changes: 155 additions & 2 deletions openssl/src/cms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::error::ErrorStack;
use crate::pkey::{HasPrivate, PKeyRef};
use crate::stack::StackRef;
use crate::symm::Cipher;
use crate::x509::{X509Ref, X509};
use crate::x509::{store::X509StoreRef, X509Ref, X509};
use crate::{cvt, cvt_p};

bitflags! {
Expand Down Expand Up @@ -219,14 +219,61 @@ impl CmsContentInfo {
Ok(CmsContentInfo::from_ptr(cms))
}
}

/// Verify this CmsContentInfo's signature, given a stack of certificates
/// in certs, an X509 store in store. If the signature is detached, the
/// data can be passed in data. The data sans signature will be copied
/// into output_data if it is present.
///
/// OpenSSL documentation at [`CMS_verify`]
///
/// [`CMS_verify`]: https://www.openssl.org/docs/manmaster/man3/CMS_verify.html
pub fn verify(
&mut self,
certs: Option<&StackRef<X509>>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The man page for CMS_verify doesn't talk about certs and store being allowed to be null at all - maybe they should just be required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The man page (https://www.openssl.org/docs/manmaster/man3/CMS_verify.html) mentions a null certs parameter in this sentence:

...An attempt is made to locate all the signing certificate(s), first looking in the certs parameter (if it is not NULL) ...

and there is the option CMS_NO_SIGNER_CERT_VERIFY which disables certificate verification altogether.

Therefore, I think the optional parameters make sense. Nevertheless, the distinction is just a formal one, because the caller can always create an empty vector or an empty store. If you insist, I can change them to required, just let me know.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good find. Keeping certs nullable seems fine, but I'd lean towards requiring store since its nullability isn't mentioned in the docs and at least in 1.0.1 they assume it's present (I'd also hope people aren't using NO_SIGNER_CERT_VERIFY too often!).

store: &X509StoreRef,
indata: Option<&[u8]>,
output_data: Option<&mut Vec<u8>>,
flags: CMSOptions,
) -> Result<(), ErrorStack> {
unsafe {
let certs_ptr = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
let indata_bio = match indata {
Some(data) => Some(MemBioSlice::new(data)?),
None => None,
};
let indata_bio_ptr = indata_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr());
let out_bio = MemBio::new()?;

cvt(ffi::CMS_verify(
self.as_ptr(),
certs_ptr,
store.as_ptr(),
indata_bio_ptr,
out_bio.as_ptr(),
flags.bits(),
))?;

if let Some(out_data) = output_data {
*out_data = out_bio.get_buf().to_vec();
};

Ok(())
}
}
}

#[cfg(test)]
mod test {
use super::*;

use crate::pkcs12::Pkcs12;
use crate::pkey::PKey;
use crate::stack::Stack;
use crate::x509::X509;
use crate::x509::{
store::{X509Store, X509StoreBuilder},
X509,
};

#[test]
fn cms_encrypt_decrypt() {
Expand Down Expand Up @@ -282,4 +329,110 @@ mod test {
assert_eq!(input, decrypt);
}
}

fn cms_sign_verify_generic_helper(is_detached: bool) {
// load cert with private key
let cert_bytes = include_bytes!("../test/cert.pem");
let cert = X509::from_pem(cert_bytes).expect("failed to load cert.pem");

let key_bytes = include_bytes!("../test/key.pem");
let key = PKey::private_key_from_pem(key_bytes).expect("failed to load key.pem");

let root_bytes = include_bytes!("../test/root-ca.pem");
let root = X509::from_pem(root_bytes).expect("failed to load root-ca.pem");

// sign cms message using public key cert
let data = b"Hello world!";

let (opt, ext_data): (CMSOptions, Option<&[u8]>) = if is_detached {
(CMSOptions::DETACHED | CMSOptions::BINARY, Some(data))
} else {
(CMSOptions::empty(), None)
};

let mut cms = CmsContentInfo::sign(Some(&cert), Some(&key), None, Some(data), opt)
.expect("failed to CMS sign a message");

// check CMS signature length
let pem_cms = cms
.to_pem()
.expect("failed to pack CmsContentInfo into PEM");
assert!(!pem_cms.is_empty());

// verify CMS signature
let mut builder = X509StoreBuilder::new().expect("failed to create X509StoreBuilder");
builder
.add_cert(root)
.expect("failed to add root-ca into X509StoreBuilder");
let store: X509Store = builder.build();
let mut out_data: Vec<u8> = Vec::new();
let res = cms.verify(
None,
&store,
ext_data,
Some(&mut out_data),
CMSOptions::empty(),
);

// check verification result - valid signature
res.unwrap();
assert_eq!(data.len(), out_data.len());
}

#[test]
fn cms_sign_verify_ok() {
cms_sign_verify_generic_helper(false);
}

#[test]
fn cms_sign_verify_detached_ok() {
cms_sign_verify_generic_helper(true);
}

#[test]
fn cms_sign_verify_error() {
// load cert with private key
let priv_cert_bytes = include_bytes!("../test/cms.p12");
let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
let priv_cert = priv_cert
.parse("mypass")
.expect("failed to parse priv cert");

// sign cms message using public key cert
let data = b"Hello world!";
let mut cms = CmsContentInfo::sign(
Some(&priv_cert.cert),
Some(&priv_cert.pkey),
None,
Some(data),
CMSOptions::empty(),
)
.expect("failed to CMS sign a message");

// check CMS signature length
let pem_cms = cms
.to_pem()
.expect("failed to pack CmsContentInfo into PEM");
assert!(!pem_cms.is_empty());

let empty_store = X509StoreBuilder::new()
.expect("failed to create X509StoreBuilder")
.build();

// verify CMS signature
let res = cms.verify(None, &empty_store, Some(data), None, CMSOptions::empty());

// check verification result - this is an invalid signature
match res {
Err(es) => {
let error_array = es.errors();
assert_eq!(1, error_array.len());
let err = error_array[0]
.data()
.expect("failed to retrieve verification error data");
assert_eq!("Verify error:self signed certificate", err);
}
_ => panic!("expected CMS verification error, got Ok()"),
}
}
}