diff --git a/bindings/rust/s2n-tls/Cargo.toml b/bindings/rust/s2n-tls/Cargo.toml index bd126995a03..af9c0c31678 100644 --- a/bindings/rust/s2n-tls/Cargo.toml +++ b/bindings/rust/s2n-tls/Cargo.toml @@ -24,3 +24,4 @@ pin-project-lite = "0.2" [dev-dependencies] bytes = { version = "1" } futures-test = "0.3" +openssl = { version = "0.10" } diff --git a/bindings/rust/s2n-tls/src/callbacks.rs b/bindings/rust/s2n-tls/src/callbacks.rs index aefb2705724..1848bff5a0a 100644 --- a/bindings/rust/s2n-tls/src/callbacks.rs +++ b/bindings/rust/s2n-tls/src/callbacks.rs @@ -32,6 +32,9 @@ pub use async_cb::*; mod client_hello; pub use client_hello::*; +mod pkey; +pub use pkey::*; + /// Convert the connection pointer provided to a callback into a Connection /// and Context useable with the Rust bindings. /// diff --git a/bindings/rust/s2n-tls/src/callbacks/async_cb.rs b/bindings/rust/s2n-tls/src/callbacks/async_cb.rs index d98d64b9891..93008064c3c 100644 --- a/bindings/rust/s2n-tls/src/callbacks/async_cb.rs +++ b/bindings/rust/s2n-tls/src/callbacks/async_cb.rs @@ -102,7 +102,7 @@ impl ConnectionFuture for OptionalFuture { #[derive(PartialEq)] enum MarkDone { ClientHello, - // None, + None, } pin_project! { @@ -141,14 +141,11 @@ impl AsyncCallback { CallbackResult::Success } - // pub(crate) fn trigger( - // future: ConnectionFutureResult, - // conn: &mut Connection, - //) -> CallbackResult { - // let future = OptionalFuture::new(future); - // let cleanup = MarkDone::None; - // let callback = AsyncCallback { future, cleanup }; - // conn.set_async_callback(callback); - // CallbackResult::Success - //} + pub(crate) fn trigger(future: ConnectionFutureResult, conn: &mut Connection) -> CallbackResult { + let future = OptionalFuture::new(future); + let cleanup = MarkDone::None; + let callback = AsyncCallback { future, cleanup }; + conn.set_async_callback(callback); + CallbackResult::Success + } } diff --git a/bindings/rust/s2n-tls/src/callbacks/pkey.rs b/bindings/rust/s2n-tls/src/callbacks/pkey.rs new file mode 100644 index 00000000000..22e63070791 --- /dev/null +++ b/bindings/rust/s2n-tls/src/callbacks/pkey.rs @@ -0,0 +1,376 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + callbacks::*, + connection::Connection, + enums::{HashAlgorithm, Mode, SignatureAlgorithm}, + error::{Error, Fallible}, + ffi::*, +}; +use std::{pin::Pin, ptr::NonNull}; + +#[non_exhaustive] +#[derive(Debug)] +pub enum OperationType { + Decrypt, + Sign(SignatureAlgorithm, HashAlgorithm), +} + +pub struct PrivateKeyOperation { + raw: NonNull, + kind: OperationType, +} + +/// # Safety +/// +/// Safety: s2n_async_pkey_op objects can be sent across threads +unsafe impl Send for PrivateKeyOperation {} + +/// # Safety +/// +/// Safety: All C methods that mutate the s2n_async_pkey_op are wrapped +/// in Rust methods that require a mutable reference. +unsafe impl Sync for PrivateKeyOperation {} + +impl PrivateKeyOperation { + pub(crate) fn try_from_cb( + conn: &Connection, + op_ptr: *mut s2n_async_pkey_op, + ) -> Result { + let mut raw_kind = 0; + unsafe { s2n_async_pkey_op_get_op_type(op_ptr, &mut raw_kind) }.into_result()?; + + let kind = match raw_kind { + s2n_async_pkey_op_type::SIGN => { + let sig_alg = match conn.mode() { + Mode::Client => conn + .selected_client_signature_algorithm()? + .ok_or(Error::INVALID_INPUT)?, + Mode::Server => conn.selected_signature_algorithm()?, + }; + let hash_alg = match conn.mode() { + Mode::Client => conn + .selected_client_hash_algorithm()? + .ok_or(Error::INVALID_INPUT)?, + Mode::Server => conn.selected_hash_algorithm()?, + }; + OperationType::Sign(sig_alg, hash_alg) + } + s2n_async_pkey_op_type::DECRYPT => OperationType::Decrypt, + _ => return Err(Error::INVALID_INPUT), + }; + + let raw = NonNull::new(op_ptr).ok_or(Error::INVALID_INPUT)?; + Ok(PrivateKeyOperation { raw, kind }) + } + + /// Do we need to sign or decrypt with the private key? + pub fn kind(&self) -> Result<&OperationType, Error> { + Ok(&self.kind) + } + + /// The size of the slice returned by [`input()`] + pub fn input_size(&self) -> Result { + let mut size = 0; + unsafe { s2n_async_pkey_op_get_input_size(self.raw.as_ptr(), &mut size) }.into_result()?; + size.try_into().map_err(|_| Error::INVALID_INPUT) + } + + /// Provides the input for the operation. + /// + /// If this is an [`OperationType::Sign`] operation, then this input has + /// already been hashed and is the resultant digest. + pub fn input(&self, buf: &mut [u8]) -> Result<(), Error> { + let buf_len: u32 = buf.len().try_into().map_err(|_| Error::INVALID_INPUT)?; + let buf_ptr = buf.as_ptr() as *mut u8; + unsafe { s2n_async_pkey_op_get_input(self.raw.as_ptr(), buf_ptr, buf_len) } + .into_result()?; + Ok(()) + } + + /// Sets the output of the operation + pub fn set_output(self, conn: &mut Connection, buf: &[u8]) -> Result<(), Error> { + let buf_len: u32 = buf.len().try_into().map_err(|_| Error::INVALID_INPUT)?; + let buf_ptr = buf.as_ptr() as *const u8; + unsafe { + s2n_async_pkey_op_set_output(self.raw.as_ptr(), buf_ptr, buf_len).into_result()?; + s2n_async_pkey_op_apply(self.raw.as_ptr(), conn.as_ptr()).into_result()?; + } + Ok(()) + } +} + +impl Drop for PrivateKeyOperation { + fn drop(&mut self) { + unsafe { + let _ = s2n_async_pkey_op_free(self.raw.as_ptr()); + } + } +} + +pub trait PrivateKeyCallback { + fn handle_operation( + &self, + connection: &mut Connection, + operation: PrivateKeyOperation, + ) -> Result>>, Error>; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + config, connection, error, security, testing, + testing::{s2n_tls::*, *}, + }; + use core::task::{Poll, Waker}; + use futures_test::task::new_count_waker; + use openssl::{ec::EcKey, ecdsa::EcdsaSig}; + + const MAX_POLLS: usize = 100; + type Error = Box; + + const KEY: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/pems/ecdsa_p384_pkcs1_key.pem" + )); + const CERT: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/pems/ecdsa_p384_pkcs1_cert.pem" + )); + + fn new_pair( + callback: T, + waker: Waker, + ) -> Result, Error> + where + T: 'static + PrivateKeyCallback, + { + let config = { + let mut config = config::Builder::new(); + config.set_security_policy(&security::DEFAULT_TLS13)?; + config.load_pem(CERT, KEY)?; + config.set_private_key_callback(callback)?; + // Our test certificates are untrusted, but disabling certificate + // verification does not affect handshake signatures. + unsafe { config.disable_x509_verification() }?; + config.build()? + }; + + let server = { + let mut server = connection::Connection::new_server(); + server.set_config(config.clone())?; + server.set_waker(Some(&waker))?; + Harness::new(server) + }; + + let client = { + let mut client = connection::Connection::new_client(); + client.set_config(config)?; + Harness::new(client) + }; + + Ok(Pair::new(server, client, MAX_POLLS)) + } + + fn ecdsa_sign( + op: PrivateKeyOperation, + conn: &mut connection::Connection, + key: &[u8], + ) -> Result<(), error::Error> { + match op.kind()? { + OperationType::Sign(SignatureAlgorithm::ECDSA, _) => { + let in_buf_size = op.input_size()?; + let mut in_buf = vec![0; in_buf_size]; + op.input(&mut in_buf)?; + + let key = + EcKey::private_key_from_pem(key).expect("Failed to create EcKey from pem"); + let sig = EcdsaSig::sign(&in_buf, &key).expect("Failed to sign input"); + let out = sig.to_der().expect("Failed to convert signature to der"); + + op.set_output(conn, &out)?; + } + _ => panic!("Unexpected pkey operation"), + } + Ok(()) + } + + #[test] + fn sync_offload_success() -> Result<(), Error> { + struct TestPkeyCallback(Counter); + impl PrivateKeyCallback for TestPkeyCallback { + fn handle_operation( + &self, + conn: &mut connection::Connection, + op: PrivateKeyOperation, + ) -> Result>>, error::Error> { + self.0.increment(); + ecdsa_sign(op, conn, KEY)?; + Ok(None) + } + } + + let (waker, wake_count) = new_count_waker(); + let counter = testing::Counter::default(); + let callback = TestPkeyCallback(counter.clone()); + let pair = new_pair(callback, waker)?; + + assert_eq!(counter.count(), 0); + assert_eq!(wake_count, 0); + poll_tls_pair(pair); + assert_eq!(counter.count(), 1); + assert_eq!(wake_count, 0); + + Ok(()) + } + + #[test] + fn async_offload_success() -> Result<(), Error> { + const POLL_COUNT: usize = 10; + + struct TestPkeyFuture { + counter: usize, + op: Option, + } + impl ConnectionFuture for TestPkeyFuture { + fn poll( + mut self: Pin<&mut Self>, + conn: &mut connection::Connection, + ctx: &mut core::task::Context, + ) -> Poll> { + ctx.waker().wake_by_ref(); + self.counter += 1; + if self.counter < POLL_COUNT { + Poll::Pending + } else if let Some(op) = self.op.take() { + Poll::Ready(ecdsa_sign(op, conn, KEY)) + } else { + Poll::Ready(Err(error::Error::application( + "missing pkey operation".into(), + ))) + } + } + } + + struct TestPkeyCallback(Counter); + impl PrivateKeyCallback for TestPkeyCallback { + fn handle_operation( + &self, + _conn: &mut connection::Connection, + op: PrivateKeyOperation, + ) -> Result>>, error::Error> { + self.0.increment(); + let future = TestPkeyFuture { + counter: 0, + op: Some(op), + }; + Ok(Some(Box::pin(future))) + } + } + + let (waker, wake_count) = new_count_waker(); + let counter = testing::Counter::default(); + let callback = TestPkeyCallback(counter.clone()); + let pair = new_pair(callback, waker)?; + + assert_eq!(counter.count(), 0); + assert_eq!(wake_count, 0); + poll_tls_pair(pair); + assert_eq!(counter.count(), 1); + assert_eq!(wake_count, POLL_COUNT); + + Ok(()) + } + + #[test] + fn sync_failure() -> Result<(), Error> { + const ERROR: &str = "sync_failure error"; + + struct TestPkeyCallback(Counter); + impl PrivateKeyCallback for TestPkeyCallback { + fn handle_operation( + &self, + _conn: &mut connection::Connection, + _op: PrivateKeyOperation, + ) -> Result>>, error::Error> { + self.0.increment(); + Err(testing::test_error(ERROR)) + } + } + + let (waker, wake_count) = new_count_waker(); + let counter = testing::Counter::default(); + let callback = TestPkeyCallback(counter.clone()); + let pair = new_pair(callback, waker)?; + + assert_eq!(counter.count(), 0); + assert_eq!(wake_count, 0); + let result = poll_tls_pair_result(pair); + assert_eq!(counter.count(), 1); + assert_eq!(wake_count, 0); + + match result { + Ok(_) => panic!("Handshake unexpectedly succeeded"), + Err(e) => testing::assert_test_error(e, ERROR), + }; + Ok(()) + } + + #[test] + fn async_failure() -> Result<(), Error> { + const POLL_COUNT: usize = 10; + const ERROR: &str = "async_failure error"; + + struct TestPkeyFuture { + counter: usize, + _op: PrivateKeyOperation, + } + impl ConnectionFuture for TestPkeyFuture { + fn poll( + mut self: Pin<&mut Self>, + _conn: &mut connection::Connection, + ctx: &mut core::task::Context, + ) -> Poll> { + ctx.waker().wake_by_ref(); + self.counter += 1; + if self.counter < POLL_COUNT { + Poll::Pending + } else { + Poll::Ready(Err(testing::test_error(ERROR))) + } + } + } + + struct TestPkeyCallback(Counter); + impl PrivateKeyCallback for TestPkeyCallback { + fn handle_operation( + &self, + _conn: &mut connection::Connection, + _op: PrivateKeyOperation, + ) -> Result>>, error::Error> { + self.0.increment(); + let future = TestPkeyFuture { counter: 0, _op }; + Ok(Some(Box::pin(future))) + } + } + + let (waker, wake_count) = new_count_waker(); + let counter = testing::Counter::default(); + let callback = TestPkeyCallback(counter.clone()); + let pair = new_pair(callback, waker)?; + + assert_eq!(counter.count(), 0); + assert_eq!(wake_count, 0); + let result = poll_tls_pair_result(pair); + assert_eq!(counter.count(), 1); + assert_eq!(wake_count, POLL_COUNT); + + match result { + Ok(_) => panic!("Handshake unexpectedly succeeded"), + Err(e) => testing::assert_test_error(e, ERROR), + }; + Ok(()) + } +} diff --git a/bindings/rust/s2n-tls/src/config.rs b/bindings/rust/s2n-tls/src/config.rs index 76864dbc3f9..f9884c18bff 100644 --- a/bindings/rust/s2n-tls/src/config.rs +++ b/bindings/rust/s2n-tls/src/config.rs @@ -450,6 +450,39 @@ impl Builder { Ok(self) } + /// Set a callback function triggered by operations requiring the private key. + /// + /// See https://github.com/aws/s2n-tls/blob/main/docs/USAGE-GUIDE.md#private-key-operation-related-calls + pub fn set_private_key_callback( + &mut self, + handler: T, + ) -> Result<&mut Self, Error> { + unsafe extern "C" fn private_key_cb( + conn_ptr: *mut s2n_connection, + op_ptr: *mut s2n_async_pkey_op, + ) -> libc::c_int { + with_context(conn_ptr, |conn, context| { + let state = PrivateKeyOperation::try_from_cb(conn, op_ptr); + let callback = context.private_key_callback.as_ref(); + let future_result = state.and_then(|state| { + callback.map_or(Ok(None), |callback| callback.handle_operation(conn, state)) + }); + AsyncCallback::trigger(future_result, conn) + }) + .into() + } + + let handler = Box::new(handler); + let context = self.0.context_mut(); + context.private_key_callback = Some(handler); + + unsafe { + s2n_config_set_async_pkey_callback(self.as_mut_ptr(), Some(private_key_cb)) + .into_result()?; + } + Ok(self) + } + /// Set a callback function that will be used to get the system time. /// /// The wall clock time is the best-guess at the real time, measured since the epoch. @@ -544,6 +577,7 @@ impl Builder { pub(crate) struct Context { refcount: AtomicUsize, pub(crate) client_hello_callback: Option>, + pub(crate) private_key_callback: Option>, pub(crate) verify_host_callback: Option>, pub(crate) wall_clock: Option>, pub(crate) monotonic_clock: Option>, @@ -558,6 +592,7 @@ impl Default for Context { Self { refcount, client_hello_callback: None, + private_key_callback: None, verify_host_callback: None, wall_clock: None, monotonic_clock: None, diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index d054a41a12e..67a8de16669 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -80,12 +80,12 @@ impl Connection { } let mut connection = Self { connection }; - connection.init_context(); + connection.init_context(mode); connection } - fn init_context(&mut self) { - let context = Box::::default(); + fn init_context(&mut self, mode: Mode) { + let context = Box::new(Context::new(mode)); let context = Box::into_raw(context) as *mut c_void; // allocate a new context object unsafe { @@ -108,6 +108,10 @@ impl Connection { Self::new(Mode::Server) } + pub(crate) fn as_ptr(&mut self) -> *mut s2n_connection { + self.connection.as_ptr() + } + /// # Safety /// /// Caller must ensure s2n_connection is a valid reference to a [`s2n_connection`] object @@ -115,6 +119,10 @@ impl Connection { Self { connection } } + pub(crate) fn mode(&self) -> Mode { + self.context().mode + } + /// can be used to configure s2n to either use built-in blinding (set blinding /// to Blinding::BuiltIn) or self-service blinding (set blinding to /// Blinding::SelfService). @@ -349,6 +357,7 @@ impl Connection { /// called. Reusing the same connection handle(s) is more performant than repeatedly /// calling s2n_connection_new and s2n_connection_free pub fn wipe(&mut self) -> Result<&mut Self, Error> { + let mode = self.mode(); unsafe { // Wiping the connection will wipe the pointer to the context, // so retrieve and drop that memory first. @@ -358,7 +367,7 @@ impl Connection { s2n_connection_wipe(self.connection.as_ptr()).into_result() }?; - self.init_context(); + self.init_context(mode); Ok(self) } @@ -657,14 +666,72 @@ impl Connection { // are static and immutable since they are const fields on static const structs static_const_str!(cipher) } + + pub fn selected_signature_algorithm(&self) -> Result { + let mut sig_alg = s2n_tls_signature_algorithm::ANONYMOUS; + unsafe { + s2n_connection_get_selected_signature_algorithm(self.connection.as_ptr(), &mut sig_alg) + .into_result()?; + } + sig_alg.try_into() + } + + pub fn selected_hash_algorithm(&self) -> Result { + let mut hash_alg = s2n_tls_hash_algorithm::NONE; + unsafe { + s2n_connection_get_selected_digest_algorithm(self.connection.as_ptr(), &mut hash_alg) + .into_result()?; + } + hash_alg.try_into() + } + + pub fn selected_client_signature_algorithm(&self) -> Result, Error> { + let mut sig_alg = s2n_tls_signature_algorithm::ANONYMOUS; + unsafe { + s2n_connection_get_selected_client_cert_signature_algorithm( + self.connection.as_ptr(), + &mut sig_alg, + ) + .into_result()?; + } + Ok(match sig_alg { + s2n_tls_signature_algorithm::ANONYMOUS => None, + sig_alg => Some(sig_alg.try_into()?), + }) + } + + pub fn selected_client_hash_algorithm(&self) -> Result, Error> { + let mut hash_alg = s2n_tls_hash_algorithm::NONE; + unsafe { + s2n_connection_get_selected_client_cert_digest_algorithm( + self.connection.as_ptr(), + &mut hash_alg, + ) + .into_result()?; + } + Ok(match hash_alg { + s2n_tls_hash_algorithm::NONE => None, + hash_alg => Some(hash_alg.try_into()?), + }) + } } -#[derive(Default)] struct Context { + mode: Mode, waker: Option, async_callback: Option, } +impl Context { + fn new(mode: Mode) -> Self { + Context { + mode, + waker: None, + async_callback: None, + } + } +} + #[cfg(feature = "quic")] impl Connection { pub fn enable_quic(&mut self) -> Result<&mut Self, Error> { diff --git a/bindings/rust/s2n-tls/src/enums.rs b/bindings/rust/s2n-tls/src/enums.rs index 99ea3b2c3c2..5bc32d47b14 100644 --- a/bindings/rust/s2n-tls/src/enums.rs +++ b/bindings/rust/s2n-tls/src/enums.rs @@ -123,3 +123,57 @@ impl From for s2n_alert_behavior::Type { } } } + +#[non_exhaustive] +#[derive(Debug, PartialEq, Copy, Clone)] +#[allow(non_camel_case_types)] +pub enum SignatureAlgorithm { + RSA_PKCS1, + RSA_PSS_RSAE, + RSA_PSS_PSS, + ECDSA, +} + +impl TryFrom for SignatureAlgorithm { + type Error = Error; + + fn try_from(input: s2n_tls_signature_algorithm::Type) -> Result { + let version = match input { + s2n_tls_signature_algorithm::RSA => Self::RSA_PKCS1, + s2n_tls_signature_algorithm::RSA_PSS_RSAE => Self::RSA_PSS_RSAE, + s2n_tls_signature_algorithm::RSA_PSS_PSS => Self::RSA_PSS_PSS, + s2n_tls_signature_algorithm::ECDSA => Self::ECDSA, + _ => return Err(Error::INVALID_INPUT), + }; + Ok(version) + } +} + +#[non_exhaustive] +#[derive(Debug, PartialEq, Copy, Clone)] +#[allow(non_camel_case_types)] +pub enum HashAlgorithm { + MD5, + SHA1, + SHA224, + SHA256, + SHA384, + SHA512, +} + +impl TryFrom for HashAlgorithm { + type Error = Error; + + fn try_from(input: s2n_tls_hash_algorithm::Type) -> Result { + let version = match input { + s2n_tls_hash_algorithm::MD5 => Self::MD5, + s2n_tls_hash_algorithm::SHA1 => Self::SHA1, + s2n_tls_hash_algorithm::SHA224 => Self::SHA224, + s2n_tls_hash_algorithm::SHA256 => Self::SHA256, + s2n_tls_hash_algorithm::SHA384 => Self::SHA384, + s2n_tls_hash_algorithm::SHA512 => Self::SHA512, + _ => return Err(Error::INVALID_INPUT), + }; + Ok(version) + } +} diff --git a/bindings/rust/s2n-tls/src/testing.rs b/bindings/rust/s2n-tls/src/testing.rs index 0d9457f90b6..0e1652dc71b 100644 --- a/bindings/rust/s2n-tls/src/testing.rs +++ b/bindings/rust/s2n-tls/src/testing.rs @@ -2,9 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{callbacks::VerifyHostNameCallback, config::*, security, testing::s2n_tls::Harness}; -use alloc::collections::VecDeque; +use alloc::{collections::VecDeque, sync::Arc}; use bytes::Bytes; -use core::task::Poll; +use core::{ + sync::atomic::{AtomicUsize, Ordering}, + task::Poll, +}; pub mod client_hello; pub mod s2n_tls; @@ -17,6 +20,40 @@ type Result = core::result::Result; /// This is to prevent endless looping without making progress on the connection. const SAMPLES: usize = 100; +pub fn test_error(msg: &str) -> crate::error::Error { + crate::error::Error::application(msg.into()) +} + +pub fn assert_test_error(input: Error, msg: &str) { + let error = input + .downcast::() + .expect("Unexpected generic error type"); + if let Some(inner) = error.application_error() { + assert_eq!(msg, inner.to_string()) + } else { + panic!("Unexpected known error type"); + } +} + +#[derive(Clone)] +pub struct Counter(Arc); +impl Counter { + fn new() -> Self { + Counter(Arc::new(AtomicUsize::new(0))) + } + pub fn count(&self) -> usize { + self.0.load(Ordering::Relaxed) + } + pub fn increment(&self) { + self.0.fetch_add(1, Ordering::Relaxed); + } +} +impl Default for Counter { + fn default() -> Self { + Self::new() + } +} + pub trait Connection: core::fmt::Debug { fn poll(&mut self, context: &mut Ctx) -> Poll>; } @@ -199,3 +236,12 @@ pub fn poll_tls_pair(mut pair: Pair) -> Pair pair } + +pub fn poll_tls_pair_result(mut pair: Pair) -> Result<()> { + loop { + match pair.poll() { + Poll::Ready(result) => return result, + Poll::Pending => continue, + } + } +} diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index ddd97bef3be..a4c25b012f7 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -538,6 +538,46 @@ mod tests { Ok(()) } + #[test] + fn no_client_auth() -> Result<(), Error> { + use crate::enums::ClientAuthType; + + let config = { + let mut config = config_builder(&security::DEFAULT_TLS13)?; + config.set_client_auth_type(ClientAuthType::None)?; + config.build()? + }; + + let server = { + let mut server = crate::connection::Connection::new_server(); + server.set_config(config.clone())?; + Harness::new(server) + }; + + let client = { + let mut client = crate::connection::Connection::new_client(); + client.set_config(config)?; + Harness::new(client) + }; + + let pair = Pair::new(server, client, SAMPLES); + let pair = poll_tls_pair(pair); + let server = pair.server.0.connection; + let client = pair.client.0.connection; + + for conn in [server, client] { + assert!(!conn.client_cert_used()); + let cert = conn.client_cert_chain_bytes()?; + assert!(cert.is_none()); + let sig_alg = conn.selected_client_signature_algorithm()?; + assert!(sig_alg.is_none()); + let hash_alg = conn.selected_client_hash_algorithm()?; + assert!(hash_alg.is_none()); + } + + Ok(()) + } + #[test] fn client_auth() -> Result<(), Error> { use crate::enums::ClientAuthType; @@ -565,12 +605,18 @@ mod tests { let server = pair.server.0.connection; let client = pair.client.0.connection; - assert!(server.client_cert_used()); - assert!(client.client_cert_used()); let cert = server.client_cert_chain_bytes()?; assert!(cert.is_some()); assert!(!cert.unwrap().is_empty()); + for conn in [server, client] { + assert!(conn.client_cert_used()); + let sig_alg = conn.selected_client_signature_algorithm()?; + assert!(sig_alg.is_some()); + let hash_alg = conn.selected_client_hash_algorithm()?; + assert!(hash_alg.is_some()); + } + Ok(()) } }