| // 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::{ | |
| future::{ | |
| Ready, | |
| ready, // | |
| }, | |
| sync::{ | |
| Arc, | |
| Mutex, // | |
| }, // | |
| }; | |
| use bssl_crypto::ecdsa::ParsedPrivateKey; | |
| use bssl_x509::{ | |
| keys::{ | |
| PrivateKey, | |
| PrivateKeyAlgorithm, // | |
| }, | |
| params::Trust, // | |
| }; | |
| use futures::future::try_join; | |
| use super::*; | |
| use crate::{ | |
| connection::lifecycle::{ | |
| HandshakeComplete, | |
| HandshakeInfo, // | |
| }, | |
| context::{ | |
| TlsContextBuilder, | |
| TlsMode, // | |
| }, | |
| errors::Error, | |
| ffi::ReceiveBuffer, | |
| tests::{ | |
| P256_SERVER_CERT, | |
| P256_SERVER_CERT_DER, | |
| P256_SERVER_KEY, | |
| P256_SERVER_KEY_DER, | |
| RSA_SERVER_CERT, | |
| RSA_SERVER_CERT_DER, | |
| TEST_CA_DN, | |
| create_mock_pipe, | |
| load_trust_store, | |
| load_x509_credential, | |
| run_async_handshake, // | |
| }, // | |
| }; | |
| #[test] | |
| fn parse_none() { | |
| let certs = Certificate::parse_all_from_pem(b"", None).unwrap(); | |
| assert!(certs.is_empty()); | |
| } | |
| #[test] | |
| fn parse_one() { | |
| let pem = [b"extra data\n", P256_SERVER_CERT, b"\nextra data\n"].concat(); | |
| let certs = Certificate::parse_all_from_pem(&pem, None).unwrap(); | |
| assert_eq!(certs.len(), 1); | |
| assert_eq!(certs[0].as_der_bytes(), P256_SERVER_CERT_DER); | |
| } | |
| #[test] | |
| fn parse_all_pems() { | |
| let pem = [ | |
| b"extra data\n", | |
| P256_SERVER_CERT, | |
| b"\nextra data\n", | |
| RSA_SERVER_CERT, | |
| b"\nextra data\n", | |
| ] | |
| .concat(); | |
| let certs = Certificate::parse_all_from_pem(&pem, None).unwrap(); | |
| assert_eq!(certs.len(), 2); | |
| assert_eq!(certs[0].as_der_bytes(), P256_SERVER_CERT_DER); | |
| assert_eq!(certs[1].as_der_bytes(), RSA_SERVER_CERT_DER); | |
| } | |
| #[test] | |
| fn parse_all_pems_fail() { | |
| let pem = [ | |
| b"extra data\n", | |
| P256_SERVER_CERT, | |
| b"\nextra data\n", | |
| RSA_SERVER_CERT, | |
| // Invalid PEM block. | |
| b"\n-----BEGIN CERTIFICATE-----\n", | |
| ] | |
| .concat(); | |
| let _ = Certificate::parse_all_from_pem(&pem, None).unwrap_err(); | |
| } | |
| #[test] | |
| fn parse_private_key() { | |
| const PEM: &'_ [u8] = b"-----BEGIN ENCRYPTED PRIVATE KEY----- | |
| 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 | |
| -----END ENCRYPTED PRIVATE KEY----- | |
| "; | |
| let priv_key = PrivateKey::from_pem(PEM, || b"Hello BoringSSL!").unwrap(); | |
| assert!(matches!( | |
| priv_key.algorithm(), | |
| Some(PrivateKeyAlgorithm::Rsa) | |
| )); | |
| } | |
| #[test] | |
| fn psk_tls13_handshake() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| let key_a = b"test-key-aaaa-test-key-aaaa-aaaa"; | |
| let key_b = b"test-key-bbbb-test-key-bbbb-bbbb"; | |
| let identity_a = b"identity-a"; | |
| let identity_b = b"identity-b"; | |
| let context = b"test-context"; | |
| let cred_a = TlsCredential::new_pre_shared_key(key_a, identity_a, PskHash::Sha256, context)?; | |
| let cred_b = TlsCredential::new_pre_shared_key(key_b, identity_b, PskHash::Sha256, context)?; | |
| // Server only has cred_b, so it must select cred_b. | |
| let mut server_ctx = TlsContextBuilder::new_tls(); | |
| server_ctx.with_credential(cred_b.clone())?; | |
| let server_ctx = server_ctx.build(); | |
| // Client offers both cred_a and cred_b. | |
| let mut client_ctx = TlsContextBuilder::new_tls(); | |
| client_ctx | |
| .with_credential(cred_a)? | |
| .with_credential(cred_b)?; | |
| let client_ctx = client_ctx.build(); | |
| let (client_socket, server_socket, mut executor) = create_mock_pipe(); | |
| let selected_cred: Arc<Mutex<Option<TlsCredential>>> = Arc::new(Mutex::new(None)); | |
| struct Callback { | |
| selected: Arc<Mutex<Option<TlsCredential>>>, | |
| } | |
| impl HandshakeComplete for Callback { | |
| fn handshake_complete(&mut self, hs: &HandshakeInfo) { | |
| let cred = hs.get_selected_credential(); | |
| *self.selected.lock().unwrap() = cred; | |
| } | |
| } | |
| let mut server_builder = server_ctx.new_server_connection(); | |
| server_builder.with_handshake_complete_callback(Callback { | |
| selected: selected_cred.clone(), | |
| }); | |
| let mut server_conn = server_builder.build(); | |
| let mut client_conn = client_ctx.new_client_connection().build(); | |
| client_conn.set_io(client_socket)?; | |
| server_conn.set_io(server_socket)?; | |
| let test_future = async { | |
| let client_handshake = async { | |
| assert!(client_conn.async_handshake().await?.is_none()); | |
| Ok::<(), Error>(()) | |
| }; | |
| let server_handshake = async { | |
| assert!(server_conn.async_handshake().await?.is_none()); | |
| Ok::<(), Error>(()) | |
| }; | |
| try_join(client_handshake, server_handshake).await?; | |
| Ok::<(), Error>(()) | |
| }; | |
| executor.run(test_future)?; | |
| let client_conn = client_ctx.new_client_connection().build(); | |
| let server_conn = server_ctx.new_server_connection().build(); | |
| run_async_handshake(client_conn, server_conn)?; | |
| let selected = selected_cred.lock().unwrap(); | |
| let selected = selected | |
| .as_ref() | |
| .expect("handshake complete callback should have been called"); | |
| assert_eq!( | |
| selected.get_pre_shared_key_id(), | |
| Some(identity_b.as_slice()), | |
| "server should have selected cred_b (identity-b)" | |
| ); | |
| Ok(()) | |
| } | |
| #[cfg(all(unix, feature = "std"))] | |
| #[test] | |
| fn psk_tls13_handshake_sync() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| use crate::io::sync_io::{NoAsync, StdIoWithReactor}; | |
| use std::io::pipe; | |
| let (server_rx, client_tx) = pipe().unwrap(); | |
| let (client_rx, server_tx) = pipe().unwrap(); | |
| let client_reader = StdIoWithReactor::new(client_rx, NoAsync); | |
| let client_writer = StdIoWithReactor::new(client_tx, NoAsync); | |
| let server_reader = StdIoWithReactor::new(server_rx, NoAsync); | |
| let server_writer = StdIoWithReactor::new(server_tx, NoAsync); | |
| let key = b"test-key-test-key-test-key-test-key"; | |
| let identity = b"test-identity"; | |
| let context = b"test-context"; | |
| let cred = TlsCredential::new_pre_shared_key(key, identity, PskHash::Sha256, context)?; | |
| let mut server_ctx = TlsContextBuilder::new_tls(); | |
| server_ctx.with_credential(cred.clone())?; | |
| let server_ctx = server_ctx.build(); | |
| let mut client_ctx = TlsContextBuilder::new_tls(); | |
| client_ctx.with_credential(cred)?; | |
| let client_ctx = client_ctx.build(); | |
| let mut client_conn = client_ctx.new_client_connection().build(); | |
| let mut server_conn = server_ctx.new_server_connection().build(); | |
| client_conn.set_split_io(client_reader, client_writer)?; | |
| server_conn.set_split_io(server_reader, server_writer)?; | |
| let server_thread = std::thread::spawn(move || { | |
| server_conn.do_handshake().unwrap(); | |
| server_conn | |
| }); | |
| client_conn.do_handshake().unwrap(); | |
| let _server_conn = server_thread.join().unwrap(); | |
| Ok(()) | |
| } | |
| /// Helper: perform an RPK handshake with the given server credential and client verifier. | |
| fn rpk_handshake_test( | |
| cred: TlsCredential, | |
| verifier: impl VerifyCertificate + 'static, | |
| ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| let mut server_ctx = TlsContextBuilder::new_tls(); | |
| server_ctx.with_credential(cred)?; | |
| let server_ctx = server_ctx.build(); | |
| let mut client_ctx = TlsContextBuilder::new_tls(); | |
| client_ctx | |
| .with_accepted_peer_cert_types(&[CertificateType::Rpk])? | |
| .with_certificate_verifier(CertificateVerificationMode::PeerCertMandatory, verifier); | |
| let client_ctx = client_ctx.build(); | |
| let mut client_conn = client_ctx.new_client_connection().build(); | |
| let mut server_conn = server_ctx.new_server_connection().build(); | |
| let (sock_client, sock_server, mut executor) = create_mock_pipe(); | |
| client_conn.set_io(sock_client)?; | |
| server_conn.set_io(sock_server)?; | |
| executor.run(try_join( | |
| Pin::new(&mut client_conn).async_write(b"hello"), | |
| Pin::new(&mut server_conn).async_write(b"world"), | |
| ))?; | |
| assert_eq!( | |
| client_conn.get_peer_certificate_type(), | |
| Some(CertificateType::Rpk) | |
| ); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn rpk_tls13_handshake() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| let priv_key = PrivateKey::from_pem(crate::tests::RSA_SERVER_KEY, || unreachable!()).unwrap(); | |
| let cred = TlsCredentialBuilder::new_raw_public_key(priv_key) | |
| .build() | |
| .unwrap(); | |
| rpk_handshake_test(cred, AcceptAnyVerifier) | |
| } | |
| #[test] | |
| fn rpk_tls13_handshake_with_delegate() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| use crate::credentials::{ | |
| AsyncPrivateKeyDelegate, AsyncPrivateKeyDelegateAdapter, SignatureAlgorithm, | |
| }; | |
| let priv_key = PrivateKey::from_pem(P256_SERVER_KEY, || unreachable!()).unwrap(); | |
| let pub_key = priv_key.to_public_key().clone(); | |
| let expected_spki_der = pub_key.to_der(); | |
| struct P256KeyDelegate { | |
| key_der: &'static [u8], | |
| } | |
| impl AsyncPrivateKeyDelegate for P256KeyDelegate { | |
| type SignOp = Ready<Option<Vec<u8>>>; | |
| type DecryptOp = Ready<Option<Vec<u8>>>; | |
| fn sign(&self, message: &[u8], algorithm: SignatureAlgorithm) -> Self::SignOp { | |
| assert!(matches!( | |
| algorithm, | |
| SignatureAlgorithm::EcdsaSecp256r1Sha256 | |
| )); | |
| let Some(ParsedPrivateKey::P256(key)) = ParsedPrivateKey::from_der(self.key_der) else { | |
| panic!("failed to parse P256 key") | |
| }; | |
| ready(Some(key.sign(message))) | |
| } | |
| fn decrypt(&self, _ciphertext: &[u8]) -> Self::DecryptOp { | |
| unreachable!() | |
| } | |
| } | |
| let delegate = AsyncPrivateKeyDelegateAdapter(P256KeyDelegate { | |
| key_der: P256_SERVER_KEY_DER, | |
| }); | |
| let cred = TlsCredentialBuilder::new_raw_public_key_with_delegate(pub_key, delegate) | |
| .build() | |
| .unwrap(); | |
| rpk_handshake_test(cred, RpkVerifier { expected_spki_der }) | |
| } | |
| struct AcceptAnyVerifier; | |
| impl VerifyCertificate for AcceptAnyVerifier { | |
| fn verify<'a>( | |
| &self, | |
| _: &'a VerifyCertificateContext, | |
| _: crate::credentials::CertificateChainIterator<'a>, | |
| ) -> Box<dyn VerifyCertificateTask> { | |
| Box::new(crate::credentials::VerifyCertificateAccepted) | |
| } | |
| } | |
| struct RpkVerifier { | |
| expected_spki_der: Vec<u8>, | |
| } | |
| impl VerifyCertificate for RpkVerifier { | |
| fn verify<'a>( | |
| &self, | |
| ctx: &'a VerifyCertificateContext, | |
| _: crate::credentials::CertificateChainIterator<'a>, | |
| ) -> Box<dyn VerifyCertificateTask> { | |
| let Some(der) = ctx.get_peer_raw_public_key() else { | |
| return Box::new(VerifyCertificateRejected(None)); | |
| }; | |
| if der != self.expected_spki_der { | |
| return Box::new(VerifyCertificateRejected(None)); | |
| } | |
| Box::new(crate::credentials::VerifyCertificateAccepted) | |
| } | |
| } | |
| #[test] | |
| fn psk_rpk_fallback_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| use std::sync::{ | |
| Arc, | |
| atomic::{AtomicBool, Ordering}, | |
| }; | |
| use bssl_x509::keys::PrivateKey; | |
| use crate::{ | |
| context::TlsContextBuilder, | |
| credentials::{ | |
| CertificateType, CertificateVerificationMode, PskHash, RawPublicKeyMode, TlsCredential, | |
| TlsCredentialBuilder, | |
| early_callback::{ClientHello, EarlyCallback, EarlyCallbackResult, ExtensionType}, | |
| }, | |
| io::IoStatus, | |
| }; | |
| let priv_key = PrivateKey::from_pem(crate::tests::RSA_SERVER_KEY, || unreachable!())?; | |
| let rpk_cred = TlsCredentialBuilder::<RawPublicKeyMode>::new_raw_public_key(priv_key.clone()) | |
| .build() | |
| .ok_or("raw public key failed to parse")?; | |
| let server_certs = | |
| crate::credentials::Certificate::parse_all_from_pem(crate::tests::RSA_SERVER_CERT, None)?; | |
| let mut server_cred_builder = TlsCredentialBuilder::<crate::credentials::X509Mode>::new(); | |
| server_cred_builder | |
| .with_certificate_chain(&server_certs)? | |
| .with_private_key(priv_key.clone())?; | |
| let server_cred = server_cred_builder | |
| .build() | |
| .ok_or("credential is incomplete")?; | |
| let x509_cert = bssl_x509::certificates::X509Certificate::parse_one_from_pem( | |
| crate::tests::RSA_SERVER_CERT, | |
| )?; | |
| let expected_rpk_der = x509_cert | |
| .public_key() | |
| .ok_or("public key is missing in x509")? | |
| .to_der(); | |
| let key = b"test-key-test-key-test-key-test-key"; | |
| let identity = b"test-identity"; | |
| let context = b"test-context"; | |
| let psk_cred = TlsCredential::new_pre_shared_key(key, identity, PskHash::Sha256, context)?; | |
| type AtomicFlag = Arc<AtomicBool>; | |
| struct ServerSelectCallback { | |
| server_cred: TlsCredential, | |
| psk_cred: TlsCredential, | |
| selected_rpk: AtomicFlag, | |
| selected_psk: AtomicFlag, | |
| failed: AtomicFlag, | |
| } | |
| impl EarlyCallback<TlsMode> for ServerSelectCallback { | |
| fn process(&self, client_hello: &mut ClientHello<'_, TlsMode>) -> EarlyCallbackResult { | |
| let offered_rpk = client_hello | |
| .get_extension(ExtensionType::CLIENT_CERT_TYPE) | |
| .is_some(); | |
| let offered_psk = client_hello | |
| .get_extension(ExtensionType::PRE_SHARED_KEY) | |
| .is_some(); | |
| if offered_rpk { | |
| // TODO: switch to `try` block on stabilisation | |
| if (|| { | |
| client_hello | |
| .connection_mut() | |
| .add_credential(&self.server_cred)? | |
| .set_certificate_verification_mode( | |
| CertificateVerificationMode::PeerCertMandatory, | |
| ); | |
| Ok::<_, Error>(()) | |
| })() | |
| .is_err() | |
| { | |
| return EarlyCallbackResult::ErrorResult; | |
| }; | |
| self.selected_rpk.store(true, Ordering::SeqCst); | |
| EarlyCallbackResult::Success | |
| } else if offered_psk { | |
| let Ok(_) = client_hello.connection_mut().add_credential(&self.psk_cred) else { | |
| return EarlyCallbackResult::ErrorResult; | |
| }; | |
| self.selected_psk.store(true, Ordering::SeqCst); | |
| EarlyCallbackResult::Success | |
| } else { | |
| self.failed.store(true, Ordering::SeqCst); | |
| EarlyCallbackResult::ErrorResult | |
| } | |
| } | |
| } | |
| for &(offer_psk, offer_rpk) in &[(true, false), (false, true), (true, true), (false, false)] { | |
| let selected_rpk = AtomicFlag::default(); | |
| let selected_psk = AtomicFlag::default(); | |
| let failed = AtomicFlag::default(); | |
| let cb = ServerSelectCallback { | |
| server_cred: server_cred.clone(), | |
| psk_cred: psk_cred.clone(), | |
| selected_rpk: selected_rpk.clone(), | |
| selected_psk: selected_psk.clone(), | |
| failed: failed.clone(), | |
| }; | |
| let mut server_ctx_builder = TlsContextBuilder::new_tls(); | |
| server_ctx_builder | |
| .with_accepted_peer_cert_types(&[CertificateType::Rpk])? | |
| .with_early_callback(cb) | |
| .with_certificate_verifier( | |
| CertificateVerificationMode::None, | |
| RpkVerifier { | |
| expected_spki_der: expected_rpk_der.clone(), | |
| }, | |
| ); | |
| let server_ctx = server_ctx_builder.build(); | |
| let mut client_ctx_builder = TlsContextBuilder::new_tls(); | |
| if offer_psk { | |
| client_ctx_builder.with_credential(psk_cred.clone())?; | |
| } | |
| if offer_rpk { | |
| client_ctx_builder | |
| .with_credential(rpk_cred.clone())? | |
| .with_available_client_cert_types(&[CertificateType::Rpk])?; | |
| } | |
| let client_ctx = client_ctx_builder.build(); | |
| let (sock_client, sock_server, mut executor) = create_mock_pipe(); | |
| let mut server_conn = server_ctx.new_server_connection().build(); | |
| server_conn.set_io(sock_server)?; | |
| let mut client_conn_builder = client_ctx.new_client_connection(); | |
| client_conn_builder | |
| .with_certificate_verification_mode(CertificateVerificationMode::None) | |
| .with_certificate_verifier( | |
| CertificateVerificationMode::PeerCertRequested, | |
| AcceptAnyVerifier, | |
| ); | |
| let mut client_conn = client_conn_builder.build(); | |
| client_conn.set_io(sock_client)?; | |
| let test_future = async { | |
| let client_handshake = client_conn.async_handshake(); | |
| let server_handshake = server_conn.async_handshake(); | |
| let handshake_result = | |
| futures::future::try_join(client_handshake, server_handshake).await; | |
| if !offer_psk && !offer_rpk { | |
| assert!(handshake_result.is_err(), "Handshake should have failed"); | |
| return Ok::<(), Error>(()); | |
| } | |
| handshake_result?; | |
| client_conn.as_pin_mut().async_write(b"hello").await?; | |
| let mut server_buf = [0u8; 5]; | |
| let mut recv_buf = ReceiveBuffer::new(&mut server_buf); | |
| let read_len = server_conn.as_pin_mut().async_read(&mut recv_buf).await?; | |
| assert!(matches!(read_len, IoStatus::Ok(5))); | |
| assert_eq!(&server_buf, b"hello"); | |
| server_conn.as_pin_mut().async_write(b"world").await?; | |
| let mut client_buf = [0u8; 5]; | |
| let mut recv_buf = ReceiveBuffer::new(&mut client_buf); | |
| let read_len = client_conn.as_pin_mut().async_read(&mut recv_buf).await?; | |
| assert!(matches!(read_len, IoStatus::Ok(5))); | |
| assert_eq!(&client_buf, b"world"); | |
| Ok::<(), Error>(()) | |
| }; | |
| executor.run(test_future)?; | |
| if !offer_psk && !offer_rpk { | |
| assert!( | |
| failed.load(Ordering::SeqCst), | |
| "Should fail if no valid credentials offered" | |
| ); | |
| continue; | |
| } | |
| if offer_rpk { | |
| assert!( | |
| selected_rpk.load(Ordering::SeqCst), | |
| "Should select RPK if offered" | |
| ); | |
| assert_eq!( | |
| server_conn.get_peer_certificate_type(), | |
| Some(CertificateType::Rpk) | |
| ); | |
| } else if offer_psk { | |
| assert!( | |
| selected_psk.load(Ordering::SeqCst), | |
| "Should fallback to PSK if only PSK offered" | |
| ); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_mutual_certificate_selectors() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| use crate::credentials::{ | |
| DistinguishedName, | |
| select_cert::{ | |
| CertificateSelectionResult, | |
| ClientCertificateSelectionContext, | |
| ClientCertificateSelector, | |
| ServerCertificateSelectionContext, | |
| ServerCertificateSelector, // | |
| }, | |
| }; | |
| use std::sync::{ | |
| Arc, | |
| atomic::{ | |
| AtomicBool, | |
| Ordering, // | |
| }, // | |
| }; | |
| let cred = load_x509_credential(); | |
| struct TestServerSelector { | |
| cred: TlsCredential, | |
| called: Arc<AtomicBool>, | |
| } | |
| impl ServerCertificateSelector<TlsMode> for TestServerSelector { | |
| fn select( | |
| &self, | |
| mut ctx: ServerCertificateSelectionContext<'_, TlsMode>, | |
| _waker: Option<&'_ mut Context<'_>>, | |
| ) -> CertificateSelectionResult { | |
| self.called.store(true, Ordering::SeqCst); | |
| ctx.add_credential(&self.cred).unwrap(); | |
| CertificateSelectionResult::Success | |
| } | |
| } | |
| struct TestClientSelector { | |
| cred: TlsCredential, | |
| called: Arc<AtomicBool>, | |
| ca_dn: &'static [u8], | |
| } | |
| impl ClientCertificateSelector<TlsMode> for TestClientSelector { | |
| fn select( | |
| &self, | |
| mut ctx: ClientCertificateSelectionContext<'_, TlsMode>, | |
| _waker: Option<&'_ mut Context<'_>>, | |
| ) -> CertificateSelectionResult { | |
| self.called.store(true, Ordering::SeqCst); | |
| assert!(matches!( | |
| ctx.protocol_version(), | |
| Some(crate::config::ProtocolVersion::Tls13) | |
| )); | |
| let _algs = ctx.get_tls13_peer_verification_algorithms(); | |
| let server_ca_list = ctx.get_server_ca_list(); | |
| assert!(!server_ca_list.is_empty()); | |
| assert_eq!(server_ca_list[0].as_ref(), self.ca_dn); | |
| ctx.add_credential(&self.cred).unwrap(); | |
| CertificateSelectionResult::Success | |
| } | |
| } | |
| let server_called = Arc::new(AtomicBool::new(false)); | |
| let server_selector = TestServerSelector { | |
| cred: cred.clone(), | |
| called: server_called.clone(), | |
| }; | |
| let client_called = Arc::new(AtomicBool::new(false)); | |
| let client_selector = TestClientSelector { | |
| cred: cred.clone(), | |
| called: client_called.clone(), | |
| ca_dn: TEST_CA_DN, | |
| }; | |
| let mut server_ctx_builder = TlsContextBuilder::new_tls(); | |
| let server_cert_store = load_trust_store(Trust::SslClient); | |
| server_ctx_builder | |
| .with_server_side_certificate_callback(server_selector) | |
| .with_certificate_store(&server_cert_store) | |
| .set_ca_names(vec![DistinguishedName::from_bytes(TEST_CA_DN, None)?]); | |
| let server_ctx = server_ctx_builder.build(); | |
| let mut client_ctx_builder = TlsContextBuilder::new_tls(); | |
| let client_cert_store = load_trust_store(Trust::SslServer); | |
| client_ctx_builder | |
| .with_client_side_certificate_callback(client_selector) | |
| .with_certificate_store(&client_cert_store) | |
| .set_ca_names(vec![DistinguishedName::from_bytes(TEST_CA_DN, None)?]); | |
| let client_ctx = client_ctx_builder.build(); | |
| let mut server_conn = server_ctx.new_server_connection(); | |
| server_conn.with_certificate_verification_mode(CertificateVerificationMode::PeerCertMandatory); | |
| let server_conn = server_conn.build(); | |
| let mut client_conn = client_ctx.new_client_connection(); | |
| 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")?; | |
| run_async_handshake(client_conn, server_conn)?; | |
| assert!(server_called.load(Ordering::SeqCst)); | |
| assert!(client_called.load(Ordering::SeqCst)); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_mutual_certificate_selectors_connection() | |
| -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| use crate::credentials::{ | |
| DistinguishedName, | |
| select_cert::{ | |
| CertificateSelectionResult, ClientCertificateSelectionContext, | |
| ClientCertificateSelector, ServerCertificateSelectionContext, | |
| ServerCertificateSelector, | |
| }, | |
| }; | |
| use std::sync::{ | |
| Arc, | |
| atomic::{AtomicBool, Ordering}, | |
| }; | |
| let cred = load_x509_credential(); | |
| struct TestServerSelector { | |
| cred: TlsCredential, | |
| called: Arc<AtomicBool>, | |
| } | |
| impl ServerCertificateSelector<TlsMode> for TestServerSelector { | |
| fn select( | |
| &self, | |
| mut ctx: ServerCertificateSelectionContext<'_, TlsMode>, | |
| _waker: Option<&'_ mut Context<'_>>, | |
| ) -> CertificateSelectionResult { | |
| self.called.store(true, Ordering::SeqCst); | |
| ctx.add_credential(&self.cred).unwrap(); | |
| CertificateSelectionResult::Success | |
| } | |
| } | |
| struct TestClientSelector { | |
| cred: TlsCredential, | |
| called: Arc<AtomicBool>, | |
| ca_dn: &'static [u8], | |
| } | |
| impl ClientCertificateSelector<TlsMode> for TestClientSelector { | |
| fn select( | |
| &self, | |
| mut ctx: ClientCertificateSelectionContext<'_, TlsMode>, | |
| _waker: Option<&'_ mut Context<'_>>, | |
| ) -> CertificateSelectionResult { | |
| self.called.store(true, Ordering::SeqCst); | |
| assert!(matches!( | |
| ctx.protocol_version(), | |
| Some(crate::config::ProtocolVersion::Tls13) | |
| )); | |
| let _algs = ctx.get_tls13_peer_verification_algorithms(); | |
| let server_ca_list = ctx.get_server_ca_list(); | |
| assert!(!server_ca_list.is_empty()); | |
| assert_eq!(server_ca_list[0].as_ref(), self.ca_dn); | |
| ctx.add_credential(&self.cred).unwrap(); | |
| CertificateSelectionResult::Success | |
| } | |
| } | |
| let server_called = Arc::new(AtomicBool::new(false)); | |
| let server_selector = TestServerSelector { | |
| cred: cred.clone(), | |
| called: server_called.clone(), | |
| }; | |
| let client_called = Arc::new(AtomicBool::new(false)); | |
| let client_selector = TestClientSelector { | |
| cred: cred.clone(), | |
| called: client_called.clone(), | |
| ca_dn: TEST_CA_DN, | |
| }; | |
| let mut server_ctx_builder = TlsContextBuilder::new_tls(); | |
| let server_cert_store = load_trust_store(Trust::SslClient); | |
| server_ctx_builder | |
| .with_certificate_store(&server_cert_store) | |
| .set_ca_names(vec![DistinguishedName::from_bytes(TEST_CA_DN, None)?]); | |
| let server_ctx = server_ctx_builder.build(); | |
| let mut client_ctx_builder = TlsContextBuilder::new_tls(); | |
| let client_cert_store = load_trust_store(Trust::SslServer); | |
| client_ctx_builder | |
| .with_certificate_store(&client_cert_store) | |
| .set_ca_names(vec![DistinguishedName::from_bytes(TEST_CA_DN, None)?]); | |
| let client_ctx = client_ctx_builder.build(); | |
| let mut server_conn_builder = server_ctx.new_server_connection(); | |
| server_conn_builder | |
| .with_certificate_verification_mode(CertificateVerificationMode::PeerCertMandatory) | |
| .with_server_side_certificate_callback(server_selector); | |
| let server_conn = server_conn_builder.build(); | |
| let mut client_conn_builder = client_ctx.new_client_connection(); | |
| client_conn_builder | |
| .with_certificate_verification_mode(CertificateVerificationMode::PeerCertMandatory) | |
| .with_client_side_certificate_callback(client_selector); | |
| let mut client_conn = client_conn_builder.build(); | |
| client_conn | |
| .in_handshake() | |
| .unwrap() | |
| .set_host("www.google.com")?; | |
| run_async_handshake(client_conn, server_conn)?; | |
| assert!(server_called.load(Ordering::SeqCst)); | |
| assert!(client_called.load(Ordering::SeqCst)); | |
| Ok(()) | |
| } |