use tonic::transport::{Certificate, ClientTlsConfig, Endpoint, Identity, ServerTlsConfig};
use crate::config::PeerTlsConfig;
use crate::error::StandaloneError;
pub(crate) struct PeerTlsMaterial {
pub server: ServerTlsConfig,
pub client: ClientTlsConfig,
}
fn read(path: &std::path::Path) -> Result<Vec<u8>, StandaloneError> {
std::fs::read(path).map_err(|source| StandaloneError::Tls {
path: path.to_path_buf(),
source: Box::new(source),
})
}
pub(crate) fn build_peer_tls(cfg: &PeerTlsConfig) -> Result<PeerTlsMaterial, StandaloneError> {
let cert = read(&cfg.cert)?;
let key = read(&cfg.key)?;
let ca = read(&cfg.ca)?;
let identity = Identity::from_pem(&cert, &key);
let ca_cert = Certificate::from_pem(&ca);
let server = ServerTlsConfig::new()
.identity(identity.clone())
.client_ca_root(ca_cert.clone());
let client = ClientTlsConfig::new()
.ca_certificate(ca_cert)
.identity(identity);
tonic::transport::Server::builder()
.tls_config(server.clone())
.map_err(|source| StandaloneError::Tls {
path: cfg.cert.clone(),
source: Box::new(source),
})?;
Endpoint::from_static("https://127.0.0.1:65535")
.tls_config(client.clone())
.map_err(|source| StandaloneError::Tls {
path: cfg.cert.clone(),
source: Box::new(source),
})?;
Ok(PeerTlsMaterial { server, client })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::PeerTlsConfig;
fn write_node_certs(dir: &std::path::Path) -> PeerTlsConfig {
use rcgen::{BasicConstraints, CertificateParams, IsCa, KeyPair};
let ca_key = KeyPair::generate().unwrap();
let mut ca_params = CertificateParams::new(vec!["tso-ca".to_string()]).unwrap();
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
let ca_cert = ca_params.self_signed(&ca_key).unwrap();
let node_key = KeyPair::generate().unwrap();
let node_params = CertificateParams::new(vec!["127.0.0.1".to_string()]).unwrap();
let node_cert = node_params.signed_by(&node_key, &ca_cert, &ca_key).unwrap();
let cert = dir.join("node.crt");
let key = dir.join("node.key");
let ca = dir.join("ca.crt");
std::fs::write(&cert, node_cert.pem()).unwrap();
std::fs::write(&key, node_key.serialize_pem()).unwrap();
std::fs::write(&ca, ca_cert.pem()).unwrap();
PeerTlsConfig { cert, key, ca }
}
#[tokio::test]
async fn valid_trio_builds_both_configs() {
let dir = tempfile::tempdir().unwrap();
let cfg = write_node_certs(dir.path());
let mat = build_peer_tls(&cfg).expect("valid trio must build");
let _ = mat.server;
let _ = mat.client;
}
#[tokio::test]
async fn missing_file_is_a_tls_error() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = write_node_certs(dir.path());
cfg.cert = dir.path().join("does-not-exist.crt");
let err = match build_peer_tls(&cfg) {
Ok(_) => panic!("expected a Tls error"),
Err(e) => e,
};
assert!(matches!(err, StandaloneError::Tls { .. }), "got {err:?}");
}
#[tokio::test]
async fn invalid_pem_is_a_tls_error() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = write_node_certs(dir.path());
let garbage = dir.path().join("garbage.crt");
std::fs::write(&garbage, b"not a pem").unwrap();
cfg.cert = garbage;
let err = match build_peer_tls(&cfg) {
Ok(_) => panic!("expected a Tls error"),
Err(e) => e,
};
assert!(matches!(err, StandaloneError::Tls { .. }), "got {err:?}");
}
}