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 #[cfg(not(any(tongsuo, feature = "vendored")))]
280 use crate::pkcs12::Pkcs12;
281 use crate::pkey::PKey;
282 #[cfg(not(any(tongsuo, feature = "vendored")))]
283 use crate::stack::Stack;
284 use crate::x509::{
285 store::{X509Store, X509StoreBuilder},
286 X509,
287 };
288
289 #[test]
290 #[cfg(not(any(tongsuo, feature = "vendored")))]
291 fn cms_encrypt_decrypt() {
292 #[cfg(ossl300)]
293 let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
294
295 let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der");
297 let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert");
298
299 let priv_cert_bytes = include_bytes!("../test/cms.p12");
301 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
302 let priv_cert = priv_cert
303 .parse2("mypass")
304 .expect("failed to parse priv cert");
305
306 let input = String::from("My Message");
308 let mut cert_stack = Stack::new().expect("failed to create stack");
309 cert_stack
310 .push(pub_cert)
311 .expect("failed to add pub cert to stack");
312
313 let encrypt = CmsContentInfo::encrypt(
314 &cert_stack,
315 input.as_bytes(),
316 Cipher::des_ede3_cbc(),
317 CMSOptions::empty(),
318 )
319 .expect("failed create encrypted cms");
320
321 {
323 let encrypted_der = encrypt.to_der().expect("failed to create der from cms");
324 let decrypt =
325 CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der");
326
327 let decrypt_with_cert_check = decrypt
328 .decrypt(
329 priv_cert.pkey.as_ref().unwrap(),
330 priv_cert.cert.as_ref().unwrap(),
331 )
332 .expect("failed to decrypt cms");
333 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
334 .expect("failed to create string from cms content");
335
336 let decrypt_without_cert_check = decrypt
337 .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
338 .expect("failed to decrypt cms");
339 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
340 .expect("failed to create string from cms content");
341
342 assert_eq!(input, decrypt_with_cert_check);
343 assert_eq!(input, decrypt_without_cert_check);
344 }
345
346 {
348 let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms");
349 let decrypt =
350 CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem");
351
352 let decrypt_with_cert_check = decrypt
353 .decrypt(
354 priv_cert.pkey.as_ref().unwrap(),
355 priv_cert.cert.as_ref().unwrap(),
356 )
357 .expect("failed to decrypt cms");
358 let decrypt_with_cert_check = String::from_utf8(decrypt_with_cert_check)
359 .expect("failed to create string from cms content");
360
361 let decrypt_without_cert_check = decrypt
362 .decrypt_without_cert_check(priv_cert.pkey.as_ref().unwrap())
363 .expect("failed to decrypt cms");
364 let decrypt_without_cert_check = String::from_utf8(decrypt_without_cert_check)
365 .expect("failed to create string from cms content");
366
367 assert_eq!(input, decrypt_with_cert_check);
368 assert_eq!(input, decrypt_without_cert_check);
369 }
370 }
371
372 fn cms_sign_verify_generic_helper(is_detached: bool) {
373 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 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 let pem_cms = cms
397 .to_pem()
398 .expect("failed to pack CmsContentInfo into PEM");
399 assert!(!pem_cms.is_empty());
400
401 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 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 #[cfg(not(any(tongsuo, feature = "vendored")))]
433 fn cms_sign_verify_error() {
434 #[cfg(ossl300)]
435 let _provider = crate::provider::Provider::try_load(None, "legacy", true).unwrap();
436
437 let priv_cert_bytes = include_bytes!("../test/cms.p12");
439 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
440 let priv_cert = priv_cert
441 .parse2("mypass")
442 .expect("failed to parse priv cert");
443
444 let data = b"Hello world!";
446 let mut cms = CmsContentInfo::sign(
447 Some(&priv_cert.cert.unwrap()),
448 Some(&priv_cert.pkey.unwrap()),
449 None,
450 Some(data),
451 CMSOptions::empty(),
452 )
453 .expect("failed to CMS sign a message");
454
455 let pem_cms = cms
457 .to_pem()
458 .expect("failed to pack CmsContentInfo into PEM");
459 assert!(!pem_cms.is_empty());
460
461 let empty_store = X509StoreBuilder::new()
462 .expect("failed to create X509StoreBuilder")
463 .build();
464
465 let res = cms.verify(
467 None,
468 Some(&empty_store),
469 Some(data),
470 None,
471 CMSOptions::empty(),
472 );
473
474 const CMS_R_CERTIFICATE_VERIFY_ERROR: i32 = 100;
477 let es = res.unwrap_err();
478 let error_array = es.errors();
479 assert_eq!(1, error_array.len());
480 let code = error_array[0].reason_code();
481 assert_eq!(code, CMS_R_CERTIFICATE_VERIFY_ERROR);
482 }
483}