Clean up tls-related feature flags

This provides a clear and consistent interface for selecting TLS-related
features on both (xmpp and tokio-xmpp) crates. All feature checks have
been revamped. All working combinations have been tested (including a
connectivity test + --all-features docs build) using:

```
set -xeuo pipefail
features=(aws_lc_rs ring ktls,aws_lc_rs ktls,ring aws_lc_rs,ring aws_lc_rs,ring,ktls native-tls rustls-any-backend)
export RUSTFLAGS=" -D warnings"
cargo test --no-default-features
cargo test
for feature in ${features[@]}; do
  echo ">>> BUILDING with $feature" 2>&1
  # Running code or building examples cannot succeed with rustls-any-backend.
  features="starttls,$feature"
  if [ "$feature" != 'rustls-any-backend' ]; then
    if ! cargo test --no-default-features --features="$features"; then
      echo ">>> BUILD FAILED for tls feature set: $features" >&2
      exit 1
    fi
    set +e
    timeout -sINT -p -k 2 3 cargo run --no-default-features --features="$features" --example keep_connection -- test@hub.sotecware.net "$(pass xmpp/test@hub.sotecware.net)"
    status="$?"
    set -e
    if [ $status -ne 0 ]; then
      echo ">>> keep_connection did not shut down cleanly! (status: $status)" >&2
      exit 1
    fi
  else
    if ! cargo build --no-default-features --features="$feature"; then
      echo ">>> BUILD FAILED for tls feature set: $features" >&2
      exit 1
    fi
  fi
done
RUSTDOCFLAGS="--cfg docsrs" RUSTFLAGS="--cfg xmpprs_doc_build" cargo +nightly doc -Zrustdoc-map --all-features
```
This commit is contained in:
Jonas Schäfer 2025-05-10 10:45:43 +02:00
commit fc8b581593
15 changed files with 273 additions and 135 deletions

View file

@ -2,18 +2,22 @@
use alloc::borrow::Cow;
use core::{error::Error as StdError, fmt};
#[cfg(feature = "tls-native")]
#[cfg(feature = "native-tls")]
use native_tls::Error as TlsError;
use std::io;
use std::os::fd::AsRawFd;
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
#[cfg(feature = "rustls-any-backend")]
use tokio_rustls::rustls::pki_types::InvalidDnsNameError;
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
// Note: feature = "rustls-any-backend" and feature = "native-tls" are
// mutually exclusive during normal compiles, but we allow it for rustdoc
// builds. Thus, we have to make sure that the compilation still succeeds in
// such a case.
#[cfg(all(feature = "rustls-any-backend", not(feature = "native-tls")))]
use tokio_rustls::rustls::Error as TlsError;
use futures::{sink::SinkExt, stream::StreamExt};
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
#[cfg(all(feature = "rustls-any-backend", not(feature = "native-tls")))]
use {
alloc::sync::Arc,
tokio_rustls::{
@ -24,16 +28,16 @@ use {
};
#[cfg(all(
feature = "tls-rust",
not(feature = "tls-native"),
not(feature = "tls-rust-ktls")
feature = "rustls-any-backend",
not(feature = "ktls"),
not(feature = "native-tls")
))]
use tokio_rustls::client::TlsStream;
#[cfg(all(feature = "tls-rust-ktls", not(feature = "tls-native")))]
#[cfg(all(feature = "ktls", not(feature = "native-tls")))]
type TlsStream<S> = ktls::KtlsStream<S>;
#[cfg(feature = "tls-native")]
#[cfg(feature = "native-tls")]
use {
native_tls::TlsConnector as NativeTlsConnector,
tokio_native_tls::{TlsConnector, TlsStream},
@ -123,7 +127,7 @@ impl ServerConnector for StartTlsServerConnector {
}
}
#[cfg(feature = "tls-native")]
#[cfg(feature = "native-tls")]
async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin>(
xmpp_stream: XmppStream<BufStream<S>>,
domain: &str,
@ -140,7 +144,7 @@ async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin>(
Ok((tls_stream, ChannelBinding::None))
}
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
#[cfg(all(feature = "rustls-any-backend", not(feature = "native-tls")))]
async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin + AsRawFd>(
xmpp_stream: XmppStream<BufStream<S>>,
domain: &str,
@ -160,7 +164,7 @@ async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin + AsRawFd>(
let mut config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
#[cfg(feature = "tls-rust-ktls")]
#[cfg(feature = "ktls")]
let stream = {
config.enable_secret_extraction = true;
ktls::CorkStream::new(stream)
@ -184,7 +188,7 @@ async fn get_tls_stream<S: AsyncRead + AsyncWrite + Unpin + AsRawFd>(
_ => ChannelBinding::None,
};
#[cfg(feature = "tls-rust-ktls")]
#[cfg(feature = "ktls")]
let tls_stream = ktls::config_ktls_client(tls_stream)
.await
.map_err(StartTlsError::KtlsError)?;
@ -228,10 +232,10 @@ pub async fn starttls<S: AsyncRead + AsyncWrite + Unpin + AsRawFd>(
pub enum StartTlsError {
/// TLS error
Tls(TlsError),
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
#[cfg(feature = "rustls-any-backend")]
/// DNS name parsing error
DnsNameError(InvalidDnsNameError),
#[cfg(feature = "tls-rust-ktls")]
#[cfg(feature = "ktls")]
/// Error while setting up kernel TLS
KtlsError(ktls::Error),
}
@ -242,9 +246,9 @@ impl fmt::Display for StartTlsError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Tls(e) => write!(fmt, "TLS error: {}", e),
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
#[cfg(feature = "rustls-any-backend")]
Self::DnsNameError(e) => write!(fmt, "DNS name error: {}", e),
#[cfg(feature = "tls-rust-ktls")]
#[cfg(feature = "ktls")]
Self::KtlsError(e) => write!(fmt, "Kernel TLS error: {}", e),
}
}
@ -258,7 +262,7 @@ impl From<TlsError> for StartTlsError {
}
}
#[cfg(all(feature = "tls-rust", not(feature = "tls-native")))]
#[cfg(feature = "rustls-any-backend")]
impl From<InvalidDnsNameError> for StartTlsError {
fn from(e: InvalidDnsNameError) -> Self {
Self::DnsNameError(e)

View file

@ -22,6 +22,50 @@
//! - [ ] Websockets
//! - [ ] BOSH
//!
//! # Cargo features
//!
//! ## TLS backends
//!
//! - `aws_lc_rs` (default) enables rustls with the `aws_lc_rs` backend.
//! - `ring` enables rustls with the `ring` backend`.
//! - `rustls-any-backend` enables rustls, but without enabling a backend. It
//! is the application's responsibility to ensure that a backend is enabled
//! and installed.
//! - `ktls` enables the use of ktls.
//! **Important:** Currently, connections will fail if the `tls` kernel
//! module is not available. There is no fallback to non-ktls connections!
//! - `native-tls` enables the system-native TLS library (commonly
//! libssl/OpenSSL).
//!
//! **Note:** It is not allowed to mix rustls-based TLS backends with
//! `tls-native`. Attempting to do so will result in a compilation error.
//!
//! **Note:** The `ktls` feature requires at least one `rustls` backend to be
//! enabled (`aws_lc_rs` or `ring`).
//!
//! **Note:** When enabling not exactly one rustls backend, it is the
//! application's responsibility to make sure that a default crypto provider is
//! installed in `rustls`. Otherwise, all TLS connections will fail.
//!
//! ## Certificate validation
//!
//! When using `native-tls`, the system's native certificate store is used.
//! Otherwise, you need to pick one of the following to ensure that TLS
//! connections will succeed:
//!
//! - `rustls-native-certs` (default): Uses [rustls-native-certs](https://crates.io/crates/rustls-native-certs).
//! - `webpki-roots`: Uses [webpki-roots](https://crates.io/crates/webpki-roots).
//!
//! ## Other features
//!
//! - `starttls` (default): Enables support for `<starttls/>`. Required as per
//! RFC 6120.
//! - `insecure-tcp`: Allow the use of insecure TCP connections to connect to
//! XMPP servers. Required for XMPP components, but disabled by default to
//! prevent accidental use.
//! - `serde`: Enable the `serde` feature in `xmpp-parsers`.
//! - `component`: Enable component support (implies `insecure-tcp`).
//!
//! # More information
//!
//! You can find more information on our website [xmpp.rs](https://xmpp.rs/) or by joining our chatroom [chat@xmpp.rs](xmpp:chat@xmpp.rs?join).
@ -29,21 +73,35 @@
#![deny(unsafe_code, missing_docs, bare_trait_objects)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(all(
not(xmpprs_doc_build),
not(doc),
feature = "tls-native",
feature = "tls-rust"
))]
compile_error!("Both tls-native and tls-rust features can't be enabled at the same time.");
macro_rules! fail_native_with_any {
($($feature:literal),+) => {
$(
#[cfg(all(
not(xmpprs_doc_build),
not(doc),
feature = "native-tls",
feature = $feature,
))]
compile_error!(
concat!(
"native-tls cannot be mixed with the ",
$feature,
" feature. Pick one or the other."
)
);
)+
}
}
fail_native_with_any!("ring", "aws_lc_rs", "ktls", "rustls-any-backend");
#[cfg(all(
feature = "starttls",
not(feature = "tls-native"),
not(feature = "tls-rust")
not(feature = "native-tls"),
not(any(feature = "rustls-any-backend"))
))]
compile_error!(
"when starttls feature enabled one of tls-native and tls-rust features must be enabled."
"When the starttls feature is enabled, either native-tls or any of the rustls (aws_lc_rs, ring, or rustls-any-backend) features must be enabled."
);
extern crate alloc;
@ -51,7 +109,7 @@ extern crate alloc;
pub use parsers::{jid, minidom};
pub use xmpp_parsers as parsers;
#[cfg(feature = "tls-rust")]
#[cfg(any(feature = "ring", feature = "aws_lc_rs", feature = "ktls"))]
pub use tokio_rustls::rustls;
mod client;