Skip to content

Commit 55c81d2

Browse files
committed
feat(dgw): encrypt in-memory credentials
Add ChaCha20-Poly1305 encryption for credentials stored in the credential store. Passwords are encrypted at rest with a master key protected via libsodium's mlock/mprotect facilities, preventing exposure in memory dumps or swap. Issue: DGW-326
1 parent 04fc600 commit 55c81d2

7 files changed

Lines changed: 432 additions & 101 deletions

File tree

Cargo.lock

Lines changed: 93 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

devolutions-gateway/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ bitflags = "2.9"
7474
# Security, crypto…
7575
picky = { version = "7.0.0-rc.15", default-features = false, features = ["jose", "x509", "pkcs12", "time_conversion"] }
7676
zeroize = { version = "1.8", features = ["derive"] }
77+
chacha20poly1305 = "0.10"
78+
secrets = "1.2"
79+
secrecy = { version = "0.10", features = ["serde"] }
80+
rand = "0.8"
7781
multibase = "0.9"
7882
argon2 = { version = "0.5", features = ["std"] }
7983
x509-cert = { version = "0.2", default-features = false, features = ["std"] }

devolutions-gateway/src/api/preflight.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use uuid::Uuid;
1111

1212
use crate::DgwState;
1313
use crate::config::Conf;
14-
use crate::credential::{AppCredentialMapping, CredentialStoreHandle};
14+
use crate::credential::CredentialStoreHandle;
1515
use crate::extract::PreflightScope;
1616
use crate::http::HttpError;
1717
use crate::session::SessionMessageSender;
@@ -45,7 +45,7 @@ struct ProvisionTokenParams {
4545
struct ProvisionCredentialsParams {
4646
token: String,
4747
#[serde(flatten)]
48-
mapping: AppCredentialMapping,
48+
mapping: crate::credential::CleartextAppCredentialMapping,
4949
time_to_live: Option<u32>,
5050
}
5151

@@ -337,10 +337,19 @@ async fn handle_operation(
337337
});
338338
}
339339

340+
// Encrypt passwords before storing.
341+
let encrypted_mapping = mapping.map(|m| m.encrypt()).transpose().map_err(|e| {
342+
error!(error = format!("{e:#}"), "Failed to encrypt credentials");
343+
PreflightError::new(
344+
PreflightAlertStatus::InternalServerError,
345+
"credential encryption failed",
346+
)
347+
})?;
348+
340349
let previous_entry = credential_store
341-
.insert(token, mapping, time_to_live)
350+
.insert(token, encrypted_mapping, time_to_live)
342351
.inspect_err(|error| warn!(%operation.id, error = format!("{error:#}"), "Failed to insert credentials"))
343-
.map_err(|e| PreflightError::new(PreflightAlertStatus::InvalidParams, format!("{e:#}")))?;
352+
.map_err(|e| PreflightError::new(PreflightAlertStatus::InternalServerError, format!("{e:#}")))?;
344353

345354
if previous_entry.is_some() {
346355
outputs.push(PreflightOutput {

devolutions-gateway/src/config.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use camino::{Utf8Path, Utf8PathBuf};
99
use cfg_if::cfg_if;
1010
use picky::key::{PrivateKey, PublicKey};
1111
use picky::pem::Pem;
12+
use secrecy::SecretString;
1213
use tap::prelude::*;
1314
use tokio::sync::Notify;
1415
use tokio_rustls::rustls::pki_types;
@@ -17,7 +18,6 @@ use url::Url;
1718
use uuid::Uuid;
1819

1920
use crate::SYSTEM_LOGGER;
20-
use crate::credential::Password;
2121
use crate::listener::ListenerUrls;
2222
use crate::target_addr::TargetAddr;
2323
use crate::token::Subkey;
@@ -216,7 +216,7 @@ pub enum WebAppAuth {
216216
pub struct WebAppUser {
217217
pub name: String,
218218
/// Hash of the password, in the PHC string format
219-
pub password_hash: Password,
219+
pub password_hash: SecretString,
220220
}
221221

222222
/// AI Router configuration (experimental)
@@ -1243,7 +1243,7 @@ fn generate_self_signed_certificate(
12431243

12441244
fn read_pfx_file(
12451245
path: &Utf8Path,
1246-
password: Option<&Password>,
1246+
password: Option<&SecretString>,
12471247
) -> anyhow::Result<(
12481248
Vec<pki_types::CertificateDer<'static>>,
12491249
pki_types::PrivateKeyDer<'static>,
@@ -1627,7 +1627,7 @@ pub mod dto {
16271627
pub tls_private_key_file: Option<Utf8PathBuf>,
16281628
/// Password to use for decrypting the TLS private key
16291629
#[serde(skip_serializing_if = "Option::is_none")]
1630-
pub tls_private_key_password: Option<Password>,
1630+
pub tls_private_key_password: Option<SecretString>,
16311631
/// Subject name of the certificate to use for TLS
16321632
#[serde(skip_serializing_if = "Option::is_none")]
16331633
pub tls_certificate_subject_name: Option<String>,

0 commit comments

Comments
 (0)