Skip to content

Commit 4b15a9c

Browse files
authored
perf(dgw): caching of system store certificates (#1339)
Basic caching is implemented for certificates fetched from the system store reducing considerably the number of system calls. The lifetime is 45 seconds, so the certificate is still refreshed on a regular basis. Issue: DGW-266
1 parent d8b2fdf commit 4b15a9c

1 file changed

Lines changed: 35 additions & 6 deletions

File tree

devolutions-gateway/src/tls.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ pub mod windows {
106106
use std::sync::Arc;
107107

108108
use anyhow::Context as _;
109+
use parking_lot::Mutex;
109110
use rustls_cng::signer::CngSigningKey;
110111
use rustls_cng::store::{CertStore, CertStoreType};
111112
use tokio_rustls::rustls::pki_types::CertificateDer;
@@ -114,12 +115,21 @@ pub mod windows {
114115

115116
use crate::config::dto;
116117

118+
const CACHE_DURATION: time::Duration = time::Duration::seconds(45);
119+
117120
#[derive(Debug)]
118121
pub struct ServerCertResolver {
119122
machine_hostname: String,
120123
subject_name: String,
121124
store_type: CertStoreType,
122125
store_name: String,
126+
cached_key: Mutex<Option<KeyCache>>,
127+
}
128+
129+
#[derive(Debug)]
130+
struct KeyCache {
131+
key: Arc<CertifiedKey>,
132+
expires_at: time::OffsetDateTime,
123133
}
124134

125135
impl ServerCertResolver {
@@ -140,6 +150,7 @@ pub mod windows {
140150
subject_name: cert_subject_name,
141151
store_type,
142152
store_name,
153+
cached_key: Mutex::new(None),
143154
})
144155
}
145156

@@ -170,10 +181,20 @@ pub mod windows {
170181
)
171182
}
172183

184+
let mut cache_guard = self.cached_key.lock();
185+
186+
let now = time::OffsetDateTime::now_utc();
187+
188+
if let Some(cache) = cache_guard.as_ref() {
189+
if now < cache.expires_at {
190+
trace!("Used certified key from cache");
191+
return Ok(Arc::clone(&cache.key));
192+
}
193+
}
194+
173195
let store = CertStore::open(self.store_type, &self.store_name).context("open Windows certificate store")?;
174196

175197
// Look up certificate by subject.
176-
// TODO(perf): the resolution result could probably be cached.
177198
let contexts = store.find_by_subject_str(&self.subject_name).with_context(|| {
178199
format!(
179200
"failed to find server certificate for {} from system store",
@@ -189,7 +210,7 @@ pub mod windows {
189210

190211
trace!(subject_name = %self.subject_name, count = contexts.len(), "Found certificate contexts");
191212

192-
let now = picky::x509::date::UtcDate::now();
213+
let x509_date_now = picky::x509::date::UtcDate::from(now);
193214

194215
// Initial processing and filtering of the available candidates.
195216
let mut contexts: Vec<CertHandleCtx> = contexts
@@ -212,7 +233,7 @@ pub mod windows {
212233

213234
trace!(%idx, %serial_number, %subject, %issuer, %not_before, %not_after, "Parsed store certificate");
214235

215-
if now < not_before {
236+
if x509_date_now < not_before {
216237
debug!(
217238
%idx, %serial_number, %not_before, "Filtered out certificate based on not before validity date"
218239
);
@@ -284,12 +305,20 @@ pub mod windows {
284305
.context("certification chain is not available for this certificate")?;
285306
let certs = chain.into_iter().map(CertificateDer::from).collect();
286307

287-
// Return CertifiedKey instance.
288-
return Ok(Arc::new(CertifiedKey {
308+
let key = Arc::new(CertifiedKey {
289309
cert: certs,
290310
key: Arc::new(key),
291311
ocsp: None,
292-
}));
312+
});
313+
314+
*cache_guard = Some(KeyCache {
315+
key: key.clone(),
316+
expires_at: now + CACHE_DURATION,
317+
});
318+
trace!("Cached certified key");
319+
320+
// Return CertifiedKey instance.
321+
return Ok(key);
293322

294323
struct CertHandleCtx {
295324
idx: usize,

0 commit comments

Comments
 (0)