| use crate::grpc::telemetry_server::BmcTelemetryService; |
| use crate::grpc::third_party_voyager::machine_telemetry_server::MachineTelemetryServer; |
| use axum::Router; |
| use hyper::body::Incoming; |
| use hyper_util::rt::{TokioExecutor, TokioIo}; |
| use hyper_util::server; |
| use libcertificate_authority_policy_sys::*; |
| use rustls::{ |
| client::danger::HandshakeSignatureValid, pki_types::UnixTime, |
| server::danger::ClientCertVerifier, DigitallySignedStruct, DistinguishedName, Error, |
| RootCertStore, SignatureScheme, |
| }; |
| use rustls_pki_types::{CertificateDer, PrivateKeyDer}; |
| use std::ffi::CString; |
| use std::fs::File; |
| use std::pin::Pin; |
| use std::sync::Arc; |
| use std::task::{Context, Poll}; |
| use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; |
| use tower::ServiceExt; |
| use x509_parser::prelude::*; |
| |
| #[derive(Clone)] |
| struct Loas3ConnectInfo { |
| #[allow(dead_code)] |
| remote_addr: std::net::SocketAddr, |
| } |
| |
| struct TlsStreamWithAddr { |
| stream: tokio_rustls::server::TlsStream<tokio::net::TcpStream>, |
| connect_info: Loas3ConnectInfo, |
| } |
| |
| impl AsyncRead for TlsStreamWithAddr { |
| fn poll_read( |
| mut self: Pin<&mut Self>, |
| cx: &mut Context<'_>, |
| buf: &mut ReadBuf<'_>, |
| ) -> Poll<std::io::Result<()>> { |
| Pin::new(&mut self.stream).poll_read(cx, buf) |
| } |
| } |
| |
| impl AsyncWrite for TlsStreamWithAddr { |
| fn poll_write( |
| mut self: Pin<&mut Self>, |
| cx: &mut Context<'_>, |
| buf: &[u8], |
| ) -> Poll<std::io::Result<usize>> { |
| Pin::new(&mut self.stream).poll_write(cx, buf) |
| } |
| |
| fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { |
| Pin::new(&mut self.stream).poll_flush(cx) |
| } |
| |
| fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { |
| Pin::new(&mut self.stream).poll_shutdown(cx) |
| } |
| } |
| |
| impl tonic::transport::server::Connected for TlsStreamWithAddr { |
| type ConnectInfo = Loas3ConnectInfo; |
| |
| fn connect_info(&self) -> Self::ConnectInfo { |
| self.connect_info.clone() |
| } |
| } |
| |
| #[derive(Debug)] |
| struct CustomClientCertVerifier { |
| inner: Arc<dyn ClientCertVerifier>, |
| crls: Option<Vec<u8>>, |
| } |
| |
| impl CustomClientCertVerifier { |
| fn new(root_cert_store: RootCertStore, crls_path: Option<&str>) -> Self { |
| let client_verifier = |
| rustls::server::WebPkiClientVerifier::builder(Arc::new(root_cert_store)) |
| .build() |
| .unwrap(); |
| |
| let crls = match crls_path { |
| Some(path) => match std::fs::read(path) { |
| Ok(data) => Some(data), |
| Err(_) => { |
| println!("WARNING! Failed to load CRL from {}", path); |
| None |
| } |
| }, |
| None => None, |
| }; |
| |
| CustomClientCertVerifier { |
| inner: client_verifier, |
| crls, |
| } |
| } |
| } |
| |
| fn loas3_verify_client_cert( |
| peer_dns_names: Vec<String>, |
| peer_uri_names: Vec<String>, |
| root_subject_str: &str, |
| ) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> { |
| let mut uri_cstrings = Vec::new(); |
| let mut dns_cstrings = Vec::new(); |
| |
| assert!(peer_dns_names.len() < 8 && peer_uri_names.len() < 8); |
| let mut uri_names = [c_string_view { |
| data: std::ptr::null(), |
| length: 0, |
| }; 8]; |
| let mut uri_count = 0; |
| for uri in peer_uri_names { |
| let c_uri = CString::new(uri).unwrap(); |
| uri_names[uri_count] = c_string_view { |
| data: c_uri.as_ptr(), |
| length: c_uri.as_bytes().len(), |
| }; |
| uri_cstrings.push(c_uri); // Keep the CString alive, Rust can not check lifetime over ffi |
| uri_count += 1; |
| } |
| |
| let mut dns_names = [c_string_view { |
| data: std::ptr::null(), |
| length: 0, |
| }; 8]; |
| let mut dns_count = 0; |
| for dns in peer_dns_names { |
| let c_dns = CString::new(dns).unwrap(); |
| dns_names[dns_count] = c_string_view { |
| data: c_dns.as_ptr(), |
| length: c_dns.as_bytes().len(), |
| }; |
| dns_cstrings.push(c_dns); // Keep the CString alive, Rust can not check lifetime over ffi |
| dns_count += 1; |
| } |
| |
| let c_subject_str = CString::new(root_subject_str).unwrap(); |
| let subject_str = c_string_view { |
| data: c_subject_str.as_ptr(), |
| // TODO: bypass root_subject_str check for now |
| // length: c_subject_str.as_bytes_with_nul().len(), |
| length: 0, |
| }; |
| |
| // Calling C wrapper for certificate-authority-policy |
| let status = unsafe { |
| ValidatePeer( |
| &c_vector_string_view { |
| data: &mut uri_names[0] as *mut c_string_view, |
| size: uri_count, |
| }, |
| c_span_string_view { |
| data: &mut dns_names[0] as *mut c_string_view, |
| size: dns_count, |
| }, |
| subject_str, |
| ) |
| }; |
| |
| if status.code != 0 { |
| println!("LOAS3 client validation error: {}", status.code); |
| Err(rustls::Error::General(format!( |
| "ValidatePeer returned error: {}", |
| status.code |
| ))) |
| } else { |
| println!("LOAS3 client validation passed"); |
| Ok(rustls::server::danger::ClientCertVerified::assertion()) |
| } |
| } |
| |
| impl ClientCertVerifier for CustomClientCertVerifier { |
| fn root_hint_subjects(&self) -> &[DistinguishedName] { |
| self.inner.root_hint_subjects() |
| } |
| |
| fn verify_client_cert( |
| &self, |
| end_entity: &CertificateDer<'_>, |
| intermediates: &[CertificateDer<'_>], |
| now: UnixTime, |
| ) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> { |
| // Verify the client certificate chain |
| self.inner |
| .verify_client_cert(end_entity, intermediates, now)?; |
| |
| let root_subject_str = "".to_string(); |
| let subject = self.inner.root_hint_subjects().to_owned(); |
| for name in &subject { |
| match parse_distinguished_name(name) { |
| // TODO: set the name_string to root_subject_str |
| Ok(name_string) => println!("Distinguished Name: {}", name_string), |
| Err(e) => eprintln!("Failed to parse distinguished name: {}", e), |
| } |
| } |
| |
| // Parse the DER-encoded certificate |
| let (_rem, cert) = match X509Certificate::from_der(end_entity) { |
| Ok((rem, cert)) => { |
| if !rem.is_empty() { |
| return Err(rustls::Error::General( |
| "Certificate parser did not consume all input".to_string(), |
| )); |
| } |
| (rem, cert) |
| } |
| Err(e) => { |
| return Err(rustls::Error::General(format!( |
| "Error parsing certificate: {}", |
| e |
| ))) |
| } |
| }; |
| |
| // Check if the certificate is revoked |
| if self.check_crl(&cert) { |
| return Err(rustls::Error::General( |
| "Client certificate revoked".to_string(), |
| )); |
| } |
| |
| let sans = extract_sans(&cert); |
| match sans { |
| Ok((dns_names, uris)) => { |
| println!("DNS Names: {:?}", dns_names); |
| println!("URIs: {:?}", uris); |
| loas3_verify_client_cert(dns_names, uris, &root_subject_str) |
| } |
| Err(e) => { |
| println!("Error extracting SANs, but continuing: {}", e); |
| Err(rustls::Error::General( |
| "ValidatePeer failed to extract SANs".to_string(), |
| )) |
| } |
| } |
| } |
| |
| fn verify_tls12_signature( |
| &self, |
| message: &[u8], |
| cert: &CertificateDer<'_>, |
| dss: &DigitallySignedStruct, |
| ) -> Result<HandshakeSignatureValid, Error> { |
| self.inner.verify_tls12_signature(message, cert, dss) |
| } |
| |
| fn verify_tls13_signature( |
| &self, |
| message: &[u8], |
| cert: &CertificateDer<'_>, |
| dss: &DigitallySignedStruct, |
| ) -> Result<HandshakeSignatureValid, Error> { |
| self.inner.verify_tls13_signature(message, cert, dss) |
| } |
| |
| fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { |
| self.inner.supported_verify_schemes() |
| } |
| } |
| |
| fn extract_sans( |
| cert: &X509Certificate, |
| ) -> Result<(Vec<String>, Vec<String>), Box<dyn std::error::Error>> { |
| // Extract SANs |
| let sans = cert.tbs_certificate.subject_alternative_name()?; |
| let sans = sans.ok_or("No SAN extension found")?; |
| let dns_names = sans |
| .value |
| .general_names |
| .iter() |
| .filter_map(|name| { |
| if let GeneralName::DNSName(dns) = name { |
| Some(dns.to_string()) |
| } else { |
| None |
| } |
| }) |
| .collect(); |
| |
| let uris = sans |
| .value |
| .general_names |
| .iter() |
| .filter_map(|name| { |
| if let GeneralName::URI(uri) = name { |
| Some(uri.to_string()) |
| } else { |
| None |
| } |
| }) |
| .collect(); |
| |
| Ok((dns_names, uris)) |
| } |
| |
| fn parse_distinguished_name(dn: &DistinguishedName) -> Result<String, Box<dyn std::error::Error>> { |
| // Attempt to parse the DistinguishedName as an X509Name |
| let parsed_name = X509Name::from_der(dn.as_ref())?; |
| Ok(parsed_name.1.to_string()) |
| } |
| |
| pub fn load_cert(file_name: &str) -> std::io::Result<Vec<CertificateDer<'static>>> { |
| let file = File::open(file_name)?; |
| let mut reader = std::io::BufReader::new(file); |
| rustls_pemfile::certs(&mut reader).collect() |
| } |
| |
| fn load_key(file_name: &str) -> std::io::Result<PrivateKeyDer<'static>> { |
| let file = File::open(file_name)?; |
| let mut reader = std::io::BufReader::new(file); |
| rustls_pemfile::private_key(&mut reader).map(|key| key.unwrap()) |
| } |
| |
| impl CustomClientCertVerifier { |
| fn check_crl(&self, certificate: &X509Certificate) -> bool { |
| if let Some(data) = &self.crls { |
| // Determine if the data is DER or PEM format |
| let der_data: Vec<u8> = if data.starts_with(&[0x30, 0x82]) { |
| // Data is likely in DER format |
| data.clone() |
| } else { |
| // Data is likely in PEM format, try to parse it |
| match parse_x509_pem(data) { |
| Ok((_, pem)) => pem.contents.clone(), |
| Err(_) => { |
| println!("Could not decode the PEM file"); |
| return false; // If parsing fails, return false |
| } |
| } |
| }; |
| |
| match parse_x509_crl(&der_data) { |
| Ok((_, crl)) => { |
| // Check if the certificate's serial number is in the list of revoked certificates |
| crl.iter_revoked_certificates().any(|rc| { |
| println!("revoked serial {:?}", rc.raw_serial()); |
| println!( |
| "certificate serial {:?}, is revoked {:?}", |
| certificate.tbs_certificate.raw_serial(), |
| rc.raw_serial() == certificate.tbs_certificate.raw_serial() |
| ); |
| rc.raw_serial() == certificate.tbs_certificate.raw_serial() |
| }) |
| } |
| Err(_) => { |
| println!("Could not decode DER data"); |
| false // If parsing fails, return false |
| } |
| } |
| } else { |
| false // If no CRL data is present, return false |
| } |
| } |
| } |
| |
| pub async fn run_secure_server( |
| port: u16, |
| key: &str, |
| cert: &str, |
| cacert: &str, |
| policy: &str, |
| crls: Option<&str>, |
| app: Router, |
| grpc: MachineTelemetryServer<BmcTelemetryService>, |
| run_redfish: bool, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| // Calling C wrapper for certificate-authority-policy |
| unsafe { |
| let c_policy = CString::new(policy).unwrap(); |
| let policy_string_view = c_string_view { |
| data: c_policy.as_ptr(), |
| length: c_policy.as_bytes().len(), |
| }; |
| SetCertificateAuthorityPolicyFilePath(policy_string_view); |
| // SetCertificateAuthorityPolicyFileMonitorIntervalSeconds(3600); |
| } |
| |
| // Load server's private key and certificate |
| let cert = load_cert(cert)?; |
| let key = load_key(key)?; |
| |
| // Load root CA certificate to verify clients |
| let client_ca_cert: Vec<CertificateDer<'static>> = load_cert(cacert)?; |
| let mut root_cert_store = rustls::RootCertStore::empty(); |
| for ca_cert in client_ca_cert { |
| let _ = root_cert_store.add(ca_cert); |
| } |
| |
| let client_verifier = Arc::new(CustomClientCertVerifier::new(root_cert_store, crls)); |
| let mut tls_config = rustls::ServerConfig::builder() |
| .with_client_cert_verifier(client_verifier) |
| .with_single_cert(cert, key) |
| .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; |
| tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; |
| |
| let tls_config = Arc::new(tls_config); |
| |
| let acceptor = tokio_rustls::TlsAcceptor::from(tls_config); |
| |
| let addr = format!("127.0.0.1:{port}"); |
| let addr: std::net::SocketAddr = addr.parse().unwrap(); |
| let listener = tokio::net::TcpListener::bind(addr).await?; |
| println!("Server listening on {}", addr); |
| |
| if run_redfish { |
| loop { |
| let (stream, _) = listener.accept().await?; |
| let acceptor = acceptor.clone(); |
| let tower_service = app.clone(); |
| tokio::spawn(async move { |
| if let Ok(tls_stream) = acceptor.accept(stream).await { |
| let hyper_service = hyper::service::service_fn( |
| move |request: axum::extract::Request<Incoming>| { |
| tower_service.clone().oneshot(request) |
| }, |
| ); |
| |
| let conn = TokioIo::new(tls_stream); |
| if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) |
| .serve_connection_with_upgrades(conn, hyper_service) |
| .await |
| { |
| eprintln!("failed to serve connection: {err:#}"); |
| } |
| } |
| }); |
| } |
| } else { |
| let incoming_tls_stream = futures_util::stream::unfold(listener, |listener| async { |
| let (socket, remote_addr) = match listener.accept().await { |
| Ok(conn) => conn, |
| Err(_) => return None, |
| }; |
| match acceptor.accept(socket).await { |
| Ok(tls_stream) => { |
| let connect_info = Loas3ConnectInfo { remote_addr }; |
| Some(( |
| Ok(TlsStreamWithAddr { |
| stream: tls_stream, |
| connect_info, |
| }), |
| listener, |
| )) |
| } |
| Err(_) => Some(( |
| Err(std::io::Error::new(std::io::ErrorKind::Other, "TLS Error")), |
| listener, |
| )), |
| } |
| }); |
| |
| tonic::transport::Server::builder() |
| .add_service(grpc) |
| .serve_with_incoming(incoming_tls_stream) |
| .await?; |
| } |
| |
| Ok(()) |
| } |