| // 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::{ |
| ffi::c_int, |
| |
| ptr::{ |
| NonNull, |
| null_mut, // |
| }, // |
| }; |
| |
| use once_cell::sync::Lazy; |
| |
| use crate::{ |
| Methods, |
| PrivateKeyMethods, |
| abort_on_panic, |
| connection::methods::private_key_op_from_ssl, |
| credentials::{ |
| DecryptionOperation, |
| PrivateKeyDelegate, |
| PrivateKeyOperationResult, |
| SignatureOperation, |
| waker_data_from_ssl, // |
| }, |
| ffi::{ |
| sanitise_mut_byteslice, |
| sanitize_slice, // |
| }, |
| methods::drop_box_rust_methods, // |
| }; |
| |
| pub(super) static TLS_CREDENTIAL_METHOD: Lazy<c_int> = Lazy::new(|| unsafe { |
| // Safety: this a one-time registration uses only valid function pointers. |
| let ret = bssl_sys::SSL_CREDENTIAL_get_ex_new_index( |
| 0, |
| null_mut(), |
| null_mut(), |
| None, |
| Some(drop_box_rust_methods::<RustCredentialMethods>), |
| ); |
| if ret < 0 { |
| panic!("Failed to register TLS Credential ex-data") |
| } else { |
| ret |
| } |
| }); |
| |
| #[derive(Default)] |
| pub(crate) struct RustCredentialMethods { |
| pub(crate) private_key_methods: Option<Box<dyn PrivateKeyDelegate>>, |
| } |
| |
| impl Methods for RustCredentialMethods { |
| unsafe extern "C" fn from_ssl<'a>(ssl: *mut bssl_sys::SSL) -> Option<&'a Self> { |
| unsafe { |
| // Safety: `ssl` is valid per BoringSSL invariant. |
| let cred = bssl_sys::SSL_get0_selected_credential(ssl); |
| if cred.is_null() { |
| return None; |
| } |
| // Safety: `cred` is valid and originated from `TlsCredential::new`. |
| let methods = bssl_sys::SSL_CREDENTIAL_get_ex_data(cred, *TLS_CREDENTIAL_METHOD); |
| if methods.is_null() { |
| return None; |
| } |
| // Safety: `cred` is originated from `Box::into_raw`. |
| Some(&*(methods as *mut RustCredentialMethods)) |
| } |
| } |
| } |
| |
| impl PrivateKeyMethods for RustCredentialMethods { |
| fn private_key_methods(&self) -> Option<&dyn PrivateKeyDelegate> { |
| self.private_key_methods.as_deref() |
| } |
| } |
| |
| macro_rules! private_key_method_prelude { |
| ($M:ty, $ssl:ident => $context:ident, $private_key_methods:ident, $task:ident) => { |
| let Some(ssl) = NonNull::new($ssl) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| let Some(methods) = (unsafe { |
| // Safety: `ssl` outlives `methods` and must be valid by BoringSSL contract. |
| <$M>::from_ssl(ssl.as_ptr()) |
| }) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| let waker = unsafe { |
| // Safety: `ssl` must be constructed by `TlsConnection` as this is called from |
| waker_data_from_ssl(ssl) |
| }; |
| let mut $context = if let Some(waker) = &waker { |
| Some(core::task::Context::from_waker(waker)) |
| } else { |
| None |
| }; |
| #[allow(unused_mut)] |
| let $task = unsafe { |
| // Safety: `ssl` must be constructed by `TlsConnection` as this is called from |
| private_key_op_from_ssl(ssl) |
| }; |
| let Some($private_key_methods) = methods.private_key_methods() else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| }; |
| } |
| |
| pub(crate) unsafe extern "C" fn sign<Method: PrivateKeyMethods>( |
| ssl: *mut bssl_sys::SSL, |
| out: *mut u8, |
| out_len: *mut usize, |
| max_out: usize, |
| sig_alg: u16, |
| msg: *const u8, |
| msg_len: usize, |
| ) -> bssl_sys::ssl_private_key_result_t { |
| private_key_method_prelude!(Method, ssl => context, private_key_methods, task); |
| if task.is_some() { |
| abort_on_panic(|| { |
| let _ = task.take(); |
| }); |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| } |
| |
| // Unwind-safety: later when panic happens, we detach the poisoned private key method |
| // without calling destructor. |
| let algorithm = match sig_alg.try_into() { |
| Ok(sig_alg) => sig_alg, |
| // TODO(@xfding) maybe we should log this error? |
| Err(_) => return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure, |
| }; |
| let Some(output) = (unsafe { |
| // Safety: the slice will only be used within this callback. |
| sanitise_mut_byteslice(out, max_out) |
| }) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| if output.is_empty() { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| } |
| let Some(message) = (unsafe { |
| // Safety: `msg` outlives `message` because it is owned by BoringSSL. |
| sanitize_slice(msg, msg_len) |
| }) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| // Unwind-safety: when panic happens, we will not inspect the `output` buffer. |
| abort_on_panic(move || { |
| let sign_op = SignatureOperation { |
| output, |
| message, |
| algorithm, |
| }; |
| let mut outstanding_task = private_key_methods.sign(sign_op); |
| match outstanding_task.complete(context.as_mut(), output) { |
| PrivateKeyOperationResult::Success(len) => { |
| unsafe { |
| // Safety: `out_len` is a valid pointer by BoringSSL invariant. |
| *out_len = len; |
| } |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_success |
| } |
| PrivateKeyOperationResult::Pending => { |
| *task = Some(outstanding_task); |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_retry |
| } |
| PrivateKeyOperationResult::Error => { |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_failure |
| } |
| } |
| }) |
| } |
| |
| pub(crate) unsafe extern "C" fn decrypt<Method: PrivateKeyMethods>( |
| ssl: *mut bssl_sys::SSL, |
| out: *mut u8, |
| out_len: *mut usize, |
| max_out: usize, |
| ciphertext: *const u8, |
| ciphertext_len: usize, |
| ) -> bssl_sys::ssl_private_key_result_t { |
| private_key_method_prelude!(Method, ssl => context, private_key_methods, task); |
| if task.is_some() { |
| abort_on_panic(|| { |
| let _ = task.take(); |
| }); |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| } |
| |
| // Unwind-safety: later when panic happens, we detach the poisoned private key method |
| // without calling destructor. |
| let Some(output) = (unsafe { |
| // Safety: the slice will only be used within this callback. |
| sanitise_mut_byteslice(out, max_out) |
| }) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| if output.is_empty() { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| } |
| let Some(ciphertext) = (unsafe { |
| // Safety: `ciphertext` is now owned by BoringSSL and outlives the slice. |
| sanitize_slice(ciphertext, ciphertext_len) |
| }) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| // Unwind-safety: when panic happens, we will not inspect the `output` buffer. |
| abort_on_panic(move || { |
| let decrypt_op = DecryptionOperation { output, ciphertext }; |
| let mut outstanding_task = private_key_methods.decrypt(decrypt_op); |
| match outstanding_task.complete(context.as_mut(), output) { |
| PrivateKeyOperationResult::Success(len) => { |
| unsafe { |
| // Safety: `out_len` is a valid pointer by BoringSSL invariant. |
| *out_len = len; |
| } |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_success |
| } |
| PrivateKeyOperationResult::Pending => { |
| *task = Some(outstanding_task); |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_retry |
| } |
| PrivateKeyOperationResult::Error => { |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_failure |
| } |
| } |
| }) |
| } |
| |
| pub(crate) unsafe extern "C" fn complete<Method: PrivateKeyMethods>( |
| ssl: *mut bssl_sys::SSL, |
| out: *mut u8, |
| out_len: *mut usize, |
| max_out: usize, |
| ) -> bssl_sys::ssl_private_key_result_t { |
| private_key_method_prelude!(Method, ssl => context, _private_key_methods, task); |
| |
| // Unwind-safety: later when panic happens, we detach the poisoned private key method |
| // without calling destructor. |
| let Some(output) = (unsafe { |
| // Safety: the slice will only be used within this callback. |
| sanitise_mut_byteslice(out, max_out) |
| }) else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| if output.is_empty() { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| } |
| // Unwind-safety: when panic happens, we will not inspect the `output` buffer. |
| abort_on_panic(move || { |
| let Some(outstanding_task) = task else { |
| return bssl_sys::ssl_private_key_result_t_ssl_private_key_failure; |
| }; |
| match outstanding_task.complete(context.as_mut(), output) { |
| PrivateKeyOperationResult::Success(len) => { |
| unsafe { |
| // Safety: `out_len` is a valid pointer by BoringSSL invariant. |
| *out_len = len; |
| } |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_success |
| } |
| PrivateKeyOperationResult::Pending => { |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_retry |
| } |
| PrivateKeyOperationResult::Error => { |
| bssl_sys::ssl_private_key_result_t_ssl_private_key_failure |
| } |
| } |
| }) |
| } |
| |
| pub(super) const PRIVATE_KEY_METHODS: *const bssl_sys::SSL_PRIVATE_KEY_METHOD = { |
| &bssl_sys::SSL_PRIVATE_KEY_METHOD { |
| sign: Some(sign::<RustCredentialMethods>), |
| decrypt: Some(decrypt::<RustCredentialMethods>), |
| complete: Some(complete::<RustCredentialMethods>), |
| } as _ |
| }; |