use futures::prelude::*;
use rustls_pemfile::certs;
use std::io::{self, BufReader, Cursor};
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::rustls::server::danger::ClientCertVerifier;
use tokio_rustls::rustls::server::WebPkiClientVerifier;
use tokio_rustls::rustls::{self, RootCertStore};
use tokio_rustls::{TlsAcceptor, TlsConnector};
use logimesh::context::Context;
use logimesh::server::{BaseChannel, Channel};
use logimesh::tokio_serde::formats::Bincode;
use logimesh::tokio_util::codec::length_delimited::LengthDelimitedCodec;
use logimesh::transport;
#[logimesh::component]
pub trait PingService {
async fn ping() -> String;
}
#[derive(Clone)]
struct Service;
impl PingService for Service {
async fn ping(self, _: Context) -> String {
"🔒".to_owned()
}
}
const END_CHAIN: &str = include_str!("certs/eddsa/end.chain");
const CLIENT_PRIVATEKEY_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.key");
const CLIENT_CERT_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.cert");
const END_CERT: &str = include_str!("certs/eddsa/end.cert");
const END_PRIVATEKEY: &str = include_str!("certs/eddsa/end.key");
const CLIENT_CHAIN_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.chain");
pub fn load_certs(data: &str) -> Vec<rustls::pki_types::CertificateDer<'static>> {
certs(&mut BufReader::new(Cursor::new(data))).map(|result| result.unwrap()).collect()
}
pub fn load_private_key(key: &str) -> rustls::pki_types::PrivateKeyDer {
let mut reader = BufReader::new(Cursor::new(key));
loop {
match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
Some(rustls_pemfile::Item::Pkcs1Key(key)) => return key.into(),
Some(rustls_pemfile::Item::Pkcs8Key(key)) => return key.into(),
Some(rustls_pemfile::Item::Sec1Key(key)) => return key.into(),
None => break,
_ => continue,
}
}
panic!("no keys found in {:?} (encrypted keys not supported)", key);
}
async fn spawn(fut: impl Future<Output = ()> + Send + 'static) {
tokio::spawn(fut);
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cert = load_certs(END_CERT);
let key = load_private_key(END_PRIVATEKEY);
let server_addr = (IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
let mut client_auth_roots = RootCertStore::empty();
for root in load_certs(CLIENT_CHAIN_CLIENT_AUTH) {
client_auth_roots.add(root).unwrap();
}
let client_auth: Arc<dyn ClientCertVerifier> = WebPkiClientVerifier::builder(
client_auth_roots.into(),
)
.build()
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{}", err)))
.unwrap();
let config = rustls::ServerConfig::builder()
.with_client_cert_verifier(client_auth) .with_single_cert(cert, key)
.unwrap();
let acceptor = TlsAcceptor::from(Arc::new(config));
let listener = TcpListener::bind(&server_addr).await.unwrap();
let codec_builder = LengthDelimitedCodec::builder();
tokio::spawn(async move {
loop {
let (stream, _peer_addr) = listener.accept().await.unwrap();
let tls_stream = acceptor.accept(stream).await.unwrap();
let framed = codec_builder.new_framed(tls_stream);
let transport = transport::new(framed, Bincode::default());
let fut = BaseChannel::with_defaults(transport).execute(Service.logimesh_serve()).for_each(spawn);
tokio::spawn(fut);
}
});
let mut root_store = rustls::RootCertStore::empty();
for root in load_certs(END_CHAIN) {
root_store.add(root).unwrap();
}
let client_auth_private_key = load_private_key(CLIENT_PRIVATEKEY_CLIENT_AUTH);
let client_auth_certs = load_certs(CLIENT_CERT_CLIENT_AUTH);
let config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(client_auth_certs, client_auth_private_key)?;
let domain = rustls::pki_types::ServerName::try_from("localhost")?;
let connector = TlsConnector::from(Arc::new(config));
let stream = TcpStream::connect(server_addr).await?;
let stream = connector.connect(domain, stream).await?;
let transport = transport::new(codec_builder.new_framed(stream), Bincode::default());
let answer = PingServiceClient::new(Default::default(), transport).spawn().ping(logimesh::context::current()).await?;
println!("ping answer: {answer}");
Ok(())
}