use anyhow::{Context, Result};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::ServerConfig;
use sha2::{Digest, Sha256};
use std::sync::Arc;
pub struct ControlChannelTls {
pub config: Arc<ServerConfig>,
pub fingerprint: String,
}
pub fn build_control_channel_tls() -> Result<ControlChannelTls> {
let (cert, key) = generate_self_signed()?;
let fingerprint = fingerprint_sha256(&cert);
let provider = rustls::crypto::ring::default_provider();
let config = ServerConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])
.context("Failed to select TLS protocol versions for the OpenVPN control channel")?
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.context("Failed to build the OpenVPN control-channel TLS configuration")?;
Ok(ControlChannelTls {
config: Arc::new(config),
fingerprint,
})
}
fn generate_self_signed() -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
use rcgen::{CertificateParams, KeyPair};
let mut params = CertificateParams::new(vec!["netget-openvpn".to_string()])
.context("Failed to build certificate parameters")?;
params.distinguished_name = rcgen::DistinguishedName::new();
params
.distinguished_name
.push(rcgen::DnType::CommonName, "NetGet OpenVPN");
let key_pair = KeyPair::generate().context("Failed to generate a certificate key pair")?;
let cert = params
.self_signed(&key_pair)
.context("Failed to self-sign the control-channel certificate")?;
Ok((
CertificateDer::from(cert.der().to_vec()),
PrivateKeyDer::Pkcs8(key_pair.serialize_der().into()),
))
}
fn fingerprint_sha256(cert: &CertificateDer<'_>) -> String {
let digest = Sha256::digest(cert.as_ref());
digest
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(":")
}
pub fn protocol_version_name(conn: &rustls::ServerConnection) -> String {
match conn.protocol_version() {
Some(rustls::ProtocolVersion::TLSv1_3) => "TLSv1.3".to_string(),
Some(rustls::ProtocolVersion::TLSv1_2) => "TLSv1.2".to_string(),
Some(other) => format!("{:?}", other),
None => "unknown".to_string(),
}
}
pub fn cipher_suite_name(conn: &rustls::ServerConnection) -> String {
conn.negotiated_cipher_suite()
.map(|s| format!("{:?}", s.suite()))
.unwrap_or_else(|| "unknown".to_string())
}