I am working in a file transfer system that has p2p connectivity library using Rust. It works using just tcp but now I want to improve it using TLS but I do not understand 2 things. First is how can 2 peers share the Certificate Authorities CA so they can verify that the connection is trustworthy.
Secondly, I am trying to use the tokio_native_tls which wraps the native_tls library and adds async. I am following the examples in the native_tls docs. However they use connector with a domain and as the domain they use Google.
This is the example they provide.
use native_tls::TlsConnector;
use std::io::{Read, Write};
use std::net::TcpStream;
let connector = TlsConnector::new().unwrap();
let stream = TcpStream::connect("google.com:443").unwrap();
let mut stream = connector.connect("google.com", stream).unwrap();
stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
let mut res = vec![];
stream.read_to_end(&mut res).unwrap();
println!("{}", String::from_utf8_lossy(&res));
In the peer to peer connection I describe there is only 2 peers trying to transfer some files. Since they don't have a domain only IP it would not work. I am pretty lost. How can I approach this project?
native-tls
The docs for the synchronous connect
function say that:
The domain is ignored if both SNI and hostname verification are disabled.
Taking that into account, switching to the async
API's, and generating a self-signed certificate, we arrive at the following working code:
[package]
name = "tls"
version = "0.1.0"
edition = "2021"
[dependencies]
rcgen = "0.13.1"
tokio = { version = "1.41.1", features = ["full"] }
tokio-native-tls = "0.3.1"
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio_native_tls::native_tls::Identity;
use tokio_native_tls::{native_tls, TlsAcceptor, TlsConnector};
#[tokio::main]
async fn main() {
let cert = rcgen::generate_simple_self_signed([]).unwrap();
let listener = TcpListener::bind("127.0.0.1:8001").await.unwrap();
let trusted = native_tls::Certificate::from_pem(cert.cert.pem().as_bytes()).unwrap();
tokio::spawn(async move {
let connector = TlsConnector::from(
native_tls::TlsConnector::builder()
.disable_built_in_roots(true)
.add_root_certificate(trusted)
.danger_accept_invalid_hostnames(true)
.use_sni(false)
.build()
.unwrap(),
);
let tcp = TcpStream::connect("0.0.0.0:8001").await.unwrap();
let mut tls = connector.connect("N/A", tcp).await.unwrap();
tls.write_all(b"hello!").await.unwrap();
tls.shutdown().await.unwrap();
});
let acceptor = TlsAcceptor::from(
native_tls::TlsAcceptor::new(
Identity::from_pkcs8(
cert.cert.pem().as_bytes(),
cert.key_pair.serialize_pem().as_bytes(),
)
.unwrap(),
)
.unwrap(),
);
let (tcp, _) = listener.accept().await.unwrap();
let mut tls = acceptor.accept(tcp).await.unwrap();
let mut buf = String::new();
tls.read_to_string(&mut buf).await.unwrap();
println!("read: {buf}");
}
rustls
Here is similar code using rustls
. Differences include:
[package]
name = "tls"
version = "0.1.0"
edition = "2021"
[dependencies]
rcgen = "0.13.1"
tokio = { version = "1.41.1", features = ["full"] }
tokio-rustls = "0.26.0"
use std::net::Ipv4Addr;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::rustls::pki_types::pem::PemObject;
use tokio_rustls::rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, ServerName};
use tokio_rustls::rustls::{ClientConfig, RootCertStore};
use tokio_rustls::{rustls, TlsAcceptor, TlsConnector};
#[tokio::main]
async fn main() {
let cert = rcgen::generate_simple_self_signed(["127.0.0.1".to_owned()]).unwrap();
let listener = TcpListener::bind("127.0.0.1:8001").await.unwrap();
let mut trusted = RootCertStore::empty();
trusted.add(cert.cert.der().clone()).unwrap();
tokio::spawn(async move {
let connector: TlsConnector = TlsConnector::from(Arc::new(
ClientConfig::builder()
.with_root_certificates(trusted)
.with_no_client_auth(),
));
let tcp = TcpStream::connect("127.0.0.1:8001").await.unwrap();
let mut tls = connector
.connect(
ServerName::IpAddress(Ipv4Addr::new(127, 0, 0, 1).into()),
tcp,
)
.await
.unwrap();
tls.write_all(b"hello!").await.unwrap();
tls.shutdown().await.unwrap();
});
let acceptor = TlsAcceptor::from(Arc::new(
rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
vec![cert.cert.der().clone()],
PrivateKeyDer::Pkcs8(
PrivatePkcs8KeyDer::from_pem_slice(cert.key_pair.serialize_pem().as_bytes())
.unwrap(),
),
)
.unwrap(),
));
let (tcp, _) = listener.accept().await.unwrap();
let mut tls = acceptor.accept(tcp).await.unwrap();
let mut buf = String::new();
tls.read_to_string(&mut buf).await.unwrap();
println!("read: {buf}");
}
If you wanted to use a CA-signed certificate, you would add the CA certificate as a trusted root instead.