Skip to main content

deboa_compio/
cert.rs

1//! Client certificate handling for secure connections.
2//!
3//! This module provides the `Identity` struct for working with client certificates
4//! in HTTPS connections, enabling mutual TLS (mTLS) authentication.
5//!
6//! It also provides the `Certificate` struct for working with CA certificates.
7
8#[cfg(feature = "native-tls")]
9use compio_tls::native_tls::{Certificate as NativeCertificate, Identity as NativeIdentity};
10#[cfg(feature = "rust-tls")]
11use deboa::cert::Certificate as _;
12#[cfg(feature = "native-tls")]
13use deboa::cert::{Certificate as _, IdentityNativeExt};
14use deboa::cert::{CertificateExt, ContentEncoding, IdentityExt};
15#[cfg(feature = "rust-tls")]
16use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
17
18/// Type alias for backward compatibility
19pub type Identity = DeboaIdentity;
20/// Type alias for backward compatibility
21pub type Certificate = DeboaCertificate;
22
23/// Represents a client certificate and its associated data for mutual TLS authentication.
24///
25/// `Identity` encapsulates the client certificate, its password.
26/// It's used to authenticate the client to the server during the
27/// TLS handshake.
28///
29/// # Examples
30///
31/// ```igmore
32/// use deboa::cert::Identity;
33///
34/// // Load a DER encoded PKCS#12 archive from a slice of bytes using a password
35/// let cert = Identity::from_pkcs12(
36///     &[1, 2, 3],
37///     Some("cert-password".to_string()),
38/// );
39///
40/// // Load a DER encoded certificate and key from a slice of bytes
41/// let cert_with_ca = Identity::from_pkcs8(
42///     &[1, 2, 3],
43///     &[4, 5, 6],
44///     ContentEncoding::DER,
45/// );
46///
47/// ```
48#[derive(Debug, Clone)]
49pub struct DeboaIdentity {
50    cert: Vec<u8>,
51    key: Option<Vec<u8>>,
52    #[allow(unused)]
53    password: Option<String>,
54    #[allow(unused)]
55    encoding: Option<ContentEncoding>,
56}
57
58#[cfg(feature = "native-tls")]
59impl IdentityNativeExt for DeboaIdentity {
60    /// Load a DER encoded PKCS#12 archive from a slice of bytes
61    ///
62    /// # Arguments
63    ///
64    /// * `bundle` - The DER encoded PKCS#12 archive.
65    /// * `password` - The password for the PKCS#12 archive.
66    ///
67    /// # Returns
68    ///
69    /// * `Identity` - The new Identity instance.
70    ///
71    fn from_pkcs12(bundle: &[u8], password: Option<String>) -> Self {
72        Identity { cert: bundle.to_vec(), key: None, password, encoding: None }
73    }
74
75    /// Load a DER encoded PKCS#12 archive from a file
76    ///
77    /// # Arguments
78    ///
79    /// * `file` - The path to the DER encoded PKCS#12 archive.
80    /// * `password` - The password for the PKCS#12 archive.
81    ///
82    /// # Returns
83    ///
84    /// * `Identity` - The new Identity instance.
85    ///
86    async fn from_pkcs12_file(file: &str, password: Option<String>) -> std::io::Result<Self> {
87        let data = compio::fs::read(file).await?;
88        Ok(Identity { cert: data, key: None, password, encoding: None })
89    }
90}
91
92impl deboa::cert::Identity for DeboaIdentity {
93    fn cert(&self) -> &Vec<u8> {
94        &self.cert
95    }
96
97    fn key(&self) -> &Option<Vec<u8>> {
98        &self.key
99    }
100
101    fn encoding(&self) -> &Option<ContentEncoding> {
102        &self.encoding
103    }
104}
105
106impl IdentityExt for DeboaIdentity {
107    /// Load DER encoded certificate and key from a slice of bytes
108    ///
109    /// # Arguments
110    ///
111    /// * `cert` - The DER encoded certificate.
112    /// * `key` - The DER encoded PKCS8 private key.
113    /// * `encoding` - The encoding of the certificate and key.
114    ///
115    /// # Returns
116    ///
117    /// * `Identity` - The new Identity instance.
118    fn from_pkcs8(cert: &[u8], key: &[u8], encoding: ContentEncoding) -> Self {
119        DeboaIdentity {
120            cert: cert.to_vec(),
121            key: Some(key.to_vec()),
122            password: None,
123            encoding: Some(encoding),
124        }
125    }
126
127    async fn from_pkcs8_file(
128        cert: &str,
129        key: &str,
130        encoding: ContentEncoding,
131    ) -> std::io::Result<Self> {
132        let cert = compio::fs::read(cert).await?;
133        let key = compio::fs::read(key).await?;
134        Ok(DeboaIdentity { cert, key: Some(key), password: None, encoding: Some(encoding) })
135    }
136}
137
138#[cfg(feature = "rust-tls")]
139impl TryFrom<&Identity> for (CertificateDer<'static>, PrivateKeyDer<'static>) {
140    type Error = std::io::Error;
141
142    fn try_from(value: &Identity) -> std::result::Result<Self, Self::Error> {
143        let cert = value.cert.clone();
144        let key = value
145            .key
146            .as_ref()
147            .unwrap()
148            .clone();
149
150        let pair = match value.encoding {
151            Some(ContentEncoding::DER) => {
152                let cert = CertificateDer::from(cert);
153
154                let key = PrivateKeyDer::try_from(key);
155                if key.is_err() {
156                    return Err(std::io::Error::new(
157                        std::io::ErrorKind::InvalidData,
158                        "Invalid certificate",
159                    ));
160                }
161                (cert, key.unwrap())
162            }
163            Some(ContentEncoding::PEM) => {
164                let cert = CertificateDer::from_pem_slice(&cert);
165                if let Err(e) = cert {
166                    return Err(std::io::Error::new(
167                        std::io::ErrorKind::InvalidData,
168                        format!("Invalid certificate: {}", e),
169                    ));
170                }
171
172                let key = PrivateKeyDer::from_pem_slice(&key);
173                if let Err(e) = key {
174                    return Err(std::io::Error::new(
175                        std::io::ErrorKind::InvalidData,
176                        format!("Invalid certificate: {}", e),
177                    ));
178                }
179                (cert.unwrap(), key.unwrap())
180            }
181            None => {
182                return Err(std::io::Error::new(
183                    std::io::ErrorKind::InvalidData,
184                    "Invalid certificate",
185                ));
186            }
187        };
188
189        Ok(pair)
190    }
191}
192
193#[cfg(feature = "native-tls")]
194impl TryFrom<&Identity> for NativeIdentity {
195    type Error = std::io::Error;
196
197    fn try_from(value: &Identity) -> std::result::Result<Self, Self::Error> {
198        let identity = if let Some(password) = &value.password {
199            let identity = NativeIdentity::from_pkcs12(&value.cert, password);
200            if identity.is_err() {
201                return Err(std::io::Error::new(
202                    std::io::ErrorKind::InvalidData,
203                    "Invalid certificate",
204                ));
205            }
206            identity.unwrap()
207        } else if let Some(key) = &value.key {
208            let identity = NativeIdentity::from_pkcs8(&value.cert, key);
209            if identity.is_err() {
210                return Err(std::io::Error::new(
211                    std::io::ErrorKind::InvalidData,
212                    "Invalid certificate",
213                ));
214            }
215            identity.unwrap()
216        } else {
217            return Err(std::io::Error::new(
218                std::io::ErrorKind::InvalidData,
219                "You need provide a password or a key",
220            ));
221        };
222
223        Ok(identity)
224    }
225}
226
227/// Represents a ca certificate.
228///
229/// # Examples
230///
231/// ```rust, ignore
232/// use deboa::cert::{CertificateExt as _, ContentEncoding};
233///
234/// // Load a DER encoded certificate from a file
235/// let cert = DeboaCertificate::from_file(
236///     "/path/to/cert.crt",
237///     ContentEncoding::DER,
238/// );
239///
240/// ```
241#[derive(Debug, Clone)]
242pub struct DeboaCertificate {
243    data: Vec<u8>,
244    encoding: ContentEncoding,
245}
246
247impl CertificateExt for DeboaCertificate {
248    /// Create certificate from slice of DER encoded bytes.
249    ///
250    /// # Arguments
251    ///
252    /// * `data` - The client certificate data.
253    ///
254    /// # Returns
255    ///
256    /// * `Certificate` - The new Certificate instance.
257    ///
258    fn from_slice(data: &[u8], encoding: ContentEncoding) -> Self {
259        DeboaCertificate { data: data.to_vec(), encoding }
260    }
261
262    /// Create certificate from file of DER encoded file.
263    ///
264    /// # Arguments
265    ///
266    /// * `file` - The client certificate file path.
267    ///
268    /// # Returns
269    ///
270    /// * `Result<Certificate, std::io::Error>` - The new Certificate instance.
271    ///
272    async fn from_file(file: &str, encoding: ContentEncoding) -> std::io::Result<Self> {
273        let data = compio::fs::read(file).await?;
274        Ok(DeboaCertificate { data, encoding })
275    }
276}
277
278impl deboa::cert::Certificate for DeboaCertificate {
279    /// Allow get the client certificate path.
280    ///
281    /// # Returns
282    ///
283    /// * `&str` - The client certificate path.
284    ///
285    #[inline]
286    fn as_bytes(&self) -> &Vec<u8> {
287        &self.data
288    }
289}
290
291#[cfg(feature = "rust-tls")]
292impl TryFrom<&DeboaCertificate> for CertificateDer<'static> {
293    type Error = std::io::Error;
294
295    fn try_from(value: &DeboaCertificate) -> std::result::Result<Self, Self::Error> {
296        let cert = match value.encoding {
297            ContentEncoding::DER => CertificateDer::from(
298                value
299                    .as_bytes()
300                    .to_vec(),
301            ),
302            ContentEncoding::PEM => {
303                let result = CertificateDer::from_pem_slice(value.as_bytes());
304                if let Err(e) = result {
305                    return Err(std::io::Error::new(
306                        std::io::ErrorKind::InvalidData,
307                        format!("Invalid certificate: {}", e),
308                    ));
309                }
310                result.unwrap()
311            }
312        };
313
314        Ok(cert)
315    }
316}
317
318#[cfg(feature = "native-tls")]
319impl TryFrom<&DeboaCertificate> for NativeCertificate {
320    type Error = std::io::Error;
321
322    fn try_from(value: &DeboaCertificate) -> std::result::Result<Self, Self::Error> {
323        let cert = match value.encoding {
324            ContentEncoding::DER => NativeCertificate::from_der(value.as_bytes()),
325            ContentEncoding::PEM => NativeCertificate::from_pem(value.as_bytes()),
326        };
327
328        if let Err(e) = cert {
329            return Err(std::io::Error::new(
330                std::io::ErrorKind::InvalidData,
331                format!("Invalid certificate: {}", e),
332            ));
333        }
334
335        Ok(cert.unwrap())
336    }
337}