summaryrefslogtreecommitdiff
path: root/rust
diff options
context:
space:
mode:
authorRubens Brandao <git@rubens.io>2024-06-19 09:40:05 -0300
committerMason Reed <mason@vector35.com>2025-02-06 22:52:47 -0500
commit4075371ae3e3b7f0f7d42be3b83dbd4bc83fdfb5 (patch)
tree9a77a3f2aaf3309a7095abce346dac66f2b4e1c9 /rust
parenta370cf02b39ce6f25e428d70e1aff89a9889bb6a (diff)
Implement Rust SecretsProvider
Diffstat (limited to 'rust')
-rw-r--r--rust/src/lib.rs1
-rw-r--r--rust/src/secrets_provider.rs166
-rw-r--r--rust/tests/secrets_provider.rs44
3 files changed, 211 insertions, 0 deletions
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 0cb0484e..38fe377c 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 secrets_provider;
pub mod section;
pub mod segment;
pub mod settings;
diff --git a/rust/src/secrets_provider.rs b/rust/src/secrets_provider.rs
new file mode 100644
index 00000000..e61cd466
--- /dev/null
+++ b/rust/src/secrets_provider.rs
@@ -0,0 +1,166 @@
+use binaryninjacore_sys::*;
+use std::ffi::{c_char, c_void, CStr};
+use std::fmt::Debug;
+use std::ptr::NonNull;
+
+use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
+use crate::string::{BnStrCompatible, BnString};
+
+pub trait SecretsProvider {
+ 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;
+}
+
+/// Struct for storing secrets (e.g. tokens) in a system-specific manner
+#[repr(transparent)]
+pub struct CoreSecretsProvider {
+ handle: NonNull<BNSecretsProvider>,
+}
+
+impl CoreSecretsProvider {
+ pub(crate) unsafe fn from_raw(handle: NonNull<BNSecretsProvider>) -> Self {
+ Self { handle }
+ }
+
+ /// Register a new provider
+ pub fn new<C: SecretsProvider>(name: &str, callback: C) -> Self {
+ // SAFETY: once create SecretsProvider is never dropped
+ let name = name.into_bytes_with_nul();
+ let callback = Box::leak(Box::new(callback));
+ let mut callbacks = BNSecretsProviderCallbacks {
+ context: callback as *mut C as *mut c_void,
+ hasData: Some(cb_has_data::<C>),
+ getData: Some(cb_get_data::<C>),
+ storeData: Some(cb_store_data::<C>),
+ deleteData: Some(cb_delete_data::<C>),
+ };
+ let result =
+ unsafe { BNRegisterSecretsProvider(name.as_ptr() as *const c_char, &mut callbacks) };
+ unsafe { Self::from_raw(NonNull::new(result).unwrap()) }
+ }
+
+ /// Retrieve the list of providers
+ pub fn all() -> Array<CoreSecretsProvider> {
+ 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<S: BnStrCompatible>(name: S) -> Option<CoreSecretsProvider> {
+ let name = name.into_bytes_with_nul();
+ let result = unsafe { BNGetSecretsProviderByName(name.as_ref().as_ptr() as *const c_char) };
+ NonNull::new(result).map(|h| unsafe { Self::from_raw(h) })
+ }
+
+ pub fn name(&self) -> BnString {
+ let result = unsafe { BNGetSecretsProviderName(self.handle.as_ptr()) };
+ 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<S: BnStrCompatible>(&self, key: S) -> bool {
+ let key = key.into_bytes_with_nul();
+ unsafe {
+ BNSecretsProviderHasData(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
+ }
+ }
+
+ /// Retrieve data for the given key, if it exists
+ pub fn get_data<S: BnStrCompatible>(&self, key: S) -> BnString {
+ let key = key.into_bytes_with_nul();
+ let result = unsafe {
+ BNGetSecretsProviderData(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
+ };
+ unsafe { BnString::from_raw(result) }
+ }
+
+ /// Store data with the given key
+ pub fn store_data<K: BnStrCompatible, V: BnStrCompatible>(&self, key: K, value: V) -> bool {
+ let key = key.into_bytes_with_nul();
+ let value = value.into_bytes_with_nul();
+ unsafe {
+ BNStoreSecretsProviderData(
+ self.handle.as_ptr(),
+ key.as_ref().as_ptr() as *const c_char,
+ value.as_ref().as_ptr() as *const c_char,
+ )
+ }
+ }
+
+ /// Delete stored data with the given key
+ pub fn delete_data<S: BnStrCompatible>(&self, key: S) -> bool {
+ let key = key.into_bytes_with_nul();
+ unsafe {
+ BNDeleteSecretsProviderData(
+ self.handle.as_ptr(),
+ key.as_ref().as_ptr() as *const c_char,
+ )
+ }
+ }
+}
+
+impl Debug for CoreSecretsProvider {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CoreSecretsProvider")
+ .field("name", &self.name())
+ .finish()
+ }
+}
+
+impl CoreArrayProvider for CoreSecretsProvider {
+ type Raw = *mut BNSecretsProvider;
+ type Context = ();
+ type Wrapped<'a> = Self;
+}
+
+unsafe impl CoreArrayProviderInner for CoreSecretsProvider {
+ 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> {
+ let raw_ptr = NonNull::new(*raw).unwrap();
+ Self::from_raw(raw_ptr)
+ }
+}
+
+unsafe extern "C" fn cb_has_data<C: SecretsProvider>(
+ ctxt: *mut c_void,
+ key: *const c_char,
+) -> bool {
+ let ctxt: &mut C = &mut *(ctxt as *mut C);
+ ctxt.has_data(&CStr::from_ptr(key).to_string_lossy())
+}
+
+unsafe extern "C" fn cb_get_data<C: SecretsProvider>(
+ ctxt: *mut c_void,
+ key: *const c_char,
+) -> *mut c_char {
+ let ctxt: &mut C = &mut *(ctxt as *mut C);
+ let result = ctxt.get_data(&CStr::from_ptr(key).to_string_lossy());
+ BnString::into_raw(BnString::new(result))
+}
+
+unsafe extern "C" fn cb_store_data<C: SecretsProvider>(
+ ctxt: *mut c_void,
+ key: *const c_char,
+ data: *const c_char,
+) -> bool {
+ let ctxt: &mut C = &mut *(ctxt as *mut C);
+ let key = CStr::from_ptr(key).to_string_lossy();
+ let data = CStr::from_ptr(data).to_string_lossy();
+ ctxt.store_data(&key, &data)
+}
+
+unsafe extern "C" fn cb_delete_data<C: SecretsProvider>(
+ ctxt: *mut c_void,
+ key: *const c_char,
+) -> bool {
+ let ctxt: &mut C = &mut *(ctxt as *mut C);
+ ctxt.delete_data(&CStr::from_ptr(key).to_string_lossy())
+}
diff --git a/rust/tests/secrets_provider.rs b/rust/tests/secrets_provider.rs
new file mode 100644
index 00000000..c7b87df1
--- /dev/null
+++ b/rust/tests/secrets_provider.rs
@@ -0,0 +1,44 @@
+use binaryninja::headless::Session;
+use binaryninja::secrets_provider::{CoreSecretsProvider, SecretsProvider};
+use rstest::*;
+
+#[fixture]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn list_secrets_provider(_session: Session) {
+ let providers = CoreSecretsProvider::all();
+ for provider in &providers {
+ println!("{}", provider.name());
+ }
+}
+
+struct MySecretsProvider {}
+
+impl SecretsProvider 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 = CoreSecretsProvider::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");
+}