Skip to content

Commit 6018fed

Browse files
authored
Merge pull request #1589 from sladecek/CMS_verify
Add CMS_verify() method.
2 parents ba8cee4 + bfb7518 commit 6018fed

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed

openssl-sys/src/handwritten/cms.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ extern "C" {
3535
flags: c_uint,
3636
) -> *mut CMS_ContentInfo;
3737

38+
#[cfg(ossl101)]
39+
pub fn CMS_verify(
40+
cms: *mut CMS_ContentInfo,
41+
certs: *mut stack_st_X509,
42+
store: *mut X509_STORE,
43+
detached_data: *mut BIO,
44+
out: *mut BIO,
45+
flags: c_uint,
46+
) -> c_int;
47+
3848
#[cfg(ossl101)]
3949
pub fn CMS_encrypt(
4050
certs: *mut stack_st_X509,

openssl/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased]
44

5+
* Added `CMS_verify`.
6+
57
## [v0.10.45] - 2022-12-20
68

79
### Fixed

openssl/src/cms.rs

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::error::ErrorStack;
1515
use crate::pkey::{HasPrivate, PKeyRef};
1616
use crate::stack::StackRef;
1717
use crate::symm::Cipher;
18-
use crate::x509::{X509Ref, X509};
18+
use crate::x509::{store::X509StoreRef, X509Ref, X509};
1919
use crate::{cvt, cvt_p};
2020
use openssl_macros::corresponds;
2121

@@ -227,14 +227,65 @@ impl CmsContentInfo {
227227
Ok(CmsContentInfo::from_ptr(cms))
228228
}
229229
}
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)]
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+
}
230276
}
231277

232278
#[cfg(test)]
233279
mod test {
234280
use super::*;
281+
235282
use crate::pkcs12::Pkcs12;
283+
use crate::pkey::PKey;
236284
use crate::stack::Stack;
237-
use crate::x509::X509;
285+
use crate::x509::{
286+
store::{X509Store, X509StoreBuilder},
287+
X509,
288+
};
238289

239290
#[test]
240291
fn cms_encrypt_decrypt() {
@@ -317,4 +368,119 @@ mod test {
317368
assert_eq!(input, decrypt_without_cert_check);
318369
}
319370
}
371+
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]
422+
fn cms_sign_verify_ok() {
423+
cms_sign_verify_generic_helper(false);
424+
}
425+
426+
#[test]
427+
fn cms_sign_verify_detached_ok() {
428+
cms_sign_verify_generic_helper(true);
429+
}
430+
431+
#[test]
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+
}
320486
}

0 commit comments

Comments
 (0)