#![cfg_attr(not(feature = "openssl"), allow(unused))]
use std::any::Any;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::sync::Arc;
use async_trait::async_trait;
use clap::Parser;
use http::header::{CONTENT_LENGTH, CONTENT_TYPE};
use http::{Response, StatusCode};
use pingora_core::apps::http_app::ServeHttp;
use pingora_core::listeners::tls::TlsSettings;
use pingora_core::listeners::TlsAccept;
use pingora_core::protocols::http::ServerSession;
use pingora_core::protocols::tls::TlsRef;
use pingora_core::server::configuration::Opt;
use pingora_core::server::Server;
use pingora_core::services::listening::Service;
use pingora_core::Result;
#[cfg(feature = "openssl")]
use pingora_openssl::{
nid::Nid,
ssl::{NameType, SslFiletype, SslVerifyMode},
x509::{GeneralName, X509Name},
};
struct MyTlsInfo {
sni: Option<String>,
sans: Vec<String>,
common_name: Option<String>,
}
struct MyApp;
#[async_trait]
impl ServeHttp for MyApp {
async fn response(&self, session: &mut ServerSession) -> http::Response<Vec<u8>> {
static EMPTY_VEC: Vec<String> = vec![];
let my_tls_info = session
.digest()
.and_then(|digest| digest.ssl_digest.as_ref())
.and_then(|ssl_digest| ssl_digest.extension.get::<MyTlsInfo>());
let sni = my_tls_info
.and_then(|my_tls_info| my_tls_info.sni.as_deref())
.unwrap_or("<none>");
let sans = my_tls_info
.map(|my_tls_info| &my_tls_info.sans)
.unwrap_or(&EMPTY_VEC);
let common_name = my_tls_info
.and_then(|my_tls_info| my_tls_info.common_name.as_deref())
.unwrap_or("<none>");
let mut message = String::new();
message += &format!("Your SNI was: {sni}\n");
message += &format!("Your SANs were: {sans:?}\n");
message += &format!("Client Common Name (CN): {}\n", common_name);
let message = message.into_bytes();
Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, "text/plain")
.header(CONTENT_LENGTH, message.len())
.body(message)
.unwrap()
}
}
struct MyTlsCallbacks;
#[async_trait]
impl TlsAccept for MyTlsCallbacks {
#[cfg(feature = "openssl")]
async fn handshake_complete_callback(
&self,
tls_ref: &TlsRef,
) -> Option<Arc<dyn Any + Send + Sync>> {
let sni = tls_ref
.servername(NameType::HOST_NAME)
.map(ToOwned::to_owned);
let sans = tls_ref
.peer_certificate()
.and_then(|cert| cert.subject_alt_names())
.map_or(vec![], |sans| {
sans.into_iter()
.filter_map(|san| san_to_string(&san))
.collect::<Vec<_>>()
});
let common_name = tls_ref.peer_certificate().and_then(|cert| {
let cn = cert.subject_name().entries_by_nid(Nid::COMMONNAME).next()?;
Some(cn.data().as_utf8().ok()?.to_string())
});
let tls_info = MyTlsInfo {
sni,
sans,
common_name,
};
Some(Arc::new(tls_info))
}
}
#[cfg(feature = "openssl")]
fn san_to_string(san: &GeneralName) -> Option<String> {
if let Some(dnsname) = san.dnsname() {
return Some(dnsname.to_owned());
}
if let Some(uri) = san.uri() {
return Some(uri.to_owned());
}
if let Some(email) = san.email() {
return Some(email.to_owned());
}
if let Some(ip) = san.ipaddress() {
return bytes_to_ip_addr(ip).map(|addr| addr.to_string());
}
None
}
fn bytes_to_ip_addr(bytes: &[u8]) -> Option<IpAddr> {
match bytes.len() {
4 => {
let addr = Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]);
Some(IpAddr::V4(addr))
}
16 => {
let mut octets = [0u8; 16];
octets.copy_from_slice(bytes);
let addr = Ipv6Addr::from(octets);
Some(IpAddr::V6(addr))
}
_ => None,
}
}
#[cfg(feature = "openssl")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let opt = Opt::parse();
let mut my_server = Server::new(Some(opt))?;
my_server.bootstrap();
let mut my_app = Service::new("my app".to_owned(), MyApp);
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let server_cert_path = format!("{manifest_dir}/examples/keys/server/cert.pem");
let server_key_path = format!("{manifest_dir}/examples/keys/server/key.pem");
let client_ca_path = format!("{manifest_dir}/examples/keys/client-ca/cert.pem");
let callbacks = Box::new(MyTlsCallbacks);
let mut tls_settings = TlsSettings::with_callbacks(callbacks)?;
tls_settings.set_certificate_chain_file(&server_cert_path)?;
tls_settings.set_private_key_file(server_key_path, SslFiletype::PEM)?;
tls_settings.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT);
tls_settings.set_ca_file(&client_ca_path)?;
tls_settings.set_client_ca_list(X509Name::load_client_ca_file(&client_ca_path)?);
my_app.add_tls_with_settings("0.0.0.0:6196", None, tls_settings);
my_server.add_service(my_app);
my_server.run_forever();
}
#[cfg(not(feature = "openssl"))]
fn main() {
eprintln!("This example requires the 'openssl' feature to be enabled.");
}