From eaceef5aa059f5537530fadf8d5aa7fa24c8fb51 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Tue, 14 Apr 2026 10:42:10 +0200 Subject: [PATCH] vm-migration: server-side of a TLS connection Code for the TLS server, i.e. the receiver of a live migration. On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- vm-migration/src/tls.rs | 75 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/vm-migration/src/tls.rs b/vm-migration/src/tls.rs index 149933de9..93f339721 100644 --- a/vm-migration/src/tls.rs +++ b/vm-migration/src/tls.rs @@ -26,8 +26,10 @@ use std::sync::Arc; use log::warn; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, InvalidDnsNameError, PrivateKeyDer, ServerName}; -use rustls::server::VerifierBuilderError; -use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned}; +use rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; +use rustls::{ + ClientConfig, ClientConnection, RootCertStore, ServerConfig, ServerConnection, StreamOwned, +}; use thiserror::Error; use crate::MigratableError; @@ -35,6 +37,8 @@ use crate::MigratableError; const CA_CERT_FILE: &str = "ca-cert.pem"; const CLIENT_CERT_FILE: &str = "client-cert.pem"; const CLIENT_KEY_FILE: &str = "client-key.pem"; +const SERVER_CERT_FILE: &str = "server-cert.pem"; +const SERVER_KEY_FILE: &str = "server-key.pem"; /// Errors that can occur when establishing a TLS-encrypted migration channel. #[derive(Error, Debug)] @@ -67,6 +71,7 @@ pub enum TlsError { #[derive(Debug)] enum TlsStreamParticipant { Client(StreamOwned), + Server(StreamOwned), } /// Server/Client-agnostic TLS stream. @@ -122,6 +127,72 @@ impl TlsStream { stream: TlsStreamParticipant::Client(tls), }) } + + /// Creates a server [`TlsStream`]. Encrypts and decrypts data sent through + /// this stream using the certificates and key from the provided + /// [`TlsServerConfig`]. + pub fn new_server( + socket: TcpStream, + config: &TlsServerConfig, + ) -> result::Result { + let conn = ServerConnection::new(config.config.clone()) + .map_err(TlsError::RustlsError) + .map_err(MigratableError::Tls)?; + + let mut tls = StreamOwned::new(conn, socket); + while tls.conn.is_handshaking() { + let (rd, wr) = tls + .conn + .complete_io(&mut tls.sock) + .map_err(TlsError::RustlsIoError) + .map_err(MigratableError::Tls)?; + // No handshake progress on a connection that should be handshaking, we treat + // that as a failure. + if rd == 0 && wr == 0 { + return Err(MigratableError::Tls(TlsError::HandshakeError)); + } + } + + Ok(Self { + stream: TlsStreamParticipant::Server(tls), + }) + } +} + +/// Carries a TLS server configuration. Intended to be turned into a [`TlsStream`] +/// when paired with a [`TcpStream`]. +#[derive(Debug)] +pub struct TlsServerConfig { + /// This config is shared between all server connections. + config: Arc, +} + +impl TlsServerConfig { + /// Creates a [`TlsServerConfig`] from the certificate chain in + /// `server-cert.pem`, the private key in `server-key.pem`, and the client + /// trust anchors in `ca-cert.pem`. + /// + /// Client certificates presented during the TLS handshake must chain to a CA in + /// `ca-cert.pem`. + pub fn new(cert_dir: &Path) -> result::Result { + let server_certs = load_cert_chain(&cert_dir.join(SERVER_CERT_FILE))?; + let server_key = load_private_key(&cert_dir.join(SERVER_KEY_FILE))?; + // Trust anchors used to verify client certificates for mTLS. + let client_roots = Arc::new(load_root_store(&cert_dir.join(CA_CERT_FILE))?); + + let client_verifier = WebPkiClientVerifier::builder(client_roots) + .build() + .map_err(TlsError::RustlsVerifierBuilderError) + .map_err(MigratableError::Tls)?; + + let config = ServerConfig::builder() + .with_client_cert_verifier(client_verifier) + .with_single_cert(server_certs, server_key) + .map_err(TlsError::RustlsError) + .map_err(MigratableError::Tls)?; + let config = Arc::new(config); + Ok(Self { config }) + } } /// Loads trusted CA certificates into a root store, i.e. the set of trust anchors