rust: bssl-tls: General functional tests Bug: 479599893 Signed-off-by: Xiangfei Ding <xfding@google.com> Change-Id: I7bc900c38a43ba00c936e679bfe927396a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/92347 Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/rust/bssl-tls/src/lib.rs b/rust/bssl-tls/src/lib.rs index c52d705..1c43067 100644 --- a/rust/bssl-tls/src/lib.rs +++ b/rust/bssl-tls/src/lib.rs
@@ -47,6 +47,9 @@ #[macro_use] #[doc(hidden)] mod macros; +#[cfg(test)] +#[cfg(feature = "std")] +mod tests; pub use ffi::ReceiveBuffer;
diff --git a/rust/bssl-tls/src/tests.rs b/rust/bssl-tls/src/tests.rs new file mode 100644 index 0000000..166a95a --- /dev/null +++ b/rust/bssl-tls/src/tests.rs
@@ -0,0 +1,177 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::mem::MaybeUninit; + +use crate::{ + ReceiveBuffer, + connection::{ + Client, + Server, + TlsConnection, // + }, + context::TlsContextBuilder, + credentials::{ + Certificate, + TlsCredentialBuilder, // + }, + errors::Error, + io::IoStatus, // +}; + +use bssl_x509::{ + certificates::X509Certificate, + keys::PrivateKey, + params::Trust, + store::X509StoreBuilder, // +}; + +const CA: &[u8] = include_bytes!("../../test-data/BoringSSLCATest.crt"); +const RSA_SERVER_CERT: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.crt"); +const RSA_SERVER_KEY: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.key"); + +mod datagram; +mod handshake; +mod transport; + +/// Dumb server-client pair that does no certificate verification. +fn dumb_server_client() -> Result<(TlsConnection<Server>, TlsConnection<Client>), Error> { + let ca = Certificate::parse_one_from_pem(CA, None)?; + let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None)?; + let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!())?; + + let mut server_ctx_builder = TlsContextBuilder::new_tls(); + let server_cred = { + let mut builder = TlsCredentialBuilder::new(); + builder + .with_certificate_chain(&[server_cert, ca])? + .with_private_key(server_key)?; + builder.build() + }; + server_ctx_builder.with_credential(server_cred.unwrap())?; + let server_ctx = server_ctx_builder.build(); + let server_conn = server_ctx.new_server_connection(None)?.build(); + + let mut client_ctx_builder = TlsContextBuilder::new_tls(); + let mut cert_store = X509StoreBuilder::new(); + cert_store + .set_trust(Trust::SslServer)? + .add_cert(X509Certificate::parse_one_from_pem(CA)?)?; + let cert_store = cert_store.build(); + client_ctx_builder.with_certificate_store(&cert_store); + let client_ctx = client_ctx_builder.build(); + let client_conn = client_ctx.new_client_connection(None)?.build(); + + Ok((server_conn, client_conn)) +} + +fn sync_ping_pong< + M: crate::connection::methods::HasTlsConnectionMethod + + crate::context::SupportedMode + + crate::context::HasBasicIo + + 'static, +>( + mut server_conn: TlsConnection<Server, M>, + mut client_conn: TlsConnection<Client, M>, +) -> Result<(), Error> { + let thread = std::thread::spawn(move || { + server_conn.in_handshake().unwrap().accept()?; + assert!(!server_conn.is_in_handshake()); + // TODO: switch to `From` impls when Rust compiler is bumped to 1.95.0. + let mut message = [MaybeUninit::uninit(); 21]; + let mut message = ReceiveBuffer::new_uninit(&mut message); + assert!(matches!( + server_conn.sync_read(&mut message)?, + IoStatus::Ok(21) + )); + assert_eq!(*message, *b"BoringSSL is awesome!"); + server_conn.sync_write(b"Oh yeah definitely!")?; + server_conn.established().unwrap().sync_shutdown()?; + // Second shutdown poll. + server_conn.established().unwrap().sync_shutdown()?; + Ok::<_, Error>(()) + }); + + client_conn.in_handshake().unwrap().connect()?; + assert!(!client_conn.is_in_handshake()); + client_conn.sync_write(b"BoringSSL is awesome!")?; + let mut message = [MaybeUninit::uninit(); 19]; + let mut message = ReceiveBuffer::new_uninit(&mut message); + assert!(matches!( + client_conn.sync_read(&mut message)?, + IoStatus::Ok(19) + )); + assert_eq!(*message, *b"Oh yeah definitely!"); + client_conn.established().unwrap().sync_shutdown()?; + thread.join().unwrap()?; + + Ok(()) +} + +#[cfg(feature = "tokio_io")] +async fn async_ping_pong< + M: crate::connection::methods::HasTlsConnectionMethod + + crate::context::SupportedMode + + crate::context::HasBasicIo + + 'static, +>( + mut server_conn: TlsConnection<Server, M>, + mut client_conn: TlsConnection<Client, M>, +) -> Result<(), Error> { + use std::time::Duration; + + let task = tokio::spawn(async move { + server_conn + .in_handshake() + .unwrap() + .async_handshake() + .await?; + + let mut message = [0; 21]; + assert!(matches!( + server_conn.as_pin_mut().async_read(&mut message).await?, + IoStatus::Ok(21) + )); + assert_eq!(message, *b"BoringSSL is awesome!"); + tokio::time::sleep(Duration::from_secs(2)).await; + server_conn + .as_pin_mut() + .async_write(b"Oh yeah definitely!") + .await?; + server_conn.as_pin_mut().async_shutdown().await?; + Ok::<_, Error>(()) + }); + + client_conn + .in_handshake() + .unwrap() + .async_handshake() + .await?; + client_conn + .as_pin_mut() + .async_write(b"BoringSSL is awesome!") + .await?; + let mut message = [0; 19]; + assert!(matches!( + client_conn.as_pin_mut().async_read(&mut message).await?, + IoStatus::Ok(19) + )); + assert_eq!(message, *b"Oh yeah definitely!"); + assert!(matches!( + client_conn.as_pin_mut().async_shutdown().await, + Ok(_) | Err(Error::Io(crate::errors::IoError::EndOfStream)) + )); + task.await.unwrap()?; + Ok(()) +}
diff --git a/rust/bssl-tls/src/tests/datagram.rs b/rust/bssl-tls/src/tests/datagram.rs new file mode 100644 index 0000000..0b22229 --- /dev/null +++ b/rust/bssl-tls/src/tests/datagram.rs
@@ -0,0 +1,103 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bssl_x509::{ + certificates::X509Certificate, keys::PrivateKey, params::Trust, store::X509StoreBuilder, +}; + +use crate::{ + connection::{Client, Server, TlsConnection}, + context::{DtlsMode, TlsContextBuilder}, + credentials::{Certificate, TlsCredentialBuilder}, + errors::Error, +}; + +fn dumb_dtls_server_client() -> Result< + ( + TlsConnection<Server, DtlsMode>, + TlsConnection<Client, DtlsMode>, + ), + Error, +> { + let ca = Certificate::parse_one_from_pem(super::CA, None)?; + let server_cert = Certificate::parse_one_from_pem(super::RSA_SERVER_CERT, None)?; + let server_key = PrivateKey::from_pem(super::RSA_SERVER_KEY, || unreachable!())?; + + let mut server_ctx_builder = TlsContextBuilder::new_dtls(); + let server_cred = { + let mut builder = TlsCredentialBuilder::new(); + builder + .with_certificate_chain(&[server_cert, ca])? + .with_private_key(server_key)?; + builder.build() + }; + server_ctx_builder.with_credential(server_cred.unwrap())?; + let server_ctx = server_ctx_builder.build(); + let server_conn = server_ctx.new_server_connection(None)?.build(); + + let mut client_ctx_builder = TlsContextBuilder::new_dtls(); + let ca = X509Certificate::parse_one_from_pem(super::CA)?; + let mut cert_store = X509StoreBuilder::new(); + cert_store.set_trust(Trust::SslServer)?.add_cert(ca)?; + let cert_store = cert_store.build(); + client_ctx_builder.with_certificate_store(&cert_store); + let client_ctx = client_ctx_builder.build(); + let client_conn = client_ctx.new_client_connection(None)?.build(); + + Ok((server_conn, client_conn)) +} + +#[cfg(unix)] +#[test] +fn dtls() { + use crate::{io::sync_io::NoAsync, io::unix::StdDatagram, tests::sync_ping_pong}; + + let (mut server_conn, mut client_conn) = dumb_dtls_server_client().unwrap(); + let (server_sock, client_sock) = std::os::unix::net::UnixDatagram::pair().unwrap(); + let server_sock = StdDatagram::new(server_sock, NoAsync); + let client_sock = StdDatagram::new(client_sock, NoAsync); + server_conn.set_io(server_sock).unwrap(); + client_conn.set_io(client_sock).unwrap(); + sync_ping_pong(server_conn, client_conn).unwrap(); +} + +#[cfg(all(unix, feature = "tokio_net"))] +#[tokio::test] +async fn async_dtls() -> Result<(), Error> { + use crate::tests::async_ping_pong; + + let (mut server_conn, mut client_conn) = dumb_dtls_server_client().unwrap(); + let (server_sock, client_sock) = tokio::net::UnixDatagram::pair().unwrap(); + server_conn.set_io(server_sock).unwrap(); + client_conn.set_io(client_sock).unwrap(); + + async_ping_pong(server_conn, client_conn).await +} + +#[cfg(all(feature = "tokio_net", unix))] +#[tokio::test] +async fn async_dtls_over_fd() -> Result<(), Error> { + use crate::{io::unix::StdDatagram, tests::async_ping_pong}; + + let (mut server_conn, mut client_conn) = dumb_dtls_server_client().unwrap(); + let (server_sock, client_sock) = std::os::unix::net::UnixDatagram::pair().unwrap(); + server_sock.set_nonblocking(true).unwrap(); + client_sock.set_nonblocking(true).unwrap(); + let server_sock = StdDatagram::new_with_tokio(server_sock).unwrap(); + let client_sock = StdDatagram::new_with_tokio(client_sock).unwrap(); + server_conn.set_io(server_sock).unwrap(); + client_conn.set_io(client_sock).unwrap(); + + async_ping_pong(server_conn, client_conn).await +}
diff --git a/rust/bssl-tls/src/tests/handshake.rs b/rust/bssl-tls/src/tests/handshake.rs new file mode 100644 index 0000000..0425ae5 --- /dev/null +++ b/rust/bssl-tls/src/tests/handshake.rs
@@ -0,0 +1,80 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::errors::Error; + +#[cfg(unix)] +#[test] +fn ping_pong() -> Result<(), Error> { + use std::sync::Arc; + + use bssl_x509::{ + certificates::X509Certificate, keys::PrivateKey, params::Trust, store::X509StoreBuilder, + }; + + use super::{CA, RSA_SERVER_CERT, RSA_SERVER_KEY}; + use crate::{ + context::{CertificateCache, TlsContextBuilder}, + credentials::{Certificate, CertificateVerificationMode, TlsCredentialBuilder}, + io::sync_io::{NoAsync, StdIoWithReactor}, + tests::sync_ping_pong, + }; + + let cache = Arc::new(CertificateCache::new()); + let ca = Certificate::parse_one_from_pem(CA, Some(&cache))?; + let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, Some(&cache))?; + let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!())?; + + let mut server_ctx_builder = TlsContextBuilder::new_tls(); + let server_cred = { + let mut builder = TlsCredentialBuilder::new(); + builder + .with_certificate_chain(&[server_cert, ca])? + .with_private_key(server_key)?; + builder.build() + }; + server_ctx_builder + .with_certificate_cache(Some(Arc::clone(&cache))) + .with_credential(server_cred.unwrap())?; + let server_ctx = server_ctx_builder.build(); + let mut server_conn = server_ctx.new_server_connection(None)?.build(); + + let mut client_ctx_builder = TlsContextBuilder::new_tls(); + let mut cert_store = X509StoreBuilder::new(); + cert_store + .set_trust(Trust::SslServer)? + .add_cert(X509Certificate::parse_one_from_pem(&CA)?)?; + let cert_store = cert_store.build(); + client_ctx_builder + .with_certificate_cache(Some(Arc::clone(&cache))) + .with_certificate_store(&cert_store); + let client_ctx = client_ctx_builder.build(); + let mut client_conn = client_ctx.new_client_connection(None)?; + client_conn.with_certificate_verification_mode(CertificateVerificationMode::PeerCertMandatory); + let mut client_conn = client_conn.build(); + client_conn + .in_handshake() + .unwrap() + .set_host("www.google.com")?; + + let (server_rx, server_tx) = std::io::pipe().unwrap(); + let (client_rx, client_tx) = std::io::pipe().unwrap(); + let server_rx = StdIoWithReactor::new(server_rx, NoAsync); + let server_tx = StdIoWithReactor::new(server_tx, NoAsync); + let client_rx = StdIoWithReactor::new(client_rx, NoAsync); + let client_tx = StdIoWithReactor::new(client_tx, NoAsync); + server_conn.set_split_io(client_rx, server_tx)?; + client_conn.set_split_io(server_rx, client_tx)?; + sync_ping_pong(server_conn, client_conn) +}
diff --git a/rust/bssl-tls/src/tests/transport.rs b/rust/bssl-tls/src/tests/transport.rs new file mode 100644 index 0000000..a50a9db --- /dev/null +++ b/rust/bssl-tls/src/tests/transport.rs
@@ -0,0 +1,105 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::tests::dumb_server_client; + +#[cfg(unix)] +#[test] +fn stdio() { + use std::io::{Read, Write}; + + use crate::io::sync_io::{NoAsync, StdIoWithReactor}; + + let (mut server_conn, mut client_conn) = dumb_server_client().unwrap(); + + let (server_rx, server_tx) = std::io::pipe().unwrap(); + let (client_rx, client_tx) = std::io::pipe().unwrap(); + let server_rx = StdIoWithReactor::new(server_rx, NoAsync); + let server_tx = StdIoWithReactor::new(server_tx, NoAsync); + let client_rx = StdIoWithReactor::new(client_rx, NoAsync); + let client_tx = StdIoWithReactor::new(client_tx, NoAsync); + + server_conn.set_split_io(client_rx, server_tx).unwrap(); + client_conn.set_split_io(server_rx, client_tx).unwrap(); + + let thread = std::thread::spawn(move || { + let mut message = [0; 21]; + // Use `std::io::Read::read_exact` + server_conn.read_exact(&mut message).unwrap(); + assert_eq!(message, *b"BoringSSL is awesome!"); + // Use `std::io::Write::write_all` + server_conn.write_all(b"Oh yeah definitely!").unwrap(); + server_conn.established().unwrap().sync_shutdown().unwrap(); + // Second shutdown poll. + server_conn.established().unwrap().sync_shutdown().unwrap(); + }); + + // Use `std::io::Write::write_all` + client_conn.write_all(b"BoringSSL is awesome!").unwrap(); + let mut message = [0; 19]; + // Use `std::io::Read::read_exact` + client_conn.read_exact(&mut message).unwrap(); + assert_eq!(message, *b"Oh yeah definitely!"); + client_conn.established().unwrap().sync_shutdown().unwrap(); + thread.join().unwrap(); +} + +#[cfg(all(unix, feature = "tokio_net"))] +#[tokio::test] +async fn tokio_io() -> Result<(), crate::errors::Error> { + use crate::io::unix::TokioIo; + + use futures::prelude::*; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + let (mut server_conn, mut client_conn) = dumb_server_client()?; + + let (server_tx, server_rx) = tokio::net::unix::pipe::pipe().unwrap(); + let (client_tx, client_rx) = tokio::net::unix::pipe::pipe().unwrap(); + let (send_signal, recv_signal) = tokio::sync::oneshot::channel(); + let server_rx = TokioIo(server_rx); + let server_tx = TokioIo(server_tx); + let client_rx = TokioIo(client_rx); + let client_tx = TokioIo(client_tx); + + server_conn.set_split_io(client_rx, server_tx)?; + client_conn.set_split_io(server_rx, client_tx)?; + + let thread = tokio::spawn(async move { + let mut message = [0; 21]; + server_conn.read_exact(&mut message).await.unwrap(); + assert_eq!(message, *b"BoringSSL is awesome!"); + server_conn.write_all(b"Oh yeah definitely!").await.unwrap(); + server_conn.established().unwrap().sync_shutdown().unwrap(); + + // The following shutdown call should not make progress. + let res = server_conn.established().unwrap().shutdown().now_or_never(); + assert!(res.is_none(), "{res:?}"); + // Make the client progress. + send_signal.send(()).unwrap(); + server_conn.established().unwrap().shutdown().await.unwrap(); + }); + client_conn + .write_all(b"BoringSSL is awesome!") + .await + .unwrap(); + recv_signal.await.unwrap(); + let mut message = [0; 19]; + client_conn.read_exact(&mut message).await.unwrap(); + assert_eq!(message, *b"Oh yeah definitely!"); + client_conn.established().unwrap().sync_shutdown().unwrap(); + thread.await.unwrap(); + + Ok(()) +}