use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::fmt::{self, Debug};
use core::hash::{Hash, Hasher};
use core::time::Duration;
use pki_types::{FipsStatus, PrivateKeyDer, SignatureVerificationAlgorithm};
use crate::crypto::kx::KeyExchangeAlgorithm;
use crate::enums::ProtocolVersion;
#[cfg(feature = "webpki")]
use crate::error::PeerMisbehaved;
use crate::error::{ApiMisuse, Error};
use crate::msgs::ALL_KEY_EXCHANGE_ALGORITHMS;
use crate::sync::Arc;
#[cfg(feature = "webpki")]
pub use crate::webpki::{verify_tls12_signature, verify_tls13_signature};
#[cfg(doc)]
use crate::{ClientConfig, ConfigBuilder, ServerConfig, client, crypto, server};
use crate::{SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite};
pub mod cipher;
mod enums;
pub use enums::{CipherSuite, HashAlgorithm, SignatureAlgorithm, SignatureScheme};
pub mod hash;
pub mod hmac;
pub mod kx;
use kx::{NamedGroup, SupportedKxGroup};
pub mod tls12;
pub mod tls13;
pub mod hpke;
#[cfg(any(doc, test))]
pub(crate) mod test_provider;
#[cfg(test)]
pub(crate) use test_provider::TEST_PROVIDER;
#[cfg(doc)]
#[doc(hidden)]
pub use test_provider::TEST_PROVIDER;
#[cfg(all(test, any(target_arch = "aarch64", target_arch = "x86_64")))]
pub(crate) use test_provider::TLS13_TEST_SUITE;
mod signer;
pub use signer::{
CertificateIdentity, Credentials, Identity, InconsistentKeys, SelectedCredential, Signer,
SigningKey, SingleCredential, public_key_to_spki,
};
pub use crate::suites::CipherSuiteCommon;
#[expect(clippy::exhaustive_structs)]
#[derive(Debug, Clone)]
pub struct CryptoProvider {
pub tls12_cipher_suites: Cow<'static, [&'static Tls12CipherSuite]>,
pub tls13_cipher_suites: Cow<'static, [&'static Tls13CipherSuite]>,
pub kx_groups: Cow<'static, [&'static dyn SupportedKxGroup]>,
pub signature_verification_algorithms: WebPkiSupportedAlgorithms,
pub secure_random: &'static dyn SecureRandom,
pub key_provider: &'static dyn KeyProvider,
pub ticketer_factory: &'static dyn TicketerFactory,
}
impl CryptoProvider {
pub fn install_default(self) -> Result<(), Arc<Self>> {
static_default::install_default(self)
}
}
impl CryptoProvider {
pub fn get_default() -> Option<&'static Arc<Self>> {
static_default::get_default()
}
pub fn fips(&self) -> FipsStatus {
let Self {
tls12_cipher_suites,
tls13_cipher_suites,
kx_groups,
signature_verification_algorithms,
secure_random,
key_provider,
ticketer_factory,
} = self;
let mut status = Ord::min(
signature_verification_algorithms.fips(),
secure_random.fips(),
);
status = Ord::min(status, key_provider.fips());
status = Ord::min(status, ticketer_factory.fips());
for cs in tls12_cipher_suites.iter() {
status = Ord::min(status, cs.fips());
}
for cs in tls13_cipher_suites.iter() {
status = Ord::min(status, cs.fips());
}
for kx in kx_groups.iter() {
status = Ord::min(status, kx.fips());
}
status
}
pub(crate) fn consistency_check(&self) -> Result<(), Error> {
if self.tls12_cipher_suites.is_empty() && self.tls13_cipher_suites.is_empty() {
return Err(ApiMisuse::NoCipherSuitesConfigured.into());
}
if self.kx_groups.is_empty() {
return Err(ApiMisuse::NoKeyExchangeGroupsConfigured.into());
}
for group in self.kx_groups.iter() {
if group.name().key_exchange_algorithm() == KeyExchangeAlgorithm::DHE
&& group.ffdhe_group().is_none()
{
return Err(Error::General(alloc::format!(
"SupportedKxGroup {group:?} must return Some() from `ffdhe_group()`"
)));
}
}
let mut supported_kx_algos = Vec::with_capacity(ALL_KEY_EXCHANGE_ALGORITHMS.len());
for group in self.kx_groups.iter() {
let kx = group.name().key_exchange_algorithm();
if !supported_kx_algos.contains(&kx) {
supported_kx_algos.push(kx);
}
if supported_kx_algos.len() == ALL_KEY_EXCHANGE_ALGORITHMS.len() {
break;
}
}
for cs in self.tls12_cipher_suites.iter() {
if supported_kx_algos.contains(&cs.kx) {
continue;
}
let suite_name = cs.common.suite;
return Err(Error::General(alloc::format!(
"TLS1.2 cipher suite {suite_name:?} requires {0:?} key exchange, but no {0:?}-compatible \
key exchange groups were present in `CryptoProvider`'s `kx_groups` field",
cs.kx,
)));
}
Ok(())
}
pub(crate) fn iter_cipher_suites(&self) -> impl Iterator<Item = SupportedCipherSuite> + '_ {
self.tls13_cipher_suites
.iter()
.copied()
.map(SupportedCipherSuite::Tls13)
.chain(
self.tls12_cipher_suites
.iter()
.copied()
.map(SupportedCipherSuite::Tls12),
)
}
pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool {
match v {
ProtocolVersion::TLSv1_2 => !self.tls12_cipher_suites.is_empty(),
ProtocolVersion::TLSv1_3 => !self.tls13_cipher_suites.is_empty(),
_ => false,
}
}
pub(crate) fn find_kx_group(
&self,
name: NamedGroup,
version: ProtocolVersion,
) -> Option<&'static dyn SupportedKxGroup> {
if !name.usable_for_version(version) {
return None;
}
self.kx_groups
.iter()
.find(|skxg| skxg.name() == name)
.copied()
}
}
impl Borrow<[&'static Tls12CipherSuite]> for CryptoProvider {
fn borrow(&self) -> &[&'static Tls12CipherSuite] {
&self.tls12_cipher_suites
}
}
impl Borrow<[&'static Tls13CipherSuite]> for CryptoProvider {
fn borrow(&self) -> &[&'static Tls13CipherSuite] {
&self.tls13_cipher_suites
}
}
#[derive(Clone, Copy)]
pub struct WebPkiSupportedAlgorithms {
pub(crate) all: &'static [&'static dyn SignatureVerificationAlgorithm],
pub(crate) mapping: &'static [(
SignatureScheme,
&'static [&'static dyn SignatureVerificationAlgorithm],
)],
}
impl WebPkiSupportedAlgorithms {
pub const fn new(
all: &'static [&'static dyn SignatureVerificationAlgorithm],
mapping: &'static [(
SignatureScheme,
&'static [&'static dyn SignatureVerificationAlgorithm],
)],
) -> Result<Self, ApiMisuse> {
let s = Self { all, mapping };
if mapping.is_empty() {
return Err(ApiMisuse::NoSignatureVerificationAlgorithms);
}
let mut i = 0;
while i < s.mapping.len() {
if s.mapping[i].1.is_empty() {
return Err(ApiMisuse::NoSignatureVerificationAlgorithms);
}
assert!(!s.mapping[i].1.is_empty());
i += 1;
}
Ok(s)
}
pub fn supported_schemes(&self) -> Vec<SignatureScheme> {
self.mapping
.iter()
.map(|item| item.0)
.collect()
}
pub fn fips(&self) -> FipsStatus {
let algs = self
.all
.iter()
.map(|alg| alg.fips_status())
.min();
let mapped = self
.mapping
.iter()
.flat_map(|(_, algs)| algs.iter().map(|alg| alg.fips_status()))
.min();
match (algs, mapped) {
(Some(algs), Some(mapped)) => Ord::min(algs, mapped),
(Some(status), None) | (None, Some(status)) => status,
(None, None) => FipsStatus::Unvalidated,
}
}
pub fn mapping(
&self,
) -> &'static [(
SignatureScheme,
&'static [&'static dyn SignatureVerificationAlgorithm],
)] {
self.mapping
}
#[cfg(feature = "webpki")]
pub(crate) fn convert_scheme(
&self,
scheme: SignatureScheme,
) -> Result<&[&'static dyn SignatureVerificationAlgorithm], Error> {
self.mapping
.iter()
.filter_map(|item| if item.0 == scheme { Some(item.1) } else { None })
.next()
.ok_or_else(|| PeerMisbehaved::SignedHandshakeWithUnadvertisedSigScheme.into())
}
}
impl Debug for WebPkiSupportedAlgorithms {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WebPkiSupportedAlgorithms {{ all: [ .. ], mapping: ")?;
f.debug_list()
.entries(self.mapping.iter().map(|item| item.0))
.finish()?;
write!(f, " }}")
}
}
impl Hash for WebPkiSupportedAlgorithms {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self { all, mapping } = self;
write_algs(state, all);
state.write_usize(mapping.len());
for (scheme, algs) in *mapping {
state.write_u16(u16::from(*scheme));
write_algs(state, algs);
}
fn write_algs<H: Hasher>(
state: &mut H,
algs: &[&'static dyn SignatureVerificationAlgorithm],
) {
state.write_usize(algs.len());
for alg in algs {
state.write(alg.public_key_alg_id().as_ref());
state.write(alg.signature_alg_id().as_ref());
}
}
}
}
pub(crate) mod rand {
use super::{GetRandomFailed, SecureRandom};
pub(crate) fn random_array<const N: usize>(
secure_random: &dyn SecureRandom,
) -> Result<[u8; N], GetRandomFailed> {
let mut v = [0; N];
secure_random.fill(&mut v)?;
Ok(v)
}
pub(crate) fn random_u32(secure_random: &dyn SecureRandom) -> Result<u32, GetRandomFailed> {
Ok(u32::from_be_bytes(random_array(secure_random)?))
}
pub(crate) fn random_u16(secure_random: &dyn SecureRandom) -> Result<u16, GetRandomFailed> {
Ok(u16::from_be_bytes(random_array(secure_random)?))
}
}
#[expect(clippy::exhaustive_structs)]
#[derive(Debug)]
pub struct GetRandomFailed;
pub trait SecureRandom: Send + Sync + Debug {
fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed>;
fn fips(&self) -> FipsStatus {
FipsStatus::Unvalidated
}
}
pub trait KeyProvider: Send + Sync + Debug {
fn load_private_key(
&self,
key_der: PrivateKeyDer<'static>,
) -> Result<Box<dyn SigningKey>, Error>;
fn fips(&self) -> FipsStatus {
FipsStatus::Unvalidated
}
}
pub trait TicketerFactory: Debug + Send + Sync {
fn ticketer(&self) -> Result<Arc<dyn TicketProducer>, Error>;
fn fips(&self) -> FipsStatus {
FipsStatus::Unvalidated
}
}
pub trait TicketProducer: Debug + Send + Sync {
fn encrypt(&self, plain: &[u8]) -> Option<Vec<u8>>;
fn decrypt(&self, cipher: &[u8]) -> Option<Vec<u8>>;
fn lifetime(&self) -> Duration;
}
mod static_default {
use std::sync::OnceLock;
use super::CryptoProvider;
use crate::sync::Arc;
pub(crate) fn install_default(
default_provider: CryptoProvider,
) -> Result<(), Arc<CryptoProvider>> {
PROCESS_DEFAULT_PROVIDER.set(Arc::new(default_provider))
}
pub(crate) fn get_default() -> Option<&'static Arc<CryptoProvider>> {
PROCESS_DEFAULT_PROVIDER.get()
}
static PROCESS_DEFAULT_PROVIDER: OnceLock<Arc<CryptoProvider>> = OnceLock::new();
}
#[cfg(test)]
#[track_caller]
pub(crate) fn tls13_suite(
suite: CipherSuite,
provider: &CryptoProvider,
) -> &'static Tls13CipherSuite {
provider
.tls13_cipher_suites
.iter()
.find(|cs| cs.common.suite == suite)
.unwrap()
}
#[cfg(test)]
#[track_caller]
pub(crate) fn tls12_suite(
suite: CipherSuite,
provider: &CryptoProvider,
) -> &'static Tls12CipherSuite {
provider
.tls12_cipher_suites
.iter()
.find(|cs| cs.common.suite == suite)
.unwrap()
}
#[cfg(test)]
#[track_caller]
pub(crate) fn tls13_only(provider: CryptoProvider) -> CryptoProvider {
CryptoProvider {
tls12_cipher_suites: Cow::default(),
..provider
}
}
#[cfg(test)]
#[track_caller]
pub(crate) fn tls12_only(provider: CryptoProvider) -> CryptoProvider {
CryptoProvider {
tls13_cipher_suites: Cow::default(),
..provider
}
}