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}