| // Copyright 2026 The BoringSSL Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! BoringSSL-backed [`rustls::crypto::CryptoProvider`]. |
| //! |
| //! This module provides [`CryptoProviderBuilder`], which constructs a |
| //! [`rustls::crypto::CryptoProvider`] backed by BoringSSL. The resulting |
| //! provider can be used with [`rustls`] to establish TLS 1.2 and TLS 1.3 |
| //! connections. |
| //! |
| //! ``` |
| //! use std::sync::Arc; |
| //! use bssl_tls::rustls_provider::CryptoProviderBuilder; |
| //! use rustls::client::ClientConfig; |
| //! |
| //! let provider = CryptoProviderBuilder::full(); |
| //! let _config = ClientConfig::builder_with_provider(Arc::new(provider)) |
| //! .with_safe_default_protocol_versions() |
| //! .unwrap() |
| //! .with_root_certificates(rustls::RootCertStore::empty()) |
| //! .with_no_client_auth(); |
| //! ``` |
| //! |
| //! For finer-grained control, individual cipher suites and key exchange |
| //! groups can be selected: |
| //! |
| //! ``` |
| //! use std::sync::Arc; |
| //! use bssl_tls::rustls_provider::{CryptoProviderBuilder, cipher_suites, key_exchange}; |
| //! use rustls::{SupportedCipherSuite, client::ClientConfig}; |
| //! |
| //! let provider = CryptoProviderBuilder::new() |
| //! .with_key_exchange_group(key_exchange::X25519) |
| //! .with_cipher_suite(SupportedCipherSuite::Tls13( |
| //! cipher_suites::TLS13_AES_256_GCM_SHA384, |
| //! )) |
| //! .build(); |
| //! let _config = ClientConfig::builder_with_provider(Arc::new(provider)) |
| //! .with_safe_default_protocol_versions() |
| //! .unwrap() |
| //! .with_root_certificates(rustls::RootCertStore::empty()) |
| //! .with_no_client_auth(); |
| //! ``` |
| |
| use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; |
| use bssl_sys::RAND_bytes; |
| use core::{ |
| fmt::{Debug, Formatter, Result as FmtResult}, |
| marker::PhantomData, |
| }; |
| |
| use rustls::{ |
| Error, SignatureScheme, SupportedCipherSuite, |
| crypto::{ |
| self, CryptoProvider, KeyProvider as RustlsKeyProvider, SecureRandom, SupportedKxGroup, |
| WebPkiSupportedAlgorithms, |
| }, |
| pki_types::PrivateKeyDer, |
| sign::SigningKey, |
| }; |
| |
| use bssl_crypto::{FfiMutSlice, digest, ec, pkcs8}; |
| |
| mod aead; |
| pub mod cipher_suites; |
| pub mod key_exchange; |
| pub mod pki; |
| mod prf; |
| mod sign; |
| |
| // A sealed trait only for supported digests |
| pub(crate) trait RsaSignatureDigest: digest::Algorithm { |
| /// A description of the digest algorithm. |
| const ALGORITHM: pki::DigestAlgorithm; |
| } |
| |
| impl RsaSignatureDigest for digest::Sha256 { |
| const ALGORITHM: pki::DigestAlgorithm = pki::DigestAlgorithm::Sha256; |
| } |
| |
| impl RsaSignatureDigest for digest::Sha384 { |
| const ALGORITHM: pki::DigestAlgorithm = pki::DigestAlgorithm::Sha384; |
| } |
| |
| impl RsaSignatureDigest for digest::Sha512 { |
| const ALGORITHM: pki::DigestAlgorithm = pki::DigestAlgorithm::Sha512; |
| } |
| |
| /// This random source delegates to [`RAND_bytes`] which aborts on insufficient |
| /// entropy. |
| struct Rand; |
| |
| impl Debug for Rand { |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| f.debug_struct("Rand").finish() |
| } |
| } |
| |
| impl SecureRandom for Rand { |
| fn fill(&self, buf: &mut [u8]) -> Result<(), crypto::GetRandomFailed> { |
| // Safety: `RAND_bytes` guarantees that the writes are in-bound |
| unsafe { |
| RAND_bytes(buf.as_mut_ffi_ptr(), buf.len()); |
| } |
| Ok(()) |
| } |
| } |
| |
| const ALL_SIGNATURE_ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { |
| all: &[ |
| pki::ECDSA_NISTP256_SHA256, |
| pki::ECDSA_NISTP384_SHA384, |
| pki::ED25519, |
| pki::RSA_PKCS1_SHA256, |
| pki::RSA_PKCS1_SHA384, |
| pki::RSA_PKCS1_SHA512, |
| pki::RSA_PSS_SHA256, |
| pki::RSA_PSS_SHA384, |
| pki::RSA_PSS_SHA512, |
| ], |
| mapping: &[ |
| ( |
| SignatureScheme::ECDSA_NISTP256_SHA256, |
| &[pki::ECDSA_NISTP256_SHA256], |
| ), |
| ( |
| SignatureScheme::ECDSA_NISTP384_SHA384, |
| &[pki::ECDSA_NISTP384_SHA384], |
| ), |
| (SignatureScheme::ED25519, &[pki::ED25519]), |
| (SignatureScheme::RSA_PKCS1_SHA256, &[pki::RSA_PKCS1_SHA256]), |
| (SignatureScheme::RSA_PKCS1_SHA384, &[pki::RSA_PKCS1_SHA384]), |
| (SignatureScheme::RSA_PKCS1_SHA512, &[pki::RSA_PKCS1_SHA512]), |
| (SignatureScheme::RSA_PSS_SHA256, &[pki::RSA_PSS_SHA256]), |
| (SignatureScheme::RSA_PSS_SHA384, &[pki::RSA_PSS_SHA384]), |
| (SignatureScheme::RSA_PSS_SHA512, &[pki::RSA_PSS_SHA512]), |
| ], |
| }; |
| |
| struct KeyProvider; |
| |
| impl Debug for KeyProvider { |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| f.debug_struct("FipsKeyProvider").finish() |
| } |
| } |
| |
| impl RustlsKeyProvider for KeyProvider { |
| fn load_private_key( |
| &self, |
| key_der: PrivateKeyDer<'static>, |
| ) -> Result<Arc<dyn SigningKey>, Error> { |
| match key_der { |
| PrivateKeyDer::Pkcs1(der) => { |
| sign::RsaPrivateKey::try_from(der).map(|key| Arc::new(key) as _) |
| } |
| PrivateKeyDer::Sec1(ref der) => sign::EcdsaPrivateKey::<ec::P256>::try_from(der) |
| .map(|key| Arc::new(key) as _) |
| .or_else(|_| { |
| sign::EcdsaPrivateKey::<ec::P384>::try_from(der).map(|key| Arc::new(key) as _) |
| }), |
| PrivateKeyDer::Pkcs8(ref der) => { |
| pkcs8::SigningKey::from_der_private_key_info(der.secret_pkcs8_der()) |
| .map(|key| match key { |
| pkcs8::SigningKey::Rsa(rsa) => Arc::new(sign::RsaPrivateKey(rsa)) as _, |
| pkcs8::SigningKey::EcP256(key) => Arc::new(sign::EcdsaPrivateKey(key)) as _, |
| pkcs8::SigningKey::EcP384(key) => Arc::new(sign::EcdsaPrivateKey(key)) as _, |
| pkcs8::SigningKey::Ed25519(key) => { |
| Arc::new(sign::EddsaPrivateKey(key)) as _ |
| } |
| }) |
| .ok_or(Error::General("unsupported PKCS #8 private key".into())) |
| } |
| _ => Err(Error::General("type of key to load is unrecognised".into())), |
| } |
| } |
| |
| fn fips(&self) -> bool { |
| false |
| } |
| } |
| |
| #[derive(Clone)] |
| struct HashContext<A>(A); |
| impl<A> Debug for HashContext<A> { |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| f.debug_tuple("HashContext").finish() |
| } |
| } |
| |
| impl<A: digest::Algorithm + Send + Sync + Clone + 'static> crypto::hash::Context |
| for HashContext<A> |
| { |
| fn fork_finish(&self) -> crypto::hash::Output { |
| let digest = self.0.clone().digest_to_vec(); |
| assert!(digest.len() <= crypto::hash::Output::MAX_LEN); |
| crypto::hash::Output::new(&digest) |
| } |
| |
| fn fork(&self) -> Box<dyn crypto::hash::Context> { |
| Box::new(self.clone()) |
| } |
| |
| fn finish(self: Box<Self>) -> crypto::hash::Output { |
| let digest = self.0.digest_to_vec(); |
| assert!(digest.len() <= crypto::hash::Output::MAX_LEN); |
| crypto::hash::Output::new(&digest) |
| } |
| |
| fn update(&mut self, data: &[u8]) { |
| self.0.update(data); |
| } |
| } |
| |
| struct HashAlgorithm<A>(PhantomData<fn() -> A>); |
| |
| impl<A> HashAlgorithm<A> { |
| const fn new() -> Self { |
| Self(PhantomData) |
| } |
| } |
| |
| macro_rules! impl_crypto_hash { |
| ($algo:path, $id:path) => { |
| impl crypto::hash::Hash for HashAlgorithm<$algo> { |
| fn start(&self) -> Box<dyn crypto::hash::Context> { |
| Box::new(HashContext(<$algo as digest::Algorithm>::new())) |
| } |
| |
| fn hash(&self, data: &[u8]) -> crypto::hash::Output { |
| let mut ctx = <$algo as digest::Algorithm>::new(); |
| ctx.update(data); |
| let digest = digest::Algorithm::digest_to_vec(ctx); |
| assert!(digest.len() <= crypto::hash::Output::MAX_LEN); |
| crypto::hash::Output::new(&digest) |
| } |
| |
| fn output_len(&self) -> usize { |
| <$algo as digest::Algorithm>::OUTPUT_LEN |
| } |
| |
| fn algorithm(&self) -> crypto::hash::HashAlgorithm { |
| $id |
| } |
| } |
| }; |
| } |
| |
| impl_crypto_hash!(digest::Sha256, crypto::hash::HashAlgorithm::SHA256); |
| impl_crypto_hash!(digest::Sha384, crypto::hash::HashAlgorithm::SHA384); |
| impl_crypto_hash!(digest::Sha512, crypto::hash::HashAlgorithm::SHA512); |
| |
| /// The main provider builder. |
| pub struct CryptoProviderBuilder { |
| kx_groups: Vec<&'static dyn SupportedKxGroup>, |
| cipher_suites: Vec<SupportedCipherSuite>, |
| } |
| |
| impl CryptoProviderBuilder { |
| /// Make a new provider builder. |
| pub fn new() -> Self { |
| Self { |
| kx_groups: vec![], |
| cipher_suites: vec![], |
| } |
| } |
| |
| /// Include all possible cipher suites and key agreement groups. |
| pub fn full() -> CryptoProvider { |
| Self::new() |
| .with_default_key_exchange_groups() |
| .with_full_cipher_suites() |
| .build() |
| } |
| |
| /// Include a key exchange group, with a lower priority than previously registered groups. |
| pub fn with_key_exchange_group(mut self, group: &'static dyn SupportedKxGroup) -> Self { |
| self.kx_groups.push(group); |
| self |
| } |
| |
| /// Use the default key exchange groups, with a lower priority than previously registered |
| /// groups. |
| pub fn with_default_key_exchange_groups(mut self) -> Self { |
| self.kx_groups.extend_from_slice(&[ |
| key_exchange::X25519, |
| key_exchange::ECDH_P256, |
| key_exchange::ECDH_P384, |
| ]); |
| self |
| } |
| |
| #[cfg(feature = "mlalgs")] |
| /// Include post-quantum MLKEM hybrid key exchange groups, with a lower priority than previously |
| /// registered groups. |
| pub fn with_mlkem_groups(mut self) -> Self { |
| self.kx_groups |
| .extend_from_slice(&[key_exchange::X25519MLKEM768]); |
| self |
| } |
| |
| /// Use all the provided cipher suites |
| #[inline] |
| pub fn with_full_cipher_suites(mut self) -> Self { |
| self.cipher_suites.extend([ |
| SupportedCipherSuite::Tls12( |
| cipher_suites::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, |
| ), |
| SupportedCipherSuite::Tls12(cipher_suites::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), |
| SupportedCipherSuite::Tls12(cipher_suites::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), |
| SupportedCipherSuite::Tls12(cipher_suites::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), |
| SupportedCipherSuite::Tls12(cipher_suites::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), |
| SupportedCipherSuite::Tls12(cipher_suites::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), |
| SupportedCipherSuite::Tls13(cipher_suites::TLS13_AES_128_GCM_SHA256), |
| SupportedCipherSuite::Tls13(cipher_suites::TLS13_AES_256_GCM_SHA384), |
| SupportedCipherSuite::Tls13(cipher_suites::TLS13_CHACHA20_POLY1305_SHA256), |
| ]); |
| self |
| } |
| |
| /// Add a [`SupportedCipherSuite`]. |
| /// More cipher suites are available in [`cipher_suites`]. |
| #[inline] |
| pub fn with_cipher_suite(mut self, cipher_suite: SupportedCipherSuite) -> Self { |
| self.cipher_suites.push(cipher_suite); |
| self |
| } |
| |
| #[inline] |
| /// Finalise and build the [`CryptoProvider`] for `rustls`. |
| pub fn build(self) -> CryptoProvider { |
| CryptoProvider { |
| cipher_suites: self.cipher_suites, |
| kx_groups: self.kx_groups, |
| signature_verification_algorithms: ALL_SIGNATURE_ALGORITHMS, |
| secure_random: &Rand, |
| key_provider: &KeyProvider, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests; |