rustls-spiffe 0.3.0

SPIFFE Integration with Rustls to provide Server and Client certs/keys and CA bundle
use rustls_spiffe::{
    ClientConfigProvider, ServerConfigProvider, SpiffeClientConfigStream, SpiffeServerConfigStream,
    extract_leaf_cert, extract_spiffe_id,
};
use spiffe::SpiffeId;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::test(flavor = "multi_thread")]
async fn successful_handshake() {
    rustls::crypto::CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider())
        .unwrap();
    let (req, res) = tokio::join!(oneshot_server(), client());
    let res = res.unwrap();
    let req = req.unwrap();
    assert_eq!(req.data, "PING",);
    assert_eq!(
        req.svid,
        "spiffe://example.org/testservice".try_into().unwrap()
    );
    assert_eq!(res, "PONG",);
}

struct Request {
    data: String,
    svid: SpiffeId,
}

async fn oneshot_server() -> Result<Request, Box<dyn std::error::Error>> {
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    let config_stream_builder =
        SpiffeServerConfigStream::builder(vec!["example.org".try_into().unwrap()]);
    let config_provider = ServerConfigProvider::start(config_stream_builder)
        .await
        .unwrap();

    let (stream, _) = listener.accept().await.unwrap();

    let acceptor =
        tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::default(), stream);
    tokio::pin!(acceptor);

    let config_provider = config_provider.clone();
    match acceptor.as_mut().await {
        Ok(start) => {
            let config = config_provider.get_config();
            let mut stream = start.into_stream(config).await.unwrap();
            let leaf = extract_leaf_cert(&stream);
            let svid = extract_spiffe_id(leaf).unwrap();
            let mut buf = Vec::new();
            stream.read_to_end(&mut buf).await.unwrap();
            stream.write_all("PONG".as_bytes()).await.unwrap();
            stream.shutdown().await.unwrap();
            Ok(Request {
                data: String::from_utf8_lossy(&buf).to_string(),
                svid,
            })
        }
        Err(err) => {
            if let Some(mut stream) = acceptor.take_io() {
                stream.write_all("FAIL".as_bytes()).await.unwrap();
            }
            Err(err.into())
        }
    }
}

async fn client() -> Result<String, Box<dyn std::error::Error>> {
    let stream = tokio::net::TcpStream::connect("127.0.0.1:3000")
        .await
        .unwrap();
    let config_provider = ClientConfigProvider::start(SpiffeClientConfigStream::builder(vec![
        "example.org".try_into().unwrap(),
    ]))
    .await
    .unwrap();
    let connector = tokio_rustls::TlsConnector::from(config_provider.get_config());
    let tstream = connector
        .connect("localhost".try_into().unwrap(), stream)
        .await
        .unwrap();
    let (mut rx, mut tx) = tokio::io::split(tstream);
    tx.write_all("PING".as_bytes()).await.unwrap();
    tx.shutdown().await.unwrap();
    let mut buf = Vec::new();
    rx.read_to_end(&mut buf).await.unwrap();
    let res = String::from_utf8_lossy(&buf).to_string();
    Ok(res)
}