rust: bssl-tls: Introduce early callback support

Bug: 479599893

Signed-off-by: Xiangfei Ding <xfding@google.com>
Change-Id: Ic5d56b30c1c988e2c4bf4ebec56ec1d26a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/92807
Reviewed-by: Adam Langley <agl@google.com>
Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/rust/bssl-tls/src/config.rs b/rust/bssl-tls/src/config.rs
index 4ba72c6..706ed63 100644
--- a/rust/bssl-tls/src/config.rs
+++ b/rust/bssl-tls/src/config.rs
@@ -147,6 +147,101 @@
     pub strength: u16,
 }
 
+/// Supported cipher suites as registered with [IANA].
+///
+/// The following cipher suite values are assigned by IANA and correspond to
+/// both TLS 1.3 and TLS 1.2 suites.
+/// TLS 1.3 suites are mentioned again in [RFC 8446].
+/// TLS 1.2 suites are defined in the relevant RFCs for each algorithm family.
+///
+/// [IANA]: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
+/// [RFC 8446]: https://www.rfc-editor.org/rfc/rfc8446
+/// [RFC 5489]: https://www.rfc-editor.org/rfc/rfc5489
+/// [RFC 8422]: https://www.rfc-editor.org/rfc/rfc8422
+/// [RFC 7905]: https://www.rfc-editor.org/rfc/rfc7905
+/// [RFC 5288]: https://www.rfc-editor.org/rfc/rfc5288
+/// [RFC 5246]: https://www.rfc-editor.org/rfc/rfc5246
+/// [RFC 4279]: https://www.rfc-editor.org/rfc/rfc4279
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub struct CipherSuite(pub u16);
+
+#[allow(non_upper_case_globals)]
+impl CipherSuite {
+    /// TLS 1.3 cipher suite `TLS_AES_128_GCM_SHA256`.
+    pub const Aes128GcmSha256: Self = Self(bssl_sys::SSL_CIPHER_AES_128_GCM_SHA256 as u16);
+    /// TLS 1.3 cipher suite `TLS_AES_256_GCM_SHA384`.
+    pub const Aes256GcmSha384: Self = Self(bssl_sys::SSL_CIPHER_AES_256_GCM_SHA384 as u16);
+    /// TLS 1.3 cipher suite `TLS_CHACHA20_POLY1305_SHA256`.
+    pub const Chacha20Poly1305Sha256: Self =
+        Self(bssl_sys::SSL_CIPHER_CHACHA20_POLY1305_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256` from RFC 5288.
+    pub const EcdheEcdsaWithAes128GcmSha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384` from RFC 5288.
+    pub const EcdheEcdsaWithAes256GcmSha384: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` from RFC 5288.
+    pub const EcdheRsaWithAes128GcmSha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` from RFC 5288.
+    pub const EcdheRsaWithAes256GcmSha384: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256` from RFC 7905.
+    pub const EcdheRsaWithChacha20Poly1305Sha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256` from RFC 7905.
+    pub const EcdheEcdsaWithChacha20Poly1305Sha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 as u16);
+    /// TLS cipher suite `TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256` from RFC 7905.
+    pub const EcdhePskWithChacha20Poly1305Sha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA` from RFC 8422.
+    pub const EcdheEcdsaWithAes128CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA` from RFC 8422.
+    pub const EcdheEcdsaWithAes256CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` from RFC 8422.
+    pub const EcdheRsaWithAes128CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA` from RFC 8422.
+    pub const EcdheRsaWithAes256CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA as u16);
+    /// TLS cipher suite `TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA` from RFC 5489.
+    pub const EcdhePskWithAes128CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_PSK_WITH_AES_128_CBC_SHA as u16);
+    /// TLS cipher suite `TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA` from RFC 5489.
+    pub const EcdhePskWithAes256CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_PSK_WITH_AES_256_CBC_SHA as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256` from RFC 5289.
+    pub const EcdheEcdsaWithAes128CbcSha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256` from RFC 5289.
+    pub const EcdheRsaWithAes128CbcSha256: Self =
+        Self(bssl_sys::SSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_RSA_WITH_AES_128_GCM_SHA256` from RFC 5288.
+    pub const RsaWithAes128GcmSha256: Self =
+        Self(bssl_sys::SSL_CIPHER_RSA_WITH_AES_128_GCM_SHA256 as u16);
+    /// TLS 1.2 cipher suite `TLS_RSA_WITH_AES_256_GCM_SHA384` from RFC 5288.
+    pub const RsaWithAes256GcmSha384: Self =
+        Self(bssl_sys::SSL_CIPHER_RSA_WITH_AES_256_GCM_SHA384 as u16);
+    /// TLS 1.2 cipher suite `TLS_RSA_WITH_AES_128_CBC_SHA` from RFC 5246.
+    pub const RsaWithAes128CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_RSA_WITH_AES_128_CBC_SHA as u16);
+    /// TLS 1.2 cipher suite `TLS_RSA_WITH_AES_256_CBC_SHA` from RFC 5246.
+    pub const RsaWithAes256CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_RSA_WITH_AES_256_CBC_SHA as u16);
+    /// TLS cipher suite `TLS_PSK_WITH_AES_128_CBC_SHA` from RFC 4279.
+    pub const PskWithAes128CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_PSK_WITH_AES_128_CBC_SHA as u16);
+    /// TLS cipher suite `TLS_PSK_WITH_AES_256_CBC_SHA` from RFC 4279.
+    pub const PskWithAes256CbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_PSK_WITH_AES_256_CBC_SHA as u16);
+    /// TLS 1.2 cipher suite `TLS_RSA_WITH_3DES_EDE_CBC_SHA` from RFC 5246.
+    pub const RsaWith3desEdeCbcSha: Self =
+        Self(bssl_sys::SSL_CIPHER_RSA_WITH_3DES_EDE_CBC_SHA as u16);
+}
+
 bssl_enum! {
     /// Compliance Policy.
     #[derive(Clone, Copy, PartialEq, Eq)]
diff --git a/rust/bssl-tls/src/connection/credentials.rs b/rust/bssl-tls/src/connection/credentials.rs
index b2cca82..c492f3e 100644
--- a/rust/bssl-tls/src/connection/credentials.rs
+++ b/rust/bssl-tls/src/connection/credentials.rs
@@ -185,7 +185,7 @@
 }
 
 /// # Authenticating with the peer
-impl<M> TlsConnectionInHandshake<'_, Client, M>
+impl<R, M> TlsConnectionInHandshake<'_, R, M>
 where
     M: HasTlsConnectionMethod,
 {
diff --git a/rust/bssl-tls/src/context/credentials.rs b/rust/bssl-tls/src/context/credentials.rs
index 69d35c1..caa24f6 100644
--- a/rust/bssl-tls/src/context/credentials.rs
+++ b/rust/bssl-tls/src/context/credentials.rs
@@ -33,7 +33,11 @@
         SignatureAlgorithm,
         TlsCredential,
         VerifyCertificate,
-        cert_cb, //
+        cert_cb,
+        early_callback::{
+            EarlyCallback,
+            early_select_cert_cb, //
+        }, //
     },
     errors::Error,
     ffi::slice_into_ffi_raw_parts,
@@ -133,6 +137,38 @@
         self
     }
 
+    /// Set custom certificate selection callback.
+    ///
+    /// See [`EarlyCallback`] for its semantics.
+    pub fn with_early_callback<S>(&mut self, handler: S) -> &mut Self
+    where
+        S: EarlyCallback<M> + 'static,
+    {
+        let ctx = self.ptr();
+        let methods = self.get_context_methods();
+        unsafe {
+            // Safety: we only install our own vtable.
+            bssl_sys::SSL_CTX_set_select_certificate_cb(
+                ctx,
+                Some(early_select_cert_cb::<M, super::methods::RustContextMethods<M>>),
+            );
+        }
+        methods.early_callback_handler = Some(Box::new(handler) as _);
+        self
+    }
+
+    /// Remove custom certificate selection callback.
+    pub fn without_early_callback(&mut self) -> &mut Self {
+        let ctx = self.ptr();
+        let methods = self.get_context_methods();
+        unsafe {
+            // Safety: we only uninstall the vtable.
+            bssl_sys::SSL_CTX_set_select_certificate_cb(ctx, None);
+        }
+        methods.early_callback_handler = None;
+        self
+    }
+
     /// Append `credential` to the list of credentials of this context.
     pub fn with_credential(&mut self, credential: TlsCredential) -> Result<&mut Self, Error> {
         check_lib_error!(unsafe {
diff --git a/rust/bssl-tls/src/context/methods.rs b/rust/bssl-tls/src/context/methods.rs
index e701810..fd51c52 100644
--- a/rust/bssl-tls/src/context/methods.rs
+++ b/rust/bssl-tls/src/context/methods.rs
@@ -21,6 +21,7 @@
 use once_cell::sync::Lazy;
 
 use crate::{
+    EarlyCallbackMethods,
     Methods,
     VerifyCertificateMethods,
     context::{
@@ -29,11 +30,13 @@
         TlsMode, //
     },
     credentials::VerifyCertificate,
+    credentials::early_callback::EarlyCallback,
     methods::drop_box_rust_methods, //
 };
 
 pub(crate) struct RustContextMethods<M> {
     pub(crate) verify_certificate_methods: Option<Box<dyn VerifyCertificate>>,
+    pub(crate) early_callback_handler: Option<Box<dyn EarlyCallback<M>>>,
     _p: PhantomData<fn() -> M>,
 }
 
@@ -43,6 +46,7 @@
     pub fn new() -> Self {
         Self {
             verify_certificate_methods: None,
+            early_callback_handler: None,
             _p: PhantomData,
         }
     }
@@ -70,6 +74,12 @@
     }
 }
 
+impl<M: HasTlsContextMethod> EarlyCallbackMethods<M> for RustContextMethods<M> {
+    fn early_callback_handler(&self) -> Option<&dyn EarlyCallback<M>> {
+        self.early_callback_handler.as_deref()
+    }
+}
+
 fn register_tls_context_vtable<M: HasTlsContextMethod>() -> c_int {
     unsafe {
         // Safety: this a one-time registration uses only valid function pointers.
diff --git a/rust/bssl-tls/src/credentials.rs b/rust/bssl-tls/src/credentials.rs
index 5298574..2a05af8 100644
--- a/rust/bssl-tls/src/credentials.rs
+++ b/rust/bssl-tls/src/credentials.rs
@@ -71,6 +71,7 @@
     has_duplicates, //
 };
 
+pub mod early_callback;
 pub(crate) mod methods;
 
 /// TLS credentials builder
diff --git a/rust/bssl-tls/src/credentials/early_callback.rs b/rust/bssl-tls/src/credentials/early_callback.rs
new file mode 100644
index 0000000..94352fd
--- /dev/null
+++ b/rust/bssl-tls/src/credentials/early_callback.rs
@@ -0,0 +1,315 @@
+// 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.
+
+//! Select Certificate Callback types
+
+use core::{
+    marker::PhantomData,
+    mem::transmute,
+    ptr::{
+        NonNull, //
+        null,
+    }, //
+};
+
+use crate::{
+    EarlyCallbackMethods,
+    abort_on_panic,
+    config::CipherSuite,
+    connection::{
+        Server,
+        lifecycle::TlsConnectionInHandshake, //
+    },
+    ffi::sanitize_slice, //
+};
+
+bssl_macros::bssl_enum! {
+    /// Results from selecting a certificate in the callback.
+    pub enum EarlyCallbackResult: i32 {
+        /// The certificate selection was successful.
+        Success = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_success as i32,
+        /// The operation could not be immediately completed and must be reattempted at a later point.
+        Retry = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_retry as i32,
+        /// A fatal error occurred and the handshake should be terminated.
+        ErrorResult = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error as i32,
+        /// Disable ECH.
+        /// The callback should return this result when an encrypted `ClientHelloInner` was
+        /// decrypted but should be discarded.
+        /// In this case, the callback will be called again with `ClientHelloOuter` as input
+        /// instead and the handshake will proceed with `retry_config`s to signal to the client that
+        /// ECH is disabled.
+        /// This value may only be returned when `SSL_ech_accepted` returns one.
+        /// It may be useful if the `ClientHelloInner` indicated lack of ECH support in the service,
+        /// for example if it is a TLS-1.2 only service.
+        DisableEch = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_disable_ech as i32,
+    }
+}
+
+/// TLS Extension Types.
+///
+/// See [IANA assignment](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1)
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+#[repr(transparent)]
+pub struct ExtensionType(pub u16);
+
+impl ExtensionType {
+    /// Server Name Indication
+    ///
+    /// [RFC 6066, §3](https://datatracker.ietf.org/doc/html/rfc6066#section-3)
+    pub const SERVER_NAME: Self = Self(bssl_sys::TLSEXT_TYPE_server_name as u16);
+
+    /// Status Request
+    ///
+    /// [RFC 6066, §8](https://datatracker.ietf.org/doc/html/rfc6066#section-8)
+    pub const STATUS_REQUEST: Self = Self(bssl_sys::TLSEXT_TYPE_status_request as u16);
+
+    /// Supported Groups
+    ///
+    /// [RFC 8446, §4.2.7](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7)
+    pub const SUPPORTED_GROUPS: Self = Self(bssl_sys::TLSEXT_TYPE_supported_groups as u16);
+
+    /// Signature Algorithms
+    ///
+    /// [RFC 8446, §4.2.3](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3)
+    pub const SIGNATURE_ALGORITHMS: Self = Self(bssl_sys::TLSEXT_TYPE_signature_algorithms as u16);
+
+    /// Application Layer Protocol Negotiation
+    ///
+    /// [RFC 7301, §3](https://datatracker.ietf.org/doc/html/rfc7301#section-3)
+    pub const ALPN: Self =
+        Self(bssl_sys::TLSEXT_TYPE_application_layer_protocol_negotiation as u16);
+
+    /// Key Share
+    ///
+    /// [RFC 8446, §4.2.8](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8)
+    pub const KEY_SHARE: Self = Self(bssl_sys::TLSEXT_TYPE_key_share as u16);
+
+    /// Supported Versions
+    ///
+    /// [RFC 8446, §4.2.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.1)
+    pub const SUPPORTED_VERSIONS: Self = Self(bssl_sys::TLSEXT_TYPE_supported_versions as u16);
+
+    /// Pre-Shared Key
+    ///
+    /// [RFC 8446, §4.2.11](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.11)
+    pub const PRE_SHARED_KEY: Self = Self(bssl_sys::TLSEXT_TYPE_pre_shared_key as u16);
+
+    /// Cookie
+    ///
+    /// [RFC 8446, §4.2.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.2)
+    pub const COOKIE: Self = Self(bssl_sys::TLSEXT_TYPE_cookie as u16);
+
+    /// PSK Key Exchange Modes
+    ///
+    /// [RFC 8446, §4.2.9](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.9)
+    pub const PSK_KEY_EXCHANGE_MODES: Self =
+        Self(bssl_sys::TLSEXT_TYPE_psk_key_exchange_modes as u16);
+
+    /// Certificate Compression
+    ///
+    /// [RFC 8879](https://datatracker.ietf.org/doc/html/rfc8879)
+    pub const CERT_COMPRESSION: Self = Self(bssl_sys::TLSEXT_TYPE_cert_compression as u16);
+
+    /// Session Ticket
+    ///
+    /// [RFC 5077](https://datatracker.ietf.org/doc/html/rfc5077)
+    pub const SESSION_TICKET: Self = Self(bssl_sys::TLSEXT_TYPE_session_ticket as u16);
+
+    /// Extended Master Secret
+    ///
+    /// [RFC 7627](https://datatracker.ietf.org/doc/html/rfc7627)
+    pub const EXTENDED_MASTER_SECRET: Self =
+        Self(bssl_sys::TLSEXT_TYPE_extended_master_secret as u16);
+
+    /// Client Certificate Type
+    ///
+    /// [RFC 7250](https://datatracker.ietf.org/doc/html/rfc7250)
+    pub const CLIENT_CERT_TYPE: Self = Self(bssl_sys::TLSEXT_TYPE_client_cert_type as u16);
+
+    /// Server Certificate Type
+    ///
+    /// [RFC 7250](https://datatracker.ietf.org/doc/html/rfc7250)
+    pub const SERVER_CERT_TYPE: Self = Self(bssl_sys::TLSEXT_TYPE_server_cert_type as u16);
+}
+
+/// A wrapper around the `ClientHello` data passed to the select certificate callback.
+///
+/// The structure is prescribed in [RFC 8446] §4.1.2.
+///
+/// [RFC 8446]: <https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2>
+pub struct ClientHello<'a, M> {
+    ptr: *const bssl_sys::SSL_CLIENT_HELLO,
+    conn: NonNull<bssl_sys::SSL>,
+    _p: PhantomData<&'a M>,
+}
+
+macro_rules! client_hello_getter {
+    ($client_hello:expr, $data:ident, $len:ident) => {
+        unsafe {
+            // Safety: `$client_hello` is a valid pointer
+            sanitize_slice($client_hello.$data, $client_hello.$len)?
+        }
+    };
+}
+
+impl<'a, M> ClientHello<'a, M> {
+    /// Exposes a mutable reference to the associated server connection in handshake.
+    ///
+    /// The return handle has full capability to configure your server-side connection for the
+    /// subsequent handshake.
+    /// This handle can be used to set connection-specific credentials or certificates during the
+    /// callback, for example.
+    pub fn connection_mut(&mut self) -> TlsConnectionInHandshake<'_, Server, M> {
+        unsafe {
+            // Safety: `self.conn` is a `repr(transparent)` wrapper around a `NonNull<SSL>`.
+            let conn_ref = transmute(&mut self.conn);
+            TlsConnectionInHandshake(conn_ref)
+        }
+    }
+
+    /// Get the client random bytes of length 32.
+    ///
+    /// This method returns [`None`] if the random bytes are absent or of wrong length.
+    pub fn random(&self) -> Option<[u8; 32]> {
+        let random = client_hello_getter!(*self.ptr, random, random_len);
+        random.try_into().ok()
+    }
+
+    /// Get the legacy session ID bytes.
+    pub fn legacy_session_id(&self) -> Option<[u8; 32]> {
+        let session_id = client_hello_getter!(*self.ptr, session_id, session_id_len);
+        session_id.try_into().ok()
+    }
+
+    /// Get the cipher suites bytes.
+    ///
+    /// This method returns [`None`] if the `cipher_suites` field is absent or contains error while
+    /// parsing.
+    pub fn cipher_suites(&self) -> Option<Vec<CipherSuite>> {
+        let cipher_suites = client_hello_getter!(*self.ptr, cipher_suites, cipher_suites_len);
+        if cipher_suites.len() % 2 != 0 {
+            return None;
+        }
+        let mut result = Vec::with_capacity(cipher_suites.len() / 2);
+        for suite in cipher_suites.chunks_exact(2) {
+            let suite = u16::from_be_bytes(
+                suite
+                    .try_into()
+                    .expect("we have checked that length of cipher_suites is multiple of 2"),
+            );
+            result.push(CipherSuite(suite));
+        }
+        Some(result)
+    }
+
+    /// Get the legacy compression methods bytes.
+    pub fn legacy_compression_methods(&self) -> &'a [u8] {
+        unsafe {
+            // Safety: `self.ptr` is a valid pointer to `SSL_CLIENT_HELLO` provided by BoringSSL.
+            sanitize_slice(
+                (*self.ptr).compression_methods,
+                (*self.ptr).compression_methods_len,
+            )
+            .unwrap_or(&[])
+        }
+    }
+
+    /// Get the extensions bytes.
+    pub fn extensions(&self) -> &'a [u8] {
+        unsafe {
+            // Safety: `self.ptr` is a valid pointer to `SSL_CLIENT_HELLO` provided by BoringSSL.
+            sanitize_slice((*self.ptr).extensions, (*self.ptr).extensions_len).unwrap_or(&[])
+        }
+    }
+
+    /// Extract a specific extension from the client hello.
+    ///
+    /// The returned slice contains the raw extension data, excluding the
+    /// extension type and length headers. The format of these bytes depends
+    /// on the specific extension type as defined in the relevant RFCs.
+    pub fn get_extension(&self, extension_type: ExtensionType) -> Option<&'a [u8]> {
+        let mut out_data = null();
+        let mut out_len = 0;
+        let ret = unsafe {
+            // Safety: `self.ptr` is a valid pointer to `SSL_CLIENT_HELLO` provided by BoringSSL.
+            bssl_sys::SSL_early_callback_ctx_extension_get(
+                self.ptr,
+                extension_type.0,
+                &mut out_data,
+                &mut out_len,
+            )
+        };
+        if ret == 1 {
+            unsafe {
+                // Safety: `out_data` and `out_len` are valid if the function returns 1.
+                sanitize_slice(out_data, out_len)
+            }
+        } else {
+            None
+        }
+    }
+}
+
+/// Early callback handler.
+///
+/// This callback happens before TLS server state machine makes progress when [`ClientHello`] is
+/// received.
+/// A good use case is when a TLS server could make further configuration in response to the client
+/// requests.
+pub trait EarlyCallback<M>: Send + Sync {
+    /// Decide whether a certificate can be selected.
+    ///
+    /// The connection handle is accessible through [`ClientHello::connection_mut`].
+    fn process(&self, client_hello: &mut ClientHello<'_, M>) -> EarlyCallbackResult;
+}
+
+pub(crate) unsafe extern "C" fn early_select_cert_cb<Mode, MethodsT>(
+    client_hello: *const bssl_sys::SSL_CLIENT_HELLO,
+) -> bssl_sys::ssl_select_cert_result_t
+where
+    MethodsT: EarlyCallbackMethods<Mode>,
+{
+    let Some(client_hello_ptr) = NonNull::new(client_hello as *mut bssl_sys::SSL_CLIENT_HELLO)
+    else {
+        return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error;
+    };
+    let ssl = unsafe {
+        // Safety: By BoringSSL invariant the `SSL_CLIENT_HELLO` handle must be valid
+        // and only contains object handles that are managed by this crate or their derivatives.
+        (*client_hello_ptr.as_ptr()).ssl
+    };
+    let Some(ssl_ptr) = NonNull::new(ssl) else {
+        return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error;
+    };
+
+    let Some(methods) = (unsafe {
+        // Safety: the connection handle must be originated from this crate
+        MethodsT::from_ssl(ssl_ptr.as_ptr())
+    }) else {
+        return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error;
+    };
+
+    let Some(handler) = methods.early_callback_handler() else {
+        return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_success;
+    };
+
+    let mut wrapped_hello = ClientHello {
+        ptr: client_hello_ptr.as_ptr(),
+        conn: ssl_ptr,
+        _p: PhantomData,
+    };
+
+    let res = abort_on_panic(move || handler.process(&mut wrapped_hello));
+    res as bssl_sys::ssl_select_cert_result_t
+}
diff --git a/rust/bssl-tls/src/credentials/tests.rs b/rust/bssl-tls/src/credentials/tests.rs
index 1f2c2e5..02a70e8 100644
--- a/rust/bssl-tls/src/credentials/tests.rs
+++ b/rust/bssl-tls/src/credentials/tests.rs
@@ -17,13 +17,8 @@
 
 use super::*;
 use crate::{
-    credentials::{
-        PskHash,
-        TlsCredential, //
-    },
-    tests::{
-        create_mock_pipe, //
-    },
+    context::TlsMode,
+    tests::create_mock_pipe, //
 };
 
 #[test]
@@ -195,7 +190,6 @@
 #[cfg(all(unix, feature = "std"))]
 #[test]
 fn psk_tls13_handshake_sync() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
-    use crate::credentials::{PskHash, TlsCredential};
     use crate::io::sync_io::{NoAsync, StdIoWithReactor};
     use std::io::pipe;
 
@@ -250,68 +244,9 @@
 
 #[test]
 fn rpk_tls13_handshake() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
-    use crate::credentials::{
-        CertificateType, CertificateVerificationMode, RawPublicKeyMode, TlsCredentialBuilder,
-    };
-    use crate::tests::create_mock_pipe;
     use bssl_x509::keys::PrivateKey;
 
-    const PEM: &'_ [u8] = b"-----BEGIN ENCRYPTED PRIVATE KEY-----
-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
------END ENCRYPTED PRIVATE KEY-----
-";
-    let priv_key = PrivateKey::from_pem(PEM, || b"Hello BoringSSL!").unwrap();
+    let priv_key = PrivateKey::from_pem(crate::tests::RSA_SERVER_KEY, || unreachable!()).unwrap();
 
     let cred = TlsCredentialBuilder::<RawPublicKeyMode>::new_raw_public_key(priv_key)
         .build()
@@ -357,3 +292,254 @@
 
     Ok(())
 }
+
+struct AcceptAnyVerifier;
+
+impl VerifyCertificate for AcceptAnyVerifier {
+    fn verify<'a>(
+        &self,
+        _: &'a VerifyCertificateContext,
+        _: crate::credentials::CertificateChainIterator<'a>,
+    ) -> Box<dyn VerifyCertificateTask> {
+        Box::new(crate::credentials::VerifyCertificateAccepted)
+    }
+}
+
+struct RpkVerifier {
+    expected_spki_der: Vec<u8>,
+}
+
+impl VerifyCertificate for RpkVerifier {
+    fn verify<'a>(
+        &self,
+        ctx: &'a VerifyCertificateContext,
+        _: crate::credentials::CertificateChainIterator<'a>,
+    ) -> Box<dyn VerifyCertificateTask> {
+        let Some(der) = ctx.get_peer_raw_public_key() else {
+            return Box::new(VerifyCertificateRejected(None));
+        };
+        if der != self.expected_spki_der {
+            return Box::new(VerifyCertificateRejected(None));
+        }
+
+        Box::new(crate::credentials::VerifyCertificateAccepted)
+    }
+}
+
+#[test]
+fn psk_rpk_fallback_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+    use std::sync::{
+        Arc,
+        atomic::{AtomicBool, Ordering},
+    };
+
+    use bssl_x509::keys::PrivateKey;
+
+    use crate::{
+        context::TlsContextBuilder,
+        credentials::{
+            CertificateType, CertificateVerificationMode, PskHash, RawPublicKeyMode, TlsCredential,
+            TlsCredentialBuilder,
+            early_callback::{ClientHello, EarlyCallback, EarlyCallbackResult, ExtensionType},
+        },
+        io::IoStatus,
+    };
+
+    let priv_key = PrivateKey::from_pem(crate::tests::RSA_SERVER_KEY, || unreachable!())?;
+
+    let rpk_cred = TlsCredentialBuilder::<RawPublicKeyMode>::new_raw_public_key(priv_key.clone())
+        .build()
+        .ok_or("raw public key failed to parse".to_string())?;
+
+    let server_certs =
+        crate::credentials::Certificate::parse_all_from_pem(crate::tests::RSA_SERVER_CERT, None)?;
+    let mut server_cred_builder = TlsCredentialBuilder::<crate::credentials::X509Mode>::new();
+    server_cred_builder
+        .with_certificate_chain(&server_certs)?
+        .with_private_key(priv_key.clone())?;
+    let server_cred = server_cred_builder
+        .build()
+        .ok_or("credential is incomplete".to_string())?;
+
+    let x509_cert = bssl_x509::certificates::X509Certificate::parse_one_from_pem(
+        crate::tests::RSA_SERVER_CERT,
+    )?;
+    let expected_rpk_der = x509_cert
+        .public_key()
+        .ok_or("public key is missing in x509".to_string())?
+        .to_der();
+
+    let key = b"test-key-test-key-test-key-test-key";
+    let identity = b"test-identity";
+    let context = b"test-context";
+    let psk_cred = TlsCredential::new_pre_shared_key(key, identity, PskHash::Sha256, context)?;
+
+    type AtomicFlag = Arc<AtomicBool>;
+    struct ServerSelectCallback {
+        server_cred: TlsCredential,
+        psk_cred: TlsCredential,
+        selected_rpk: AtomicFlag,
+        selected_psk: AtomicFlag,
+        failed: AtomicFlag,
+    }
+
+    impl EarlyCallback<TlsMode> for ServerSelectCallback {
+        fn process(&self, client_hello: &mut ClientHello<'_, TlsMode>) -> EarlyCallbackResult {
+            let offered_rpk = client_hello
+                .get_extension(ExtensionType::CLIENT_CERT_TYPE)
+                .is_some();
+            let offered_psk = client_hello
+                .get_extension(ExtensionType::PRE_SHARED_KEY)
+                .is_some();
+
+            if offered_rpk {
+                if (|| {
+                    client_hello
+                        .connection_mut()
+                        .add_credential(&self.server_cred)?
+                        .set_certificate_verification_mode(
+                            CertificateVerificationMode::PeerCertMandatory,
+                        );
+                    Ok::<_, crate::errors::Error>(())
+                })()
+                .is_err()
+                {
+                    return EarlyCallbackResult::ErrorResult;
+                };
+                self.selected_rpk.store(true, Ordering::SeqCst);
+                EarlyCallbackResult::Success
+            } else if offered_psk {
+                let Ok(_) = client_hello.connection_mut().add_credential(&self.psk_cred) else {
+                    return EarlyCallbackResult::ErrorResult;
+                };
+                self.selected_psk.store(true, Ordering::SeqCst);
+                EarlyCallbackResult::Success
+            } else {
+                self.failed.store(true, Ordering::SeqCst);
+                EarlyCallbackResult::ErrorResult
+            }
+        }
+    }
+
+    for &(offer_psk, offer_rpk) in &[(true, false), (false, true), (true, true), (false, false)] {
+        let selected_rpk = AtomicFlag::default();
+        let selected_psk = AtomicFlag::default();
+        let failed = AtomicFlag::default();
+
+        let cb = ServerSelectCallback {
+            server_cred: server_cred.clone(),
+            psk_cred: psk_cred.clone(),
+            selected_rpk: selected_rpk.clone(),
+            selected_psk: selected_psk.clone(),
+            failed: failed.clone(),
+        };
+
+        let mut server_ctx_builder = TlsContextBuilder::new_tls();
+        server_ctx_builder
+            .with_accepted_peer_cert_types(&[CertificateType::Rpk])?
+            .with_early_callback(cb)
+            .with_certificate_verifier(
+                CertificateVerificationMode::None,
+                RpkVerifier {
+                    expected_spki_der: expected_rpk_der.clone(),
+                },
+            );
+        let server_ctx = server_ctx_builder.build();
+
+        let mut client_ctx_builder = TlsContextBuilder::new_tls();
+        if offer_psk {
+            client_ctx_builder.with_credential(psk_cred.clone())?;
+        }
+        if offer_rpk {
+            client_ctx_builder
+                .with_credential(rpk_cred.clone())?
+                .with_available_client_cert_types(&[CertificateType::Rpk])?;
+        }
+        let client_ctx = client_ctx_builder.build();
+
+        let (sock_client, sock_server, mut executor) = create_mock_pipe();
+
+        let mut server_conn = server_ctx.new_server_connection(None)?.build();
+        server_conn.set_io(sock_server)?;
+
+        let mut client_conn_builder = client_ctx.new_client_connection(None)?;
+        client_conn_builder
+            .with_certificate_verification_mode(CertificateVerificationMode::None)
+            .with_certificate_verifier(
+                CertificateVerificationMode::PeerCertRequested,
+                AcceptAnyVerifier,
+            );
+        let mut client_conn = client_conn_builder.build();
+        client_conn.set_io(sock_client)?;
+
+        let test_future = async {
+            let client_handshake = async {
+                if let Some(mut in_handshake) = client_conn.in_handshake() {
+                    in_handshake.async_handshake().await?;
+                }
+                Ok::<(), crate::errors::Error>(())
+            };
+
+            let server_handshake = async {
+                if let Some(mut in_handshake) = server_conn.in_handshake() {
+                    in_handshake.async_handshake().await?;
+                }
+                Ok::<(), crate::errors::Error>(())
+            };
+
+            let handshake_result =
+                futures::future::try_join(client_handshake, server_handshake).await;
+
+            if !offer_psk && !offer_rpk {
+                assert!(handshake_result.is_err(), "Handshake should have failed");
+                return Ok::<(), crate::errors::Error>(());
+            }
+
+            handshake_result?;
+
+            client_conn.as_pin_mut().async_write(b"hello").await?;
+
+            let mut server_buf = [0u8; 5];
+            let read_len = server_conn.as_pin_mut().async_read(&mut server_buf).await?;
+            assert!(matches!(read_len, IoStatus::Ok(5)));
+            assert_eq!(&server_buf, b"hello");
+
+            server_conn.as_pin_mut().async_write(b"world").await?;
+
+            let mut client_buf = [0u8; 5];
+            let read_len = client_conn.as_pin_mut().async_read(&mut client_buf).await?;
+            assert!(matches!(read_len, IoStatus::Ok(5)));
+            assert_eq!(&client_buf, b"world");
+
+            Ok::<(), crate::errors::Error>(())
+        };
+
+        executor.run(test_future)?;
+
+        if !offer_psk && !offer_rpk {
+            assert!(
+                failed.load(Ordering::SeqCst),
+                "Should fail if no valid credentials offered"
+            );
+            continue;
+        }
+
+        if offer_rpk {
+            assert!(
+                selected_rpk.load(Ordering::SeqCst),
+                "Should select RPK if offered"
+            );
+            assert_eq!(
+                server_conn.get_peer_certificate_type(),
+                Some(CertificateType::Rpk)
+            );
+        } else if offer_psk {
+            assert!(
+                selected_psk.load(Ordering::SeqCst),
+                "Should fallback to PSK if only PSK offered"
+            );
+        }
+    }
+
+    Ok(())
+}
diff --git a/rust/bssl-tls/src/lib.rs b/rust/bssl-tls/src/lib.rs
index 88dd1ef..ac98e1f 100644
--- a/rust/bssl-tls/src/lib.rs
+++ b/rust/bssl-tls/src/lib.rs
@@ -69,6 +69,10 @@
     fn verify_certificate_methods(&self) -> Option<&dyn credentials::VerifyCertificate>;
 }
 
+pub(crate) trait EarlyCallbackMethods<M>: Methods {
+    fn early_callback_handler(&self) -> Option<&dyn credentials::early_callback::EarlyCallback<M>>;
+}
+
 #[inline]
 fn abort_on_panic<T>(work: impl FnOnce() -> T) -> T {
     let assert_unwind_safe = AssertUnwindSafe(work);
diff --git a/rust/bssl-tls/src/tests.rs b/rust/bssl-tls/src/tests.rs
index f78f29f..f7e92ea 100644
--- a/rust/bssl-tls/src/tests.rs
+++ b/rust/bssl-tls/src/tests.rs
@@ -53,9 +53,11 @@
 };
 use futures::future::join;
 
-const CA: &[u8] = include_bytes!("../../test-data/BoringSSLCATest.crt");
-const RSA_SERVER_CERT: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.crt");
-const RSA_SERVER_KEY: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.key");
+pub(crate) const CA: &[u8] = include_bytes!("../../test-data/BoringSSLCATest.crt");
+pub(crate) const RSA_SERVER_CERT: &[u8] =
+    include_bytes!("../../test-data/BoringSSLServerTest-RSA.crt");
+pub(crate) const RSA_SERVER_KEY: &[u8] =
+    include_bytes!("../../test-data/BoringSSLServerTest-RSA.key");
 
 mod datagram;
 mod handshake;