redact_crypto/storage/
redact.rs

1use crate::{
2    CryptoError, Entry, IndexedStorer, IndexedTypeStorer, StorableType, Storer, TypeStorer,
3};
4use async_trait::async_trait;
5use mongodb::bson::Document;
6use once_cell::sync::Lazy;
7use reqwest::StatusCode;
8use serde::{Deserialize, Serialize};
9use std::{
10    error::Error,
11    fmt::{self, Display, Formatter},
12    fs::File,
13    io::Read,
14    sync::{Arc, RwLock},
15};
16
17static CLIENT_TLS_CONFIG: Lazy<RwLock<Arc<Option<ClientTlsConfig>>>> =
18    Lazy::new(|| RwLock::new(Default::default()));
19
20#[derive(Debug)]
21pub enum RedactStorerError {
22    /// Represents an error which occurred in some internal system
23    InternalError {
24        source: Box<dyn Error + Send + Sync>,
25    },
26
27    /// Requested document was not found
28    NotFound,
29
30    /// PKCS12 file could not be read at the given path
31    Pkcs12FileNotReadable { source: std::io::Error },
32
33    /// Server CA cert file could not be read at the given path
34    ServerCaCertFileNotReadable { source: std::io::Error },
35
36    /// Bytes in PKCS12 file are not valid PKCS12 bytes
37    HttpClientNotBuildable { source: reqwest::Error },
38}
39
40impl Error for RedactStorerError {
41    fn source(&self) -> Option<&(dyn Error + 'static)> {
42        match *self {
43            RedactStorerError::InternalError { ref source } => Some(source.as_ref()),
44            RedactStorerError::NotFound => None,
45            RedactStorerError::Pkcs12FileNotReadable { ref source } => Some(source),
46            RedactStorerError::HttpClientNotBuildable { ref source } => Some(source),
47            RedactStorerError::ServerCaCertFileNotReadable { ref source } => Some(source),
48        }
49    }
50}
51
52impl Display for RedactStorerError {
53    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
54        match *self {
55            RedactStorerError::InternalError { .. } => {
56                write!(f, "Internal error occurred")
57            }
58            RedactStorerError::NotFound => {
59                write!(f, "Requested document not found")
60            }
61            RedactStorerError::Pkcs12FileNotReadable { .. } => {
62                write!(f, "Could not open PKCS12 client TLS file")
63            }
64            RedactStorerError::HttpClientNotBuildable { .. } => {
65                write!(f, "Could not build HTTP request client")
66            }
67            RedactStorerError::ServerCaCertFileNotReadable { .. } => {
68                write!(f, "Could not read server CA certificate")
69            }
70        }
71    }
72}
73
74impl From<RedactStorerError> for CryptoError {
75    fn from(rse: RedactStorerError) -> Self {
76        match rse {
77            RedactStorerError::InternalError { .. } => CryptoError::InternalError {
78                source: Box::new(rse),
79            },
80            RedactStorerError::NotFound => CryptoError::NotFound {
81                source: Box::new(rse),
82            },
83            RedactStorerError::Pkcs12FileNotReadable { .. } => CryptoError::InternalError {
84                source: Box::new(rse),
85            },
86            RedactStorerError::HttpClientNotBuildable { .. } => CryptoError::InternalError {
87                source: Box::new(rse),
88            },
89            RedactStorerError::ServerCaCertFileNotReadable { .. } => CryptoError::InternalError {
90                source: Box::new(rse),
91            },
92        }
93    }
94}
95
96#[derive(Serialize, Deserialize, Debug, Clone, Default)]
97pub struct ClientTlsConfig {
98    pub pkcs12_path: String,
99    pub server_ca_path: Option<String>,
100}
101
102impl ClientTlsConfig {
103    pub fn current() -> Arc<Option<ClientTlsConfig>> {
104        CLIENT_TLS_CONFIG.read().unwrap().clone()
105    }
106
107    pub fn make_current(self) {
108        *CLIENT_TLS_CONFIG.write().unwrap() = Arc::new(Some(self))
109    }
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone)]
113pub struct RedactStorer {
114    url: String,
115}
116
117/// Stores an instance of a redact-backed key storer.
118/// The redact-store server is an example implementation of a redact storage backing.
119impl RedactStorer {
120    pub fn new(url: &str) -> Self {
121        Self {
122            url: url.to_owned(),
123        }
124    }
125}
126
127impl From<RedactStorer> for IndexedTypeStorer {
128    fn from(rs: RedactStorer) -> Self {
129        IndexedTypeStorer::Redact(rs)
130    }
131}
132
133impl From<RedactStorer> for TypeStorer {
134    fn from(rs: RedactStorer) -> Self {
135        TypeStorer::Indexed(IndexedTypeStorer::Redact(rs))
136    }
137}
138
139impl RedactStorer {
140    fn get_http_client() -> Result<reqwest::Client, RedactStorerError> {
141        let mut headers = reqwest::header::HeaderMap::new();
142        headers.insert(
143            reqwest::header::CONNECTION,
144            reqwest::header::HeaderValue::from_static("close"),
145        );
146        match *ClientTlsConfig::current() {
147            Some(ref ctc) => {
148                let mut pkcs12_vec: Vec<u8> = vec![];
149                File::open(&ctc.pkcs12_path)
150                    .map_err(|source| RedactStorerError::Pkcs12FileNotReadable { source })?
151                    .read_to_end(&mut pkcs12_vec)
152                    .map_err(|source| RedactStorerError::Pkcs12FileNotReadable { source })?;
153                let pkcs12 = reqwest::Identity::from_pem(&pkcs12_vec)
154                    .map_err(|source| RedactStorerError::HttpClientNotBuildable { source })?;
155
156                match &ctc.server_ca_path {
157                    Some(path) => {
158                        let mut ca_cert_vec: Vec<u8> = vec![];
159                        File::open(path)
160                            .map_err(|source| RedactStorerError::ServerCaCertFileNotReadable {
161                                source,
162                            })?
163                            .read_to_end(&mut ca_cert_vec)
164                            .map_err(|source| RedactStorerError::ServerCaCertFileNotReadable {
165                                source,
166                            })?;
167                        let ca_cert =
168                            reqwest::Certificate::from_pem(&ca_cert_vec).map_err(|source| {
169                                RedactStorerError::HttpClientNotBuildable { source }
170                            })?;
171                        Ok::<_, RedactStorerError>(
172                            reqwest::Client::builder()
173                                .identity(pkcs12)
174                                .add_root_certificate(ca_cert)
175                                .tls_built_in_root_certs(false)
176                                .use_rustls_tls()
177                                .default_headers(headers)
178                                .build()
179                                .map_err(|source| RedactStorerError::HttpClientNotBuildable {
180                                    source,
181                                })?,
182                        )
183                    }
184                    None => Ok::<_, RedactStorerError>(
185                        reqwest::Client::builder()
186                            .identity(pkcs12)
187                            .use_rustls_tls()
188                            .default_headers(headers)
189                            .build()
190                            .map_err(|source| RedactStorerError::HttpClientNotBuildable {
191                                source,
192                            })?,
193                    ),
194                }
195            }
196            None => Ok(reqwest::Client::builder()
197                .use_rustls_tls()
198                .default_headers(headers)
199                .build()
200                .map_err(|source| RedactStorerError::HttpClientNotBuildable { source })?),
201        }
202    }
203}
204
205#[async_trait]
206impl IndexedStorer for RedactStorer {
207    async fn get_indexed<T: StorableType>(
208        &self,
209        path: &str,
210        index: &Option<Document>,
211    ) -> Result<Entry<T>, CryptoError> {
212        let mut req_url = format!("{}/{}?", &self.url, path);
213        if let Some(i) = index {
214            req_url.push_str(format!("index={}", i).as_ref());
215        }
216        let http_client = RedactStorer::get_http_client()?;
217
218        match http_client.get(&req_url).send().await {
219            Ok(r) => Ok(r
220                .error_for_status()
221                .map_err(|source| -> CryptoError {
222                    if source.status() == Some(reqwest::StatusCode::NOT_FOUND) {
223                        RedactStorerError::NotFound.into()
224                    } else {
225                        RedactStorerError::InternalError {
226                            source: Box::new(source),
227                        }
228                        .into()
229                    }
230                })?
231                .json::<Entry<T>>()
232                .await
233                .map_err(|source| -> CryptoError {
234                    RedactStorerError::InternalError {
235                        source: Box::new(source),
236                    }
237                    .into()
238                })?),
239            Err(source) => Err(RedactStorerError::InternalError {
240                source: Box::new(source),
241            }
242            .into()),
243        }
244    }
245
246    async fn list_indexed<T: StorableType>(
247        &self,
248        path: &str,
249        skip: u64,
250        page_size: i64,
251        index: &Option<Document>,
252    ) -> Result<Vec<Entry<T>>, CryptoError> {
253        let mut req_url = format!(
254            "{}/{}?skip={}&page_size={}",
255            &self.url, path, skip, page_size
256        );
257        if let Some(i) = index {
258            req_url.push_str(format!("&index={}", i).as_ref());
259        }
260        let http_client = RedactStorer::get_http_client()?;
261
262        match http_client.get(&req_url).send().await {
263            Ok(r) => Ok(r
264                .error_for_status()
265                .map_err(|source| -> CryptoError {
266                    if source.status() == Some(reqwest::StatusCode::NOT_FOUND) {
267                        RedactStorerError::NotFound.into()
268                    } else {
269                        RedactStorerError::InternalError {
270                            source: Box::new(source),
271                        }
272                        .into()
273                    }
274                })?
275                .json::<Vec<Entry<T>>>()
276                .await
277                .map_err(|source| -> CryptoError {
278                    RedactStorerError::InternalError {
279                        source: Box::new(source),
280                    }
281                    .into()
282                })?),
283            Err(source) => Err(RedactStorerError::InternalError {
284                source: Box::new(source),
285            }
286            .into()),
287        }
288    }
289}
290
291#[async_trait]
292impl Storer for RedactStorer {
293    async fn get<T: StorableType>(&self, path: &str) -> Result<Entry<T>, CryptoError> {
294        self.get_indexed::<T>(path, &T::get_index()).await
295    }
296
297    async fn create<T: StorableType>(&self, entry: Entry<T>) -> Result<Entry<T>, CryptoError> {
298        let value = serde_json::to_value(&entry).map_err(|e| RedactStorerError::InternalError {
299            source: Box::new(e),
300        })?;
301        let http_client = RedactStorer::get_http_client()?;
302
303        http_client
304            .post(&format!("{}/", self.url))
305            .json(&value)
306            .send()
307            .await
308            .and_then(|res| res.error_for_status().map(|_| entry))
309            .map_err(|e| {
310                if let Some(status) = e.status() {
311                    if status == StatusCode::NOT_FOUND {
312                        RedactStorerError::NotFound.into()
313                    } else {
314                        RedactStorerError::InternalError {
315                            source: Box::new(e),
316                        }
317                        .into()
318                    }
319                } else {
320                    RedactStorerError::InternalError {
321                        source: Box::new(e),
322                    }
323                    .into()
324                }
325            })
326    }
327}