]> git.proxmox.com Git - cargo.git/blob - vendor/openssl/src/cms.rs
New upstream version 0.47.0
[cargo.git] / vendor / openssl / src / cms.rs
1 //! SMIME implementation using CMS
2 //!
3 //! CMS (PKCS#7) is an encyption standard. It allows signing and ecrypting data using
4 //! X.509 certificates. The OpenSSL implementation of CMS is used in email encryption
5 //! generated from a `Vec` of bytes. This `Vec` follows the smime protocol standards.
6 //! Data accepted by this module will be smime type `enveloped-data`.
7
8 use ffi;
9 use foreign_types::{ForeignType, ForeignTypeRef};
10 use std::ptr;
11
12 use bio::{MemBio, MemBioSlice};
13 use error::ErrorStack;
14 use libc::c_uint;
15 use pkey::{HasPrivate, PKeyRef};
16 use stack::StackRef;
17 use symm::Cipher;
18 use x509::{X509Ref, X509};
19 use {cvt, cvt_p};
20
21 bitflags! {
22 pub struct CMSOptions : c_uint {
23 const TEXT = ffi::CMS_TEXT;
24 const CMS_NOCERTS = ffi::CMS_NOCERTS;
25 const NO_CONTENT_VERIFY = ffi::CMS_NO_CONTENT_VERIFY;
26 const NO_ATTR_VERIFY = ffi::CMS_NO_ATTR_VERIFY;
27 const NOSIGS = ffi::CMS_NOSIGS;
28 const NOINTERN = ffi::CMS_NOINTERN;
29 const NO_SIGNER_CERT_VERIFY = ffi::CMS_NO_SIGNER_CERT_VERIFY;
30 const NOVERIFY = ffi::CMS_NOVERIFY;
31 const DETACHED = ffi::CMS_DETACHED;
32 const BINARY = ffi::CMS_BINARY;
33 const NOATTR = ffi::CMS_NOATTR;
34 const NOSMIMECAP = ffi::CMS_NOSMIMECAP;
35 const NOOLDMIMETYPE = ffi::CMS_NOOLDMIMETYPE;
36 const CRLFEOL = ffi::CMS_CRLFEOL;
37 const STREAM = ffi::CMS_STREAM;
38 const NOCRL = ffi::CMS_NOCRL;
39 const PARTIAL = ffi::CMS_PARTIAL;
40 const REUSE_DIGEST = ffi::CMS_REUSE_DIGEST;
41 const USE_KEYID = ffi::CMS_USE_KEYID;
42 const DEBUG_DECRYPT = ffi::CMS_DEBUG_DECRYPT;
43 #[cfg(all(not(libressl), not(ossl101)))]
44 const KEY_PARAM = ffi::CMS_KEY_PARAM;
45 #[cfg(all(not(libressl), not(ossl101), not(ossl102)))]
46 const ASCIICRLF = ffi::CMS_ASCIICRLF;
47 }
48 }
49
50 foreign_type_and_impl_send_sync! {
51 type CType = ffi::CMS_ContentInfo;
52 fn drop = ffi::CMS_ContentInfo_free;
53
54 /// High level CMS wrapper
55 ///
56 /// CMS supports nesting various types of data, including signatures, certificates,
57 /// encrypted data, smime messages (encrypted email), and data digest. The ContentInfo
58 /// content type is the encapsulation of all those content types. [`RFC 5652`] describes
59 /// CMS and OpenSSL follows this RFC's implmentation.
60 ///
61 /// [`RFC 5652`]: https://tools.ietf.org/html/rfc5652#page-6
62 pub struct CmsContentInfo;
63 /// Reference to [`CMSContentInfo`]
64 ///
65 /// [`CMSContentInfo`]:struct.CmsContentInfo.html
66 pub struct CmsContentInfoRef;
67 }
68
69 impl CmsContentInfoRef {
70 /// Given the sender's private key, `pkey` and the recipient's certificiate, `cert`,
71 /// decrypt the data in `self`.
72 ///
73 /// OpenSSL documentation at [`CMS_decrypt`]
74 ///
75 /// [`CMS_decrypt`]: https://www.openssl.org/docs/man1.1.0/crypto/CMS_decrypt.html
76 pub fn decrypt<T>(&self, pkey: &PKeyRef<T>, cert: &X509) -> Result<Vec<u8>, ErrorStack>
77 where
78 T: HasPrivate,
79 {
80 unsafe {
81 let pkey = pkey.as_ptr();
82 let cert = cert.as_ptr();
83 let out = MemBio::new()?;
84
85 cvt(ffi::CMS_decrypt(
86 self.as_ptr(),
87 pkey,
88 cert,
89 ptr::null_mut(),
90 out.as_ptr(),
91 0,
92 ))?;
93
94 Ok(out.get_buf().to_owned())
95 }
96 }
97
98 to_der! {
99 /// Serializes this CmsContentInfo using DER.
100 ///
101 /// OpenSSL documentation at [`i2d_CMS_ContentInfo`]
102 ///
103 /// [`i2d_CMS_ContentInfo`]: https://www.openssl.org/docs/man1.0.2/crypto/i2d_CMS_ContentInfo.html
104 to_der,
105 ffi::i2d_CMS_ContentInfo
106 }
107
108 to_pem! {
109 /// Serializes this CmsContentInfo using DER.
110 ///
111 /// OpenSSL documentation at [`PEM_write_bio_CMS`]
112 ///
113 /// [`PEM_write_bio_CMS`]: https://www.openssl.org/docs/man1.1.0/man3/PEM_write_bio_CMS.html
114 to_pem,
115 ffi::PEM_write_bio_CMS
116 }
117 }
118
119 impl CmsContentInfo {
120 /// Parses a smime formatted `vec` of bytes into a `CmsContentInfo`.
121 ///
122 /// OpenSSL documentation at [`SMIME_read_CMS`]
123 ///
124 /// [`SMIME_read_CMS`]: https://www.openssl.org/docs/man1.0.2/crypto/SMIME_read_CMS.html
125 pub fn smime_read_cms(smime: &[u8]) -> Result<CmsContentInfo, ErrorStack> {
126 unsafe {
127 let bio = MemBioSlice::new(smime)?;
128
129 let cms = cvt_p(ffi::SMIME_read_CMS(bio.as_ptr(), ptr::null_mut()))?;
130
131 Ok(CmsContentInfo::from_ptr(cms))
132 }
133 }
134
135 from_der! {
136 /// Deserializes a DER-encoded ContentInfo structure.
137 ///
138 /// This corresponds to [`d2i_CMS_ContentInfo`].
139 ///
140 /// [`d2i_CMS_ContentInfo`]: https://www.openssl.org/docs/manmaster/man3/d2i_X509.html
141 from_der,
142 CmsContentInfo,
143 ffi::d2i_CMS_ContentInfo
144 }
145
146 from_pem! {
147 /// Deserializes a PEM-encoded ContentInfo structure.
148 ///
149 /// This corresponds to [`PEM_read_bio_CMS`].
150 ///
151 /// [`PEM_read_bio_CMS`]: https://www.openssl.org/docs/man1.1.0/man3/PEM_read_bio_CMS.html
152 from_pem,
153 CmsContentInfo,
154 ffi::PEM_read_bio_CMS
155 }
156
157 /// Given a signing cert `signcert`, private key `pkey`, a certificate stack `certs`,
158 /// data `data` and flags `flags`, create a CmsContentInfo struct.
159 ///
160 /// All arguments are optional.
161 ///
162 /// OpenSSL documentation at [`CMS_sign`]
163 ///
164 /// [`CMS_sign`]: https://www.openssl.org/docs/manmaster/man3/CMS_sign.html
165 pub fn sign<T>(
166 signcert: Option<&X509Ref>,
167 pkey: Option<&PKeyRef<T>>,
168 certs: Option<&StackRef<X509>>,
169 data: Option<&[u8]>,
170 flags: CMSOptions,
171 ) -> Result<CmsContentInfo, ErrorStack>
172 where
173 T: HasPrivate,
174 {
175 unsafe {
176 let signcert = signcert.map_or(ptr::null_mut(), |p| p.as_ptr());
177 let pkey = pkey.map_or(ptr::null_mut(), |p| p.as_ptr());
178 let data_bio = match data {
179 Some(data) => Some(MemBioSlice::new(data)?),
180 None => None,
181 };
182 let data_bio_ptr = data_bio.as_ref().map_or(ptr::null_mut(), |p| p.as_ptr());
183 let certs = certs.map_or(ptr::null_mut(), |p| p.as_ptr());
184
185 let cms = cvt_p(ffi::CMS_sign(
186 signcert,
187 pkey,
188 certs,
189 data_bio_ptr,
190 flags.bits(),
191 ))?;
192
193 Ok(CmsContentInfo::from_ptr(cms))
194 }
195 }
196
197 /// Given a certificate stack `certs`, data `data`, cipher `cipher` and flags `flags`,
198 /// create a CmsContentInfo struct.
199 ///
200 /// OpenSSL documentation at [`CMS_encrypt`]
201 ///
202 /// [`CMS_encrypt`]: https://www.openssl.org/docs/manmaster/man3/CMS_encrypt.html
203 pub fn encrypt(
204 certs: &StackRef<X509>,
205 data: &[u8],
206 cipher: Cipher,
207 flags: CMSOptions,
208 ) -> Result<CmsContentInfo, ErrorStack> {
209 unsafe {
210 let data_bio = MemBioSlice::new(data)?;
211
212 let cms = cvt_p(ffi::CMS_encrypt(
213 certs.as_ptr(),
214 data_bio.as_ptr(),
215 cipher.as_ptr(),
216 flags.bits(),
217 ))?;
218
219 Ok(CmsContentInfo::from_ptr(cms))
220 }
221 }
222 }
223
224 #[cfg(test)]
225 mod test {
226 use super::*;
227 use pkcs12::Pkcs12;
228 use stack::Stack;
229 use x509::X509;
230
231 #[test]
232 fn cms_encrypt_decrypt() {
233 // load cert with public key only
234 let pub_cert_bytes = include_bytes!("../test/cms_pubkey.der");
235 let pub_cert = X509::from_der(pub_cert_bytes).expect("failed to load pub cert");
236
237 // load cert with private key
238 let priv_cert_bytes = include_bytes!("../test/cms.p12");
239 let priv_cert = Pkcs12::from_der(priv_cert_bytes).expect("failed to load priv cert");
240 let priv_cert = priv_cert
241 .parse("mypass")
242 .expect("failed to parse priv cert");
243
244 // encrypt cms message using public key cert
245 let input = String::from("My Message");
246 let mut cert_stack = Stack::new().expect("failed to create stack");
247 cert_stack
248 .push(pub_cert)
249 .expect("failed to add pub cert to stack");
250
251 let encrypt = CmsContentInfo::encrypt(
252 &cert_stack,
253 &input.as_bytes(),
254 Cipher::des_ede3_cbc(),
255 CMSOptions::empty(),
256 )
257 .expect("failed create encrypted cms");
258
259 // decrypt cms message using private key cert (DER)
260 {
261 let encrypted_der = encrypt.to_der().expect("failed to create der from cms");
262 let decrypt =
263 CmsContentInfo::from_der(&encrypted_der).expect("failed read cms from der");
264 let decrypt = decrypt
265 .decrypt(&priv_cert.pkey, &priv_cert.cert)
266 .expect("failed to decrypt cms");
267 let decrypt =
268 String::from_utf8(decrypt).expect("failed to create string from cms content");
269 assert_eq!(input, decrypt);
270 }
271
272 // decrypt cms message using private key cert (PEM)
273 {
274 let encrypted_pem = encrypt.to_pem().expect("failed to create pem from cms");
275 let decrypt =
276 CmsContentInfo::from_pem(&encrypted_pem).expect("failed read cms from pem");
277 let decrypt = decrypt
278 .decrypt(&priv_cert.pkey, &priv_cert.cert)
279 .expect("failed to decrypt cms");
280 let decrypt =
281 String::from_utf8(decrypt).expect("failed to create string from cms content");
282 assert_eq!(input, decrypt);
283 }
284 }
285 }