diff --git a/tarpc/Cargo.toml b/tarpc/Cargo.toml index ff0f59b..9c6415e 100644 --- a/tarpc/Cargo.toml +++ b/tarpc/Cargo.toml @@ -78,6 +78,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } tokio = { version = "1", features = ["full", "test-util"] } tokio-serde = { version = "0.8", features = ["json", "bincode"] } trybuild = "1.0" +tokio-rustls = "0.23" +rustls-pemfile = "1.0" [package.metadata.docs.rs] all-features = true @@ -103,6 +105,10 @@ required-features = ["full"] name = "custom_transport" required-features = ["serde1", "tokio1", "serde-transport"] +[[example]] +name = "tls_over_tcp" +required-features = ["full"] + [[test]] name = "service_functional" required-features = ["serde-transport"] diff --git a/tarpc/examples/certs/eddsa/client.cert b/tarpc/examples/certs/eddsa/client.cert new file mode 100644 index 0000000..0d31445 --- /dev/null +++ b/tarpc/examples/certs/eddsa/client.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk +RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMDMxNzEwMTEwNFoXDTI4MDkw +NjEwMTEwNFowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA +NTKuLume19IhJfEFd/5OZUuYDKZH6xvy4AGver17OoejgZswgZgwDAYDVR0TAQH/ +BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O +BBYEFDjdrlMu4tyw5MHtbg7WnzSGRBpFMEQGA1UdIwQ9MDuAFHIl7fHKWP6/l8FE +fI2YEIM3oHxKoSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF +BgMrZXADQQCaahfj/QLxoCOpvl6y0ZQ9CpojPqBnxV3460j5nUOp040Va2MpF137 +izCBY7LwgUE/YG6E+kH30G4jMEnqVEYK +-----END CERTIFICATE----- diff --git a/tarpc/examples/certs/eddsa/client.chain b/tarpc/examples/certs/eddsa/client.chain new file mode 100644 index 0000000..cd760dc --- /dev/null +++ b/tarpc/examples/certs/eddsa/client.chain @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIBeDCCASqgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE +U0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0MTAxMTA0WjAuMSwwKgYDVQQD +DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh +AEFsAexz4x2R4k4+PnTbvRVn0r3F/qw/zVnNBxfGcoEpo38wfTAdBgNVHQ4EFgQU +ciXt8cpY/r+XwUR8jZgQgzegfEowIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG +AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MB8GA1UdIwQYMBaAFKYU +oLdKeY7mp7QgMZKrkVtSWYBKMAUGAytlcANBAHVpNpCV8nu4fkH3Smikx5A9qtHc +zgLIyp+wrF1a4YSa6sfTvuQmJd5aF23OXgq5grCOPXtdpHO50Mx5Qy74zQg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBTDCB/6ADAgECAhRZLuF0TWjDs/31OO8VeKHkNIJQaDAFBgMrZXAwHDEaMBgG +A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0 +MTAxMTA0WjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh +ABRPZ4TiuBE8CqAFByZvqpMo/unjnnryfG2AkkWGXpa3o1MwUTAdBgNVHQ4EFgQU +phSgt0p5juantCAxkquRW1JZgEowHwYDVR0jBBgwFoAUphSgt0p5juantCAxkquR +W1JZgEowDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQB29o8erJA0/a8/xOHilOCC +t/s5wPHHnS5NSKx/m2N2nRn3zPxEnETlrAmGulJoeKOx8OblwmPi9rBT2K+QY2UB +-----END CERTIFICATE----- diff --git a/tarpc/examples/certs/eddsa/client.key b/tarpc/examples/certs/eddsa/client.key new file mode 100644 index 0000000..a407ea8 --- /dev/null +++ b/tarpc/examples/certs/eddsa/client.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIIJX9ThTHpVS1SNZb6HP4myg4fRInIVGunTRdgnc+weH +-----END PRIVATE KEY----- diff --git a/tarpc/examples/certs/eddsa/end.cert b/tarpc/examples/certs/eddsa/end.cert new file mode 100644 index 0000000..b2eb159 --- /dev/null +++ b/tarpc/examples/certs/eddsa/end.cert @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBuDCCAWqgAwIBAgICAcgwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk +RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMDMxNzEwMTEwNFoXDTI4MDkw +NjEwMTEwNFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wKjAFBgMrZXADIQDc +RLl3/N2tPoWnzBV3noVn/oheEl8IUtiY11Vg/QXTUKOBwDCBvTAMBgNVHRMBAf8E +AjAAMAsGA1UdDwQEAwIGwDAdBgNVHQ4EFgQUk7U2mnxedNWBAH84BsNy5si3ZQow +RAYDVR0jBD0wO4AUciXt8cpY/r+XwUR8jZgQgzegfEqhIKQeMBwxGjAYBgNVBAMM +EXBvbnl0b3duIEVkRFNBIENBggF7MDsGA1UdEQQ0MDKCDnRlc3RzZXJ2ZXIuY29t +ghVzZWNvbmQudGVzdHNlcnZlci5jb22CCWxvY2FsaG9zdDAFBgMrZXADQQCFWIcF +9FiztCuUNzgXDNu5kshuflt0RjkjWpGlWzQjGoYM2IvYhNVPeqnCiY92gqwDSBtq +amD2TBup4eNUCsQB +-----END CERTIFICATE----- diff --git a/tarpc/examples/certs/eddsa/end.chain b/tarpc/examples/certs/eddsa/end.chain new file mode 100644 index 0000000..cd760dc --- /dev/null +++ b/tarpc/examples/certs/eddsa/end.chain @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIBeDCCASqgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE +U0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0MTAxMTA0WjAuMSwwKgYDVQQD +DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh +AEFsAexz4x2R4k4+PnTbvRVn0r3F/qw/zVnNBxfGcoEpo38wfTAdBgNVHQ4EFgQU +ciXt8cpY/r+XwUR8jZgQgzegfEowIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG +AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MB8GA1UdIwQYMBaAFKYU +oLdKeY7mp7QgMZKrkVtSWYBKMAUGAytlcANBAHVpNpCV8nu4fkH3Smikx5A9qtHc +zgLIyp+wrF1a4YSa6sfTvuQmJd5aF23OXgq5grCOPXtdpHO50Mx5Qy74zQg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBTDCB/6ADAgECAhRZLuF0TWjDs/31OO8VeKHkNIJQaDAFBgMrZXAwHDEaMBgG +A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0 +MTAxMTA0WjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh +ABRPZ4TiuBE8CqAFByZvqpMo/unjnnryfG2AkkWGXpa3o1MwUTAdBgNVHQ4EFgQU +phSgt0p5juantCAxkquRW1JZgEowHwYDVR0jBBgwFoAUphSgt0p5juantCAxkquR +W1JZgEowDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQB29o8erJA0/a8/xOHilOCC +t/s5wPHHnS5NSKx/m2N2nRn3zPxEnETlrAmGulJoeKOx8OblwmPi9rBT2K+QY2UB +-----END CERTIFICATE----- diff --git a/tarpc/examples/certs/eddsa/end.key b/tarpc/examples/certs/eddsa/end.key new file mode 100644 index 0000000..f5541b3 --- /dev/null +++ b/tarpc/examples/certs/eddsa/end.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIMU6xGVe8JTpZ3bN/wajHfw6pEHt0Rd7wPBxds9eEFy2 +-----END PRIVATE KEY----- diff --git a/tarpc/examples/tls_over_tcp.rs b/tarpc/examples/tls_over_tcp.rs new file mode 100644 index 0000000..92d76c9 --- /dev/null +++ b/tarpc/examples/tls_over_tcp.rs @@ -0,0 +1,152 @@ +use rustls_pemfile::certs; +use std::io::{BufReader, Cursor}; +use std::net::{IpAddr, Ipv4Addr}; +use tokio_rustls::rustls::server::AllowAnyAuthenticatedClient; + +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio::net::TcpStream; +use tokio_rustls::rustls::{self, Certificate, OwnedTrustAnchor, RootCertStore}; +use tokio_rustls::{webpki, TlsAcceptor, TlsConnector}; + +use tarpc::context::Context; +use tarpc::serde_transport as transport; +use tarpc::server::{BaseChannel, Channel}; +use tarpc::tokio_serde::formats::Bincode; +use tarpc::tokio_util::codec::length_delimited::LengthDelimitedCodec; + +#[tarpc::service] +pub trait PingService { + async fn ping() -> String; +} + +#[derive(Clone)] +struct Service; + +#[tarpc::server] +impl PingService for Service { + async fn ping(self, _: Context) -> String { + "🔒".to_owned() + } +} + +// certs were generated with openssl 3 https://github.com/rustls/rustls/tree/main/test-ca +// used on client-side for server tls +const END_CHAIN: &[u8] = include_bytes!("certs/eddsa/end.chain"); +// used on client-side for client-auth +const CLIENT_PRIVATEKEY_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.key"); +const CLIENT_CERT_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.cert"); + +// used on server-side for server tls +const END_CERT: &str = include_str!("certs/eddsa/end.cert"); +const END_PRIVATEKEY: &str = include_str!("certs/eddsa/end.key"); +// used on server-side for client-auth +const CLIENT_CHAIN_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.chain"); + +pub fn load_private_key(key: &str) -> rustls::PrivateKey { + 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::RSAKey(key)) => return rustls::PrivateKey(key), + Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key), + Some(rustls_pemfile::Item::ECKey(key)) => return rustls::PrivateKey(key), + None => break, + _ => {} + } + } + panic!("no keys found in {:?} (encrypted keys not supported)", key); +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // -------------------- start here to setup tls tcp tokio stream -------------------------- + // ref certs and loading from: https://github.com/tokio-rs/tls/blob/master/tokio-rustls/tests/test.rs + // ref basic tls server setup from: https://github.com/tokio-rs/tls/blob/master/tokio-rustls/examples/server/src/main.rs + let cert = certs(&mut BufReader::new(Cursor::new(END_CERT))) + .unwrap() + .into_iter() + .map(rustls::Certificate) + .collect(); + let key = load_private_key(END_PRIVATEKEY); + let server_addr = (IpAddr::V4(Ipv4Addr::LOCALHOST), 5000); + + // ------------- server side client_auth cert loading start + let roots: Vec = certs(&mut BufReader::new(Cursor::new(CLIENT_CHAIN_CLIENT_AUTH))) + .unwrap() + .into_iter() + .map(rustls::Certificate) + .collect(); + let mut client_auth_roots = RootCertStore::empty(); + for root in roots { + client_auth_roots.add(&root).unwrap(); + } + let client_auth = AllowAnyAuthenticatedClient::new(client_auth_roots); + // ------------- server side client_auth cert loading end + + let config = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(client_auth) // use .with_no_client_auth() instead if you don't want 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(); + + // ref ./custom_transport.rs server side + tokio::spawn(async move { + loop { + let (stream, _peer_addr) = listener.accept().await.unwrap(); + let acceptor = acceptor.clone(); + 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.serve()); + tokio::spawn(fut); + } + }); + + // ---------------------- client connection --------------------- + // cert loading from: https://github.com/tokio-rs/tls/blob/357bc562483dcf04c1f8d08bd1a831b144bf7d4c/tokio-rustls/tests/test.rs#L113 + // tls client connection from https://github.com/tokio-rs/tls/blob/master/tokio-rustls/examples/client/src/main.rs + let chain = certs(&mut std::io::Cursor::new(END_CHAIN)).unwrap(); + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_server_trust_anchors(chain.iter().map(|cert| { + let ta = webpki::TrustAnchor::try_from_cert_der(&cert[..]).unwrap(); + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); + + let client_auth_private_key = load_private_key(CLIENT_PRIVATEKEY_CLIENT_AUTH); + let client_auth_certs: Vec = + certs(&mut BufReader::new(Cursor::new(CLIENT_CERT_CLIENT_AUTH))) + .unwrap() + .into_iter() + .map(rustls::Certificate) + .collect(); + + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_single_cert(client_auth_certs, client_auth_private_key)?; // use .with_no_client_auth() instead if you don't want client-auth + + let domain = rustls::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(tarpc::context::current()) + .await?; + + println!("ping answer: {answer}"); + + Ok(()) +}