Skip to main content

ssh_cli/tls/
provider.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2#![forbid(unsafe_code)]
3//! Process-wide rustls [`CryptoProvider`] bootstrap (binary only).
4//!
5//! Libraries must **not** call [`install_default_provider`]; they use
6//! [`rustls::ClientConfig::builder`] which reads the process default, or
7//! `builder_with_provider` when an explicit provider is required.
8
9use std::sync::atomic::{AtomicBool, Ordering};
10
11use crate::errors::SshCliResult;
12
13/// Set after a successful install (or when another party already installed).
14static PROVIDER_READY: AtomicBool = AtomicBool::new(false);
15
16/// Human-readable provider id for diagnostics / `tls provider` command.
17pub const PROVIDER_NAME: &str = "aws_lc_rs";
18
19/// Installs `aws_lc_rs::default_provider` once for the process.
20///
21/// Call from binary `main` **before** any TLS dial and preferably before the
22/// Tokio runtime is built (rules: install → log → config → runtime → sockets).
23///
24/// Idempotent: a second call succeeds if the default is already installed
25/// (including by another crate in the same process).
26///
27/// # Errors
28/// Returns [`SshCliError::Tls`] when install fails for a reason other than
29/// "already installed".
30pub fn install_default_provider() -> SshCliResult<()> {
31    if PROVIDER_READY.load(Ordering::Acquire) {
32        return Ok(());
33    }
34    // Prefer get_default first — another bootstrap may have won the race.
35    if rustls::crypto::CryptoProvider::get_default().is_some() {
36        PROVIDER_READY.store(true, Ordering::Release);
37        return Ok(());
38    }
39    match rustls::crypto::aws_lc_rs::default_provider().install_default() {
40        Ok(()) => {
41            PROVIDER_READY.store(true, Ordering::Release);
42            tracing::debug!(provider = PROVIDER_NAME, "rustls CryptoProvider installed");
43            Ok(())
44        }
45        Err(_already) => {
46            // install_default returns Err(Arc<CryptoProvider>) when already set.
47            PROVIDER_READY.store(true, Ordering::Release);
48            tracing::debug!(
49                provider = PROVIDER_NAME,
50                "rustls CryptoProvider already installed by another caller"
51            );
52            Ok(())
53        }
54    }
55}
56
57/// Returns `true` when a process default provider is available.
58#[must_use]
59pub fn provider_is_installed() -> bool {
60    PROVIDER_READY.load(Ordering::Acquire)
61        || rustls::crypto::CryptoProvider::get_default().is_some()
62}
63
64/// Static provider name string (`aws_lc_rs`).
65#[must_use]
66pub fn provider_name() -> &'static str {
67    PROVIDER_NAME
68}
69
70/// Ensures a provider is available for library builders (install if missing).
71///
72/// Prefer [`install_default_provider`] from `main`. This helper exists so
73/// library code paths that run under tests without a binary bootstrap still
74/// succeed once per process.
75pub(crate) fn ensure_provider() -> SshCliResult<()> {
76    install_default_provider()
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn install_is_idempotent() {
85        install_default_provider().expect("first");
86        install_default_provider().expect("second");
87        assert!(provider_is_installed());
88        assert_eq!(provider_name(), "aws_lc_rs");
89    }
90}