Skip to content

Commit 4075371

Browse files
rbranemesare
authored andcommitted
Implement Rust SecretsProvider
1 parent a370cf0 commit 4075371

File tree

3 files changed

+211
-0
lines changed

3 files changed

+211
-0
lines changed

rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub mod rc;
6969
pub mod references;
7070
pub mod relocation;
7171
pub mod render_layer;
72+
pub mod secrets_provider;
7273
pub mod section;
7374
pub mod segment;
7475
pub mod settings;

rust/src/secrets_provider.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use binaryninjacore_sys::*;
2+
use std::ffi::{c_char, c_void, CStr};
3+
use std::fmt::Debug;
4+
use std::ptr::NonNull;
5+
6+
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
7+
use crate::string::{BnStrCompatible, BnString};
8+
9+
pub trait SecretsProvider {
10+
fn has_data(&mut self, key: &str) -> bool;
11+
fn get_data(&mut self, key: &str) -> String;
12+
fn store_data(&mut self, key: &str, data: &str) -> bool;
13+
fn delete_data(&mut self, key: &str) -> bool;
14+
}
15+
16+
/// Struct for storing secrets (e.g. tokens) in a system-specific manner
17+
#[repr(transparent)]
18+
pub struct CoreSecretsProvider {
19+
handle: NonNull<BNSecretsProvider>,
20+
}
21+
22+
impl CoreSecretsProvider {
23+
pub(crate) unsafe fn from_raw(handle: NonNull<BNSecretsProvider>) -> Self {
24+
Self { handle }
25+
}
26+
27+
/// Register a new provider
28+
pub fn new<C: SecretsProvider>(name: &str, callback: C) -> Self {
29+
// SAFETY: once create SecretsProvider is never dropped
30+
let name = name.into_bytes_with_nul();
31+
let callback = Box::leak(Box::new(callback));
32+
let mut callbacks = BNSecretsProviderCallbacks {
33+
context: callback as *mut C as *mut c_void,
34+
hasData: Some(cb_has_data::<C>),
35+
getData: Some(cb_get_data::<C>),
36+
storeData: Some(cb_store_data::<C>),
37+
deleteData: Some(cb_delete_data::<C>),
38+
};
39+
let result =
40+
unsafe { BNRegisterSecretsProvider(name.as_ptr() as *const c_char, &mut callbacks) };
41+
unsafe { Self::from_raw(NonNull::new(result).unwrap()) }
42+
}
43+
44+
/// Retrieve the list of providers
45+
pub fn all() -> Array<CoreSecretsProvider> {
46+
let mut count = 0;
47+
let result = unsafe { BNGetSecretsProviderList(&mut count) };
48+
assert!(!result.is_null());
49+
unsafe { Array::new(result, count, ()) }
50+
}
51+
52+
/// Retrieve a provider by name
53+
pub fn by_name<S: BnStrCompatible>(name: S) -> Option<CoreSecretsProvider> {
54+
let name = name.into_bytes_with_nul();
55+
let result = unsafe { BNGetSecretsProviderByName(name.as_ref().as_ptr() as *const c_char) };
56+
NonNull::new(result).map(|h| unsafe { Self::from_raw(h) })
57+
}
58+
59+
pub fn name(&self) -> BnString {
60+
let result = unsafe { BNGetSecretsProviderName(self.handle.as_ptr()) };
61+
assert!(!result.is_null());
62+
unsafe { BnString::from_raw(result) }
63+
}
64+
65+
/// Check if data for a specific key exists, but do not retrieve it
66+
pub fn has_data<S: BnStrCompatible>(&self, key: S) -> bool {
67+
let key = key.into_bytes_with_nul();
68+
unsafe {
69+
BNSecretsProviderHasData(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
70+
}
71+
}
72+
73+
/// Retrieve data for the given key, if it exists
74+
pub fn get_data<S: BnStrCompatible>(&self, key: S) -> BnString {
75+
let key = key.into_bytes_with_nul();
76+
let result = unsafe {
77+
BNGetSecretsProviderData(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
78+
};
79+
unsafe { BnString::from_raw(result) }
80+
}
81+
82+
/// Store data with the given key
83+
pub fn store_data<K: BnStrCompatible, V: BnStrCompatible>(&self, key: K, value: V) -> bool {
84+
let key = key.into_bytes_with_nul();
85+
let value = value.into_bytes_with_nul();
86+
unsafe {
87+
BNStoreSecretsProviderData(
88+
self.handle.as_ptr(),
89+
key.as_ref().as_ptr() as *const c_char,
90+
value.as_ref().as_ptr() as *const c_char,
91+
)
92+
}
93+
}
94+
95+
/// Delete stored data with the given key
96+
pub fn delete_data<S: BnStrCompatible>(&self, key: S) -> bool {
97+
let key = key.into_bytes_with_nul();
98+
unsafe {
99+
BNDeleteSecretsProviderData(
100+
self.handle.as_ptr(),
101+
key.as_ref().as_ptr() as *const c_char,
102+
)
103+
}
104+
}
105+
}
106+
107+
impl Debug for CoreSecretsProvider {
108+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109+
f.debug_struct("CoreSecretsProvider")
110+
.field("name", &self.name())
111+
.finish()
112+
}
113+
}
114+
115+
impl CoreArrayProvider for CoreSecretsProvider {
116+
type Raw = *mut BNSecretsProvider;
117+
type Context = ();
118+
type Wrapped<'a> = Self;
119+
}
120+
121+
unsafe impl CoreArrayProviderInner for CoreSecretsProvider {
122+
unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
123+
BNFreeSecretsProviderList(raw)
124+
}
125+
126+
unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
127+
let raw_ptr = NonNull::new(*raw).unwrap();
128+
Self::from_raw(raw_ptr)
129+
}
130+
}
131+
132+
unsafe extern "C" fn cb_has_data<C: SecretsProvider>(
133+
ctxt: *mut c_void,
134+
key: *const c_char,
135+
) -> bool {
136+
let ctxt: &mut C = &mut *(ctxt as *mut C);
137+
ctxt.has_data(&CStr::from_ptr(key).to_string_lossy())
138+
}
139+
140+
unsafe extern "C" fn cb_get_data<C: SecretsProvider>(
141+
ctxt: *mut c_void,
142+
key: *const c_char,
143+
) -> *mut c_char {
144+
let ctxt: &mut C = &mut *(ctxt as *mut C);
145+
let result = ctxt.get_data(&CStr::from_ptr(key).to_string_lossy());
146+
BnString::into_raw(BnString::new(result))
147+
}
148+
149+
unsafe extern "C" fn cb_store_data<C: SecretsProvider>(
150+
ctxt: *mut c_void,
151+
key: *const c_char,
152+
data: *const c_char,
153+
) -> bool {
154+
let ctxt: &mut C = &mut *(ctxt as *mut C);
155+
let key = CStr::from_ptr(key).to_string_lossy();
156+
let data = CStr::from_ptr(data).to_string_lossy();
157+
ctxt.store_data(&key, &data)
158+
}
159+
160+
unsafe extern "C" fn cb_delete_data<C: SecretsProvider>(
161+
ctxt: *mut c_void,
162+
key: *const c_char,
163+
) -> bool {
164+
let ctxt: &mut C = &mut *(ctxt as *mut C);
165+
ctxt.delete_data(&CStr::from_ptr(key).to_string_lossy())
166+
}

rust/tests/secrets_provider.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use binaryninja::headless::Session;
2+
use binaryninja::secrets_provider::{CoreSecretsProvider, SecretsProvider};
3+
use rstest::*;
4+
5+
#[fixture]
6+
fn session() -> Session {
7+
Session::new().expect("Failed to initialize session")
8+
}
9+
10+
#[rstest]
11+
fn list_secrets_provider(_session: Session) {
12+
let providers = CoreSecretsProvider::all();
13+
for provider in &providers {
14+
println!("{}", provider.name());
15+
}
16+
}
17+
18+
struct MySecretsProvider {}
19+
20+
impl SecretsProvider for MySecretsProvider {
21+
fn has_data(&mut self, key: &str) -> bool {
22+
key == "my_key"
23+
}
24+
25+
fn get_data(&mut self, key: &str) -> String {
26+
if key == "my_key" { "my_value" } else { "" }.to_string()
27+
}
28+
29+
fn store_data(&mut self, _key: &str, _data: &str) -> bool {
30+
false
31+
}
32+
33+
fn delete_data(&mut self, _key: &str) -> bool {
34+
false
35+
}
36+
}
37+
38+
#[rstest]
39+
fn custom_secrets_provider(_session: Session) {
40+
let my_provider = CoreSecretsProvider::new("MySecretsProvider", MySecretsProvider {});
41+
assert!(my_provider.has_data("my_key"));
42+
assert!(!my_provider.has_data("not_my_key"));
43+
assert_eq!(my_provider.get_data("my_key").as_str(), "my_value");
44+
}

0 commit comments

Comments
 (0)