1use crate::bio::{MemBio, MemBioBuf};
27use crate::error::ErrorStack;
28use crate::obj::Oid;
29use crate::pkey::{Pkey, Private};
30use crate::x509::{X509Crl, X509Store, X509};
31use native_ossl_sys as sys;
32use std::marker::PhantomData;
33use std::sync::Arc;
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
41pub struct CmsVerifyFlags(pub u32);
42
43impl CmsVerifyFlags {
44 pub const NONE: Self = Self(0);
46 pub const NO_SIGNER_CERT_VERIFY: Self = Self(0x20);
48 pub const NOVERIFY: Self = Self(0x20);
50 pub const NOINTERN: Self = Self(0x10);
52 pub const NOCRL: Self = Self(0x2000);
54}
55
56impl std::ops::BitOr for CmsVerifyFlags {
57 type Output = Self;
58 fn bitor(self, rhs: Self) -> Self {
59 Self(self.0 | rhs.0)
60 }
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub struct CmsSignFlags(pub u32);
70
71impl CmsSignFlags {
72 pub const NONE: Self = Self(0);
74 pub const DETACHED: Self = Self(0x40);
76 pub const BINARY: Self = Self(0x80);
78 pub const NOSMIMECAP: Self = Self(0x200);
80 pub const NOCERTS: Self = Self(0x2);
82 pub const NOATTR: Self = Self(0x100);
84}
85
86impl std::ops::BitOr for CmsSignFlags {
87 type Output = Self;
88 fn bitor(self, rhs: Self) -> Self {
89 Self(self.0 | rhs.0)
90 }
91}
92
93pub struct CmsSignerInfo<'a> {
100 ptr: *mut sys::CMS_SignerInfo,
101 _marker: PhantomData<&'a CmsContentInfo>,
102}
103
104impl CmsSignerInfo<'_> {
105 #[must_use]
110 pub fn signer_cert(&self) -> Option<X509> {
111 let mut signer: *mut sys::X509 = std::ptr::null_mut();
112 unsafe {
115 sys::CMS_SignerInfo_get0_algs(
116 self.ptr,
117 std::ptr::null_mut(), std::ptr::addr_of_mut!(signer),
119 std::ptr::null_mut(), std::ptr::null_mut(), );
122 }
123 if signer.is_null() {
124 return None;
125 }
126 unsafe { sys::X509_up_ref(signer) };
129 Some(unsafe { X509::from_ptr(signer) })
130 }
131}
132
133pub struct CmsContentInfo {
137 ptr: *mut sys::CMS_ContentInfo,
138}
139
140unsafe impl Send for CmsContentInfo {}
143unsafe impl Sync for CmsContentInfo {}
144
145impl Drop for CmsContentInfo {
146 fn drop(&mut self) {
147 unsafe { sys::CMS_ContentInfo_free(self.ptr) };
149 }
150}
151
152impl CmsContentInfo {
153 pub fn from_der(data: &[u8]) -> Result<Self, ErrorStack> {
161 let bio = MemBioBuf::new(data)?;
162 let ptr = unsafe { sys::d2i_CMS_bio(bio.as_ptr(), std::ptr::null_mut()) };
164 if ptr.is_null() {
165 Err(ErrorStack::drain())
166 } else {
167 Ok(CmsContentInfo { ptr })
168 }
169 }
170
171 pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, data: &[u8]) -> Result<Self, ErrorStack> {
179 let shell = unsafe { sys::CMS_ContentInfo_new_ex(ctx.as_ptr(), std::ptr::null()) };
180 if shell.is_null() {
181 return Err(ErrorStack::drain());
182 }
183 let mut ptr = data.as_ptr();
184 let len = i64::try_from(data.len()).unwrap_or(i64::MAX);
185 let mut shell_mut = shell;
188 let result = unsafe {
189 sys::d2i_CMS_ContentInfo(
190 std::ptr::addr_of_mut!(shell_mut),
191 std::ptr::addr_of_mut!(ptr),
192 len,
193 )
194 };
195 if result.is_null() {
196 return Err(ErrorStack::drain());
198 }
199 Ok(CmsContentInfo { ptr: result })
200 }
201
202 pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
208 let mut bio = MemBio::new()?;
209 let rc = unsafe { sys::i2d_CMS_bio(bio.as_ptr(), self.ptr) };
211 if rc != 1 {
212 return Err(ErrorStack::drain());
213 }
214 Ok(bio.into_vec())
215 }
216
217 #[must_use]
227 pub fn content_type_oid(&self) -> Oid {
228 let obj = unsafe { sys::CMS_get0_type(self.ptr) };
231 let dup = unsafe { sys::OBJ_dup(obj.cast_mut()) };
232 assert!(!dup.is_null(), "OBJ_dup: allocation failed");
233 unsafe { Oid::from_ptr(dup) }
234 }
235
236 #[must_use]
246 pub fn econtent_type_oid(&self) -> Oid {
247 let obj = unsafe { sys::CMS_get0_eContentType(self.ptr) };
248 let dup = unsafe { sys::OBJ_dup(obj.cast_mut()) };
249 assert!(!dup.is_null(), "OBJ_dup: allocation failed");
250 unsafe { Oid::from_ptr(dup) }
251 }
252
253 #[must_use]
255 pub fn is_detached(&self) -> bool {
256 unsafe { sys::CMS_is_detached(self.ptr) == 1 }
257 }
258
259 #[must_use]
261 pub fn content(&self) -> Option<Vec<u8>> {
262 let pp = unsafe { sys::CMS_get0_content(self.ptr) };
266 if pp.is_null() {
267 return None;
268 }
269 let ostr = unsafe { *pp };
270 if ostr.is_null() {
271 return None;
272 }
273 let data_ptr = unsafe { sys::ASN1_STRING_get0_data(ostr.cast()) };
275 let data_len = usize::try_from(unsafe { sys::ASN1_STRING_length(ostr.cast()) }).ok()?;
276 if data_ptr.is_null() || data_len == 0 {
277 return None;
278 }
279 let slice = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
280 Some(slice.to_vec())
281 }
282
283 #[must_use]
285 pub fn signers(&self) -> Vec<CmsSignerInfo<'_>> {
286 let stack = unsafe { sys::CMS_get0_SignerInfos(self.ptr) };
288 if stack.is_null() {
289 return Vec::new();
290 }
291 let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
293 let count = usize::try_from(n.max(0)).unwrap_or(0);
294 let mut out = Vec::with_capacity(count);
295 for i in 0..n {
296 let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
297 if !raw.is_null() {
298 out.push(CmsSignerInfo {
299 ptr: raw.cast::<sys::CMS_SignerInfo>(),
300 _marker: PhantomData,
301 });
302 }
303 }
304 out
305 }
306
307 #[must_use]
312 pub fn certs(&self) -> Vec<X509> {
313 let stack = unsafe { sys::CMS_get1_certs(self.ptr) };
314 collect_x509_stack(stack)
315 }
316
317 #[must_use]
319 pub fn crls(&self) -> Vec<X509Crl> {
320 let stack = unsafe { sys::CMS_get1_crls(self.ptr) };
321 collect_x509_crl_stack(stack)
322 }
323
324 pub fn verify(
337 &self,
338 store: &X509Store,
339 certs: &[X509],
340 flags: CmsVerifyFlags,
341 ) -> Result<Vec<u8>, ErrorStack> {
342 let certs_stack = build_x509_stack(certs)?;
343 let mut out_bio = MemBio::new()?;
344
345 let rc = unsafe {
348 sys::CMS_verify(
349 self.ptr,
350 certs_stack,
351 store.as_ptr(),
352 std::ptr::null_mut(), out_bio.as_ptr(),
354 flags.0,
355 )
356 };
357
358 if !certs_stack.is_null() {
360 unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
361 }
362
363 if rc != 1 {
364 return Err(ErrorStack::drain());
365 }
366 Ok(out_bio.into_vec())
367 }
368
369 pub fn sign(
380 cert: &X509,
381 key: &Pkey<Private>,
382 extra_certs: &[X509],
383 content: &[u8],
384 flags: CmsSignFlags,
385 ) -> Result<Self, ErrorStack> {
386 let data_bio = MemBioBuf::new(content)?;
387 let certs_stack = build_x509_stack(extra_certs)?;
388
389 let ptr = unsafe {
391 sys::CMS_sign(
392 cert.as_ptr(),
393 key.as_ptr(),
394 certs_stack,
395 data_bio.as_ptr(),
396 flags.0,
397 )
398 };
399
400 if !certs_stack.is_null() {
401 unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
402 }
403
404 if ptr.is_null() {
405 Err(ErrorStack::drain())
406 } else {
407 Ok(CmsContentInfo { ptr })
408 }
409 }
410
411 pub fn sign_in(
417 ctx: &Arc<crate::lib_ctx::LibCtx>,
418 cert: &X509,
419 key: &Pkey<Private>,
420 extra_certs: &[X509],
421 content: &[u8],
422 flags: CmsSignFlags,
423 ) -> Result<Self, ErrorStack> {
424 let data_bio = MemBioBuf::new(content)?;
425 let certs_stack = build_x509_stack(extra_certs)?;
426
427 let ptr = unsafe {
428 sys::CMS_sign_ex(
429 cert.as_ptr(),
430 key.as_ptr(),
431 certs_stack,
432 data_bio.as_ptr(),
433 flags.0,
434 ctx.as_ptr(),
435 std::ptr::null(),
436 )
437 };
438
439 if !certs_stack.is_null() {
440 unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
441 }
442
443 if ptr.is_null() {
444 Err(ErrorStack::drain())
445 } else {
446 Ok(CmsContentInfo { ptr })
447 }
448 }
449}
450
451fn build_x509_stack(certs: &[X509]) -> Result<*mut sys::stack_st_X509, ErrorStack> {
459 if certs.is_empty() {
460 return Ok(std::ptr::null_mut());
461 }
462 let stack = unsafe { sys::OPENSSL_sk_new_null() };
463 if stack.is_null() {
464 return Err(ErrorStack::drain());
465 }
466 for cert in certs {
467 unsafe { sys::OPENSSL_sk_push(stack, cert.as_ptr().cast()) };
468 }
469 Ok(stack.cast())
470}
471
472fn collect_x509_stack(stack: *mut sys::stack_st_X509) -> Vec<X509> {
479 if stack.is_null() {
480 return Vec::new();
481 }
482 let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
484 let count = usize::try_from(n.max(0)).unwrap_or(0);
485 let mut out = Vec::with_capacity(count);
486 for i in 0..n {
487 let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
488 if !raw.is_null() {
489 out.push(unsafe { X509::from_ptr(raw.cast()) });
491 }
492 }
493 unsafe { sys::OPENSSL_sk_free(stack.cast()) };
495 out
496}
497
498fn collect_x509_crl_stack(stack: *mut sys::stack_st_X509_CRL) -> Vec<X509Crl> {
501 if stack.is_null() {
502 return Vec::new();
503 }
504 let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
505 let count = usize::try_from(n.max(0)).unwrap_or(0);
506 let mut out = Vec::with_capacity(count);
507 for i in 0..n {
508 let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
509 if !raw.is_null() {
510 out.push(unsafe { X509Crl::from_ptr(raw.cast()) });
512 }
513 }
514 unsafe { sys::OPENSSL_sk_free(stack.cast()) };
515 out
516}
517
518#[cfg(test)]
521mod tests {
522 use super::*;
523 use crate::obj::Oid;
524 use crate::params::ParamBuilder;
525 use crate::pkey::KeygenCtx;
526 use crate::x509::{X509Builder, X509NameOwned};
527
528 fn make_cert_and_key() -> (X509, Pkey<Private>) {
529 let priv_key = (|| -> Result<Pkey<Private>, ErrorStack> {
531 let mut kg = KeygenCtx::new(c"EC")?;
532 let params = ParamBuilder::new()?
533 .push_utf8_string(c"group", c"P-256")?
534 .build()?;
535 kg.set_params(¶ms)?;
536 kg.generate()
537 })()
538 .expect("EC P-256 keygen");
539
540 let pub_key: crate::pkey::Pkey<crate::pkey::Public> = priv_key.clone().into();
541 let mut name = X509NameOwned::new().expect("X509NameOwned::new");
542 name.add_entry_by_txt(c"CN", b"cms-test").expect("CN");
543
544 let cert = (|| -> Result<X509, ErrorStack> {
545 Ok(X509Builder::new()?
546 .set_version(2)?
547 .set_serial_number(1)?
548 .set_not_before_offset(0)?
549 .set_not_after_offset(365 * 86400)?
550 .set_subject_name(&name)?
551 .set_issuer_name(&name)?
552 .set_public_key(&pub_key)?
553 .sign(&priv_key, None)?
554 .build())
555 })()
556 .expect("X509Builder");
557
558 (cert, priv_key)
559 }
560
561 #[test]
562 fn sign_and_verify_roundtrip() {
563 let (cert, key) = make_cert_and_key();
564 let payload = b"native-ossl cms test payload";
565
566 let signed = CmsContentInfo::sign(
567 &cert,
568 &key,
569 &[],
570 payload,
571 CmsSignFlags::BINARY | CmsSignFlags::NOSMIMECAP,
572 )
573 .expect("CMS_sign");
574
575 let der = signed.to_der().expect("to_der");
576 assert!(!der.is_empty());
577
578 let parsed = CmsContentInfo::from_der(&der).expect("from_der");
579
580 let mut store = X509Store::new().expect("X509Store");
581 store.add_cert(&cert).expect("add_cert");
582
583 let content = parsed
584 .verify(&store, &[], CmsVerifyFlags::NONE)
585 .expect("verify");
586 assert_eq!(content, payload);
587 }
588
589 #[test]
590 fn content_type_oid_is_signed_data() {
591 let (cert, key) = make_cert_and_key();
592 let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
593 assert_eq!(cms.content_type_oid().to_string(), "1.2.840.113549.1.7.2");
594 }
595
596 #[test]
597 fn econtent_type_oid_is_data() {
598 let (cert, key) = make_cert_and_key();
599 let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
600 assert_eq!(cms.econtent_type_oid().to_string(), "1.2.840.113549.1.7.1");
601 }
602
603 #[test]
604 fn embedded_signer_cert() {
605 let (cert, key) = make_cert_and_key();
606 let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
607 let signers = cms.signers();
608 assert_eq!(signers.len(), 1);
609 let signer_cert = signers[0].signer_cert().expect("signer cert present");
610 assert_eq!(cert.to_der().unwrap(), signer_cert.to_der().unwrap());
611 }
612
613 #[test]
614 fn certs_list_contains_embedded_cert() {
615 let (cert, key) = make_cert_and_key();
616 let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
617 assert!(!cms.certs().is_empty());
618 }
619
620 #[test]
621 fn nocerts_flag_omits_embedded_certs() {
622 let (cert, key) = make_cert_and_key();
623 let cms =
624 CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NOCERTS).expect("sign");
625 assert!(
626 cms.certs().is_empty(),
627 "NOCERTS should produce no embedded certs"
628 );
629 }
630
631 #[test]
632 fn verify_fails_with_wrong_trust_anchor() {
633 let (cert, key) = make_cert_and_key();
634 let (other_cert, _) = make_cert_and_key();
635
636 let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::BINARY).expect("sign");
637 let der = cms.to_der().expect("to_der");
638 let parsed = CmsContentInfo::from_der(&der).expect("from_der");
639
640 let mut store = X509Store::new().expect("store");
641 store.add_cert(&other_cert).expect("add_cert");
642
643 assert!(
644 parsed.verify(&store, &[], CmsVerifyFlags::NONE).is_err(),
645 "verify with wrong trust anchor must fail"
646 );
647 }
648
649 #[test]
650 fn content_accessor_returns_payload() {
651 let (cert, key) = make_cert_and_key();
652 let payload = b"detectable content";
653 let cms =
654 CmsContentInfo::sign(&cert, &key, &[], payload, CmsSignFlags::BINARY).expect("sign");
655 let content = cms.content().expect("content present");
656 assert_eq!(content, payload);
657 }
658
659 #[test]
660 fn is_detached_flag() {
661 let (cert, key) = make_cert_and_key();
662 let embedded =
663 CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
664 let detached =
665 CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::DETACHED).expect("sign");
666 assert!(!embedded.is_detached());
667 assert!(detached.is_detached());
668 }
669
670 #[test]
671 fn oid_comparison_econtent() {
672 let (cert, key) = make_cert_and_key();
673 let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
674 let data_oid = Oid::from_text("1.2.840.113549.1.7.1").expect("data oid");
675 assert_eq!(cms.econtent_type_oid(), data_oid);
676 }
677}