google-labs-jules[bot] 9da28a6416 feat(spire-ffi): implement native Argon2id derivation
- Added `argon2` v0.5 dependency to `spire_ffi/Cargo.toml`
- Implemented `argon2id_derive` FFI function in `spire_ffi/src/lib.rs` with C-ABI.
- Added Deno FFI binding `deriveArgon2idKey` in `server/spire_ffi.ts` with `nonblocking: true` to prevent stalling the event loop.
- Pre-allocates output buffer on the Deno side as the standard FFI pattern.
- Included fallback mock behavior when `libspire_ffi.so` is not loaded, returning a 32-byte 0xaa filled array.
- Updated unit tests in `server/spire_ffi.test.ts` to test mock usage and successful generation.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-24 04:19:48 +00:00

211 lines
6.3 KiB
Rust

use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::Path;
use tokio::net::UnixStream;
use tonic::transport::{Endpoint, Uri};
use tower::service_fn;
// Generated by tonic-build
pub mod workload {
tonic::include_proto!("_");
}
use workload::spiffe_workload_api_client::SpiffeWorkloadApiClient;
use workload::X509svidRequest;
#[repr(C)]
pub struct SvidResponseC {
spiffe_id: *mut c_char,
x509_svid: *mut u8,
x509_svid_len: usize,
x509_svid_key: *mut u8,
x509_svid_key_len: usize,
bundle: *mut u8,
bundle_len: usize,
error: *mut c_char,
}
impl SvidResponseC {
fn with_error(err_msg: &str) -> Self {
let err_c = match CString::new(err_msg) {
Ok(c) => c.into_raw(),
Err(_) => std::ptr::null_mut(),
};
SvidResponseC {
spiffe_id: std::ptr::null_mut(),
x509_svid: std::ptr::null_mut(),
x509_svid_len: 0,
x509_svid_key: std::ptr::null_mut(),
x509_svid_key_len: 0,
bundle: std::ptr::null_mut(),
bundle_len: 0,
error: err_c,
}
}
}
#[no_mangle]
pub extern "C" fn argon2id_derive(
password_ptr: *const u8,
password_len: usize,
salt_ptr: *const u8,
salt_len: usize,
iterations: u32,
memory_kb: u32,
out_ptr: *mut u8,
out_len: usize,
) -> i32 {
if password_ptr.is_null() || salt_ptr.is_null() || out_ptr.is_null() || out_len == 0 {
return -1;
}
let password = unsafe { std::slice::from_raw_parts(password_ptr, password_len) };
let salt = unsafe { std::slice::from_raw_parts(salt_ptr, salt_len) };
let out = unsafe { std::slice::from_raw_parts_mut(out_ptr, out_len) };
let params = argon2::Params::new(
memory_kb,
iterations,
argon2::Params::DEFAULT_P_COST,
Some(out_len),
);
let params = match params {
Ok(p) => p,
Err(_) => return -1,
};
let argon2 = argon2::Argon2::new(
argon2::Algorithm::Argon2id,
argon2::Version::V0x13,
params,
);
match argon2.hash_password_into(password, salt, out) {
Ok(_) => 0,
Err(_) => -2,
}
}
async fn fetch_svid_async(socket_path: &str) -> Result<SvidResponseC, Box<dyn std::error::Error>> {
let path = Path::new(socket_path).to_path_buf();
let channel = Endpoint::try_from("http://[::]:50051")?
.connect_with_connector(service_fn(move |_: Uri| {
let path_clone = path.clone();
async move { UnixStream::connect(path_clone).await }
}))
.await?;
let mut client = SpiffeWorkloadApiClient::new(channel);
let request = tonic::Request::new(X509svidRequest {});
let mut stream = client.fetch_x509svid(request).await?.into_inner();
if let Some(response) = stream.message().await? {
if let Some(svid) = response.svids.first() {
let spiffe_id_c = CString::new(svid.spiffe_id.clone())?.into_raw();
let mut svid_box = svid.x509_svid.clone().into_boxed_slice();
let x509_svid = svid_box.as_mut_ptr();
let x509_svid_len = svid_box.len();
std::mem::forget(svid_box);
let mut key_box = svid.x509_svid_key.clone().into_boxed_slice();
let x509_svid_key = key_box.as_mut_ptr();
let x509_svid_key_len = key_box.len();
std::mem::forget(key_box);
let mut bundle_box = svid.bundle.clone().into_boxed_slice();
let bundle = bundle_box.as_mut_ptr();
let bundle_len = bundle_box.len();
std::mem::forget(bundle_box);
return Ok(SvidResponseC {
spiffe_id: spiffe_id_c,
x509_svid,
x509_svid_len,
x509_svid_key,
x509_svid_key_len,
bundle,
bundle_len,
error: std::ptr::null_mut(),
});
}
}
Err("No SVID received from Workload API".into())
}
#[no_mangle]
pub extern "C" fn fetch_svid(socket_path_ptr: *const c_char) -> *mut SvidResponseC {
if socket_path_ptr.is_null() {
let resp = Box::new(SvidResponseC::with_error("socket_path_ptr is null"));
return Box::into_raw(resp);
}
let socket_path = unsafe {
match CStr::from_ptr(socket_path_ptr).to_str() {
Ok(s) => s.to_string(),
Err(e) => {
let resp = Box::new(SvidResponseC::with_error(&format!("Invalid UTF-8 in socket path: {}", e)));
return Box::into_raw(resp);
}
}
};
let rt = match tokio::runtime::Builder::new_current_thread().enable_all().build() {
Ok(rt) => rt,
Err(e) => {
let resp = Box::new(SvidResponseC::with_error(&format!("Failed to build tokio runtime: {}", e)));
return Box::into_raw(resp);
}
};
let result = match rt.block_on(fetch_svid_async(&socket_path)) {
Ok(data) => data,
Err(e) => SvidResponseC::with_error(&e.to_string()),
};
Box::into_raw(Box::new(result))
}
#[no_mangle]
pub extern "C" fn free_svid(ptr: *mut SvidResponseC) {
if ptr.is_null() {
return;
}
unsafe {
let mut resp = Box::from_raw(ptr);
if !resp.spiffe_id.is_null() {
let _ = CString::from_raw(resp.spiffe_id);
resp.spiffe_id = std::ptr::null_mut();
}
if !resp.error.is_null() {
let _ = CString::from_raw(resp.error);
resp.error = std::ptr::null_mut();
}
if !resp.x509_svid.is_null() && resp.x509_svid_len > 0 {
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(resp.x509_svid, resp.x509_svid_len));
resp.x509_svid = std::ptr::null_mut();
resp.x509_svid_len = 0;
}
if !resp.x509_svid_key.is_null() && resp.x509_svid_key_len > 0 {
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(resp.x509_svid_key, resp.x509_svid_key_len));
resp.x509_svid_key = std::ptr::null_mut();
resp.x509_svid_key_len = 0;
}
if !resp.bundle.is_null() && resp.bundle_len > 0 {
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(resp.bundle, resp.bundle_len));
resp.bundle = std::ptr::null_mut();
resp.bundle_len = 0;
}
}
}