1use crate::agent::AgentHandle;
16use crate::api::runtime::{
17 agent_sign_with_handle, export_key_openssh_pem, export_key_openssh_pub, rotate_key,
18};
19use crate::config::{EnvironmentConfig, KeychainConfig};
20use crate::config::{current_algorithm, set_encryption_algorithm};
21use crate::crypto::EncryptionAlgorithm;
22use crate::crypto::encryption::{decrypt_bytes, encrypt_bytes};
23use crate::crypto::signer::extract_seed_from_key_bytes;
24use crate::crypto::signer::{decrypt_keypair, encrypt_keypair};
25use crate::error::AgentError;
26use crate::storage::keychain::{
27 IdentityDID, KeyAlias, KeyRole, KeyStorage, get_platform_keychain_with_config,
28};
29use log::{debug, error, info, warn};
30use parking_lot::RwLock;
31use std::ffi::{CStr, CString};
32use std::os::raw::{c_char, c_int, c_uchar};
33use std::panic;
34use std::path::PathBuf;
35use std::ptr;
36use std::slice;
37use std::sync::{Arc, LazyLock};
38
39pub const FFI_OK: c_int = 0;
43pub const FFI_ERR_INVALID_UTF8: c_int = -1;
45pub const FFI_ERR_AGENT_NOT_INITIALIZED: c_int = -2;
47pub const FFI_ERR_NULL_CONTEXT: c_int = -3;
49pub const FFI_ERR_KEYCHAIN: c_int = 5;
51pub const FFI_ERR_PANIC: c_int = -127;
53
54static FFI_AGENT: LazyLock<RwLock<Option<Arc<AgentHandle>>>> = LazyLock::new(|| RwLock::new(None));
61
62#[unsafe(no_mangle)]
74#[allow(clippy::disallowed_methods)] pub unsafe extern "C" fn ffi_init_agent(socket_path: *const c_char) -> c_int {
76 let result = panic::catch_unwind(|| {
77 let path_str = match unsafe { c_str_to_str_safe(socket_path) } {
78 Ok(s) if !s.is_empty() => s,
79 Ok(_) => {
80 let home = match dirs::home_dir() {
82 Some(h) => h,
83 None => {
84 error!("FFI ffi_init_agent: Could not determine home directory");
85 return 1;
86 }
87 };
88 let default_path = home.join(".auths").join("agent.sock");
89 let handle = Arc::new(AgentHandle::new(default_path));
90 let mut guard = FFI_AGENT.write();
91 *guard = Some(handle);
92 info!("FFI agent initialized with default socket path");
93 return 0;
94 }
95 Err(code) => return code,
96 };
97
98 let socket = PathBuf::from(path_str);
99 let handle = Arc::new(AgentHandle::new(socket));
100
101 let mut guard = FFI_AGENT.write();
102 *guard = Some(handle);
103 info!("FFI agent initialized with socket path: {}", path_str);
104 0
105 });
106 result.unwrap_or_else(|_| {
107 error!("FFI ffi_init_agent: panic occurred");
108 FFI_ERR_PANIC
109 })
110}
111
112#[unsafe(no_mangle)]
124pub unsafe extern "C" fn ffi_shutdown_agent() -> c_int {
125 let result = panic::catch_unwind(|| {
126 let mut guard = FFI_AGENT.write();
127 if let Some(handle) = guard.take() {
128 if let Err(e) = handle.shutdown() {
129 warn!("FFI ffi_shutdown_agent: Shutdown returned error: {}", e);
130 }
131 info!("FFI agent shut down");
132 } else {
133 debug!("FFI ffi_shutdown_agent: Agent was not initialized");
134 }
135 0
136 });
137 result.unwrap_or_else(|_| {
138 error!("FFI ffi_shutdown_agent: panic occurred");
139 FFI_ERR_PANIC
140 })
141}
142
143fn get_ffi_agent() -> Option<Arc<AgentHandle>> {
147 FFI_AGENT.read().clone()
148}
149
150pub unsafe fn c_str_to_str_safe<'a>(ptr: *const c_char) -> Result<&'a str, c_int> {
160 if ptr.is_null() {
161 Ok("")
162 } else {
163 unsafe {
165 CStr::from_ptr(ptr)
166 .to_str()
167 .map_err(|_| FFI_ERR_INVALID_UTF8)
168 }
169 }
170}
171
172pub unsafe fn result_to_c_int<T, E: std::fmt::Display>(
180 result: Result<T, E>,
181 fn_name: &str,
182) -> c_int {
183 match result {
184 Ok(_) => 0,
185 Err(e) => {
186 error!("FFI call {} failed: {}", fn_name, e);
187 1 }
189 }
190}
191
192pub unsafe fn malloc_and_copy_bytes(data: &[u8], out_len: *mut usize) -> *mut u8 {
202 unsafe {
204 if out_len.is_null() {
205 error!("malloc_and_copy_bytes failed: out_len pointer is null.");
206 return ptr::null_mut();
207 }
208 *out_len = data.len();
210 let ptr = libc::malloc(data.len()) as *mut u8;
212 if ptr.is_null() {
213 error!(
214 "malloc_and_copy_bytes failed: malloc returned null for size {}",
215 data.len()
216 );
217 *out_len = 0; return ptr::null_mut();
219 }
220 ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len());
222 ptr
223 }
224}
225
226fn malloc_and_copy_string(s: &str) -> *mut c_char {
235 match CString::new(s) {
236 Ok(c_string) => c_string.into_raw(), Err(e) => {
238 error!(
239 "malloc_and_copy_string failed: CString creation error: {}",
240 e
241 );
242 ptr::null_mut()
243 }
244 }
245}
246
247pub const FFI_CONTEXT_CONFIG_MAX_BYTES: usize = 64 * 1024;
251
252pub struct AuthsFfiContext {
259 env: EnvironmentConfig,
260}
261
262#[derive(Debug, Default, serde::Deserialize)]
265#[serde(deny_unknown_fields)]
266struct FfiContextConfig {
267 auths_home: Option<PathBuf>,
268 keychain_backend: Option<String>,
269 keychain_file: Option<PathBuf>,
270 keychain_passphrase: Option<String>,
271 ssh_agent_socket: Option<PathBuf>,
272}
273
274impl FfiContextConfig {
275 fn into_environment(self) -> EnvironmentConfig {
276 let mut builder = EnvironmentConfig::builder().keychain(KeychainConfig {
277 backend: self.keychain_backend,
278 file_path: self.keychain_file,
279 passphrase: self.keychain_passphrase,
280 });
281 if let Some(home) = self.auths_home {
282 builder = builder.auths_home(home);
283 }
284 if let Some(socket) = self.ssh_agent_socket {
285 builder = builder.ssh_agent_socket(socket);
286 }
287 builder.build()
288 }
289}
290
291#[unsafe(no_mangle)]
317pub unsafe extern "C" fn ffi_context_new(config_json: *const c_char) -> *mut AuthsFfiContext {
318 let result = panic::catch_unwind(|| {
319 let json = match unsafe { c_str_to_str_safe(config_json) } {
320 Ok(s) => s,
321 Err(_) => {
322 error!("FFI ffi_context_new: config_json is not valid UTF-8");
323 return ptr::null_mut();
324 }
325 };
326 if json.len() > FFI_CONTEXT_CONFIG_MAX_BYTES {
327 error!(
328 "FFI ffi_context_new: config_json length {} exceeds maximum {}",
329 json.len(),
330 FFI_CONTEXT_CONFIG_MAX_BYTES
331 );
332 return ptr::null_mut();
333 }
334 let env = if json.is_empty() {
335 EnvironmentConfig::from_env()
336 } else {
337 match serde_json::from_str::<FfiContextConfig>(json) {
338 Ok(config) => config.into_environment(),
339 Err(e) => {
340 error!("FFI ffi_context_new: invalid config JSON: {}", e);
341 return ptr::null_mut();
342 }
343 }
344 };
345 Box::into_raw(Box::new(AuthsFfiContext { env }))
346 });
347 result.unwrap_or_else(|_| {
348 error!("FFI ffi_context_new: panic occurred");
349 ptr::null_mut()
350 })
351}
352
353#[unsafe(no_mangle)]
360pub unsafe extern "C" fn ffi_context_free(ctx: *mut AuthsFfiContext) {
361 let _ = panic::catch_unwind(|| {
362 if !ctx.is_null() {
363 drop(unsafe { Box::from_raw(ctx) });
365 }
366 });
367 }
369
370unsafe fn keychain_from_context(
376 ctx: *const AuthsFfiContext,
377 fn_name: &str,
378) -> Result<Box<dyn KeyStorage + Send + Sync>, c_int> {
379 if ctx.is_null() {
380 error!("FFI {}: null context — call ffi_context_new first", fn_name);
381 return Err(FFI_ERR_NULL_CONTEXT);
382 }
383 let env = unsafe { &(*ctx).env };
385 get_platform_keychain_with_config(env).map_err(|e| {
386 error!("FFI {}: Failed to get platform keychain: {}", fn_name, e);
387 FFI_ERR_KEYCHAIN
388 })
389}
390
391#[unsafe(no_mangle)]
403pub unsafe extern "C" fn ffi_key_exists(ctx: *const AuthsFfiContext, alias: *const c_char) -> bool {
404 let result = panic::catch_unwind(|| {
405 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
406 Ok(s) => s,
407 Err(_) => return false,
408 };
409 if alias_str.is_empty() {
410 return false;
411 }
412 let keychain = match unsafe { keychain_from_context(ctx, "ffi_key_exists") } {
413 Ok(kc) => kc,
414 Err(_) => return false,
415 };
416 let alias = KeyAlias::new_unchecked(alias_str);
417 keychain.load_key(&alias).is_ok()
418 });
419 result.unwrap_or_else(|_| {
420 error!("FFI ffi_key_exists: panic occurred");
421 false
422 })
423}
424
425#[unsafe(no_mangle)]
445pub unsafe extern "C" fn ffi_import_key(
446 ctx: *const AuthsFfiContext,
447 alias: *const c_char, key_ptr: *const c_uchar, key_len: usize,
450 controller_did: *const c_char, passphrase: *const c_char, ) -> c_int {
453 let result = panic::catch_unwind(|| {
454 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
456 Ok(s) => s,
457 Err(code) => return code,
458 };
459 let did_str = match unsafe { c_str_to_str_safe(controller_did) } {
460 Ok(s) => s,
461 Err(code) => return code,
462 };
463 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
464 Ok(s) => s,
465 Err(code) => return code,
466 };
467 let key_data = unsafe { slice::from_raw_parts(key_ptr, key_len) };
468
469 if alias_str.is_empty()
471 || did_str.is_empty()
472 || !did_str.starts_with("did:")
473 || pass_str.is_empty()
474 {
475 error!(
476 "FFI import failed: Invalid arguments (alias='{}', did='{}', passphrase empty={}).",
477 alias_str,
478 did_str,
479 pass_str.is_empty()
480 );
481 return 1;
482 }
483
484 if let Err(e) = extract_seed_from_key_bytes(key_data) {
486 error!(
487 "FFI import failed: Provided key data is not valid Ed25519 for alias '{}': {}",
488 alias_str, e
489 );
490 return 2;
491 }
492
493 let encrypt_result = encrypt_keypair(key_data, pass_str);
495 let encrypted_key = match encrypt_result {
496 Ok(enc) => enc,
497 Err(e) => {
498 error!(
499 "FFI import failed: Encryption error for alias '{}': {}",
500 alias_str, e
501 );
502 return 4; }
504 };
505
506 #[allow(clippy::disallowed_methods)]
507 let did_string = IdentityDID::new_unchecked(did_str.to_string());
509 let alias = KeyAlias::new_unchecked(alias_str);
510
511 let keychain = match unsafe { keychain_from_context(ctx, "ffi_import_key") } {
513 Ok(kc) => kc,
514 Err(code) => return code,
515 };
516 let store_result =
517 keychain.store_key(&alias, &did_string, KeyRole::Primary, &encrypted_key);
518
519 unsafe { result_to_c_int(store_result, "ffi_import_key") }
520 });
521 result.unwrap_or_else(|_| {
522 error!("FFI ffi_import_key: panic occurred");
523 FFI_ERR_PANIC
524 })
525}
526
527#[unsafe(no_mangle)]
546pub unsafe extern "C" fn ffi_rotate_key(
547 ctx: *const AuthsFfiContext,
548 alias: *const c_char,
549 new_passphrase: *const c_char,
550) -> c_int {
551 let result = panic::catch_unwind(|| {
552 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
553 Ok(s) => s,
554 Err(code) => return code,
555 };
556 let pass_str = match unsafe { c_str_to_str_safe(new_passphrase) } {
557 Ok(s) => s,
558 Err(code) => return code,
559 };
560
561 let keychain = match unsafe { keychain_from_context(ctx, "ffi_rotate_key") } {
563 Ok(kc) => kc,
564 Err(code) => return code,
565 };
566 let rotate_result = rotate_key(alias_str, pass_str, keychain.as_ref());
567
568 match rotate_result {
570 Ok(()) => 0,
571 Err(e) => {
572 error!("FFI rotate_key failed for alias '{}': {}", alias_str, e);
573 match e {
574 AgentError::InvalidInput(_) => 1,
575 AgentError::KeyNotFound => 2,
576 AgentError::CryptoError(_) | AgentError::KeyDeserializationError(_) => 3,
577 _ => 4, }
579 }
580 }
581 });
582 result.unwrap_or_else(|_| {
583 error!("FFI ffi_rotate_key: panic occurred");
584 FFI_ERR_PANIC
585 })
586}
587
588#[unsafe(no_mangle)]
601pub unsafe extern "C" fn ffi_export_encrypted_key(
602 ctx: *const AuthsFfiContext,
603 alias: *const c_char,
604 out_len: *mut usize,
605) -> *mut u8 {
606 let result = panic::catch_unwind(|| {
607 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
608 Ok(s) => s,
609 Err(_) => return ptr::null_mut(),
610 };
611 if alias_str.is_empty() || out_len.is_null() {
612 if !out_len.is_null() {
613 unsafe { *out_len = 0 };
614 }
615 return ptr::null_mut();
616 }
617 unsafe { *out_len = 0 };
618
619 let keychain = match unsafe { keychain_from_context(ctx, "ffi_export_encrypted_key") } {
620 Ok(kc) => kc,
621 Err(_) => return ptr::null_mut(),
622 };
623 let alias = KeyAlias::new_unchecked(alias_str);
624 match keychain.load_key(&alias) {
625 Ok((_identity_did, _role, encrypted_data)) => {
626 debug!(
627 "FFI export encrypted key successful for alias '{}'",
628 alias_str
629 );
630 unsafe { malloc_and_copy_bytes(&encrypted_data, out_len) }
631 }
632 Err(e) => {
633 error!(
634 "FFI export encrypted key failed for alias '{}': {}",
635 alias_str, e
636 );
637 ptr::null_mut()
638 }
639 }
640 });
641 result.unwrap_or_else(|_| {
642 error!("FFI ffi_export_encrypted_key: panic occurred");
643 ptr::null_mut()
644 })
645}
646
647#[unsafe(no_mangle)]
660pub unsafe extern "C" fn ffi_export_private_key_with_passphrase(
661 ctx: *const AuthsFfiContext,
662 alias: *const c_char,
663 passphrase: *const c_char,
664 out_len: *mut usize,
665) -> *mut u8 {
666 let result = panic::catch_unwind(|| {
667 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
668 Ok(s) => s,
669 Err(_) => return ptr::null_mut(),
670 };
671 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
672 Ok(s) => s,
673 Err(_) => return ptr::null_mut(),
674 };
675
676 if alias_str.is_empty() || out_len.is_null() {
677 if !out_len.is_null() {
678 unsafe { *out_len = 0 };
679 }
680 return ptr::null_mut();
681 }
682 unsafe { *out_len = 0 };
683
684 let keychain =
685 match unsafe { keychain_from_context(ctx, "ffi_export_private_key_with_passphrase") } {
686 Ok(kc) => kc,
687 Err(_) => return ptr::null_mut(),
688 };
689 let alias = KeyAlias::new_unchecked(alias_str);
690 let export_result = || -> Result<Vec<u8>, AgentError> {
691 if keychain.is_hardware_backend() {
692 return Err(AgentError::BackendUnavailable {
693 backend: keychain.backend_name(),
694 reason: "hardware-backed keys (e.g. Secure Enclave) cannot be exported via this FFI path".to_string(),
695 });
696 }
697 let (_controller_did, _role, encrypted_bytes) = keychain.load_key(&alias)?;
698 let _decrypted_pkcs8 = decrypt_keypair(&encrypted_bytes, pass_str)?;
700 debug!(
701 "FFI export_private_key_with_passphrase: Passphrase verified for alias '{}'",
702 alias_str
703 );
704 Ok(encrypted_bytes)
705 }();
706
707 match export_result {
708 Ok(encrypted_data) => unsafe { malloc_and_copy_bytes(&encrypted_data, out_len) },
709 Err(e) => {
710 if !matches!(e, AgentError::IncorrectPassphrase) {
711 error!(
712 "FFI export_private_key_with_passphrase failed for alias '{}': {}",
713 alias_str, e
714 );
715 } else {
716 debug!(
717 "FFI export_private_key_with_passphrase: Incorrect passphrase for alias '{}'",
718 alias_str
719 );
720 }
721 ptr::null_mut()
722 }
723 }
724 });
725 result.unwrap_or_else(|_| {
726 error!("FFI ffi_export_private_key_with_passphrase: panic occurred");
727 ptr::null_mut()
728 })
729}
730
731#[unsafe(no_mangle)]
743pub unsafe extern "C" fn ffi_export_private_key_openssh(
744 ctx: *const AuthsFfiContext,
745 alias: *const c_char,
746 passphrase: *const c_char,
747) -> *mut c_char {
748 let result = panic::catch_unwind(|| {
749 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
750 Ok(s) => s,
751 Err(_) => return ptr::null_mut(),
752 };
753 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
754 Ok(s) => s,
755 Err(_) => return ptr::null_mut(),
756 };
757
758 if alias_str.is_empty() {
759 return ptr::null_mut();
760 }
761
762 let keychain = match unsafe { keychain_from_context(ctx, "ffi_export_private_key_openssh") }
763 {
764 Ok(kc) => kc,
765 Err(_) => return ptr::null_mut(),
766 };
767 match export_key_openssh_pem(alias_str, pass_str, keychain.as_ref()) {
768 Ok(pem_zeroizing) => malloc_and_copy_string(pem_zeroizing.as_str()),
769 Err(e) => {
770 error!("FFI export PEM failed for alias '{}': {}", alias_str, e);
771 ptr::null_mut()
772 }
773 }
774 });
775 result.unwrap_or_else(|_| {
776 error!("FFI ffi_export_private_key_openssh: panic occurred");
777 ptr::null_mut()
778 })
779}
780
781#[unsafe(no_mangle)]
793pub unsafe extern "C" fn ffi_export_public_key_openssh(
794 ctx: *const AuthsFfiContext,
795 alias: *const c_char,
796 passphrase: *const c_char,
797) -> *mut c_char {
798 let result = panic::catch_unwind(|| {
799 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
800 Ok(s) => s,
801 Err(_) => return ptr::null_mut(),
802 };
803 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
804 Ok(s) => s,
805 Err(_) => return ptr::null_mut(),
806 };
807
808 if alias_str.is_empty() {
809 return ptr::null_mut();
810 }
811
812 let keychain = match unsafe { keychain_from_context(ctx, "ffi_export_public_key_openssh") }
813 {
814 Ok(kc) => kc,
815 Err(_) => return ptr::null_mut(),
816 };
817 match export_key_openssh_pub(alias_str, pass_str, keychain.as_ref()) {
818 Ok(formatted_pubkey) => malloc_and_copy_string(&formatted_pubkey),
819 Err(e) => {
820 error!(
821 "FFI export OpenSSH pubkey failed for alias '{}': {}",
822 alias_str, e
823 );
824 ptr::null_mut()
825 }
826 }
827 });
828 result.unwrap_or_else(|_| {
829 error!("FFI ffi_export_public_key_openssh: panic occurred");
830 ptr::null_mut()
831 })
832}
833
834#[unsafe(no_mangle)]
848pub unsafe extern "C" fn ffi_agent_sign(
849 pubkey_ptr: *const c_uchar,
850 pubkey_len: usize,
851 data_ptr: *const c_uchar,
852 data_len: usize,
853 out_len: *mut usize,
854) -> *mut u8 {
855 let result = panic::catch_unwind(|| {
856 if pubkey_ptr.is_null() || data_ptr.is_null() || out_len.is_null() {
857 if !out_len.is_null() {
858 unsafe { *out_len = 0 };
859 }
860 error!("FFI agent_sign failed: Null pointer argument.");
861 return ptr::null_mut();
862 }
863
864 let handle = match get_ffi_agent() {
866 Some(h) => h,
867 None => {
868 error!(
869 "FFI agent_sign failed: Agent not initialized. Call ffi_init_agent() first."
870 );
871 unsafe { *out_len = 0 };
872 return ptr::null_mut();
873 }
874 };
875
876 let pubkey_slice = unsafe { slice::from_raw_parts(pubkey_ptr, pubkey_len) };
877 let data_slice = unsafe { slice::from_raw_parts(data_ptr, data_len) };
878 unsafe { *out_len = 0 };
879
880 match agent_sign_with_handle(&handle, pubkey_slice, data_slice) {
881 Ok(signature_bytes) => unsafe { malloc_and_copy_bytes(&signature_bytes, out_len) },
882 Err(e) => {
883 error!("FFI agent_sign failed: {}", e);
884 if matches!(e, AgentError::KeyNotFound) {
885 warn!(
886 "FFI agent_sign: Key not found in agent for pubkey prefix {:x?}",
887 &pubkey_slice[..std::cmp::min(pubkey_slice.len(), 8)]
888 );
889 }
890 ptr::null_mut()
891 }
892 }
893 });
894 result.unwrap_or_else(|_| {
895 error!("FFI ffi_agent_sign: panic occurred");
896 ptr::null_mut()
897 })
898}
899
900#[unsafe(no_mangle)]
913pub unsafe extern "C" fn ffi_encrypt_data(
914 passphrase: *const c_char,
915 input_ptr: *const u8,
916 input_len: usize,
917 out_len: *mut usize,
918) -> *mut u8 {
919 let result = panic::catch_unwind(|| {
920 if input_ptr.is_null() || out_len.is_null() {
921 if !out_len.is_null() {
922 unsafe { *out_len = 0 };
923 }
924 error!("FFI encrypt_data failed: Null pointer argument.");
925 return ptr::null_mut();
926 }
927 let pass = match unsafe { c_str_to_str_safe(passphrase) } {
928 Ok(s) => s,
929 Err(_) => return ptr::null_mut(),
930 };
931 let input = unsafe { slice::from_raw_parts(input_ptr, input_len) };
932 unsafe { *out_len = 0 };
933 let algo = current_algorithm();
934
935 match encrypt_bytes(input, pass, algo) {
936 Ok(encrypted) => unsafe { malloc_and_copy_bytes(&encrypted, out_len) },
937 Err(e) => {
938 error!("FFI encrypt_data failed: {}", e);
939 ptr::null_mut()
940 }
941 }
942 });
943 result.unwrap_or_else(|_| {
944 error!("FFI ffi_encrypt_data: panic occurred");
945 ptr::null_mut()
946 })
947}
948
949#[unsafe(no_mangle)]
960pub unsafe extern "C" fn ffi_decrypt_data(
961 passphrase: *const c_char,
962 input_ptr: *const u8,
963 input_len: usize,
964 out_len: *mut usize,
965) -> *mut u8 {
966 let result = panic::catch_unwind(|| {
967 if input_ptr.is_null() || out_len.is_null() {
968 if !out_len.is_null() {
969 unsafe { *out_len = 0 };
970 }
971 error!("FFI decrypt_data failed: Null pointer argument.");
972 return ptr::null_mut();
973 }
974 let pass = match unsafe { c_str_to_str_safe(passphrase) } {
975 Ok(s) => s,
976 Err(_) => return ptr::null_mut(),
977 };
978 let input = unsafe { slice::from_raw_parts(input_ptr, input_len) };
979 unsafe { *out_len = 0 };
980
981 match decrypt_bytes(input, pass) {
982 Ok(decrypted) => unsafe { malloc_and_copy_bytes(&decrypted, out_len) },
983 Err(e) => {
984 if !matches!(e, AgentError::IncorrectPassphrase) {
985 error!("FFI decrypt_data failed: {}", e);
986 } else {
987 debug!("FFI decrypt_data: Incorrect passphrase provided.");
988 }
989 ptr::null_mut()
990 }
991 }
992 });
993 result.unwrap_or_else(|_| {
994 error!("FFI ffi_decrypt_data: panic occurred");
995 ptr::null_mut()
996 })
997}
998
999#[unsafe(no_mangle)]
1008pub unsafe extern "C" fn ffi_free_str(ptr: *mut c_char) {
1009 let _ = panic::catch_unwind(|| {
1010 if !ptr.is_null() {
1011 let _ = unsafe { CString::from_raw(ptr) };
1014 }
1015 });
1016 }
1018
1019#[unsafe(no_mangle)]
1029pub unsafe extern "C" fn ffi_free_bytes(ptr: *mut u8, _len: usize) {
1030 let _ = panic::catch_unwind(|| {
1031 if !ptr.is_null() {
1032 unsafe { libc::free(ptr as *mut libc::c_void) };
1033 }
1034 });
1035 }
1037
1038#[unsafe(no_mangle)]
1045pub unsafe extern "C" fn ffi_set_encryption_algorithm(level: c_int) {
1046 let _ = panic::catch_unwind(|| {
1047 let algo = match level {
1048 1 => EncryptionAlgorithm::AesGcm256,
1049 2 => EncryptionAlgorithm::ChaCha20Poly1305,
1050 _ => {
1051 warn!(
1052 "FFI: Unknown encryption level {}, defaulting to AES-GCM.",
1053 level
1054 );
1055 EncryptionAlgorithm::AesGcm256
1056 }
1057 };
1058 info!("FFI: Setting global encryption algorithm to {:?}", algo);
1059 set_encryption_algorithm(algo);
1060 });
1061 }
1063
1064