datadog_agent_commons/ipc/
tls.rs1use std::{
4 path::Path,
5 sync::Arc,
6 time::{Duration, Instant},
7};
8
9use rustls::{
10 client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
11 crypto::CryptoProvider,
12 pki_types::{CertificateDer, ServerName, UnixTime},
13 server::danger::{ClientCertVerified, ClientCertVerifier},
14 version::TLS13,
15 CertificateError, ClientConfig, DigitallySignedStruct, DistinguishedName, ServerConfig, SignatureScheme,
16};
17use rustls_pki_types::{pem::PemObject as _, PrivateKeyDer};
18use saluki_error::{generic_error, ErrorContext as _, GenericError};
19use saluki_tls::{ensure_client_config_fips_compliant, ensure_server_config_fips_compliant};
20
21const DEFAULT_CERT_READ_TIMEOUT: Duration = Duration::from_secs(20);
22const DEFAULT_CERT_READ_INTERVAL: Duration = Duration::from_millis(100);
23
24#[derive(Debug)]
25struct DatadogAgentServerCertVerifier {
26 cert: CertificateDer<'static>,
27 provider: Arc<CryptoProvider>,
28}
29
30impl DatadogAgentServerCertVerifier {
31 fn from_certificate_and_provider(cert: CertificateDer<'static>, provider: Arc<CryptoProvider>) -> Self {
32 Self { cert, provider }
33 }
34}
35
36impl ServerCertVerifier for DatadogAgentServerCertVerifier {
37 fn verify_server_cert(
38 &self, end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>,
39 _ocsp_response: &[u8], _now: UnixTime,
40 ) -> Result<ServerCertVerified, rustls::Error> {
41 if end_entity != &self.cert {
43 return Err(rustls::Error::InvalidCertificate(CertificateError::UnknownIssuer));
44 }
45
46 Ok(ServerCertVerified::assertion())
47 }
48
49 fn verify_tls12_signature(
50 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
51 ) -> Result<HandshakeSignatureValid, rustls::Error> {
52 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
53 }
54
55 fn verify_tls13_signature(
56 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
57 ) -> Result<HandshakeSignatureValid, rustls::Error> {
58 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
59 }
60
61 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
62 self.provider.signature_verification_algorithms.supported_schemes()
63 }
64}
65
66#[derive(Debug)]
67struct DatadogAgentClientCertVerifier {
68 cert: CertificateDer<'static>,
69 provider: Arc<CryptoProvider>,
70}
71
72impl DatadogAgentClientCertVerifier {
73 fn from_certificate_and_provider(cert: CertificateDer<'static>, provider: Arc<CryptoProvider>) -> Self {
74 Self { cert, provider }
75 }
76}
77
78impl ClientCertVerifier for DatadogAgentClientCertVerifier {
79 fn offer_client_auth(&self) -> bool {
80 true
81 }
82
83 fn client_auth_mandatory(&self) -> bool {
84 true
85 }
86
87 fn root_hint_subjects(&self) -> &[DistinguishedName] {
88 &[]
89 }
90
91 fn verify_client_cert(
92 &self, end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _now: UnixTime,
93 ) -> Result<ClientCertVerified, rustls::Error> {
94 if end_entity != &self.cert {
95 return Err(rustls::Error::InvalidCertificate(CertificateError::UnknownIssuer));
96 }
97
98 Ok(ClientCertVerified::assertion())
99 }
100
101 fn verify_tls12_signature(
102 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
103 ) -> Result<HandshakeSignatureValid, rustls::Error> {
104 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
105 }
106
107 fn verify_tls13_signature(
108 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
109 ) -> Result<HandshakeSignatureValid, rustls::Error> {
110 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
111 }
112
113 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
114 self.provider.signature_verification_algorithms.supported_schemes()
115 }
116}
117
118pub async fn build_ipc_client_ipc_tls_config<P: AsRef<Path>>(cert_path: P) -> Result<ClientConfig, GenericError> {
130 let (parsed_cert, parsed_key) = read_and_parse_certificate_file(
132 cert_path.as_ref(),
133 DEFAULT_CERT_READ_TIMEOUT,
134 DEFAULT_CERT_READ_INTERVAL,
135 )
136 .await?;
137
138 let crypto_provider = rustls::crypto::CryptoProvider::get_default()
140 .map(Arc::clone)
141 .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
142 let agent_cert_verifier = Arc::new(DatadogAgentServerCertVerifier::from_certificate_and_provider(
143 parsed_cert.clone(),
144 crypto_provider,
145 ));
146
147 let config = ClientConfig::builder_with_protocol_versions(&[&TLS13])
148 .dangerous()
149 .with_custom_certificate_verifier(agent_cert_verifier)
150 .with_client_auth_cert(vec![parsed_cert], parsed_key)
151 .with_error_context(|| {
152 format!(
153 "Failed to build client TLS configuration from certificate file '{}'.",
154 cert_path.as_ref().display()
155 )
156 })?;
157
158 ensure_client_config_fips_compliant(&config)?;
159
160 Ok(config)
161}
162
163pub async fn build_ipc_server_tls_config<P: AsRef<Path>>(cert_path: P) -> Result<ServerConfig, GenericError> {
175 let (parsed_cert, parsed_key) = read_and_parse_certificate_file(
177 cert_path.as_ref(),
178 DEFAULT_CERT_READ_TIMEOUT,
179 DEFAULT_CERT_READ_INTERVAL,
180 )
181 .await?;
182
183 let crypto_provider = rustls::crypto::CryptoProvider::get_default()
184 .map(Arc::clone)
185 .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
186 let agent_cert_verifier = Arc::new(DatadogAgentClientCertVerifier::from_certificate_and_provider(
187 parsed_cert.clone(),
188 crypto_provider,
189 ));
190
191 let mut config = ServerConfig::builder()
192 .with_client_cert_verifier(agent_cert_verifier)
193 .with_single_cert(vec![parsed_cert], parsed_key)
194 .with_error_context(|| {
195 format!(
196 "Failed to build server TLS configuration from certificate file '{}'.",
197 cert_path.as_ref().display()
198 )
199 })?;
200
201 ensure_server_config_fips_compliant(&mut config)?;
202
203 Ok(config)
204}
205
206async fn read_and_parse_certificate_file(
216 cert_path: &Path, timeout: Duration, interval: Duration,
217) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>), GenericError> {
218 if timeout < interval {
219 return Err(generic_error!(
220 "Timeout is less than interval ({} < {}).",
221 timeout.as_secs(),
222 interval.as_secs()
223 ));
224 }
225
226 let start_time = Instant::now();
227 let mut last_error = String::new();
228 while start_time.elapsed() < timeout {
229 match tokio::fs::read(cert_path).await {
230 Ok(raw_cert_data) => {
231 let parsed_cert = CertificateDer::from_pem_slice(&raw_cert_data[..])
232 .with_error_context(|| format!("Failed to parse certificate file '{}'.", cert_path.display()))?
233 .into_owned();
234
235 let parsed_key = PrivateKeyDer::from_pem_slice(&raw_cert_data[..])
236 .with_error_context(|| format!("Failed to parse private key file '{}'.", cert_path.display()))?
237 .clone_key();
238
239 return Ok((parsed_cert, parsed_key));
240 }
241 Err(e) => {
242 last_error = e.to_string();
243 tokio::time::sleep(interval).await;
244 }
245 }
246 }
247
248 Err(generic_error!(
249 "Failed to read certificate file '{}' after {} seconds: {}",
250 cert_path.display(),
251 timeout.as_secs(),
252 last_error
253 ))
254}
255
256#[cfg(test)]
257mod tests {
258 use std::{fs, io, path::PathBuf, sync::Arc, time::Duration};
259
260 use rcgen::{generate_simple_self_signed, CertifiedKey};
261 use rustls::{
262 crypto::CryptoProvider,
263 pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName},
264 version::TLS13,
265 ClientConfig, ServerConfig,
266 };
267 use tempfile::TempDir;
268 use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
269 use tokio_rustls::{TlsAcceptor, TlsConnector};
270
271 use super::{build_ipc_client_ipc_tls_config, build_ipc_server_tls_config, DatadogAgentServerCertVerifier};
272
273 const APPLICATION_BYTE: u8 = 42;
274 const TEST_TIMEOUT: Duration = Duration::from_secs(5);
275
276 struct TestIdentity {
277 _temp_dir: TempDir,
278 cert_path: PathBuf,
279 cert_der: CertificateDer<'static>,
280 key_der: Vec<u8>,
281 }
282
283 impl TestIdentity {
284 fn localhost() -> Self {
285 let CertifiedKey { cert, signing_key } = generate_simple_self_signed(["localhost".to_owned()])
286 .expect("self-signed localhost certificate should be generated");
287 let temp_dir = tempfile::tempdir().expect("temporary certificate directory should be created");
288 let cert_path = temp_dir.path().join("ipc-cert.pem");
289 fs::write(&cert_path, format!("{}{}", cert.pem(), signing_key.serialize_pem()))
290 .expect("certificate and private key should be written");
291
292 Self {
293 _temp_dir: temp_dir,
294 cert_path,
295 cert_der: cert.der().clone(),
296 key_der: signing_key.serialize_der(),
297 }
298 }
299
300 fn private_key(&self) -> PrivateKeyDer<'static> {
301 PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(self.key_der.clone()))
302 }
303 }
304
305 fn initialize_crypto_provider() {
306 let _ = saluki_tls::initialize_default_crypto_provider();
307 assert!(
308 CryptoProvider::get_default().is_some(),
309 "default crypto provider should be installed"
310 );
311 }
312
313 fn client_config_pinned_to(server_identity: &TestIdentity, client_identity: Option<&TestIdentity>) -> ClientConfig {
314 let provider = CryptoProvider::get_default()
315 .cloned()
316 .expect("default crypto provider should be installed");
317 let verifier = Arc::new(DatadogAgentServerCertVerifier::from_certificate_and_provider(
318 server_identity.cert_der.clone(),
319 provider,
320 ));
321 let builder = ClientConfig::builder_with_protocol_versions(&[&TLS13])
322 .dangerous()
323 .with_custom_certificate_verifier(verifier);
324
325 match client_identity {
326 Some(identity) => builder
327 .with_client_auth_cert(vec![identity.cert_der.clone()], identity.private_key())
328 .expect("client TLS config should accept generated identity"),
329 None => builder.with_no_client_auth(),
330 }
331 }
332
333 async fn accept_client(server_config: ServerConfig, client_config: ClientConfig) -> io::Result<u8> {
334 tokio::time::timeout(TEST_TIMEOUT, async move {
335 let (client_io, server_io) = tokio::io::duplex(4096);
336 let client = async move {
337 let connector = TlsConnector::from(Arc::new(client_config));
338 let server_name = ServerName::try_from("localhost").expect("localhost should be a valid server name");
339 let mut stream = connector.connect(server_name, client_io).await?;
340 stream.write_u8(APPLICATION_BYTE).await?;
341 Ok::<_, io::Error>(stream)
342 };
343 let server = async move {
344 let acceptor = TlsAcceptor::from(Arc::new(server_config));
345 let mut stream = acceptor.accept(server_io).await?;
346 Ok(stream
347 .read_u8()
348 .await
349 .expect("accepted client should send an application byte"))
350 };
351
352 let (_, server_result) = tokio::join!(client, server);
353 server_result
354 })
355 .await
356 .expect("TLS handshake should not time out")
357 }
358
359 #[tokio::test]
360 async fn matching_ipc_identity_completes_mtls_and_delivers_application_byte() {
361 initialize_crypto_provider();
362 let identity_a = TestIdentity::localhost();
363 let server_config = build_ipc_server_tls_config(&identity_a.cert_path)
364 .await
365 .expect("production server TLS config should build");
366 let client_config = build_ipc_client_ipc_tls_config(&identity_a.cert_path)
367 .await
368 .expect("production client TLS config should build");
369
370 assert_eq!(
371 accept_client(server_config, client_config)
372 .await
373 .expect("server should accept the matching client identity"),
374 APPLICATION_BYTE
375 );
376 }
377
378 #[tokio::test]
379 async fn missing_or_mismatched_client_certificate_makes_server_accept_fail() {
380 initialize_crypto_provider();
381 let identity_a = TestIdentity::localhost();
382 let server_config = build_ipc_server_tls_config(&identity_a.cert_path)
383 .await
384 .expect("production server TLS config should build");
385 let client_config = client_config_pinned_to(&identity_a, None);
386
387 accept_client(server_config, client_config)
388 .await
389 .expect_err("server accept should reject a missing client certificate");
390
391 let identity_b = TestIdentity::localhost();
392 let server_config = build_ipc_server_tls_config(&identity_a.cert_path)
393 .await
394 .expect("production server TLS config should build");
395 let client_config = client_config_pinned_to(&identity_a, Some(&identity_b));
396
397 accept_client(server_config, client_config)
398 .await
399 .expect_err("server accept should reject a different client certificate");
400 }
401}