From 1628f875107dae81eada486124e550293865fb2e Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 19 Jun 2024 09:40:05 -0300 Subject: [PATCH 1/2] implement rust SecretsProvider --- rust/src/lib.rs | 1 + rust/src/secretsprovider.rs | 182 ++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 rust/src/secretsprovider.rs diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0cb0484e65..4ee862c3b8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -69,6 +69,7 @@ pub mod rc; pub mod references; pub mod relocation; pub mod render_layer; +pub mod secretsprovider; pub mod section; pub mod segment; pub mod settings; diff --git a/rust/src/secretsprovider.rs b/rust/src/secretsprovider.rs new file mode 100644 index 0000000000..f3c3c7ded8 --- /dev/null +++ b/rust/src/secretsprovider.rs @@ -0,0 +1,182 @@ +use core::{ffi, mem, ptr}; + +use binaryninjacore_sys::*; + +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner}; +use crate::string::{BnStrCompatible, BnString}; + +/// Struct for storing secrets (e.g. tokens) in a system-specific manner +#[repr(transparent)] +pub struct SecretsProvider { + handle: ptr::NonNull, +} + +impl SecretsProvider { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNSecretsProvider) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNSecretsProvider { + &mut *self.handle.as_ptr() + } + + /// Register a new provider + pub fn new( + name: N, + callback: C, + ) -> Self { + // SAFETY: once create SecretsProvider is never dropped + let name = name.secrets_provider_name(); + let callback = Box::leak(Box::new(callback)); + let mut callbacks = BNSecretsProviderCallbacks { + context: callback as *mut C as *mut ffi::c_void, + hasData: Some(cb_has_data::), + getData: Some(cb_get_data::), + storeData: Some(cb_store_data::), + deleteData: Some(cb_delete_data::), + }; + let result = unsafe { BNRegisterSecretsProvider(name.as_ptr(), &mut callbacks) }; + unsafe { Self::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + /// Retrieve the list of providers + pub fn all() -> Array { + let mut count = 0; + let result = unsafe { BNGetSecretsProviderList(&mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Retrieve a provider by name + pub fn by_name(name: S) -> Option { + let name = name.into_bytes_with_nul(); + let result = + unsafe { BNGetSecretsProviderByName(name.as_ref().as_ptr() as *const ffi::c_char) }; + ptr::NonNull::new(result).map(|h| unsafe { Self::from_raw(h) }) + } + + pub fn name(&self) -> BnString { + let result = unsafe { BNGetSecretsProviderName(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Check if data for a specific key exists, but do not retrieve it + pub fn has_data(&self, key: S) -> bool { + let key = key.into_bytes_with_nul(); + unsafe { + BNSecretsProviderHasData(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// Retrieve data for the given key, if it exists + pub fn get_data(&self, key: S) -> Option { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNGetSecretsProviderData(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Store data with the given key + pub fn store_data(&self, key: K, value: V) -> bool { + let key = key.into_bytes_with_nul(); + let value = value.into_bytes_with_nul(); + unsafe { + BNStoreSecretsProviderData( + self.as_raw(), + key.as_ref().as_ptr() as *const ffi::c_char, + value.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Delete stored data with the given key + pub fn delete_data(&self, key: S) -> bool { + let key = key.into_bytes_with_nul(); + unsafe { + BNDeleteSecretsProviderData(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + } + } +} + +impl CoreArrayProvider for SecretsProvider { + type Raw = *mut BNSecretsProvider; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for SecretsProvider { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeSecretsProviderList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +pub trait IntoSecretsProviderName { + fn secrets_provider_name(self) -> BnString; +} + +impl IntoSecretsProviderName for S { + fn secrets_provider_name(self) -> BnString { + BnString::new(self) + } +} + +impl IntoSecretsProviderName for &SecretsProvider { + fn secrets_provider_name(self) -> BnString { + self.name() + } +} + +pub trait SecretsProviderCallback { + fn has_data(&mut self, key: &str) -> bool; + fn get_data(&mut self, key: &str) -> String; + fn store_data(&mut self, key: &str, data: &str) -> bool; + fn delete_data(&mut self, key: &str) -> bool; +} + +unsafe extern "C" fn cb_has_data( + ctxt: *mut ffi::c_void, + key: *const ffi::c_char, +) -> bool { + let ctxt: &mut C = &mut *(ctxt as *mut C); + ctxt.has_data(&ffi::CStr::from_ptr(key).to_string_lossy()) +} + +unsafe extern "C" fn cb_get_data( + ctxt: *mut ffi::c_void, + key: *const ffi::c_char, +) -> *mut ffi::c_char { + let ctxt: &mut C = &mut *(ctxt as *mut C); + let result = ctxt.get_data(&ffi::CStr::from_ptr(key).to_string_lossy()); + BnString::into_raw(BnString::new(result)) +} + +unsafe extern "C" fn cb_store_data( + ctxt: *mut ffi::c_void, + key: *const ffi::c_char, + data: *const ffi::c_char, +) -> bool { + let ctxt: &mut C = &mut *(ctxt as *mut C); + let key = ffi::CStr::from_ptr(key).to_string_lossy(); + let data = ffi::CStr::from_ptr(data).to_string_lossy(); + ctxt.store_data(&key, &data) +} + +unsafe extern "C" fn cb_delete_data( + ctxt: *mut ffi::c_void, + key: *const ffi::c_char, +) -> bool { + let ctxt: &mut C = &mut *(ctxt as *mut C); + ctxt.delete_data(&ffi::CStr::from_ptr(key).to_string_lossy()) +} From c8df855404b7a221d210ea5f956796c562c553ba Mon Sep 17 00:00:00 2001 From: rbran Date: Wed, 5 Feb 2025 17:25:38 +0000 Subject: [PATCH 2/2] add tests for rust SecretsProvider --- rust/src/secretsprovider.rs | 4 ++-- rust/tests/secretsprovider.rs | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 rust/tests/secretsprovider.rs diff --git a/rust/src/secretsprovider.rs b/rust/src/secretsprovider.rs index f3c3c7ded8..329f900e09 100644 --- a/rust/src/secretsprovider.rs +++ b/rust/src/secretsprovider.rs @@ -76,12 +76,12 @@ impl SecretsProvider { } /// Retrieve data for the given key, if it exists - pub fn get_data(&self, key: S) -> Option { + pub fn get_data(&self, key: S) -> BnString { let key = key.into_bytes_with_nul(); let result = unsafe { BNGetSecretsProviderData(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) }; - (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + unsafe { BnString::from_raw(result) } } /// Store data with the given key diff --git a/rust/tests/secretsprovider.rs b/rust/tests/secretsprovider.rs new file mode 100644 index 0000000000..333375dea0 --- /dev/null +++ b/rust/tests/secretsprovider.rs @@ -0,0 +1,42 @@ +use binaryninja::headless::Session; +use binaryninja::secretsprovider::{SecretsProvider, SecretsProviderCallback}; +use rstest::*; + +#[fixture] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn list_secrets_provider(_session: Session) { + let providers = SecretsProvider::all(); + for _provider in &providers {} +} + +struct MySecretsProvider {} + +impl SecretsProviderCallback for MySecretsProvider { + fn has_data(&mut self, key: &str) -> bool { + key == "my_key" + } + + fn get_data(&mut self, key: &str) -> String { + if key == "my_key" { "my_value" } else { "" }.to_string() + } + + fn store_data(&mut self, _key: &str, _data: &str) -> bool { + false + } + + fn delete_data(&mut self, _key: &str) -> bool { + false + } +} + +#[rstest] +fn custom_secrets_provider(_session: Session) { + let my_provider = SecretsProvider::new("MySecretsProvider", MySecretsProvider {}); + assert!(my_provider.has_data("my_key")); + assert!(!my_provider.has_data("not_my_key")); + assert_eq!(my_provider.get_data("my_key").as_str(), "my_value"); +}