1use bitflags::bitflags;
9use foreign_types::{ForeignType, ForeignTypeRef};
10use libc::c_uint;
11use std::ptr;
12
13use crate::bio::{MemBio, MemBioSlice};
14use crate::error::ErrorStack;
15use crate::pkey::{HasPrivate, PKeyRef};
16use crate::stack::StackRef;
17use crate::symm::Cipher;
18use crate::x509::{store::X509StoreRef, X509Ref, X509};
19use crate::{cvt, cvt_p};
20use openssl_macros::corresponds;
21
22bitflags! {
23 #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24 #[repr(transparent)]
25 pub struct CMSOptions : c_uint {
26 const TEXT = ffi::CMS_TEXT;
27 const CMS_NOCERTS = ffi::CMS_NOCERTS;
28 const NO_CONTENT_VERIFY = ffi::CMS_NO_CONTENT_VERIFY;
29 const NO_ATTR_VERIFY = ffi::CMS_NO_ATTR_VERIFY;
30 const NOSIGS = ffi::CMS_NOSIGS;
31 const NOINTERN = ffi::CMS_NOINTERN;
32 const NO_SIGNER_CERT_VERIFY = ffi::CMS_NO_SIGNER_CERT_VERIFY;
33 const NOVERIFY = ffi::CMS_NOVERIFY;
34 const DETACHED = ffi::CMS_DETACHED;
35 const BINARY = ffi::CMS_BINARY;
36 const NOATTR = ffi::CMS_NOATTR;
37 const NOSMIMECAP = ffi::CMS_NOSMIMECAP;
38 const NOOLDMIMETYPE = ffi::CMS_NOOLDMIMETYPE;
39 const CRLFEOL = ffi::CMS_CRLFEOL;
40 const STREAM = ffi::CMS_STREAM;
41 const NOCRL = ffi::CMS_NOCRL;
42 const PARTIAL = ffi::CMS_PARTIAL;
43 const REUSE_DIGEST = ffi::CMS_REUSE_DIGEST;
44 const USE_KEYID = ffi::CMS_USE_KEYID;
45 const DEBUG_DECRYPT = ffi::CMS_DEBUG_DECRYPT;
46 const KEY_PARAM = ffi::CMS_KEY_PARAM;
47 const ASCIICRLF = ffi::CMS_ASCIICRLF;
48 }
49}
50
51foreign_type_and_impl_send_sync! {
52 type CType = ffi::CMS_ContentInfo;
53 fn drop = ffi::CMS_ContentInfo_free;
54
55 pub struct CmsContentInfo;
64 pub struct CmsContentInfoRef;
68}
69
70impl CmsContentInfoRef {
71 #[corresponds(CMS_decrypt)]
74 pub fn decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack>
75 where
76 T: HasPrivate,
77 {
78 unsafe {
79 let pkey = pkey.as_ptr();
80 let cert = cert.as_ptr();
81 let out = MemBio::new()?;
82
83 cvt(ffi::CMS_decrypt(
84 self.as_ptr(),
85 pkey,
86 cert,
87 ptr::null_mut(),
88 out.as_ptr(),
89 0,
90 ))?;
91
92 Ok(out.get_buf().to_owned())
93 }
94 }
95
96 #[corresponds(CMS_decrypt)]
101 pub fn decrypt_without_cert_check<T>(&self, pkey: &PKeyRef<T>) -> Result<Vec<u8>, ErrorStack>
103 where
104 T: HasPrivate,
105 {
106 unsafe {
107 let pkey = pkey.as_ptr();
108 let out = MemBio::new()?;
109
110 cvt(ffi::CMS_decrypt(
111 self.as_ptr(),
112 pkey,
113 ptr::null_mut(),
114 ptr::null_mut(),
115 out.as_ptr(),
116 0,
117 ))?;
118
119 Ok(out.get_buf().to_owned())
120 }
121 }
122
123 to_der! {
124 #[corresponds(i2d_CMS_ContentInfo)]
126 to_der,
127 ffi::i2d_CMS_ContentInfo
128 }
129
130 to_pem! {
131 #[corresponds(PEM_write_bio_CMS)]
133 to_pem,
134 ffi::PEM_write_bio_CMS
135 }
136}
137
138impl CmsContentInfo {
139 #[corresponds(SMIME_read_CMS)]
141 pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> {
142 unsafe {
143 let bio = MemBioSlice::new(smime)?;
144
145 let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?;
146
147 Ok(CmsContentInfo::from_ptr(cms))
148 }
149 }
150
151 from_der! {
152 #[corresponds(d2i_CMS_ContentInfo)]
154 from_der,
155 CmsContentInfo,
156 ffi::d2i_CMS_ContentInfo
157 }
158
159 from_pem! {
160 #[corresponds(PEM_read_bio_CMS)]
162 from_pem,
163 CmsContentInfo,
164 ffi::PEM_read_bio_CMS
165 }
166
167 #[corresponds(CMS_sign)]
172 pub fn sign<T>(
173 signcert: Option<&X509Ref>,
174 pkey: Option<&PKeyRef<T>>,
175 certs: Option<&StackRef<X509>>,
176 data: Option<&[u8]>,
177 flags: CMSOptions,
178 ) -> Result<CmsContentInfo, ErrorStack>
179 where
180 T: HasPrivate,
181 {
182 unsafe {
183 let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr());
184 let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr());
185 let data_bio = match data {
186 Some(data) => Some(MemBioSlice::new(data)?),
187 None => None,
188 };
189 let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr());
190 let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
191
192 let cms = cvt_p(ffi::CMS_sign(
193 signcert,
194 pkey,
195 certs,
196 data_bio_ptr,
197 flags.bits(),
198 ))?;
199
200 Ok(CmsContentInfo::from_ptr(cms))
201 }
202 }
203
204 #[corresponds(CMS_encrypt)]
208 pub fn encrypt(
209 certs: &StackRef<X509>,
210 data: &[u8],
211 cipher: Cipher,
212 flags: CMSOptions,
213 ) -> Result<CmsContentInfo, ErrorStack> {
214 unsafe {
215 let data_bio = MemBioSlice::new(data)?;
216
217 let cms = cvt_p(ffi::CMS_encrypt(
218 certs.as_ptr(),
219 data_bio.as_ptr(),
220 cipher.as_ptr(),
221 flags.bits(),
222 ))?;
223
224 Ok(CmsContentInfo::from_ptr(cms))
225 }
226 }
227
228 #[corresponds(CMS_verify)]
236 pub fn verify(
237 &mut self,
238 certs: Option<&StackRef<X509>>,
239 store: Option<&X509StoreRef>,
240 detached_data: Option<&[u8]>,
241 output_data: Option<&mut Vec<u8>>,
242 flags: CMSOptions,
243 ) -> Result<(), ErrorStack> {
244 unsafe {
245 let certs_ptr = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
246 let store_ptr = store.map_or(ptr::null_mut(), |p| p.as_ptr());
247 let detached_data_bio = match detached_data {
248 Some(data) => Some(MemBioSlice::new(data)?),
249 None => None,
250 };
251 let detached_data_bio_ptr = detached_data_bio
252 .as_ref()
253 .map_or(ptr::null_mut(), |p| p.as_ptr());
254 let out_bio = MemBio::new()?;
255
256 cvt(ffi::CMS_verify(
257 self.as_ptr(),
258 certs_ptr,
259 store_ptr,
260 detached_data_bio_ptr,
261 out_bio.as_ptr(),
262 flags.bits(),
263 ))?;
264
265 if let Some(data) = output_data {
266 data.clear();
267 data.extend_from_slice(out_bio.get_buf());
268 };
269
270 Ok(())
271 }
272 }
273}
274
275#[cfg(test)]
276mod test {
277 use super::*;
278
279 use crate::pkcs12::Pkcs12;
280 use crate::pkey::PKey;
281 use crate::stack::Stack;
282 use crate::x509::{
283 store::{X509Store, X509StoreBuilder},
284 X509,
285 };
286
287 #[test]
288 fn cms_encrypt_decrypt() {
289 #[cfg(ossl300)]
290 let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
291
292 let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der");
294 let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert");
295
296 let priv_cert_bytes = include_bytes!("../test/cms.p12");
298 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
299 let priv_cert = priv_cert
300 .parse2("mypass")
301 .expect("failed to parse priv cert");
302
303 let input = String::from("My Message");
305 let mut cert_stack = Stack::new().expect("failed to create stack");
306 cert_stack
307 .push(pub_cert)
308 .expect("failed to add pub cert to stack");
309
310 let encrypt = CmsContentInfo::encrypt(
311 &cert_stack,
312 input.as_bytes(),
313 Cipher::des_ede3_cbc(),
314 CMSOptions::empty(),
315 )
316 .expect("failed create encrypted cms");
317
318 {
320 let encrypted_der = encrypt.to_der().expect("failed to create der from cms");
321 let decrypt =
322 CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der");
323
324 let decrypt_with_cert_check = decrypt
325 .decrypt(
326 priv_cert.pkey.as_ref().unwrap(),
327 priv_cert.cert.as_ref().unwrap(),
328 )
329 .expect("failed to decrypt cms");
330 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
331 .expect("failed to create string from cms content");
332
333 let decrypt_without_cert_check = decrypt
334 .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
335 .expect("failed to decrypt cms");
336 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
337 .expect("failed to create string from cms content");
338
339 assert_eq!(input, decrypt_with_cert_check);
340 assert_eq!(input, decrypt_without_cert_check);
341 }
342
343 {
345 let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms");
346 let decrypt =
347 CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem");
348
349 let decrypt_with_cert_check = decrypt
350 .decrypt(
351 priv_cert.pkey.as_ref().unwrap(),
352 priv_cert.cert.as_ref().unwrap(),
353 )
354 .expect("failed to decrypt cms");
355 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
356 .expect("failed to create string from cms content");
357
358 let decrypt_without_cert_check = decrypt
359 .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
360 .expect("failed to decrypt cms");
361 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
362 .expect("failed to create string from cms content");
363
364 assert_eq!(input, decrypt_with_cert_check);
365 assert_eq!(input, decrypt_without_cert_check);
366 }
367 }
368
369 fn cms_sign_verify_generic_helper(is_detached: bool) {
370 let cert_bytes = include_bytes!("../test/cert.pem");
372 let cert = X509::from_pem(cert_bytes).expect("failed to load cert.pem");
373
374 let key_bytes = include_bytes!("../test/key.pem");
375 let key = PKey::private_key_from_pem(key_bytes).expect("failed to load key.pem");
376
377 let root_bytes = include_bytes!("../test/root-ca.pem");
378 let root = X509::from_pem(root_bytes).expect("failed to load root-ca.pem");
379
380 let data = b"Hello world!";
382
383 let (opt, ext_data): (CMSOptions, Option<&[u8]>) = if is_detached {
384 (CMSOptions::DETACHED | CMSOptions::BINARY, Some(data))
385 } else {
386 (CMSOptions::empty(), None)
387 };
388
389 let mut cms = CmsContentInfo::sign(Some(&cert), Some(&key), None, Some(data), opt)
390 .expect("failed to CMS sign a message");
391
392 let pem_cms = cms
394 .to_pem()
395 .expect("failed to pack CmsContentInfo into PEM");
396 assert!(!pem_cms.is_empty());
397
398 let mut builder = X509StoreBuilder::new().expect("failed to create X509StoreBuilder");
400 builder
401 .add_cert(root)
402 .expect("failed to add root-ca into X509StoreBuilder");
403 let store: X509Store = builder.build();
404 let mut out_data: Vec<u8> = Vec::new();
405 let res = cms.verify(
406 None,
407 Some(&store),
408 ext_data,
409 Some(&mut out_data),
410 CMSOptions::empty(),
411 );
412
413 res.unwrap();
415 assert_eq!(data.to_vec(), out_data);
416 }
417
418 #[test]
419 fn cms_sign_verify_ok() {
420 cms_sign_verify_generic_helper(false);
421 }
422
423 #[test]
424 fn cms_sign_verify_detached_ok() {
425 cms_sign_verify_generic_helper(true);
426 }
427
428 #[test]
429 fn cms_sign_verify_error() {
430 #[cfg(ossl300)]
431 let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
432
433 let priv_cert_bytes = include_bytes!("../test/cms.p12");
435 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
436 let priv_cert = priv_cert
437 .parse2("mypass")
438 .expect("failed to parse priv cert");
439
440 let data = b"Hello world!";
442 let mut cms = CmsContentInfo::sign(
443 Some(&priv_cert.cert.unwrap()),
444 Some(&priv_cert.pkey.unwrap()),
445 None,
446 Some(data),
447 CMSOptions::empty(),
448 )
449 .expect("failed to CMS sign a message");
450
451 let pem_cms = cms
453 .to_pem()
454 .expect("failed to pack CmsContentInfo into PEM");
455 assert!(!pem_cms.is_empty());
456
457 let empty_store = X509StoreBuilder::new()
458 .expect("failed to create X509StoreBuilder")
459 .build();
460
461 let res = cms.verify(
463 None,
464 Some(&empty_store),
465 Some(data),
466 None,
467 CMSOptions::empty(),
468 );
469
470 const CMS_R_CERTIFICATE_VERIFY_ERROR: i32 = 100;
473 let es = res.unwrap_err();
474 let error_array = es.errors();
475 assert_eq!(1, error_array.len());
476 let code = error_array[0].reason_code();
477 assert_eq!(code, CMS_R_CERTIFICATE_VERIFY_ERROR);
478 }
479}