blob: f91775d4dc0ac3618b761620a83f8ad86448d62a [file]
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(())
}