Skip to content

Commit 3e280aa

Browse files
authored
feat(transport): TCP-TLS (#94)
2 parents 77be1b3 + 97935bb commit 3e280aa

File tree

20 files changed

+1030
-26
lines changed

20 files changed

+1030
-26
lines changed

Cargo.lock

Lines changed: 95 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[workspace]
22
members = [
3-
"msg",
4-
"msg-socket",
5-
"msg-wire",
6-
"msg-transport",
7-
"msg-common",
8-
"msg-sim",
3+
"msg",
4+
"msg-socket",
5+
"msg-wire",
6+
"msg-transport",
7+
"msg-common",
8+
"msg-sim",
99
]
1010
resolver = "2"
1111

@@ -19,19 +19,19 @@ authors = ["Chainbound Developers <dev@chainbound.io>"]
1919
homepage = "https://github.com/chainbound/msg-rs"
2020
repository = "https://github.com/chainbound/msg-rs"
2121
keywords = [
22-
"messaging",
23-
"distributed",
24-
"systems",
25-
"networking",
26-
"quic",
27-
"quinn",
28-
"tokio",
29-
"async",
30-
"simulation",
31-
"pnet",
32-
"udp",
33-
"tcp",
34-
"socket",
22+
"messaging",
23+
"distributed",
24+
"systems",
25+
"networking",
26+
"quic",
27+
"quinn",
28+
"tokio",
29+
"async",
30+
"simulation",
31+
"pnet",
32+
"udp",
33+
"tcp",
34+
"socket",
3535
]
3636

3737
[workspace.dependencies]
@@ -45,8 +45,9 @@ msg-sim = { path = "./msg-sim" }
4545
async-trait = "0.1"
4646
tokio = { version = "1", features = ["full"] }
4747
tokio-util = { version = "0.7", features = ["codec"] }
48-
futures = "0.3"
4948
tokio-stream = { version = "0.1", features = ["sync"] }
49+
tokio-openssl = { version = "0.6" }
50+
futures = "0.3"
5051
parking_lot = "0.12"
5152

5253
# general
@@ -55,10 +56,19 @@ thiserror = "1"
5556
tracing = "0.1"
5657
rustc-hash = "1"
5758
rand = "0.8"
59+
derive_more = { version = "2.0.1", features = [
60+
"from",
61+
"into",
62+
"deref",
63+
"deref_mut",
64+
] }
5865

5966
# networking
6067
quinn = "0.11.9"
6168
rcgen = "0.14"
69+
# (rustls needs to be the same version as the one used by quinn)
70+
rustls = { version = "0.21", features = ["quic", "dangerous_configuration"] }
71+
openssl = { version = "0.10" }
6272

6373
# benchmarking & profiling
6474
criterion = { version = "0.5", features = ["async_tokio"] }

msg-socket/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ parking_lot.workspace = true
2828

2929
[dev-dependencies]
3030
rand.workspace = true
31-
msg-transport = { workspace = true, features = ["quic"] }
31+
msg-transport = { workspace = true, features = ["quic", "tcp-tls"] }
32+
openssl.workspace = true
3233

3334
msg-sim.workspace = true
3435

msg-socket/tests/it/reqrep.rs

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,64 @@
11
use bytes::Bytes;
22
use msg_socket::{RepSocket, ReqSocket};
3-
use msg_transport::tcp::Tcp;
3+
use msg_transport::{
4+
tcp::Tcp,
5+
tcp_tls::{self, TcpTls},
6+
};
47
use tokio_stream::StreamExt;
58

6-
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
7-
async fn test_reqrep() {
9+
/// Helper functions.
10+
mod helpers {
11+
use std::{path::PathBuf, str::FromStr as _};
12+
13+
use openssl::ssl::{
14+
SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, SslFiletype, SslMethod,
15+
};
16+
17+
/// Creates a default SSL acceptor builder for testing, with a trusted CA.
18+
pub fn default_acceptor_builder() -> SslAcceptorBuilder {
19+
let certificate_path =
20+
PathBuf::from_str("../testdata/certificates/server-cert.pem").unwrap();
21+
let private_key_path =
22+
PathBuf::from_str("../testdata/certificates/server-key.pem").unwrap();
23+
let ca_certificate_path =
24+
PathBuf::from_str("../testdata/certificates/ca-cert.pem").unwrap();
25+
26+
assert!(certificate_path.exists(), "Certificate file does not exist");
27+
assert!(private_key_path.exists(), "Private key file does not exist");
28+
assert!(ca_certificate_path.exists(), "CA Certificate file does not exist");
29+
30+
let mut acceptor_builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
31+
acceptor_builder.set_certificate_file(certificate_path, SslFiletype::PEM).unwrap();
32+
acceptor_builder.set_private_key_file(private_key_path, SslFiletype::PEM).unwrap();
33+
acceptor_builder.set_ca_file(ca_certificate_path).unwrap();
34+
acceptor_builder
35+
}
36+
37+
/// Creates a default SSL connector builder for testing, with a trusted CA.
38+
/// It also has client certificate and private key set for mTLS testing.
39+
pub fn default_connector_builder() -> SslConnectorBuilder {
40+
let certificate_path =
41+
PathBuf::from_str("../testdata/certificates/client-cert.pem").unwrap();
42+
let private_key_path =
43+
PathBuf::from_str("../testdata/certificates/client-key.pem").unwrap();
44+
let ca_certificate_path =
45+
PathBuf::from_str("../testdata/certificates/ca-cert.pem").unwrap();
46+
47+
assert!(certificate_path.exists(), "Certificate file does not exist");
48+
assert!(private_key_path.exists(), "Private key file does not exist");
49+
assert!(ca_certificate_path.exists(), "CA Certificate file does not exist");
50+
51+
let mut connector_builder = SslConnector::builder(SslMethod::tls()).unwrap();
52+
connector_builder.set_certificate_file(certificate_path, SslFiletype::PEM).unwrap();
53+
connector_builder.set_private_key_file(private_key_path, SslFiletype::PEM).unwrap();
54+
connector_builder.set_ca_file(ca_certificate_path).unwrap();
55+
56+
connector_builder
57+
}
58+
}
59+
60+
#[tokio::test]
61+
async fn reqrep_works() {
862
let _ = tracing_subscriber::fmt::try_init();
963

1064
let mut rep = RepSocket::new(Tcp::default());
@@ -21,6 +75,72 @@ async fn test_reqrep() {
2175
}
2276
});
2377

24-
let response = req.request(Bytes::from_static(b"hello")).await.unwrap();
25-
tracing::info!("Response: {:?}", response);
78+
let hello = Bytes::from_static(b"hello");
79+
let response = req.request(hello.clone()).await.unwrap();
80+
assert_eq!(hello, response, "expected {:?}, got {:?}", hello, response);
81+
}
82+
83+
#[tokio::test]
84+
async fn reqrep_tls_works() {
85+
let _ = tracing_subscriber::fmt::try_init();
86+
87+
let server_config = tcp_tls::config::Server::new(helpers::default_acceptor_builder().build());
88+
let tcp_tls_server = TcpTls::new_server(server_config);
89+
let mut rep = RepSocket::new(tcp_tls_server);
90+
91+
rep.bind("0.0.0.0:0").await.unwrap();
92+
93+
let domain = "localhost".to_string();
94+
let ssl_connector = helpers::default_connector_builder().build();
95+
let tcp_tls_client =
96+
TcpTls::new_client(tcp_tls::config::Client::new(domain).with_ssl_connector(ssl_connector));
97+
let mut req = ReqSocket::new(tcp_tls_client);
98+
99+
req.connect(rep.local_addr().unwrap()).await.unwrap();
100+
101+
tokio::spawn(async move {
102+
while let Some(request) = rep.next().await {
103+
let msg = request.msg().clone();
104+
request.respond(msg).unwrap();
105+
}
106+
});
107+
108+
let hello = Bytes::from_static(b"hello");
109+
let response = req.request(hello.clone()).await.unwrap();
110+
assert_eq!(hello, response, "expected {:?}, got {:?}", hello, response);
111+
}
112+
113+
#[tokio::test]
114+
async fn reqrep_mutual_tls_works() {
115+
let _ = tracing_subscriber::fmt::try_init();
116+
117+
let mut acceptor_builder = helpers::default_acceptor_builder();
118+
// By specifying peer verification mode, we essentially toggle mTLS.
119+
acceptor_builder.set_verify(
120+
openssl::ssl::SslVerifyMode::PEER | openssl::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT,
121+
);
122+
let server_config = tcp_tls::config::Server::new(acceptor_builder.build());
123+
let tcp_tls_server = TcpTls::new_server(server_config);
124+
let mut rep = RepSocket::new(tcp_tls_server);
125+
126+
rep.bind("0.0.0.0:0").await.unwrap();
127+
128+
let domain = "localhost".to_string();
129+
let ssl_connector = helpers::default_connector_builder().build();
130+
let tcp_tls_client =
131+
TcpTls::new_client(tcp_tls::config::Client::new(domain).with_ssl_connector(ssl_connector));
132+
let mut req = ReqSocket::new(tcp_tls_client);
133+
134+
req.connect(rep.local_addr().unwrap()).await.unwrap();
135+
136+
tokio::spawn(async move {
137+
while let Some(request) = rep.next().await {
138+
let msg = request.msg().clone();
139+
request.respond(msg).unwrap();
140+
}
141+
});
142+
143+
let hello = Bytes::from_static(b"hello");
144+
let response = req.request(hello.clone()).await.unwrap();
145+
assert_eq!(hello, response, "expected {:?}, got {:?}", hello, response);
26146
}

0 commit comments

Comments
 (0)