Skip to main content

htsget_config/http/
client.rs

1//! TLS configuration related to HTTP clients.
2//!
3
4use crate::config::advanced::Bytes;
5use crate::error::Error::IoError;
6use crate::error::{Error, Result};
7use crate::http::RootCertStorePair;
8use crate::http::load_certs;
9use reqwest::{Certificate, Identity};
10use serde::Deserialize;
11
12/// A certificate and key pair used for TLS. Serialization is not implemented because there
13/// is no way to convert back to a `PathBuf`.
14#[derive(Deserialize, Debug, Clone)]
15#[serde(try_from = "RootCertStorePair", deny_unknown_fields)]
16pub struct HttpClientConfig {
17  cert: Option<Vec<Certificate>>,
18  identity: Option<Identity>,
19  use_cache: bool,
20  user_agent: Option<String>,
21}
22
23impl Default for HttpClientConfig {
24  fn default() -> Self {
25    Self {
26      cert: None,
27      identity: None,
28      use_cache: true,
29      user_agent: None,
30    }
31  }
32}
33
34impl HttpClientConfig {
35  /// Create a new TlsClientConfig.
36  pub fn new(cert: Option<Vec<Certificate>>, identity: Option<Identity>, use_cache: bool) -> Self {
37    Self {
38      cert,
39      identity,
40      use_cache,
41      ..Default::default()
42    }
43  }
44
45  /// Get the inner client config.
46  pub fn into_inner(
47    self,
48  ) -> (
49    Option<Vec<Certificate>>,
50    Option<Identity>,
51    bool,
52    Option<String>,
53  ) {
54    (self.cert, self.identity, self.use_cache, self.user_agent)
55  }
56
57  /// Set the user agent string.
58  pub fn with_user_agent(mut self, user_agent: String) -> Self {
59    self.user_agent = Some(user_agent);
60    self
61  }
62}
63
64impl TryFrom<RootCertStorePair> for HttpClientConfig {
65  type Error = Error;
66
67  fn try_from(root_store_pair: RootCertStorePair) -> Result<Self> {
68    let (key_pair, root_store, use_cache) = root_store_pair.into_inner();
69
70    let cert = root_store
71      .clone()
72      .map(|cert_path| {
73        let certs = load_certs(cert_path)?;
74
75        certs
76          .into_iter()
77          .map(|cert| {
78            Certificate::from_der(&cert)
79              .map_err(|err| IoError(format!("failed to read certificate from pem: {err}")))
80          })
81          .collect::<Result<Vec<_>>>()
82      })
83      .transpose()?;
84
85    let identity = key_pair
86      .clone()
87      .map(|pair| {
88        let key = Bytes::try_from(pair.key)?.into_inner();
89        let certs = Bytes::try_from(pair.cert)?.into_inner();
90
91        Identity::from_pem(&[certs, key].concat())
92          .map_err(|err| IoError(format!("failed to load pkcs8 pem identity: {err}")))
93      })
94      .transpose()?;
95
96    Ok(Self::new(cert, identity, use_cache))
97  }
98}
99
100#[cfg(test)]
101pub(crate) mod tests {
102  use crate::http::tests::with_test_certificates;
103  use crate::http::{CertificateKeyPairPath, RootCertStorePair};
104  use std::path::Path;
105
106  use super::*;
107
108  #[tokio::test]
109  async fn test_tls_client_config() {
110    with_test_certificates(|path, _, _| {
111      let client_config = client_config_from_path(path);
112      let (certs, identity, _, _) = client_config.into_inner();
113
114      assert_eq!(certs.unwrap().len(), 1);
115      assert!(identity.is_some());
116    });
117  }
118
119  pub(crate) fn client_config_from_path(path: &Path) -> HttpClientConfig {
120    HttpClientConfig::try_from(RootCertStorePair::new(
121      Some(CertificateKeyPairPath::new(
122        path.join("cert.pem"),
123        path.join("key.pem"),
124      )),
125      Some(path.join("cert.pem")),
126      true,
127    ))
128    .unwrap()
129  }
130}