rust: Add `rustls` CryptoProvider adapters For now we target `rustls 0.23.0` and its other semver compatible versions. The support is not complete in the following way. - We lack DTLS safe binding, for which we do not plan as of writing. Change-Id: I2ebcd62cc690cd331d7e4338b49d3a3adbbb0f4a Signed-off-by: Xiangfei Ding <xfding@google.com> Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/88127 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d1c658f..d67ec88 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock
@@ -12,3 +12,374 @@ [[package]] name = "bssl-sys" version = "0.1.0" + +[[package]] +name = "bssl-tls" +version = "0.1.0" +dependencies = [ + "bssl-crypto", + "bssl-sys", + "rustls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "cc" +version = "1.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a0689b3..7a2db1d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml
@@ -2,5 +2,6 @@ members = [ "bssl-crypto", "bssl-sys", + "bssl-tls", ] resolver = "3"
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index dfa7ef6..a11ce40 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs
@@ -81,8 +81,10 @@ /// the pointer. When passing pointers into C/C++ code, that is not a valid /// pointer. Thus this method should be used whenever passing a pointer to a /// slice into BoringSSL code. -trait FfiSlice<T> { +pub trait FfiSlice<T> { + /// Cast the slice into a valid raw pointer for FFI. fn as_ffi_ptr(&self) -> *const T; + /// Cast the slice into a valid `const void *` pointer for FFI. fn as_ffi_void_ptr(&self) -> *const c_void { self.as_ffi_ptr() as *const c_void } @@ -109,7 +111,8 @@ } /// See the comment [`FfiSlice`]. -trait FfiMutSlice { +pub trait FfiMutSlice { + /// Cast the mutable slice as a valid `uint8_t*` pointer for FFI. fn as_mut_ffi_ptr(&mut self) -> *mut u8; }
diff --git a/rust/bssl-tls/Cargo.toml b/rust/bssl-tls/Cargo.toml new file mode 100644 index 0000000..c86145f --- /dev/null +++ b/rust/bssl-tls/Cargo.toml
@@ -0,0 +1,36 @@ +[package] +name = "bssl-tls" +version = "0.1.0" +edition = "2024" +publish = false +license = "Apache-2.0" + +[dependencies.bssl-crypto] +path = "../bssl-crypto" + +[dependencies.bssl-sys] +path = "../bssl-sys" + +[dependencies.rustls] +version = "0.23.0" +default-features = false +optional = true + +[dev-dependencies] +tracing = "0.1" +tracing-subscriber = "0.3" + +[dev-dependencies.rustls] +version = "0.23.0" +default-features = false +features = ["ring"] + +[features] +default = [] +# `std` depends on the Rust `std` crate, but adds some useful trait impls if +# available. +std = ["bssl-crypto/std"] +# `mlalgs` enables ML-KEM and ML-DSA support. This requires Rust 1.82. +mlalgs = ["bssl-crypto/mlalgs"] +# `rustls` enables the adapters to key traits for inter-op with `rustls` crate +rustls-adapters = ["rustls/tls12", "rustls/std"]
diff --git a/rust/bssl-tls/deny.toml b/rust/bssl-tls/deny.toml new file mode 100644 index 0000000..a4dedf9 --- /dev/null +++ b/rust/bssl-tls/deny.toml
@@ -0,0 +1,28 @@ +# Configuration file used for `cargo deny check`, which checks for licensing +# issues and security advisories. +# +# For a list of possible sections and their default values, see +# https://github.com/EmbarkStudios/cargo-deny/blob/main/deny.template.toml +# +# For further documentation, see https://embarkstudios.github.io/cargo-deny/. + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = ["Apache-2.0"] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "deny" +# List of crates that are allowed. Use with care! +# This is meant to control any external dependencies. This is effectively +# a minimalist binding library and we try to have none, so you are strongly +# encouraged not to add dependencies here. +allow = []
diff --git a/rust/bssl-tls/src/lib.rs b/rust/bssl-tls/src/lib.rs new file mode 100644 index 0000000..99111e4 --- /dev/null +++ b/rust/bssl-tls/src/lib.rs
@@ -0,0 +1,32 @@ +// 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. + +#![deny( + missing_docs, + unsafe_op_in_unsafe_fn, + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::panic, + clippy::expect_used +)] +#![allow(private_bounds)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] + +//! Rust BoringSSL bindings + +extern crate alloc; +extern crate core; + +#[cfg(feature = "rustls-adapters")] +pub mod rustls_provider;
diff --git a/rust/bssl-tls/src/rustls_provider.rs b/rust/bssl-tls/src/rustls_provider.rs new file mode 100644 index 0000000..389a948 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider.rs
@@ -0,0 +1,335 @@ +// 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. + +//! `rustls` Adapters +//! +//! This module provides a provider builder `CryptoProviderBuilder`, +//! which constructs a [`rustls::crypto::CryptoProvider`] for interop with `rustls` TLS stack. +//! +//! # Supported signature schemes +//! +//! ## [RFC 8446] IANA assignments for TLS supported signature scheme +//! +//! - `TLS_AES_128_GCM_SHA256` +//! - `TLS_AES_256_GCM_SHA384` +//! - `TLS_CHACHA20_POLY1305_SHA256` +//! +//! # Supported key exchange groups +//! +//! - [`secp256r1`] backed by [`key_exchange::ECDH_P256`] +//! - [`secp384r1`] backed by [`key_exchange::ECDH_P384`] +//! - [`X25519`] backed by [`key_exchange::X25519`] +//! +//! [RFC 8446]: https://datatracker.ietf.org/doc/html/rfc8446 +//! [`X25519`]: https://datatracker.ietf.org/doc/html/rfc7748 + +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::ECDH_P256, + key_exchange::ECDH_P384, + key_exchange::X25519, + ]); + 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;
diff --git a/rust/bssl-tls/src/rustls_provider/aead.rs b/rust/bssl-tls/src/rustls_provider/aead.rs new file mode 100644 index 0000000..fad045d --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/aead.rs
@@ -0,0 +1,587 @@ +// 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. + +use alloc::boxed::Box; +use core::marker::PhantomData; + +use rustls::{ + ConnectionTrafficSecrets, ContentType, Error, ProtocolVersion, + crypto::cipher::{ + AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, MessageDecrypter, + MessageEncrypter, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, + Tls12AeadAlgorithm, Tls13AeadAlgorithm, UnsupportedOperationError, make_tls12_aad, + make_tls13_aad, + }, +}; + +use bssl_crypto::aead; + +struct Tls12AdditionalData; + +impl TlsAdditionalData for Tls12AdditionalData { + #[inline] + fn build_additional_data( + seq: u64, + typ: ContentType, + version: ProtocolVersion, + len: usize, + ad_buf: &mut [u8], + ) -> Option<&[u8]> { + let ad_buf: &mut [u8; 13] = ad_buf.try_into().ok()?; + ad_buf.copy_from_slice(&make_tls12_aad(seq, typ, version, len)); + Some(ad_buf) + } +} + +const NONCE_LEN: usize = 12; + +enum AeadKind { + Aes128Gcm, + Aes256Gcm, + Chacha20Poly1305, +} + +trait AeadConstructible: aead::Aead + Sized { + const KEY_LEN: usize; + const TAG_LEN: usize; + const KIND: AeadKind; + fn new_with_key(key: &AeadKey) -> Option<Self>; + fn nonce_from_buf(buf: &[u8]) -> Option<&Self::Nonce>; + fn tag_from_buf(buf: &[u8]) -> Option<&Self::Tag>; + fn tag_to_buf(tag: &Self::Tag) -> &[u8]; +} + +fn make_nonce_from_iv_seq(iv: [u8; NONCE_LEN], seq: u64) -> [u8; NONCE_LEN] { + let mut buf = [0; NONCE_LEN]; + buf[4..].copy_from_slice(&seq.to_be_bytes()); + for i in 0..NONCE_LEN { + buf[i] ^= iv[i]; + } + buf +} + +macro_rules! aead_ctor { + ($($algo:ty : { + key = $key_len:literal, + tag = $tag_len:literal, + kind = $kind:expr + }),+) => { $( + impl AeadConstructible for $algo { + const KEY_LEN: usize = $key_len; + const TAG_LEN: usize = $tag_len; + const KIND: AeadKind = $kind; + fn new_with_key(key: &AeadKey) -> Option<Self> { + let key: [u8; $key_len] = key.as_ref().try_into().ok()?; + Some(Self::new(&key)) + } + fn nonce_from_buf(buf: &[u8]) -> Option<&[u8; NONCE_LEN]> { + buf.try_into().ok() + } + fn tag_from_buf(buf: &[u8]) -> Option<&[u8; $tag_len]> { + buf.try_into().ok() + } + fn tag_to_buf(tag: &[u8; $tag_len]) -> &[u8] { + &*tag + } + } + )+ }; + () => {}; + ($($algo:ty : $tt:tt),+,) => { aead_ctor!($($algo : $tt),+); } +} + +aead_ctor!( + aead::Aes128Gcm : {key = 16, tag = 16, kind = AeadKind::Aes128Gcm}, + aead::Aes256Gcm : {key = 32, tag = 16, kind = AeadKind::Aes256Gcm}, + aead::Chacha20Poly1305 : {key = 32, tag = 16, kind = AeadKind::Chacha20Poly1305}, +); + +trait Gcm: AeadConstructible {} +impl Gcm for aead::Aes128Gcm {} +impl Gcm for aead::Aes256Gcm {} + +trait Chacha20: AeadConstructible {} +impl Chacha20 for aead::Chacha20Poly1305 {} + +// =================================================== +// TLS 1.2 AES GCM cipher +// =================================================== + +enum MaybeValidByteArray<const N: usize> { + Valid([u8; N]), + Invalid, +} + +impl<const N: usize> From<&'_ [u8]> for MaybeValidByteArray<N> { + fn from(value: &[u8]) -> Self { + if let Ok(value) = value.try_into() { + Self::Valid(value) + } else { + Self::Invalid + } + } +} + +struct Tls12GcmMessageEncrypter<A, AD> { + key: AeadKey, + iv: MaybeValidByteArray<4>, + explicit_nonce: MaybeValidByteArray<8>, + _p: PhantomData<fn() -> (A, AD)>, +} + +trait TlsAdditionalData { + fn build_additional_data( + seq: u64, + typ: ContentType, + version: ProtocolVersion, + len: usize, + ad_buf: &mut [u8], + ) -> Option<&[u8]>; +} + +const TLS12_AAD_SIZE: usize = 13; +const TLS12_GCM_EXTRA_NONCE_SIZE: usize = 8; + +#[inline] +fn gcm_iv(iv: &[u8; 4], extra: &[u8; TLS12_GCM_EXTRA_NONCE_SIZE]) -> [u8; NONCE_LEN] { + let mut gcm_iv = [0; NONCE_LEN]; + gcm_iv[..4].copy_from_slice(iv); + gcm_iv[4..].copy_from_slice(extra); + gcm_iv +} + +const GCM_EXPLICIT_NONCE_LEN: usize = 8; + +impl<A: Gcm, AD: TlsAdditionalData> MessageEncrypter for Tls12GcmMessageEncrypter<A, AD> { + fn encrypt( + &mut self, + msg: OutboundPlainMessage<'_>, + seq: u64, + ) -> Result<OutboundOpaqueMessage, Error> { + let payload_len = msg.payload.len(); + let mut payload = PrefixedPayload::with_capacity(self.encrypted_payload_len(payload_len)); + let aead = A::new_with_key(&self.key).ok_or(Error::EncryptError)?; + let (MaybeValidByteArray::Valid(iv), MaybeValidByteArray::Valid(explicit_nonce)) = + (&self.iv, &self.explicit_nonce) + else { + return Err(Error::DecryptError); + }; + let write_iv = gcm_iv(iv, explicit_nonce); + let nonce = make_nonce_from_iv_seq(write_iv, seq); + let nonce = A::nonce_from_buf(&nonce).ok_or(Error::EncryptError)?; + let mut ad_buf = [0; TLS12_AAD_SIZE]; + let ad = AD::build_additional_data(seq, msg.typ, msg.version, payload_len, &mut ad_buf) + .ok_or(Error::EncryptError)?; + payload.extend_from_slice(&nonce.as_ref()[4..]); + payload.extend_from_chunks(&msg.payload); + let tag = aead.seal_in_place( + &nonce, + &mut payload.as_mut()[GCM_EXPLICIT_NONCE_LEN..GCM_EXPLICIT_NONCE_LEN + payload_len], + ad, + ); + payload.extend_from_slice(A::tag_to_buf(&tag)); + Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + GCM_EXPLICIT_NONCE_LEN + A::TAG_LEN + } +} + +struct Tls12GcmMessageDecrypter<A, AD> { + key: AeadKey, + iv: MaybeValidByteArray<4>, + _p: PhantomData<fn() -> (A, AD)>, +} + +impl<A: Gcm, AD: TlsAdditionalData> MessageDecrypter for Tls12GcmMessageDecrypter<A, AD> { + fn decrypt<'a>( + &mut self, + mut msg: InboundOpaqueMessage<'a>, + seq: u64, + ) -> Result<InboundPlainMessage<'a>, Error> { + let aead = A::new_with_key(&self.key).ok_or(Error::DecryptError)?; + let InboundOpaqueMessage { + typ, + version, + ref mut payload, + } = msg; + let Some((explicit_nonce, payload)) = payload.split_at_mut_checked(GCM_EXPLICIT_NONCE_LEN) + else { + return Err(Error::DecryptError); + }; + let Ok(extra) = explicit_nonce.try_into() else { + unreachable!("length should be 8") + }; + let MaybeValidByteArray::Valid(iv) = &self.iv else { + return Err(Error::DecryptError); + }; + let nonce = gcm_iv(iv, &extra); + let Some(ctxt_len) = payload.len().checked_sub(A::TAG_LEN) else { + return Err(Error::DecryptError); + }; + // TODO(@xfding) Should we check the fragment size? + let Some((ciphertext, tag)) = payload.split_at_mut_checked(ctxt_len) else { + return Err(Error::DecryptError); + }; + let mut ad_buf = [0; TLS12_AAD_SIZE]; + let ad = AD::build_additional_data(seq, typ, version, ctxt_len, &mut ad_buf) + .ok_or(Error::DecryptError)?; + let tag = A::tag_from_buf(tag).ok_or(Error::DecryptError)?; + let nonce = A::nonce_from_buf(&nonce).ok_or(Error::DecryptError)?; + aead.open_in_place(&nonce, ciphertext, &tag, ad) + .map_err(|_| Error::DecryptError)?; + Ok(msg.into_plain_message_range(GCM_EXPLICIT_NONCE_LEN..GCM_EXPLICIT_NONCE_LEN + ctxt_len)) + } +} + +struct Tls12GcmAeadAlgorithm<A>(PhantomData<fn() -> A>); + +impl<A> Tls12GcmAeadAlgorithm<A> {} + +impl<A: 'static + Gcm> Tls12AeadAlgorithm for Tls12GcmAeadAlgorithm<A> { + fn encrypter(&self, key: AeadKey, iv: &[u8], extra: &[u8]) -> Box<dyn MessageEncrypter> { + Box::new(Tls12GcmMessageEncrypter::<A, Tls12AdditionalData> { + key, + iv: iv.into(), + explicit_nonce: extra.into(), + _p: PhantomData, + }) + } + + fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box<dyn MessageDecrypter> { + Box::new(Tls12GcmMessageDecrypter::<A, Tls12AdditionalData> { + key, + iv: iv.into(), + _p: PhantomData, + }) + } + + fn key_block_shape(&self) -> KeyBlockShape { + KeyBlockShape { + enc_key_len: A::KEY_LEN, + fixed_iv_len: 4, + explicit_nonce_len: 8, + } + } + + fn extract_keys( + &self, + key: AeadKey, + iv: &[u8], + explicit: &[u8], + ) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> { + let iv = Iv::new(gcm_iv( + iv.try_into().map_err(|_| UnsupportedOperationError)?, + explicit.try_into().map_err(|_| UnsupportedOperationError)?, + )); + Ok(match A::KEY_LEN { + 16 => ConnectionTrafficSecrets::Aes128Gcm { key, iv }, + 32 => ConnectionTrafficSecrets::Aes256Gcm { key, iv }, + _ => unreachable!(), + }) + } +} + +/// TLS 1.2 AEAD scheme AES 128 GCM +pub(crate) const TLS12_AES_128_GCM_AEAD: &'static dyn Tls12AeadAlgorithm = + &Tls12GcmAeadAlgorithm::<aead::Aes128Gcm>(PhantomData); + +/// TLS 1.2 AEAD scheme AES 256 GCM +pub(crate) const TLS12_AES_256_GCM_AEAD: &'static dyn Tls12AeadAlgorithm = + &Tls12GcmAeadAlgorithm::<aead::Aes256Gcm>(PhantomData); + +// =================================================== +// TLS 1.2 ChaCha20 cipher +// =================================================== + +struct Tls12Chacha20PolyMessageEncrypter<A, AD> { + key: AeadKey, + iv: MaybeValidByteArray<NONCE_LEN>, + _p: PhantomData<fn() -> (A, AD)>, +} + +impl<A: Chacha20, AD: TlsAdditionalData> MessageEncrypter + for Tls12Chacha20PolyMessageEncrypter<A, AD> +{ + fn encrypt( + &mut self, + msg: OutboundPlainMessage<'_>, + seq: u64, + ) -> Result<OutboundOpaqueMessage, Error> { + let payload_len = msg.payload.len(); + let mut payload = PrefixedPayload::with_capacity(self.encrypted_payload_len(payload_len)); + let aead = A::new_with_key(&self.key).ok_or(Error::EncryptError)?; + let MaybeValidByteArray::Valid(iv) = self.iv else { + return Err(Error::EncryptError); + }; + let nonce = make_nonce_from_iv_seq(iv, seq); + let nonce = A::nonce_from_buf(&nonce).ok_or(Error::EncryptError)?; + let mut ad_buf = [0; TLS12_AAD_SIZE]; + let ad = AD::build_additional_data(seq, msg.typ, msg.version, payload_len, &mut ad_buf) + .ok_or(Error::EncryptError)?; + payload.extend_from_chunks(&msg.payload); + let tag = aead.seal_in_place(&nonce, &mut payload.as_mut()[..payload_len], ad); + payload.extend_from_slice(A::tag_to_buf(&tag)); + Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + A::TAG_LEN + } +} + +struct Tls12Chacha20PolyMessageDecrypter<A, AD> { + key: AeadKey, + iv: MaybeValidByteArray<NONCE_LEN>, + _p: PhantomData<fn() -> (A, AD)>, +} + +impl<A: Chacha20, AD: TlsAdditionalData> MessageDecrypter + for Tls12Chacha20PolyMessageDecrypter<A, AD> +{ + fn decrypt<'a>( + &mut self, + mut msg: InboundOpaqueMessage<'a>, + seq: u64, + ) -> Result<InboundPlainMessage<'a>, Error> { + let aead = A::new_with_key(&self.key).ok_or(Error::DecryptError)?; + let MaybeValidByteArray::Valid(iv) = self.iv else { + return Err(Error::DecryptError); + }; + let nonce = make_nonce_from_iv_seq(iv, seq); + let nonce = A::nonce_from_buf(&nonce).ok_or(Error::DecryptError)?; + let mut ad_buf = [0; TLS12_AAD_SIZE]; + let Some(ctxt_len) = msg.payload.len().checked_sub(A::TAG_LEN) else { + return Err(Error::DecryptError); + }; + let ad = AD::build_additional_data(seq, msg.typ, msg.version, ctxt_len, &mut ad_buf) + .ok_or(Error::DecryptError)?; + // TODO(@xfding) Should we check the fragment size? + let Some((ciphertext, tag)) = msg.payload.split_at_mut_checked(ctxt_len) else { + return Err(Error::DecryptError); + }; + let tag = A::tag_from_buf(tag).ok_or(Error::DecryptError)?; + aead.open_in_place(&nonce, ciphertext, tag, ad) + .map_err(|_| Error::DecryptError)?; + Ok(msg.into_plain_message_range(0..ctxt_len)) + } +} + +struct Tls12Chacha20AeadAlgorithm<A>(PhantomData<fn() -> A>); + +impl<A: 'static + Chacha20> Tls12AeadAlgorithm for Tls12Chacha20AeadAlgorithm<A> { + fn encrypter(&self, key: AeadKey, iv: &[u8], _extra: &[u8]) -> Box<dyn MessageEncrypter> { + Box::new( + Tls12Chacha20PolyMessageEncrypter::<A, Tls12AdditionalData> { + key, + iv: iv.into(), + _p: PhantomData, + }, + ) + } + + fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box<dyn MessageDecrypter> { + Box::new( + Tls12Chacha20PolyMessageDecrypter::<A, Tls12AdditionalData> { + key, + iv: iv.into(), + _p: PhantomData, + }, + ) + } + + fn key_block_shape(&self) -> KeyBlockShape { + KeyBlockShape { + enc_key_len: 32, + fixed_iv_len: 12, + explicit_nonce_len: 0, + } + } + + fn extract_keys( + &self, + key: AeadKey, + iv: &[u8], + _explicit: &[u8], + ) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> { + let iv = Iv::new(iv.try_into().map_err(|_| UnsupportedOperationError)?); + Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) + } + + fn fips(&self) -> bool { + false + } +} + +/// TLS 1.2 AEAD cipher with Chacha202-Poly1305 +pub(crate) static TLS12_CHACHA20_POLY1305_AEAD: &'static dyn Tls12AeadAlgorithm = + &Tls12Chacha20AeadAlgorithm::<aead::Chacha20Poly1305>(PhantomData); + +// =================================================== +// TLS 1.3 AEAD cipher family +// =================================================== + +struct Tls13AdditionalData; + +impl TlsAdditionalData for Tls13AdditionalData { + #[inline] + fn build_additional_data<'a>( + _seq: u64, + _typ: ContentType, + _version: ProtocolVersion, + len: usize, + ad_buf: &'a mut [u8], + ) -> Option<&'a [u8]> { + let ad_buf: &mut [u8; 5] = ad_buf.try_into().ok()?; + ad_buf.copy_from_slice(&make_tls13_aad(len)); + Some(ad_buf) + } +} + +/// TLS 1.3 Cipher suite TLS13_AES_128_GCM +pub(crate) const TLS13_AES_128_GCM: &'static dyn Tls13AeadAlgorithm = + &Tls13AeadAlgorithmImpl::<aead::Aes128Gcm>(PhantomData); + +/// TLS 1.3 Cipher suite TLS13_AES_256_GCM +pub(crate) const TLS13_AES_256_GCM: &'static dyn Tls13AeadAlgorithm = + &Tls13AeadAlgorithmImpl::<aead::Aes256Gcm>(PhantomData); + +/// TLS 1.3 Cipher suite TLS13_CHACHA20_POLY1305 +pub(crate) const TLS13_CHACHA20_POLY1305: &'static dyn Tls13AeadAlgorithm = + &Tls13AeadAlgorithmImpl::<aead::Chacha20Poly1305>(PhantomData); + +const TLS13_AAD_SIZE: usize = 5; + +struct Tls13MessageEncrypter<A, AD> { + key: AeadKey, + iv: MaybeValidByteArray<NONCE_LEN>, + _p: PhantomData<fn() -> (A, AD)>, +} + +impl<A: AeadConstructible, AD: TlsAdditionalData> MessageEncrypter + for Tls13MessageEncrypter<A, AD> +{ + fn encrypt( + &mut self, + msg: OutboundPlainMessage<'_>, + seq: u64, + ) -> Result<OutboundOpaqueMessage, Error> { + let plaintxt_len = msg.payload.len(); + let payload_len = self.encrypted_payload_len(plaintxt_len); + let mut payload = PrefixedPayload::with_capacity(payload_len); + let aead = A::new_with_key(&self.key).ok_or(Error::EncryptError)?; + let MaybeValidByteArray::Valid(iv) = self.iv else { + return Err(Error::EncryptError); + }; + let nonce = make_nonce_from_iv_seq(iv, seq); + let nonce = A::nonce_from_buf(&nonce).ok_or(Error::EncryptError)?; + let mut ad_buf = [0; TLS13_AAD_SIZE]; + let ad = AD::build_additional_data(seq, msg.typ, msg.version, payload_len, &mut ad_buf) + .ok_or(Error::EncryptError)?; + // Layout: [<..PAYLOAD, TYPE (size 1)>, TAG (size TAG_LEN)] + payload.extend_from_chunks(&msg.payload); + payload.extend_from_slice(&msg.typ.to_array()); + let tag = aead.seal_in_place(&nonce, &mut payload.as_mut()[0..plaintxt_len + 1], ad); + payload.extend_from_slice(A::tag_to_buf(&tag)); + Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + A::TAG_LEN + 1 + } +} + +struct Tls13MessageDecrypter<A, AD> { + key: AeadKey, + iv: MaybeValidByteArray<NONCE_LEN>, + _p: PhantomData<fn() -> (A, AD)>, +} + +impl<A: AeadConstructible, AD: TlsAdditionalData> MessageDecrypter + for Tls13MessageDecrypter<A, AD> +{ + fn decrypt<'a>( + &mut self, + mut msg: InboundOpaqueMessage<'a>, + seq: u64, + ) -> Result<InboundPlainMessage<'a>, Error> { + let aead = A::new_with_key(&self.key).ok_or(Error::DecryptError)?; + let MaybeValidByteArray::Valid(iv) = self.iv else { + return Err(Error::DecryptError); + }; + let nonce = make_nonce_from_iv_seq(iv, seq); + let nonce = A::nonce_from_buf(&nonce).ok_or(Error::DecryptError)?; + let InboundOpaqueMessage { + typ, + version, + ref mut payload, + } = msg; + // Layout: [<..PAYLOAD, TYPE (size 1)>, TAG (size TAG_LEN)] + let total_len = payload.len(); + let Some(ctxt_len) = total_len.checked_sub(A::TAG_LEN) else { + return Err(Error::DecryptError); + }; + let Some((ciphertext, tag)) = payload.split_at_mut_checked(ctxt_len) else { + return Err(Error::DecryptError); + }; + let mut ad_buf = [0; TLS13_AAD_SIZE]; + let ad = AD::build_additional_data(seq, typ, version, total_len, &mut ad_buf) + .ok_or(Error::DecryptError)?; + let tag = A::tag_from_buf(tag).ok_or(Error::DecryptError)?; + aead.open_in_place(&nonce, ciphertext, tag, ad) + .map_err(|_| Error::DecryptError)?; + payload.truncate(ctxt_len); + msg.into_tls13_unpadded_message() + } +} + +struct Tls13AeadAlgorithmImpl<A>(PhantomData<fn() -> A>); + +impl<A: 'static + AeadConstructible> Tls13AeadAlgorithm for Tls13AeadAlgorithmImpl<A> { + fn encrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageEncrypter> { + Box::new(Tls13MessageEncrypter::<A, Tls13AdditionalData> { + key, + iv: iv.as_ref().into(), + _p: PhantomData, + }) + } + + fn decrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageDecrypter> { + Box::new(Tls13MessageDecrypter::<A, Tls13AdditionalData> { + key, + iv: iv.as_ref().into(), + _p: PhantomData, + }) + } + + fn key_len(&self) -> usize { + A::KEY_LEN + } + + fn extract_keys( + &self, + key: AeadKey, + iv: Iv, + ) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> { + Ok(match A::KIND { + AeadKind::Aes128Gcm => ConnectionTrafficSecrets::Aes128Gcm { key, iv }, + AeadKind::Aes256Gcm => ConnectionTrafficSecrets::Aes256Gcm { key, iv }, + AeadKind::Chacha20Poly1305 => ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }, + }) + } + + fn fips(&self) -> bool { + false + } +}
diff --git a/rust/bssl-tls/src/rustls_provider/cipher_suites.rs b/rust/bssl-tls/src/rustls_provider/cipher_suites.rs new file mode 100644 index 0000000..f8417cb --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/cipher_suites.rs
@@ -0,0 +1,148 @@ +// 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. + +//! Supported cipher suites + +use bssl_crypto::digest; +use rustls::{ + CipherSuite, CipherSuiteCommon, SignatureScheme, Tls12CipherSuite, Tls13CipherSuite, + crypto::KeyExchangeAlgorithm, +}; + +use super::HashAlgorithm; +use super::prf::Tls12PrfImpl; + +macro_rules! tls12_suites { + ($($suite:ident { + $hash:ident, + $confid:expr, + $kx:ident, + $sign:expr, + $aead:ident + })*) => { + $( + #[doc = concat!("TLS 1.2 cipher suite `", stringify!($suite), "`")] + pub const $suite: &'static Tls12CipherSuite = &Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::$suite, + hash_provider: &HashAlgorithm::<digest::$hash>::new(), + confidentiality_limit: $confid, + }, + prf_provider: &Tls12PrfImpl::<digest::$hash>::new(), + kx: KeyExchangeAlgorithm::$kx, + sign: $sign, + aead_alg: super::aead::$aead, + }; + )* + }; + ($($tt:tt)*) => { + tls12_suites!($($tt),*) + }; +} + +tls12_suites! { + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 { + Sha256, + 1 << 24, + ECDHE, + TLS12_ECDSA_SCHEMES, + TLS12_CHACHA20_POLY1305_AEAD + } + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 { + Sha256, + u64::MAX, + ECDHE, + TLS12_RSA_SCHEMES, + TLS12_CHACHA20_POLY1305_AEAD + } + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 { + Sha256, + 1 << 24, + ECDHE, + TLS12_RSA_SCHEMES, + TLS12_AES_128_GCM_AEAD + } + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 { + Sha384, + 1 << 24, + ECDHE, + TLS12_RSA_SCHEMES, + TLS12_AES_256_GCM_AEAD + } + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 { + Sha256, + 1 << 24, + ECDHE, + TLS12_ECDSA_SCHEMES, + TLS12_AES_128_GCM_AEAD + } + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 { + Sha384, + 1 << 24, + ECDHE, + TLS12_ECDSA_SCHEMES, + TLS12_AES_256_GCM_AEAD + } +} + +const TLS12_ECDSA_SCHEMES: &[SignatureScheme] = &[ + SignatureScheme::ED25519, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, +]; + +const TLS12_RSA_SCHEMES: &[SignatureScheme] = &[ + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, +]; + +/// Cipher suite TLS13_AES_128_GCM_SHA256 +pub const TLS13_AES_128_GCM_SHA256: &'static Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_AES_128_GCM_SHA256, + hash_provider: &HashAlgorithm::<digest::Sha256>::new(), + confidentiality_limit: 1 << 24, + }, + hkdf_provider: super::prf::TLS13_HKDF_SHA256, + aead_alg: super::aead::TLS13_AES_128_GCM, + quic: None, +}; + +/// Cipher suite TLS13_AES_256_GCM_SHA384 +pub const TLS13_AES_256_GCM_SHA384: &'static Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_AES_256_GCM_SHA384, + hash_provider: &HashAlgorithm::<digest::Sha384>::new(), + confidentiality_limit: 1 << 24, + }, + hkdf_provider: super::prf::TLS13_HKDF_SHA384, + aead_alg: super::aead::TLS13_AES_256_GCM, + quic: None, +}; + +/// Cipher suite TLS13_CHACHA20_POLY1305_SHA256 +pub const TLS13_CHACHA20_POLY1305_SHA256: &'static Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + hash_provider: &HashAlgorithm::<digest::Sha256>::new(), + confidentiality_limit: u64::MAX, + }, + hkdf_provider: super::prf::TLS13_HKDF_SHA256, + aead_alg: super::aead::TLS13_CHACHA20_POLY1305, + quic: None, +};
diff --git a/rust/bssl-tls/src/rustls_provider/key_exchange.rs b/rust/bssl-tls/src/rustls_provider/key_exchange.rs new file mode 100644 index 0000000..e109171 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/key_exchange.rs
@@ -0,0 +1,146 @@ +// 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. + +//! Supported key exchange groups +//! +//! We support the following standard groups. +//! +//! - [ECDH_P256] for elliptic curve based Diffie-Hellman ECDH-P256 +//! - [ECDH_P384] for elliptic curve based Diffie-Hellman ECDH-P384 +//! - [X25519] for X25519 +//! +//! If `mlalgs` feature is enabled, we also support the following post-quantum hybrid key exchange +//! groups, for TLS 1.3. +//! +//! - [X25519MLKEM768] for X25519MLKEM768 + +use alloc::{ + boxed::Box, + fmt::{Debug, Formatter, Result as FmtResult}, + vec::Vec, +}; +use core::marker::PhantomData; + +use bssl_crypto::{ec, ecdh, x25519}; +use rustls::{ + Error, NamedGroup, PeerMisbehaved, + crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup}, +}; + +#[cfg(feature = "mlalgs")] +mod mlkem; +#[cfg(feature = "mlalgs")] +pub use mlkem::X25519MLKEM768; + +/// Elliptic Curve Diffie-Hellman key exchange group +struct EcGroup<C: ec::Curve>(PhantomData<fn() -> C>); +/// Elliptic Curve Diffie-Hellman key exchange group `ECDH-P256` +pub const ECDH_P256: &'static dyn SupportedKxGroup = &EcGroup::<ec::P256>(PhantomData); +/// Elliptic Curve Diffie-Hellman key exchange group `ECDH-P384` +pub const ECDH_P384: &'static dyn SupportedKxGroup = &EcGroup::<ec::P384>(PhantomData); + +impl<C: ec::Curve> Debug for EcGroup<C> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("EcGroup").finish() + } +} + +impl<C: ec::Curve + 'static> SupportedKxGroup for EcGroup<C> { + fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> { + let priv_key = ecdh::PrivateKey::<C>::generate(); + let pub_key = priv_key.to_public_key(); + let pub_key_x962_uncompressed = pub_key.to_x962_uncompressed().as_ref().into(); + Ok(Box::new(EcActiveKeyExchange { + priv_key, + pub_key_x962_uncompressed, + })) + } + + fn name(&self) -> NamedGroup { + match C::group() { + ec::Group::P256 => NamedGroup::secp256r1, + ec::Group::P384 => NamedGroup::secp384r1, + } + } +} + +/// This type encodes the Diffie-Hellman key exchange state during TLS. +struct EcActiveKeyExchange<C: ec::Curve> { + priv_key: ecdh::PrivateKey<C>, + pub_key_x962_uncompressed: Vec<u8>, +} +impl<C: ec::Curve> ActiveKeyExchange for EcActiveKeyExchange<C> { + fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> { + let peer_pub_key = ecdh::PublicKey::from_x962_uncompressed(peer_pub_key) + .ok_or(Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare))?; + let shared_secret = self.priv_key.compute_shared_key(&peer_pub_key); + Ok(shared_secret.into()) + } + + fn pub_key(&self) -> &[u8] { + &self.pub_key_x962_uncompressed + } + + fn group(&self) -> NamedGroup { + match C::group() { + ec::Group::P256 => NamedGroup::secp256r1, + ec::Group::P384 => NamedGroup::secp384r1, + } + } +} + +/// X25519 Key exchange group +#[derive(Debug)] +struct X25519Group; +/// X25519 key exchange group +pub const X25519: &'static dyn SupportedKxGroup = &X25519Group; + +impl SupportedKxGroup for X25519Group { + fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> { + let (pub_key, priv_key) = x25519::PrivateKey::generate(); + Ok(Box::new(X25519ActiveKeyExchange { pub_key, priv_key })) + } + + fn name(&self) -> NamedGroup { + NamedGroup::X25519 + } +} + +struct X25519ActiveKeyExchange { + pub_key: x25519::PublicKey, + priv_key: x25519::PrivateKey, +} + +impl ActiveKeyExchange for X25519ActiveKeyExchange { + fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> { + let secret = self + .priv_key + .compute_shared_key( + peer_pub_key + .try_into() + .map_err(|_| Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare))?, + ) + .ok_or(Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare))? + .to_vec(); + Ok(secret.into()) + } + + fn pub_key(&self) -> &[u8] { + &self.pub_key + } + + fn group(&self) -> NamedGroup { + NamedGroup::X25519 + } +}
diff --git a/rust/bssl-tls/src/rustls_provider/key_exchange/mlkem.rs b/rust/bssl-tls/src/rustls_provider/key_exchange/mlkem.rs new file mode 100644 index 0000000..45169a6 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/key_exchange/mlkem.rs
@@ -0,0 +1,144 @@ +// 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. + +use alloc::{boxed::Box, vec::Vec}; + +use bssl_crypto::{mlkem, x25519}; +use rustls::{ + Error, NamedGroup, PeerMisbehaved, ProtocolVersion, + crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}, +}; + +/// X25519MLKEM768 key exchange group +pub const X25519MLKEM768: &'static dyn SupportedKxGroup = &X25519Mlkem768Group; + +macro_rules! ok_or_invalid_share { + ($e:expr) => { + match $e { + Some(v) => v, + None => return Err(Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare)), + } + }; +} + +#[derive(Debug)] +struct X25519Mlkem768Group; + +impl X25519Mlkem768Group { + fn extract_keys(client_pub_key: &[u8]) -> Option<(mlkem::PublicKey768, x25519::PublicKey)> { + // X25519MLKEM768 transposed the order of concatenation in spite of the naming + let (client_encap_key, client_pub_key) = + client_pub_key.split_at_checked(mlkem::PUBLIC_KEY_BYTES_768)?; + let client_encap_key = mlkem::PublicKey768::parse(client_encap_key)?; + Some((client_encap_key, client_pub_key.try_into().ok()?)) + } +} + +impl SupportedKxGroup for X25519Mlkem768Group { + fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> { + let (encap_key, decap_key, _) = mlkem::PrivateKey768::generate(); + let (pub_key, priv_key) = x25519::PrivateKey::generate(); + // X25519MLKEM768 transposed the order of concatenation in spite of the naming + let mut client_share = encap_key; + client_share.extend_from_slice(&pub_key); + Ok(Box::new(X25519MlKem768ActiveKeyExchange { + decap_key, + priv_key, + pub_key, + client_share, + })) + } + + // This override is for server side share + fn start_and_complete(&self, client_share: &[u8]) -> Result<CompletedKeyExchange, Error> { + // X25519MLKEM768 transposed the order of concatenation in spite of the naming + let (client_encap_key, client_pub_key) = + ok_or_invalid_share!(Self::extract_keys(client_share)); + let (pub_key, priv_key) = x25519::PrivateKey::generate(); + let dh_secret = ok_or_invalid_share!(priv_key.compute_shared_key(&client_pub_key)); + let (mlkem_ctxt, quantum_secret) = client_encap_key.encapsulate(); + + let mut server_share = mlkem_ctxt; + server_share.extend(pub_key); + + let mut secret = Vec::with_capacity(mlkem::SHARED_SECRET_BYTES + x25519::SHARED_KEY_LEN); + secret.extend(quantum_secret); + secret.extend(dh_secret); + Ok(CompletedKeyExchange { + group: NamedGroup::X25519MLKEM768, + pub_key: server_share, + secret: secret.into(), + }) + } + + fn name(&self) -> NamedGroup { + NamedGroup::X25519MLKEM768 + } + + fn usable_for_version(&self, version: ProtocolVersion) -> bool { + matches!(version, ProtocolVersion::TLSv1_3) + } +} + +struct X25519MlKem768ActiveKeyExchange { + decap_key: mlkem::PrivateKey768, + priv_key: x25519::PrivateKey, + pub_key: x25519::PublicKey, + client_share: Vec<u8>, +} + +impl X25519MlKem768ActiveKeyExchange { + fn compute_dh_share(&self, peer_pub_key: &[u8]) -> Result<[u8; x25519::SHARED_KEY_LEN], Error> { + peer_pub_key + .try_into() + .ok() + .and_then(|peer_pub_key| self.priv_key.compute_shared_key(&peer_pub_key)) + .ok_or(Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare)) + } +} + +impl ActiveKeyExchange for X25519MlKem768ActiveKeyExchange { + fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> { + // X25519MLKEM768 transposed the order of concatenation in spite of the naming + let (peer_mlkem_share, peer_x25519_share) = + ok_or_invalid_share!(peer_pub_key.split_at_checked(mlkem::CIPHERTEXT_BYTES_768)); + let quantum_secret = ok_or_invalid_share!(self.decap_key.decapsulate(peer_mlkem_share)); + let dh_secret = self.compute_dh_share(peer_x25519_share)?; + let mut shared_secret = + Vec::with_capacity(mlkem::SHARED_SECRET_BYTES + x25519::SHARED_KEY_LEN); + shared_secret.extend(quantum_secret); + shared_secret.extend(dh_secret); + Ok(shared_secret.into()) + } + + fn pub_key(&self) -> &[u8] { + &self.client_share + } + + fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { + Some((NamedGroup::X25519, &self.pub_key)) + } + + fn complete_hybrid_component( + self: Box<Self>, + peer_pub_key: &[u8], + ) -> Result<SharedSecret, Error> { + self.compute_dh_share(peer_pub_key) + .map(|secret| Vec::from(secret).into()) + } + + fn group(&self) -> NamedGroup { + NamedGroup::X25519MLKEM768 + } +}
diff --git a/rust/bssl-tls/src/rustls_provider/pki.rs b/rust/bssl-tls/src/rustls_provider/pki.rs new file mode 100644 index 0000000..bb16e2d --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/pki.rs
@@ -0,0 +1,347 @@ +// 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. + +//! PKI definitions for TLS + +use core::{ + fmt::{Debug, Formatter, Result as FmtResult}, + marker::PhantomData, +}; + +use bssl_crypto::{digest, ec, ecdsa, ed25519, rsa}; +use rustls::pki_types::{ + AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, alg_id, +}; + +use crate::rustls_provider::RsaSignatureDigest; + +/// A PKI object with assigned Object Identifier +pub(crate) trait PkiIdentified { + /// The assigned identifier + const OBJECT_IDENTIFIER: AlgorithmIdentifier; +} + +/// PKI Verification algorithm +pub(crate) trait PkiPublicKeyAlgorithm: PkiIdentified { + /// Public key type that operates based on the identified algorithm. + type PublicKey; + + /// Decode a signature public key from a `subjectPublicKey` field. + fn from_der_subject_public_key(spk: &[u8]) -> Option<Self::PublicKey>; +} + +/// PKI Verification algorithm +pub(crate) trait PkiSignatureAlgorithm: PkiIdentified { + /// Supported public key, which can be deserialised from a DER-serialised + /// `SubjectPublicKeyInfo` field. + type PublicKeyAlgorithm: PkiPublicKeyAlgorithm; + + /// Perform verification of the signature against the deserialised public key + /// and the original message. + fn verify( + public_key: <Self::PublicKeyAlgorithm as PkiPublicKeyAlgorithm>::PublicKey, + message: &[u8], + signature: &[u8], + ) -> bool; +} + +/// PKI verification as prescribed in [RFC 3279](https://datatracker.ietf.org/doc/html/rfc3279) +struct PkiSignatureVerification<S>(PhantomData<fn() -> S>); + +impl<S> PkiSignatureVerification<S> { + pub(crate) const fn new() -> Self { + Self(PhantomData) + } +} + +impl<S> Debug for PkiSignatureVerification<S> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("PkiVerification").finish() + } +} + +impl<S: PkiSignatureAlgorithm> SignatureVerificationAlgorithm for PkiSignatureVerification<S> { + fn verify_signature( + &self, + subject_public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + let public_key = S::PublicKeyAlgorithm::from_der_subject_public_key(subject_public_key) + .ok_or(InvalidSignature)?; + if S::verify(public_key, message, signature) { + Ok(()) + } else { + Err(InvalidSignature) + } + } + + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + S::PublicKeyAlgorithm::OBJECT_IDENTIFIER + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + S::OBJECT_IDENTIFIER + } +} + +/// RSA encryption +/// +/// OID: 1.2.840.113549.1.1.1 +struct PkiRsaPublicKey; + +impl PkiIdentified for PkiRsaPublicKey { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_ENCRYPTION; +} + +impl PkiPublicKeyAlgorithm for PkiRsaPublicKey { + type PublicKey = rsa::PublicKey; + + fn from_der_subject_public_key(spk: &[u8]) -> Option<Self::PublicKey> { + rsa::PublicKey::from_der_rsa_public_key(spk) + } +} + +/// Elliptic curve (EC) public key encoded in X9.62 +/// +/// OID: 1.2.840.10045.2.1 +struct PkiEcPublicKey<C> { + pub(crate) _p: PhantomData<fn() -> C>, +} + +/// EC public key over curve P-256 +/// +/// OID: 1.2.840.10045.3.1.7 +impl PkiIdentified for PkiEcPublicKey<ec::P256> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::ECDSA_P256; +} + +/// EC public key over curve P-384 +/// +/// OID: 1.3.132.0.34 +impl PkiIdentified for PkiEcPublicKey<ec::P384> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::ECDSA_P384; +} + +impl<C: ec::Curve> PkiPublicKeyAlgorithm for PkiEcPublicKey<C> +where + Self: PkiIdentified, +{ + type PublicKey = ecdsa::PublicKey<C>; + + fn from_der_subject_public_key(spk: &[u8]) -> Option<Self::PublicKey> { + ecdsa::PublicKey::from_x962_uncompressed(spk) + .or_else(|| ecdsa::PublicKey::from_x962_compressed(spk)) + } +} + +/// Ed25519/EdDSA public key +/// +/// OID: 1.3.101.112 +pub(crate) struct PkiEd25519PublicKey; + +impl PkiIdentified for PkiEd25519PublicKey { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::ED25519; +} + +impl PkiPublicKeyAlgorithm for PkiEd25519PublicKey { + type PublicKey = ed25519::PublicKey; + + fn from_der_subject_public_key(spk: &[u8]) -> Option<Self::PublicKey> { + Some(ed25519::PublicKey::from_bytes(spk.try_into().ok()?)) + } +} + +/// A family of RSA signature scheme +struct PkiRsaPkcs1SignatureScheme<D> { + pub(crate) _p: PhantomData<fn() -> D>, +} + +pub(crate) enum DigestAlgorithm { + Sha256, + Sha384, + Sha512, +} + +/// RSA with SHA256 signature scheme +/// +/// OID: 1.2.840.113549.1.1.11 +impl PkiIdentified for PkiRsaPkcs1SignatureScheme<digest::Sha256> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_PKCS1_SHA256; +} + +/// RSA with SHA384 +/// +/// OID: 1.2.840.113549.1.1.12 +impl PkiIdentified for PkiRsaPkcs1SignatureScheme<digest::Sha384> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_PKCS1_SHA384; +} + +/// RSA with SHA512 +/// +/// OID: 1.2.840.113549.1.1.13 +impl PkiIdentified for PkiRsaPkcs1SignatureScheme<digest::Sha512> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_PKCS1_SHA512; +} + +impl<D: RsaSignatureDigest> PkiSignatureAlgorithm for PkiRsaPkcs1SignatureScheme<D> +where + Self: PkiIdentified, +{ + type PublicKeyAlgorithm = PkiRsaPublicKey; + + fn verify( + public_key: <Self::PublicKeyAlgorithm as PkiPublicKeyAlgorithm>::PublicKey, + message: &[u8], + signature: &[u8], + ) -> bool { + public_key.verify_pkcs1::<D>(message, signature).is_ok() + } +} + +/// A family of RSA signature scheme +struct PkiRsaPssSignatureScheme<D> { + pub(crate) _p: PhantomData<fn() -> D>, +} + +/// RSA PSS with SHA256 signature scheme +/// +/// OID: 1.2.840.113549.1.1.10 +impl PkiIdentified for PkiRsaPssSignatureScheme<digest::Sha256> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_PSS_SHA256; +} + +/// RSA PSS with SHA384 +/// +/// OID: 1.2.840.113549.1.1.10 +impl PkiIdentified for PkiRsaPssSignatureScheme<digest::Sha384> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_PSS_SHA384; +} + +/// RSA PSS with SHA512 +/// +/// OID: 1.2.840.113549.1.1.10 +impl PkiIdentified for PkiRsaPssSignatureScheme<digest::Sha512> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::RSA_PSS_SHA512; +} + +impl<D: RsaSignatureDigest> PkiSignatureAlgorithm for PkiRsaPssSignatureScheme<D> +where + Self: PkiIdentified, +{ + type PublicKeyAlgorithm = PkiRsaPublicKey; + + fn verify( + public_key: <Self::PublicKeyAlgorithm as PkiPublicKeyAlgorithm>::PublicKey, + message: &[u8], + signature: &[u8], + ) -> bool { + public_key.verify_pss::<D>(message, signature).is_ok() + } +} + +struct PkiEcdsaScheme<C> { + pub(crate) _p: PhantomData<fn() -> C>, +} + +/// ECDSA with SHA-256 +/// +/// OID: 1.2.840.10045.4.3.2 +impl PkiIdentified for PkiEcdsaScheme<ec::P256> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::ECDSA_SHA256; +} + +/// ECDSA with SHA-384 +/// +/// OID: 1.2.840.10045.4.3.3 +impl PkiIdentified for PkiEcdsaScheme<ec::P384> { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::ECDSA_SHA384; +} + +impl<C: ec::Curve> PkiSignatureAlgorithm for PkiEcdsaScheme<C> +where + Self: PkiIdentified, + PkiEcPublicKey<C>: PkiIdentified, +{ + type PublicKeyAlgorithm = PkiEcPublicKey<C>; + + fn verify( + public_key: <Self::PublicKeyAlgorithm as PkiPublicKeyAlgorithm>::PublicKey, + message: &[u8], + signature: &[u8], + ) -> bool { + // Safety: we would only allow SHA-256 hashing onto P-256 and SHA-386 hashing onto P-384 + public_key.verify(message, signature).is_ok() + } +} + +/// Signature algorithm EdDSA +/// +/// OID: 1.3.101.112 +struct PkiEddsa; + +impl PkiIdentified for PkiEddsa { + const OBJECT_IDENTIFIER: AlgorithmIdentifier = alg_id::ED25519; +} + +impl PkiSignatureAlgorithm for PkiEddsa { + type PublicKeyAlgorithm = PkiEd25519PublicKey; + + fn verify( + public_key: <Self::PublicKeyAlgorithm as PkiPublicKeyAlgorithm>::PublicKey, + message: &[u8], + signature: &[u8], + ) -> bool { + let Ok(signature) = signature.try_into() else { + return false; + }; + public_key.verify(message, signature).is_ok() + } +} + +/// PKI Signature scheme `ECDSA_NISTP256_SHA256` +pub const ECDSA_NISTP256_SHA256: &'static dyn SignatureVerificationAlgorithm = + &PkiSignatureVerification::<PkiEcdsaScheme<ec::P256>>::new(); + +/// PKI Signature scheme `ECDSA_NISTP384_SHA384` +pub const ECDSA_NISTP384_SHA384: &'static dyn SignatureVerificationAlgorithm = + &PkiSignatureVerification::<PkiEcdsaScheme<ec::P384>>::new(); + +/// PKI Signature scheme `ED25519` +pub const ED25519: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiEddsa>::new(); + +/// PKI Signature scheme `RSA_PKCS1_SHA256` +pub const RSA_PKCS1_SHA256: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiRsaPkcs1SignatureScheme<digest::Sha256>>::new(); + +/// PKI Signature scheme `RSA_PKCS1_SHA384` +pub const RSA_PKCS1_SHA384: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiRsaPkcs1SignatureScheme<digest::Sha384>>::new(); + +/// PKI Signature scheme `RSA_PKCS1_SHA512` +pub const RSA_PKCS1_SHA512: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiRsaPkcs1SignatureScheme<digest::Sha512>>::new(); + +/// PKI Signature scheme `RSA_PSS_SHA256` +pub const RSA_PSS_SHA256: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiRsaPssSignatureScheme<digest::Sha256>>::new(); + +/// PKI Signature scheme `RSA_PSS_SHA384` +pub const RSA_PSS_SHA384: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiRsaPssSignatureScheme<digest::Sha384>>::new(); + +/// PKI Signature scheme `RSA_PSS_SHA512` +pub const RSA_PSS_SHA512: &'static (dyn SignatureVerificationAlgorithm + 'static) = + &PkiSignatureVerification::<PkiRsaPssSignatureScheme<digest::Sha512>>::new();
diff --git a/rust/bssl-tls/src/rustls_provider/prf.rs b/rust/bssl-tls/src/rustls_provider/prf.rs new file mode 100644 index 0000000..b41beb0 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/prf.rs
@@ -0,0 +1,174 @@ +// 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. + +//! Supported cipher suites + +use alloc::{boxed::Box, vec::Vec}; +use core::marker::PhantomData; + +use bssl_crypto::{ + digest::{self, Algorithm}, + hkdf, hmac, + tls12_prf::Tls12Prf, +}; +use rustls::{ + Error, + crypto::{ + hmac::Tag, + tls12::Prf, + tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}, + }, + version::TLS12, +}; + +pub(crate) struct Tls12PrfImpl<A>(PhantomData<fn() -> A>); + +impl<A> Tls12PrfImpl<A> { + pub(crate) const fn new() -> Self { + Self(PhantomData) + } +} + +impl<A: digest::Algorithm> Prf for Tls12PrfImpl<A> { + fn for_key_exchange( + &self, + output: &mut [u8; 48], + kx: Box<dyn rustls::crypto::ActiveKeyExchange>, + peer_pub_key: &[u8], + label: &[u8], + seed: &[u8], + ) -> Result<(), Error> { + Tls12Prf::<A>::generate_secret( + kx.complete_for_tls_version(peer_pub_key, &TLS12)? + .secret_bytes(), + label, + seed, + None, + output, + ) + .map_err(|_| Error::General("PRF failed".into())) + } + + fn for_secret(&self, output: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]) { + Tls12Prf::<A>::generate_secret(secret, label, seed, None, output) + .expect("for_secret should be infallible"); + } +} + +/// TLS 1.3 HKDF over SHA-256 +pub(crate) const TLS13_HKDF_SHA256: &'static dyn Hkdf = &Tls13HkdfImpl::<digest::Sha256>::new(); +/// TLS 1.3 HKDF over SHA-384 +pub(crate) const TLS13_HKDF_SHA384: &'static dyn Hkdf = &Tls13HkdfImpl::<digest::Sha384>::new(); + +pub(crate) struct Tls13HkdfImpl<A>(PhantomData<fn() -> A>); + +impl<A> Tls13HkdfImpl<A> { + pub(crate) const fn new() -> Self { + Self(PhantomData) + } +} + +struct Tls13HkdfExpander<A> { + prk: hkdf::Prk, + _p: PhantomData<fn() -> A>, +} + +impl<A: Tls13HkdfDigest> HkdfExpander for Tls13HkdfExpander<A> { + fn expand_slice(&self, info: &[&[u8]], output: &mut [u8]) -> Result<(), OutputLengthError> { + let info: Vec<_> = info.iter().copied().flatten().copied().collect(); + self.prk + .expand_into(&info, output) + .map_err(|_| OutputLengthError) + } + + fn expand_block(&self, info: &[&[u8]]) -> OkmBlock { + let mut buf = A::zero_secret().to_vec(); + let info: Vec<_> = info.iter().copied().flatten().copied().collect(); + self.prk + .expand_into(&info, &mut buf) + .expect("digest length should not be too long"); + OkmBlock::new(&buf) + } + + fn hash_len(&self) -> usize { + A::OUTPUT_LEN + } +} + +trait Tls13HkdfDigest: digest::Algorithm { + fn hmac_sign(key: &[u8], data: &[u8]) -> Tag; + fn zero_secret() -> &'static [u8]; +} + +impl Tls13HkdfDigest for digest::Sha256 { + fn hmac_sign(key: &[u8], data: &[u8]) -> Tag { + Tag::new(&hmac::HmacSha256::mac(key, data)) + } + fn zero_secret() -> &'static [u8] { + &[0; Self::OUTPUT_LEN] + } +} + +impl Tls13HkdfDigest for digest::Sha384 { + fn hmac_sign(key: &[u8], data: &[u8]) -> Tag { + Tag::new(&hmac::HmacSha384::mac(key, data)) + } + fn zero_secret() -> &'static [u8] { + &[0; Self::OUTPUT_LEN] + } +} + +impl<A: 'static + Tls13HkdfDigest> Hkdf for Tls13HkdfImpl<A> { + fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn HkdfExpander> { + let prk = hkdf::Hkdf::<A>::extract( + A::zero_secret(), + if let Some(salt) = salt { + hkdf::Salt::NonEmpty(salt) + } else { + hkdf::Salt::None + }, + ); + Box::new(Tls13HkdfExpander::<A> { + prk, + _p: PhantomData, + }) + } + + fn extract_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Box<dyn HkdfExpander> { + let prk = hkdf::Hkdf::<A>::extract( + secret, + if let Some(salt) = salt { + hkdf::Salt::NonEmpty(salt) + } else { + hkdf::Salt::None + }, + ); + Box::new(Tls13HkdfExpander::<A> { + prk, + _p: PhantomData, + }) + } + + fn expander_for_okm(&self, okm: &OkmBlock) -> Box<dyn HkdfExpander> { + let okm = okm.as_ref(); + Box::new(Tls13HkdfExpander::<A> { + prk: hkdf::Prk::new::<A>(okm).expect("OKM size mismatch"), + _p: PhantomData, + }) + } + + fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> Tag { + A::hmac_sign(key.as_ref(), message) + } +}
diff --git a/rust/bssl-tls/src/rustls_provider/sign.rs b/rust/bssl-tls/src/rustls_provider/sign.rs new file mode 100644 index 0000000..9e48356 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/sign.rs
@@ -0,0 +1,269 @@ +// 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. + +use alloc::{boxed::Box, vec::Vec}; +use core::{ + fmt::{Debug, Formatter, Result as FmtResult}, + marker::PhantomData, +}; + +use bssl_crypto::{digest, ec, ecdsa, ed25519, rsa}; +use rustls::{ + Error, SignatureAlgorithm, SignatureScheme, + pki_types::{PrivatePkcs1KeyDer, PrivateSec1KeyDer}, + sign::{Signer, SigningKey}, +}; + +use crate::rustls_provider::{RsaSignatureDigest, pki}; + +/// A generic `id-rsaEncryption` RSA key +/// +/// This key type is only intended for signature +pub(crate) struct RsaPrivateKey(pub rsa::PrivateKey); + +impl TryFrom<PrivatePkcs1KeyDer<'_>> for RsaPrivateKey { + type Error = Error; + fn try_from(der: PrivatePkcs1KeyDer) -> Result<Self, Self::Error> { + let der = der.secret_pkcs1_der(); + let key = rsa::PrivateKey::from_der_rsa_private_key(der) + .ok_or(Error::General("Cannot parse PKCS#1 key from DER".into()))?; + Ok(Self(key)) + } +} + +impl Debug for RsaPrivateKey { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_tuple("RsaPrivateKey").finish() + } +} + +struct RsaPkcs1Signer<D> { + key: rsa::PrivateKey, + _p: PhantomData<fn() -> D>, +} + +impl<D> Debug for RsaPkcs1Signer<D> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("RsaPkcs1Signer").finish() + } +} + +impl<D: RsaSignatureDigest> RsaPkcs1Signer<D> { + fn new(key: rsa::PrivateKey) -> Self { + Self { + key, + _p: PhantomData, + } + } +} + +impl<D: RsaSignatureDigest> Signer for RsaPkcs1Signer<D> { + fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> { + Ok(self.key.sign_pkcs1::<D>(message)) + } + + fn scheme(&self) -> SignatureScheme { + match D::ALGORITHM { + pki::DigestAlgorithm::Sha256 => SignatureScheme::RSA_PKCS1_SHA256, + pki::DigestAlgorithm::Sha384 => SignatureScheme::RSA_PKCS1_SHA384, + pki::DigestAlgorithm::Sha512 => SignatureScheme::RSA_PKCS1_SHA512, + } + } +} + +struct RsaPssSigner<D> { + key: rsa::PrivateKey, + _p: PhantomData<fn() -> D>, +} + +impl<D> Debug for RsaPssSigner<D> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("RsaPssSigner").finish() + } +} + +impl<D: RsaSignatureDigest> RsaPssSigner<D> { + fn new(key: rsa::PrivateKey) -> Self { + Self { + key, + _p: PhantomData, + } + } +} + +impl<D: RsaSignatureDigest> Signer for RsaPssSigner<D> { + fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> { + Ok(self.key.sign_pss::<D>(message)) + } + + fn scheme(&self) -> SignatureScheme { + match D::ALGORITHM { + pki::DigestAlgorithm::Sha256 => SignatureScheme::RSA_PSS_SHA256, + pki::DigestAlgorithm::Sha384 => SignatureScheme::RSA_PSS_SHA384, + pki::DigestAlgorithm::Sha512 => SignatureScheme::RSA_PSS_SHA512, + } + } +} + +impl SigningKey for RsaPrivateKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> { + for offer in offered { + match offer { + SignatureScheme::RSA_PKCS1_SHA1 => continue, + SignatureScheme::RSA_PKCS1_SHA256 => { + return Some(Box::new(RsaPkcs1Signer::<digest::Sha256>::new( + self.0.clone(), + ))); + } + SignatureScheme::RSA_PKCS1_SHA384 => { + return Some(Box::new(RsaPkcs1Signer::<digest::Sha384>::new( + self.0.clone(), + ))); + } + SignatureScheme::RSA_PKCS1_SHA512 => { + return Some(Box::new(RsaPkcs1Signer::<digest::Sha512>::new( + self.0.clone(), + ))); + } + SignatureScheme::RSA_PSS_SHA256 => { + return Some(Box::new(RsaPssSigner::<digest::Sha256>::new( + self.0.clone(), + ))); + } + SignatureScheme::RSA_PSS_SHA384 => { + return Some(Box::new(RsaPssSigner::<digest::Sha384>::new( + self.0.clone(), + ))); + } + SignatureScheme::RSA_PSS_SHA512 => { + return Some(Box::new(RsaPssSigner::<digest::Sha512>::new( + self.0.clone(), + ))); + } + _ => continue, + } + } + None + } + + fn algorithm(&self) -> SignatureAlgorithm { + SignatureAlgorithm::RSA + } +} + +/// An ECDSA signing key +#[derive(Clone)] +pub(crate) struct EcdsaPrivateKey<C: ec::Curve>(pub ecdsa::PrivateKey<C>); + +impl<C: ec::Curve> Debug for EcdsaPrivateKey<C> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_tuple("EcdsaPrivateKey").finish() + } +} + +impl<C: ec::Curve> TryFrom<&'_ PrivateSec1KeyDer<'_>> for EcdsaPrivateKey<C> { + type Error = Error; + + fn try_from(der: &PrivateSec1KeyDer) -> Result<Self, Self::Error> { + let der = der.secret_sec1_der(); + let key = ecdsa::PrivateKey::from_der_ec_private_key(der) + .ok_or(Error::General("Cannot parse Sec1 key from DER".into()))?; + Ok(Self(key)) + } +} + +macro_rules! ecdsa_signer { + ($scheme:path, $curve:path) => { + impl SigningKey for EcdsaPrivateKey<$curve> { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> { + for offer in offered { + match offer { + // We do not support any SHA1 scheme + SignatureScheme::ECDSA_SHA1_Legacy => continue, + $scheme => return Some(Box::new(EcdsaSigner(self.0.clone()))), + _ => continue, + } + } + None + } + + fn algorithm(&self) -> SignatureAlgorithm { + SignatureAlgorithm::ECDSA + } + } + + impl Signer for EcdsaSigner<$curve> { + fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> { + Ok(self.0.sign(message)) + } + + fn scheme(&self) -> SignatureScheme { + $scheme + } + } + }; +} + +ecdsa_signer!(SignatureScheme::ECDSA_NISTP256_SHA256, ec::P256); +ecdsa_signer!(SignatureScheme::ECDSA_NISTP384_SHA384, ec::P384); + +struct EcdsaSigner<C: ec::Curve>(ecdsa::PrivateKey<C>); + +impl<C: ec::Curve> Debug for EcdsaSigner<C> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_tuple("EcdsaSigner").finish() + } +} + +#[derive(Clone)] +pub(crate) struct EddsaPrivateKey(pub ed25519::PrivateKey); + +impl Debug for EddsaPrivateKey { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_tuple("EddsaPrivateKey").finish() + } +} + +impl SigningKey for EddsaPrivateKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> { + for offer in offered { + if matches!(offer, SignatureScheme::ED25519) { + return Some(Box::new(EddsaSigner(self.0.clone()))); + } + } + None + } + + fn algorithm(&self) -> SignatureAlgorithm { + SignatureAlgorithm::ED25519 + } +} + +struct EddsaSigner(ed25519::PrivateKey); + +impl Debug for EddsaSigner { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_tuple("EddsaSigner").finish() + } +} + +impl Signer for EddsaSigner { + fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> { + Ok(self.0.sign(message).to_vec()) + } + + fn scheme(&self) -> SignatureScheme { + SignatureScheme::ED25519 + } +}
diff --git a/rust/bssl-tls/src/rustls_provider/tests.rs b/rust/bssl-tls/src/rustls_provider/tests.rs new file mode 100644 index 0000000..88d523f --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests.rs
@@ -0,0 +1,442 @@ +// 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. + +use std::io::{PipeReader, PipeWriter, Read, Write, pipe}; +use std::sync::Arc; + +use super::*; + +use rustls::crypto::ring; +use rustls::{ + RootCertStore, ServerConfig, ServerConnection, Stream, SupportedProtocolVersion, + client::{ClientConfig, ClientConnection}, + pki_types::{CertificateDer, ServerName, pem::PemObject}, + version::{TLS12, TLS13}, +}; +use tracing::{Level, debug}; + +// =================================================================================== +// All CSRs `server.csr` is generated from +// openssl req -new -nodes -key <input key> -out server.csr -subj '/CN=www.google.com' +// =================================================================================== + +const CA_CERT: &'static [u8] = include_bytes!("./tests/BoringSSLCATest.crt"); + +const RSA_SVC_CERT: &'static [u8] = include_bytes!("./tests/BoringSSLServerTest-RSA.crt"); + +const RSA_SVC_KEY: &'static [u8] = include_bytes!("./tests/BoringSSLServerTest-RSA.key"); + +const ECDSA_P256_SVC_CERT: &'static [u8] = + include_bytes!("./tests/BoringSSLServerTest-ECDSA-P256.crt"); + +const ECDSA_P256_SVC_KEY: &'static [u8] = + include_bytes!("./tests/BoringSSLServerTest-ECDSA-P256.key"); + +const RSA_PSS_SVC_CERT: &'static [u8] = + include_bytes!("./tests/BoringSSLServerTest-RSA-PSS-SHA256.crt"); + +struct PipeSocket { + tx: PipeWriter, + rx: PipeReader, +} +impl Read for PipeSocket { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + self.rx.read(buf) + } +} +impl Write for PipeSocket { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + self.tx.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.tx.flush() + } +} + +const EXPECTED_SERVER_MSG: &[u8; 18] = b"Awesome BoringSSL!"; +const EXPECTED_CLIENT_MSG: &[u8; 19] = b"Oh yeah definitely!"; + +fn init_tracing() { + let _ = tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .try_init(); +} + +fn test_cipher_suite( + ca: &[u8], + svc_cert: &[u8], + svc_key: &[u8], + client_protocols: &[&'static SupportedProtocolVersion], + server_crypto_provider: CryptoProvider, + client_crypto_provider: CryptoProvider, +) { + init_tracing(); + let ca_cert = CertificateDer::from_pem_slice(ca).unwrap(); + let svc_cert = CertificateDer::from_pem_slice(svc_cert).unwrap(); + let svc_key = PrivateKeyDer::from_pem_slice(svc_key).unwrap(); + let mut root_ca = RootCertStore::empty(); + assert_eq!(root_ca.add_parsable_certificates([ca_cert.clone()]), (1, 0)); + let client_conf = ClientConfig::builder_with_provider(Arc::new(client_crypto_provider)) + .with_protocol_versions(client_protocols) + .unwrap() + .with_root_certificates(Arc::new(root_ca)) + .with_no_client_auth(); + let server_conf = ServerConfig::builder_with_provider(Arc::new(server_crypto_provider)) + .with_protocol_versions(&[&TLS12, &TLS13]) + .unwrap() + .with_no_client_auth() + .with_single_cert(vec![svc_cert, ca_cert], svc_key) + .unwrap(); + let (server_rx, server_tx) = pipe().unwrap(); + let (client_rx, client_tx) = pipe().unwrap(); + let mut server_sock = PipeSocket { + tx: client_tx, + rx: server_rx, + }; + let mut client_sock = PipeSocket { + tx: server_tx, + rx: client_rx, + }; + let mut client_conn = ClientConnection::new( + Arc::new(client_conf), + ServerName::try_from("www.google.com").unwrap(), + ) + .unwrap(); + let client_thread = std::thread::spawn(move || { + let mut client_stream = Stream::new(&mut client_conn, &mut client_sock); + let mut buf = [0; EXPECTED_SERVER_MSG.len()]; + client_stream.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, EXPECTED_SERVER_MSG); + client_stream.write_all(EXPECTED_CLIENT_MSG).unwrap(); + }); + + let mut server_conn = ServerConnection::new(Arc::new(server_conf)).unwrap(); + let mut server_stream = Stream::new(&mut server_conn, &mut server_sock); + server_stream.write_all(EXPECTED_SERVER_MSG).unwrap(); + debug!("scheduled server message, polling client message"); + let mut buf = [0; EXPECTED_CLIENT_MSG.len()]; + server_stream.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, EXPECTED_CLIENT_MSG); + debug!("received client message"); + server_stream.conn.send_close_notify(); + if server_stream.conn.write_tls(server_stream.sock).is_err() { + return; + } + let _ = server_stream; + let _ = server_conn; + let _ = server_sock; + client_thread.join().unwrap(); +} + +#[test] +fn all_key_agreement_algorithms() { + for group in [ + super::key_exchange::ECDH_P256, + super::key_exchange::ECDH_P384, + super::key_exchange::X25519, + #[cfg(feature = "mlalgs")] + super::key_exchange::X25519MLKEM768, + ] { + test_cipher_suite( + CA_CERT, + RSA_SVC_CERT, + RSA_SVC_KEY, + &[&TLS13], + CryptoProviderBuilder::new() + .with_key_exchange_group(group) + .with_cipher_suite(SupportedCipherSuite::Tls13( + cipher_suites::TLS13_AES_256_GCM_SHA384, + )) + .build(), + CryptoProviderBuilder::new() + .with_key_exchange_group(group) + .with_full_cipher_suites() + .build(), + ); + } + for group in [ + super::key_exchange::ECDH_P256, + super::key_exchange::ECDH_P384, + super::key_exchange::X25519, + ] { + test_cipher_suite( + CA_CERT, + RSA_SVC_CERT, + RSA_SVC_KEY, + &[&TLS13], + CryptoProviderBuilder::new() + .with_key_exchange_group(group) + .with_cipher_suite(SupportedCipherSuite::Tls13( + cipher_suites::TLS13_AES_256_GCM_SHA384, + )) + .build(), + ring::default_provider(), + ); + } +} + +fn test_half_connection( + providers: &[fn() -> CryptoProvider], + test_provider: fn() -> CryptoProvider, + run_test_suite: impl Fn(CryptoProvider, CryptoProvider), +) { + for provider in providers { + run_test_suite(provider(), test_provider()); + run_test_suite(test_provider(), provider()); + } +} + +#[test] +fn tls12_ecdhe_rsa_aes_128_gcm() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls12( + cipher_suites::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + RSA_SVC_CERT, + RSA_SVC_KEY, + &[&TLS12], + server_provider, + client_provider, + ); + }, + ); +} + +#[test] +fn tls12_ecdhe_rsa_pss_aes_128_gcm() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls12( + cipher_suites::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + RSA_PSS_SVC_CERT, + RSA_SVC_KEY, + &[&TLS12], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls12_ecdhe_rsa_with_aes_256_gcm_sha384() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls12( + cipher_suites::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + RSA_SVC_CERT, + RSA_SVC_KEY, + &[&TLS12], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls12_ecdhe_ecdsa_with_chacha20_poly1305_sha256() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls12( + cipher_suites::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + ECDSA_P256_SVC_CERT, + ECDSA_P256_SVC_KEY, + &[&TLS12], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls12_ecdhe_ecdsa_with_aes_128_gcm_sha256() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls12( + cipher_suites::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + ECDSA_P256_SVC_CERT, + ECDSA_P256_SVC_KEY, + &[&TLS12], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls12_ecdhe_ecdsa_with_aes_256_gcm_sha384() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls12( + cipher_suites::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + ECDSA_P256_SVC_CERT, + ECDSA_P256_SVC_KEY, + &[&TLS12], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls13_aes_128_gcm_sha256() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls13( + cipher_suites::TLS13_AES_128_GCM_SHA256, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + ECDSA_P256_SVC_CERT, + ECDSA_P256_SVC_KEY, + &[&TLS13], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls13_aes_256_gcm_sha384() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls13( + cipher_suites::TLS13_AES_256_GCM_SHA384, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + ECDSA_P256_SVC_CERT, + ECDSA_P256_SVC_KEY, + &[&TLS13], + server_provider, + client_provider, + ) + }, + ); +} + +#[test] +fn tls13_chacha20_poly1305_sha256() { + let providers = [CryptoProviderBuilder::full, ring::default_provider]; + let test_provider = || { + CryptoProviderBuilder::new() + .with_default_key_exchange_groups() + .with_cipher_suite(SupportedCipherSuite::Tls13( + cipher_suites::TLS13_CHACHA20_POLY1305_SHA256, + )) + .build() + }; + test_half_connection( + &providers, + test_provider, + |client_provider, server_provider| { + test_cipher_suite( + CA_CERT, + ECDSA_P256_SVC_CERT, + ECDSA_P256_SVC_KEY, + &[&TLS13], + server_provider, + client_provider, + ) + }, + ); +}
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLCATest.crt b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLCATest.crt new file mode 100644 index 0000000..e5d28ce --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLCATest.crt
@@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGUTCCBDmgAwIBAgIUHlETnZyBkDglMnJR8zMbcNCbalwwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMB4XDTI2MDEyMjA5 +NTM0MVoXDTI2MDIyMTA5NTM0MVowga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBG +cmVpc3RhYXQgQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29v +Z2xlIEdlcm1hbnkgR21iSDETMBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwR +Qm9yaW5nU1NMIEF1dGhvcnMxIzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29n +bGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2WxlWVEy+qVu +q5Oj90XJdZdUC25f+28v1+1bvFcbqI6qvgNcOHhMkixfHLI5pKRTnbnX3XoAr01N +k2pbBzwQv7wCPoW5qZY/UrrFBhWsrGZUJlV33IYRj6ace0nxFvc6YibU/qlpbCJ5 +wpJiLWweY14HQYo5J0E0wJlXIswwjJXnXD3yJulNKZ7JaTc9R/VeaTxDTrbTUuGX +dw4aaS7qiv5QaBnyOOsFWFaKOSKDPK7jh0it/DBWQ11sdeTi/c0f06eBUh1/3TlX +Ax/V7b7kI5QjbXC214TsgH3s9MpE4qdh6xZf3aJObZmPMJy3dXTag0FCrlZEIXtx +z32otTKPkbMwofTqEM8+DTc8/qP68iZopRZAvrnnruDh62xlwgi91zJ+p1wJ89VA +3sidxP0F4GoSk2A1tqR6a6c1oIaygPZRbEmhtCYfonsg/xT+NvEaPis8zz6fByTT ++q22iKY/mKEZAevke+nss1hWObvzcFjWh06DGt9Ir5b7cD5zk9+Eq21Uuwtt3WXS +ml+i7bO0e1TuSf39RFQ6LdawyWKJ4u5x4WBzOt0cBfv1l52PgpusQjJ3Qtlgd1ks +Ck7Nts3F4V3SADHI0qdXRmkab7ar85LiQjgvx1+zA9AEcAV/X21MGGmQlaicAkSs +loj1/E2BSt7jXh5XwS26jvMrOa6rjQUCAwEAAaNjMGEwHQYDVR0OBBYEFOobWR5d +4zMpQX/XWIhj1CooBngQMB8GA1UdIwQYMBaAFOobWR5d4zMpQX/XWIhj1CooBngQ +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4ICAQDYcmO6yY0mGBLQq1Hjasopg6/QsUKuNtYjktPH4/Qa2RYU2j+Xmv82X4Pt ++Njyj3oqc3qNeluRzrgX6EIp9BMe5S4/vw773iBpw0aeZ2Ym3FKOyFnY9R2t1ASs +4CZxLqaGspc3RMyw++ZyIIMSepgH86YnuqIY/0CtyjO+pSPWLw7RddOhb4u7qX7g +/RjhRDWEVlVyhnI1vOVQd2no8rbMPra/5FusF8SE/+jVvvR6AN5Zjipl2nFYTtAm +gPBLhTDNw4wCL7Tcgsf38AJLZ13YoddNZJNrpOxc2VK9KLhqAqa8KXT1vU2iNqZX +lUIv7h9dY2rSCqL3BEpsygoXOp6a/nIFWFmEd2Ggc40rWsfVmI+n3r1fJc/0AXx0 +DgRJHgorby9/5+PHzndDcxA98KT8CjshHeAOPk064CMKWepbFQfRLOQiTdoBXdKU +iQt6PJ81vyw2tU2OfIhgDXRdtPDMJcfHk6T6JvpuE/YDWG+Xfs3jxM2qrmReKstw +S98TPisHO9joqJHr+qgJYFyz2tMQbiOO15U0ARY8CPzxo0EbOkWiMpVYBfWzEgBg +bkmlo8okXeuo6HNVZBwT9briumwMFWZVQGxV5k5gJ5FFI3zmju31zuQJnjJF2GMh +6DqfkaVSwife/zUxPznM2MYOjL5YK/R3BqzX+3ZYFTAo356U4g== +-----END CERTIFICATE-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLCATest.key b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLCATest.key new file mode 100644 index 0000000..7d037bb --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLCATest.key
@@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDZbGVZUTL6pW6r +k6P3Rcl1l1QLbl/7by/X7Vu8Vxuojqq+A1w4eEySLF8csjmkpFOdudfdegCvTU2T +alsHPBC/vAI+hbmplj9SusUGFaysZlQmVXfchhGPppx7SfEW9zpiJtT+qWlsInnC +kmItbB5jXgdBijknQTTAmVcizDCMledcPfIm6U0pnslpNz1H9V5pPENOttNS4Zd3 +DhppLuqK/lBoGfI46wVYVoo5IoM8ruOHSK38MFZDXWx15OL9zR/Tp4FSHX/dOVcD +H9XtvuQjlCNtcLbXhOyAfez0ykTip2HrFl/dok5tmY8wnLd1dNqDQUKuVkQhe3HP +fai1Mo+RszCh9OoQzz4NNzz+o/ryJmilFkC+ueeu4OHrbGXCCL3XMn6nXAnz1UDe +yJ3E/QXgahKTYDW2pHprpzWghrKA9lFsSaG0Jh+ieyD/FP428Ro+KzzPPp8HJNP6 +rbaIpj+YoRkB6+R76eyzWFY5u/NwWNaHToMa30ivlvtwPnOT34SrbVS7C23dZdKa +X6Lts7R7VO5J/f1EVDot1rDJYoni7nHhYHM63RwF+/WXnY+Cm6xCMndC2WB3WSwK +Ts22zcXhXdIAMcjSp1dGaRpvtqvzkuJCOC/HX7MD0ARwBX9fbUwYaZCVqJwCRKyW +iPX8TYFK3uNeHlfBLbqO8ys5rquNBQIDAQABAoICADEzgNnN8LHcpucn0WZ/AeBc +3tV5ZDoDRrnfyi8cLTOfGU9HdmKHApjfdqSJRlcWIp/iMtG5Lpd88E2oNzIzavzg +gEeCvml8iRbhEf3XAMzAmVFVbPrX0fiGdQnHSUnvp2QXsoJwdt1UDea0dogd2+CT +oiO4MkfKTzQ4XwoOV/wwXfs3P2mDyQTenGh1aiYzBerdisOwxrCOQVbdN6fOyJ+s +fiiYmoI72OlNKBlW0Ij2cKGoFksn6xVyej1RjvZtKUMduDuLVmiK9cBMv33+ASV1 ++/BjndS2jUhkdq9MaHs78oIe/ZGrjYDqy4buJ+vqBhrGtV67Qc6r3yzbnEZoyyj8 +AIavLxbT4b1vo3vZ7ba9rwXxe6+ys9UjWk8XkC3VEuAlNRrGEqS1hUtWN5rJX0YJ +k9G0DYnnlxdFD4dsRn3mVyzClmO5RCrpyA9rlE3eB41CJBM3N6xybjN/rjKkJBtC +cwcJZb5wq/YzYpFmfbIhrxsueUPgOscT60LgdnTqxcgjLVF0hcwan0WcsDrBhAd8 +JltmftL+DFIsyHbSoTLu++SzQRHNeNrI66lupvJyp4sINq+8ZN6O1ZhX8D4+UmUc +qP01wBjldgL1AWSeKJzt2ldmf2merVM6jko6FcGvCfBJ8UCecQRPqJuthpOkQAil +JlvD8UABnvzlgaanlu3xAoIBAQD6v3K++0LW0UwB1jQOxHpp1NZdfgozBp6qoQMy +b0sRtD0vL6w6PzYFweCVoXgIz2lO31nwhhFiN71iR5Ul0gZaAStjK/7G82Si7G3t +DrZId7mT7AKtWkzchZLxRlfIPTsJF0ccOldgdGvmcOtCP1S87vn3AeyJZ0o+yIEd +5XI8Mpr+MsWuh3xkbVprywOby8VEUwfo+CvEhI/t00RgWVLQj+JT8TnJOIVhfJGz +9j78D+JrinShfCqp3o/4m1wcO/Pptn9+QoZQVOZtTLdZfr+MovSvHq3gw9WMY0Xt +lNdE9k+psl5Mf8NQoyx2B6ISi1NNLldmluNwH/H9p0Pq0Kz9AoIBAQDd+kGtVgnB +w7AatSjk0GekT7vUIqsqD/UiOxl58EuiOiERNWHWEPLaXL1SHjp7+9rJYcqpT2mD +hvHUO0YQIvoKH2aufqUKG+IMLkwqx6Vroh/kOC+0gpus9PKS/UzIPx61Vo3Yf25v +lmuVwJg1NRXYfTJCSnbECJMehY1ewr0Tai4tQQkw042GWygC3SVL//9LbbQdmFjo +GfAo8nKUfbaQZmqap0neojAtIRgrg8EbF72OOaE0/XEsK9uLp55gJ+8TQAoXggGR +2PK+FAifGFl7oYa+WSyERxrdMrpIYykpQ96WbjiboZI0GuUJGJs02N2YghjUKnlq +8AqE8TuKmuKpAoIBACNfMHOqhDJDkiJMMknHA7G8OYU0y4GJNIbDce0CcCeOMnde +lUAePKOxRto0zfcIM0XSEiDw+LDPRiMAEBUmvIij05gI08cC/LZS/erMAYDVitNI +HtSPgXo2SZVJpAZ2RMayhvB/dmX/5ly6nyVYQ77nQ1HJ7rEvZfTXWgd6n5PIW77y +MJq/OBf+qRu9psOqiihqQhpmL95oCNm2zNV+pEURlw7aX5l4JLCs3uzxFs99+iXL +gUpqdqZB5DNgzyyYdH8KpI+OGN5qK5tNkCvKyoCvWC7/9+1WEuDb/DhYn8l1qaU5 +qT3HZCkS66m2x/EvwE+J8wBg1rKxfvSWTOlqCI0CggEBAMDV19piJQW8Hy+Ec2sb +lP3L+osWNwXKaRT8rGwfEUV0JCfT7RNPE/oYmKtO8VWl/HH3z1v4TdxiDZFmkL4R +9I94qfYqtOssP9p/GdIMMCtp4zSaju7Mi7rb7CM/g0VueBnmgEE0qtaroPiuIEwQ +utKgKFooYDZ6kHvyX1aT7DeChWzw07AkCA1RAVhDj1QPp1N6kP8oywuPBPA9dsaC +02dsYW3KqESNNzbtShb7VXVY0WZNsDrddUR/MTGIQvCboHhjqKC1YvG1u2Le+oJj +X9EkCG8x/pdHQhIpMGUUJ7zeZe7e/7RLzzwOpSuawbJON2t2kWU3JNV+hFTrT+Ng +HEkCggEBAIGAZ2kYYLi4G6DPKt5gumJ5jVknO5m5OzRD0LQ4pqd9p+CFHCaZsebS +Kke22A64VAB3zWU8t8GefkiJ1yZ8WljvvuZ8ItJ4SxH6SeQlWceWl6AipMe7oOJL +BTwFKQ/uIieqsxDdBX5bbbegU6eM8neizgGNZpujWQkldjPBUWETUhAhegMLVwkv +yoMUKnrb3IYrrOZ8qCFeleBSPtEGYJx+ZG23BqwhZXosl8sksticvi8+iz2w9xfz +OKErDy6xjaU0oDENQYZN6Efg6cC4Op4fTSRh6Tx51HCSfB4BuF4n3aGB3+v31lfN +RxOVo4yLyYfsyBQUIsTZE1pciQfsXNM= +-----END PRIVATE KEY-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-ECDSA-P256.crt b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-ECDSA-P256.crt new file mode 100644 index 0000000..b0282a5 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-ECDSA-P256.crt
@@ -0,0 +1,29 @@ +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-ECDSA-P256.crt -extfile svcconfig -days 730500 + +-----BEGIN CERTIFICATE----- +MIIEDzCCAfegAwIBAgIUUC0yUXu5UfDfbk7DBR8sS6/NUnQwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMCAXDTI2MDEyMzEz +NDA1MloYDzQwMjYwMjA3MTM0MDUyWjAZMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNv +bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOvR/5V1SgsI/QDvkZjUbx73PjgD +q3I7ovMEcZ/rpY0YFk9yyzkcKZgOcXIjPjxpreiEj3DjgOe2TfIjsOxdn3+jgYAw +fjAfBgNVHSMEGDAWgBTqG1keXeMzKUF/11iIY9QqKAZ4EDAJBgNVHRMEAjAAMAsG +A1UdDwQEAwIE8DAkBgNVHREEHTAbgg53d3cuZ29vZ2xlLmNvbYIJbG9jYWxob3N0 +MB0GA1UdDgQWBBTx2F/oNm4Eo+BUXY8sROyeuWchjDANBgkqhkiG9w0BAQsFAAOC +AgEAq2anPF2MuHAJOP96bGGAhDFMBoq2URD5GY6GlGziKJtbW7GbrmNrA6tjXzLs +/2uCaOnxu0MC3sWvAmeEonhoaANGBU6mNQkjmzSyxiwXWtdxaY/fQYQXOkGmNPou +myonrElqi67ddy7sh2rRsWkfs4WWkWhELej8XGgpTsby+PtvyQTRtjvaTnEOppgk +ySLN20b96X0smE6ENUfKqu1k5f6WQmIfC3TPh8dWlAbgpak7dG5AKYGxGQAyK+7R +WoBf1ex7IkynuPLIDq9XxMHBFiq8tcx0fn9GGXJyDcxUgAYu75iSOU71f9gZgY1l +kYhLYvjuSREoF6yhbVy9DPfzWy33xqIonqAOV2wSDVFpANES5Y9ojKF2eNAN5MVO +abC+MCHovcShD3/l7kfoC2KHzJjXtaEgqQUShEbRuJ/ooPvBTX3fpj6QRCOL12Ly +kIlMZLlW0a8GVQV3P5is5jF44TgCw+mTIs8A7B7wb7K27nY0XzPbg7ztKM/rmPol +Muh1g7OekKBje6npVY1mZ6lwVKWSpOwKp3lMe2KEwAEny+Zdl9oAvFLF728qVk0u +ND/h3LAERnb2zKEeobdoMYJurBLMWNrvnyvvCiK+MuBAnMowZS/wnC0T48eUJF+e +60ddB3A1SF1HP1bY/VAfVVMEwWXJKUaD0HeGcIq8BIoZ160= +-----END CERTIFICATE-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-ECDSA-P256.key b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-ECDSA-P256.key new file mode 100644 index 0000000..7a2dca2 --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-ECDSA-P256.key
@@ -0,0 +1,9 @@ +This is generated from + openssl ecparam -genkey -name prime256v1 \ + -out BoringSSLServerTest-ECDSA-P256.key -noout + +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAaBlGFkA3e1W1vgXB0XDNQh9b+NMiJWlOvd7G90RqiuoAoGCCqGSM49 +AwEHoUQDQgAE69H/lXVKCwj9AO+RmNRvHvc+OAOrcjui8wRxn+uljRgWT3LLORwp +mA5xciM+PGmt6ISPcOOA57ZN8iOw7F2ffw== +-----END EC PRIVATE KEY-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA-PSS-SHA256.crt b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA-PSS-SHA256.crt new file mode 100644 index 0000000..ad53a9c --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA-PSS-SHA256.crt
@@ -0,0 +1,43 @@ +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-RSA-PSS-SHA256.crt -extfile svcconfig \ + -days 730500 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 \ + -sigopt rsa_mgf1_md:sha256 + +-----BEGIN CERTIFICATE----- +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 +J1hof3HUwrQDu+GLkio9dR3rVD4T5w== +-----END CERTIFICATE-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA.crt b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA.crt new file mode 100644 index 0000000..ca24a8f --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA.crt
@@ -0,0 +1,39 @@ +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-RSA.crt -extfile svcconfig -days 730500 + +-----BEGIN CERTIFICATE----- +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 +1CFjnFjWbh1CedM8QPQ= +-----END CERTIFICATE-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA.key b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA.key new file mode 100644 index 0000000..9053d6b --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/BoringSSLServerTest-RSA.key
@@ -0,0 +1,55 @@ +This is generated from + openssl genrsa -out BoringSSLServerTest.key 4096 + +-----BEGIN PRIVATE KEY----- +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 +GDIXUzmOVt1IA81bYZAo78EH3G9yWZg= +-----END PRIVATE KEY-----
diff --git a/rust/bssl-tls/src/rustls_provider/tests/caconfig b/rust/bssl-tls/src/rustls_provider/tests/caconfig new file mode 100644 index 0000000..349b29d --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/caconfig
@@ -0,0 +1,29 @@ +[ca] +default_ca = CA_LOC + +[CA_LOC] +prompt = no +default_days = 730500 + +[req] +default_bits = 4096 +string_mask = utf8only +default_md = sha256 +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +prompt = no + +[req_distinguished_name] +C = DE +ST = Freistaat Bayern +L = Muenchen +O = Google Germany GmbH +OU = ISE Crypto +CN = BoringSSL Authors +emailAddress = boringssl@google.com + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical,CA:true +keyUsage = critical, digitalSignature, cRLSign, keyCertSign \ No newline at end of file
diff --git a/rust/bssl-tls/src/rustls_provider/tests/svcconfig b/rust/bssl-tls/src/rustls_provider/tests/svcconfig new file mode 100644 index 0000000..4d139ec --- /dev/null +++ b/rust/bssl-tls/src/rustls_provider/tests/svcconfig
@@ -0,0 +1,8 @@ +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = www.google.com +DNS.2 = localhost