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;
20use crate::config::{current_algorithm, set_encryption_algorithm};
21use crate::crypto::EncryptionAlgorithm;
22use crate::crypto::encryption::{decrypt_bytes, encrypt_bytes_argon2};
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, KeyStorage, get_platform_keychain_with_config,
28};
29use log::{debug, error, info, warn};
30use once_cell::sync::Lazy;
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, RwLock};
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_PANIC: c_int = -127;
49
50static FFI_AGENT: Lazy<RwLock<Option<Arc<AgentHandle>>>> = Lazy::new(|| RwLock::new(None));
57
58#[unsafe(no_mangle)]
70pub unsafe extern "C" fn ffi_init_agent(socket_path: *const c_char) -> c_int {
71 let result = panic::catch_unwind(|| {
72 let path_str = match unsafe { c_str_to_str_safe(socket_path) } {
73 Ok(s) if !s.is_empty() => s,
74 Ok(_) => {
75 let home = match dirs::home_dir() {
77 Some(h) => h,
78 None => {
79 error!("FFI ffi_init_agent: Could not determine home directory");
80 return 1;
81 }
82 };
83 let default_path = home.join(".auths").join("agent.sock");
84 let handle = Arc::new(AgentHandle::new(default_path));
85 if let Ok(mut guard) = FFI_AGENT.write() {
86 *guard = Some(handle);
87 info!("FFI agent initialized with default socket path");
88 return 0;
89 }
90 error!("FFI ffi_init_agent: Failed to acquire write lock");
91 return 1;
92 }
93 Err(code) => return code,
94 };
95
96 let socket = PathBuf::from(path_str);
97 let handle = Arc::new(AgentHandle::new(socket));
98
99 if let Ok(mut guard) = FFI_AGENT.write() {
100 *guard = Some(handle);
101 info!("FFI agent initialized with socket path: {}", path_str);
102 0
103 } else {
104 error!("FFI ffi_init_agent: Failed to acquire write lock");
105 1
106 }
107 });
108 result.unwrap_or_else(|_| {
109 error!("FFI ffi_init_agent: panic occurred");
110 FFI_ERR_PANIC
111 })
112}
113
114#[unsafe(no_mangle)]
126pub unsafe extern "C" fn ffi_shutdown_agent() -> c_int {
127 let result = panic::catch_unwind(|| {
128 if let Ok(mut guard) = FFI_AGENT.write() {
129 if let Some(handle) = guard.take() {
130 if let Err(e) = handle.shutdown() {
132 warn!("FFI ffi_shutdown_agent: Shutdown returned error: {}", e);
133 }
135 info!("FFI agent shut down");
136 } else {
137 debug!("FFI ffi_shutdown_agent: Agent was not initialized");
138 }
139 0
140 } else {
141 error!("FFI ffi_shutdown_agent: Failed to acquire write lock");
142 1
143 }
144 });
145 result.unwrap_or_else(|_| {
146 error!("FFI ffi_shutdown_agent: panic occurred");
147 FFI_ERR_PANIC
148 })
149}
150
151fn get_ffi_agent() -> Option<Arc<AgentHandle>> {
155 FFI_AGENT.read().ok().and_then(|guard| guard.clone())
156}
157
158pub unsafe fn c_str_to_str_safe<'a>(ptr: *const c_char) -> Result<&'a str, c_int> {
168 if ptr.is_null() {
169 Ok("")
170 } else {
171 unsafe {
173 CStr::from_ptr(ptr)
174 .to_str()
175 .map_err(|_| FFI_ERR_INVALID_UTF8)
176 }
177 }
178}
179
180#[deprecated(note = "Use c_str_to_str_safe for panic-safe FFI")]
192#[allow(clippy::expect_used)] pub unsafe fn c_str_to_str<'a>(ptr: *const c_char) -> &'a str {
194 if ptr.is_null() {
195 ""
196 } else {
197 unsafe {
199 CStr::from_ptr(ptr)
200 .to_str()
201 .expect("FFI string inputs must be valid UTF-8")
202 }
203 }
204}
205
206pub unsafe fn result_to_c_int<T, E: std::fmt::Display>(
214 result: Result<T, E>,
215 fn_name: &str,
216) -> c_int {
217 match result {
218 Ok(_) => 0,
219 Err(e) => {
220 error!("FFI call {} failed: {}", fn_name, e);
221 1 }
223 }
224}
225
226pub unsafe fn malloc_and_copy_bytes(data: &[u8], out_len: *mut usize) -> *mut u8 {
236 unsafe {
238 if out_len.is_null() {
239 error!("malloc_and_copy_bytes failed: out_len pointer is null.");
240 return ptr::null_mut();
241 }
242 *out_len = data.len();
244 let ptr = libc::malloc(data.len()) as *mut u8;
246 if ptr.is_null() {
247 error!(
248 "malloc_and_copy_bytes failed: malloc returned null for size {}",
249 data.len()
250 );
251 *out_len = 0; return ptr::null_mut();
253 }
254 ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len());
256 ptr
257 }
258}
259
260fn malloc_and_copy_string(s: &str) -> *mut c_char {
269 match CString::new(s) {
270 Ok(c_string) => c_string.into_raw(), Err(e) => {
272 error!(
273 "malloc_and_copy_string failed: CString creation error: {}",
274 e
275 );
276 ptr::null_mut()
277 }
278 }
279}
280
281#[unsafe(no_mangle)]
292pub unsafe extern "C" fn ffi_key_exists(alias: *const c_char) -> bool {
293 let result = panic::catch_unwind(|| {
294 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
295 Ok(s) => s,
296 Err(_) => return false,
297 };
298 if alias_str.is_empty() {
299 return false;
300 }
301 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
303 Ok(kc) => kc,
304 Err(e) => {
305 error!("FFI ffi_key_exists: Failed to get platform keychain: {}", e);
306 return false;
307 }
308 };
309 let alias = KeyAlias::new_unchecked(alias_str);
310 keychain.load_key(&alias).is_ok()
311 });
312 result.unwrap_or_else(|_| {
313 error!("FFI ffi_key_exists: panic occurred");
314 false
315 })
316}
317
318#[unsafe(no_mangle)]
336pub unsafe extern "C" fn ffi_import_key(
337 alias: *const c_char, key_ptr: *const c_uchar, key_len: usize,
340 controller_did: *const c_char, passphrase: *const c_char, ) -> c_int {
343 let result = panic::catch_unwind(|| {
344 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
346 Ok(s) => s,
347 Err(code) => return code,
348 };
349 let did_str = match unsafe { c_str_to_str_safe(controller_did) } {
350 Ok(s) => s,
351 Err(code) => return code,
352 };
353 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
354 Ok(s) => s,
355 Err(code) => return code,
356 };
357 let key_data = unsafe { slice::from_raw_parts(key_ptr, key_len) };
358
359 if alias_str.is_empty()
361 || did_str.is_empty()
362 || !did_str.starts_with("did:")
363 || pass_str.is_empty()
364 {
365 error!(
366 "FFI import failed: Invalid arguments (alias='{}', did='{}', passphrase empty={}).",
367 alias_str,
368 did_str,
369 pass_str.is_empty()
370 );
371 return 1;
372 }
373
374 if let Err(e) = extract_seed_from_key_bytes(key_data) {
376 error!(
377 "FFI import failed: Provided key data is not valid Ed25519 for alias '{}': {}",
378 alias_str, e
379 );
380 return 2;
381 }
382
383 let encrypt_result = encrypt_keypair(key_data, pass_str);
385 let encrypted_key = match encrypt_result {
386 Ok(enc) => enc,
387 Err(e) => {
388 error!(
389 "FFI import failed: Encryption error for alias '{}': {}",
390 alias_str, e
391 );
392 return 4; }
394 };
395
396 let did_string = IdentityDID::new_unchecked(did_str.to_string());
397 let alias = KeyAlias::new_unchecked(alias_str);
398
399 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
402 Ok(kc) => kc,
403 Err(e) => {
404 error!("FFI import failed: Failed to get platform keychain: {}", e);
405 return 5; }
407 };
408 let store_result = keychain.store_key(&alias, &did_string, &encrypted_key);
409
410 #[allow(deprecated)]
411 unsafe {
412 result_to_c_int(store_result, "ffi_import_key")
413 }
414 });
415 result.unwrap_or_else(|_| {
416 error!("FFI ffi_import_key: panic occurred");
417 FFI_ERR_PANIC
418 })
419}
420
421#[unsafe(no_mangle)]
437pub unsafe extern "C" fn ffi_rotate_key(
438 alias: *const c_char,
439 new_passphrase: *const c_char,
440) -> c_int {
441 let result = panic::catch_unwind(|| {
442 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
443 Ok(s) => s,
444 Err(code) => return code,
445 };
446 let pass_str = match unsafe { c_str_to_str_safe(new_passphrase) } {
447 Ok(s) => s,
448 Err(code) => return code,
449 };
450
451 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
454 Ok(kc) => kc,
455 Err(e) => {
456 error!("FFI rotate_key: Failed to get platform keychain: {}", e);
457 return 5; }
459 };
460 let rotate_result = rotate_key(alias_str, pass_str, keychain.as_ref());
461
462 match rotate_result {
464 Ok(()) => 0,
465 Err(e) => {
466 error!("FFI rotate_key failed for alias '{}': {}", alias_str, e);
467 match e {
468 AgentError::InvalidInput(_) => 1,
469 AgentError::KeyNotFound => 2,
470 AgentError::CryptoError(_) | AgentError::KeyDeserializationError(_) => 3,
471 _ => 4, }
473 }
474 }
475 });
476 result.unwrap_or_else(|_| {
477 error!("FFI ffi_rotate_key: panic occurred");
478 FFI_ERR_PANIC
479 })
480}
481
482#[unsafe(no_mangle)]
494pub unsafe extern "C" fn ffi_export_encrypted_key(
495 alias: *const c_char,
496 out_len: *mut usize,
497) -> *mut u8 {
498 let result = panic::catch_unwind(|| {
499 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
500 Ok(s) => s,
501 Err(_) => return ptr::null_mut(),
502 };
503 if alias_str.is_empty() || out_len.is_null() {
504 if !out_len.is_null() {
505 unsafe { *out_len = 0 };
506 }
507 return ptr::null_mut();
508 }
509 unsafe { *out_len = 0 };
510
511 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
513 Ok(kc) => kc,
514 Err(e) => {
515 error!(
516 "FFI export encrypted key: Failed to get platform keychain: {}",
517 e
518 );
519 return ptr::null_mut();
520 }
521 };
522 let alias = KeyAlias::new_unchecked(alias_str);
523 match keychain.load_key(&alias) {
524 Ok((_identity_did, encrypted_data)) => {
525 debug!(
526 "FFI export encrypted key successful for alias '{}'",
527 alias_str
528 );
529 unsafe { malloc_and_copy_bytes(&encrypted_data, out_len) }
530 }
531 Err(e) => {
532 error!(
533 "FFI export encrypted key failed for alias '{}': {}",
534 alias_str, e
535 );
536 ptr::null_mut()
537 }
538 }
539 });
540 result.unwrap_or_else(|_| {
541 error!("FFI ffi_export_encrypted_key: panic occurred");
542 ptr::null_mut()
543 })
544}
545
546#[unsafe(no_mangle)]
558pub unsafe extern "C" fn ffi_export_private_key_with_passphrase(
559 alias: *const c_char,
560 passphrase: *const c_char,
561 out_len: *mut usize,
562) -> *mut u8 {
563 let result = panic::catch_unwind(|| {
564 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
565 Ok(s) => s,
566 Err(_) => return ptr::null_mut(),
567 };
568 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
569 Ok(s) => s,
570 Err(_) => return ptr::null_mut(),
571 };
572
573 if alias_str.is_empty() || out_len.is_null() {
574 if !out_len.is_null() {
575 unsafe { *out_len = 0 };
576 }
577 return ptr::null_mut();
578 }
579 unsafe { *out_len = 0 };
580
581 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
583 Ok(kc) => kc,
584 Err(e) => {
585 error!(
586 "FFI export_private_key_with_passphrase: Failed to get platform keychain: {}",
587 e
588 );
589 return ptr::null_mut();
590 }
591 };
592 let alias = KeyAlias::new_unchecked(alias_str);
593 let export_result = || -> Result<Vec<u8>, AgentError> {
594 let (_controller_did, encrypted_bytes) = keychain.load_key(&alias)?;
595 let _decrypted_pkcs8 = decrypt_keypair(&encrypted_bytes, pass_str)?;
597 debug!(
598 "FFI export_private_key_with_passphrase: Passphrase verified for alias '{}'",
599 alias_str
600 );
601 Ok(encrypted_bytes)
602 }();
603
604 match export_result {
605 Ok(encrypted_data) => unsafe { malloc_and_copy_bytes(&encrypted_data, out_len) },
606 Err(e) => {
607 if !matches!(e, AgentError::IncorrectPassphrase) {
608 error!(
609 "FFI export_private_key_with_passphrase failed for alias '{}': {}",
610 alias_str, e
611 );
612 } else {
613 debug!(
614 "FFI export_private_key_with_passphrase: Incorrect passphrase for alias '{}'",
615 alias_str
616 );
617 }
618 ptr::null_mut()
619 }
620 }
621 });
622 result.unwrap_or_else(|_| {
623 error!("FFI ffi_export_private_key_with_passphrase: panic occurred");
624 ptr::null_mut()
625 })
626}
627
628#[unsafe(no_mangle)]
639pub unsafe extern "C" fn ffi_export_private_key_openssh(
640 alias: *const c_char,
641 passphrase: *const c_char,
642) -> *mut c_char {
643 let result = panic::catch_unwind(|| {
644 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
645 Ok(s) => s,
646 Err(_) => return ptr::null_mut(),
647 };
648 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
649 Ok(s) => s,
650 Err(_) => return ptr::null_mut(),
651 };
652
653 if alias_str.is_empty() {
654 return ptr::null_mut();
655 }
656
657 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
659 Ok(kc) => kc,
660 Err(e) => {
661 error!("FFI export PEM: Failed to get platform keychain: {}", e);
662 return ptr::null_mut();
663 }
664 };
665 match export_key_openssh_pem(alias_str, pass_str, keychain.as_ref()) {
666 Ok(pem_zeroizing) => malloc_and_copy_string(pem_zeroizing.as_str()),
667 Err(e) => {
668 error!("FFI export PEM failed for alias '{}': {}", alias_str, e);
669 ptr::null_mut()
670 }
671 }
672 });
673 result.unwrap_or_else(|_| {
674 error!("FFI ffi_export_private_key_openssh: panic occurred");
675 ptr::null_mut()
676 })
677}
678
679#[unsafe(no_mangle)]
690pub unsafe extern "C" fn ffi_export_public_key_openssh(
691 alias: *const c_char,
692 passphrase: *const c_char,
693) -> *mut c_char {
694 let result = panic::catch_unwind(|| {
695 let alias_str = match unsafe { c_str_to_str_safe(alias) } {
696 Ok(s) => s,
697 Err(_) => return ptr::null_mut(),
698 };
699 let pass_str = match unsafe { c_str_to_str_safe(passphrase) } {
700 Ok(s) => s,
701 Err(_) => return ptr::null_mut(),
702 };
703
704 if alias_str.is_empty() {
705 return ptr::null_mut();
706 }
707
708 let keychain = match get_platform_keychain_with_config(&EnvironmentConfig::from_env()) {
710 Ok(kc) => kc,
711 Err(e) => {
712 error!(
713 "FFI export OpenSSH pubkey: Failed to get platform keychain: {}",
714 e
715 );
716 return ptr::null_mut();
717 }
718 };
719 match export_key_openssh_pub(alias_str, pass_str, keychain.as_ref()) {
720 Ok(formatted_pubkey) => malloc_and_copy_string(&formatted_pubkey),
721 Err(e) => {
722 error!(
723 "FFI export OpenSSH pubkey failed for alias '{}': {}",
724 alias_str, e
725 );
726 ptr::null_mut()
727 }
728 }
729 });
730 result.unwrap_or_else(|_| {
731 error!("FFI ffi_export_public_key_openssh: panic occurred");
732 ptr::null_mut()
733 })
734}
735
736#[unsafe(no_mangle)]
750pub unsafe extern "C" fn ffi_agent_sign(
751 pubkey_ptr: *const c_uchar,
752 pubkey_len: usize,
753 data_ptr: *const c_uchar,
754 data_len: usize,
755 out_len: *mut usize,
756) -> *mut u8 {
757 let result = panic::catch_unwind(|| {
758 if pubkey_ptr.is_null() || data_ptr.is_null() || out_len.is_null() {
759 if !out_len.is_null() {
760 unsafe { *out_len = 0 };
761 }
762 error!("FFI agent_sign failed: Null pointer argument.");
763 return ptr::null_mut();
764 }
765
766 let handle = match get_ffi_agent() {
768 Some(h) => h,
769 None => {
770 error!(
771 "FFI agent_sign failed: Agent not initialized. Call ffi_init_agent() first."
772 );
773 unsafe { *out_len = 0 };
774 return ptr::null_mut();
775 }
776 };
777
778 let pubkey_slice = unsafe { slice::from_raw_parts(pubkey_ptr, pubkey_len) };
779 let data_slice = unsafe { slice::from_raw_parts(data_ptr, data_len) };
780 unsafe { *out_len = 0 };
781
782 match agent_sign_with_handle(&handle, pubkey_slice, data_slice) {
783 Ok(signature_bytes) => unsafe { malloc_and_copy_bytes(&signature_bytes, out_len) },
784 Err(e) => {
785 error!("FFI agent_sign failed: {}", e);
786 if matches!(e, AgentError::KeyNotFound) {
787 warn!(
788 "FFI agent_sign: Key not found in agent for pubkey prefix {:x?}",
789 &pubkey_slice[..std::cmp::min(pubkey_slice.len(), 8)]
790 );
791 }
792 ptr::null_mut()
793 }
794 }
795 });
796 result.unwrap_or_else(|_| {
797 error!("FFI ffi_agent_sign: panic occurred");
798 ptr::null_mut()
799 })
800}
801
802#[unsafe(no_mangle)]
815pub unsafe extern "C" fn ffi_encrypt_data(
816 passphrase: *const c_char,
817 input_ptr: *const u8,
818 input_len: usize,
819 out_len: *mut usize,
820) -> *mut u8 {
821 let result = panic::catch_unwind(|| {
822 if input_ptr.is_null() || out_len.is_null() {
823 if !out_len.is_null() {
824 unsafe { *out_len = 0 };
825 }
826 error!("FFI encrypt_data failed: Null pointer argument.");
827 return ptr::null_mut();
828 }
829 let pass = match unsafe { c_str_to_str_safe(passphrase) } {
830 Ok(s) => s,
831 Err(_) => return ptr::null_mut(),
832 };
833 let input = unsafe { slice::from_raw_parts(input_ptr, input_len) };
834 unsafe { *out_len = 0 };
835 let algo = current_algorithm();
836
837 match encrypt_bytes_argon2(input, pass, algo) {
838 Ok(encrypted) => unsafe { malloc_and_copy_bytes(&encrypted, out_len) },
839 Err(e) => {
840 error!("FFI encrypt_data failed: {}", e);
841 ptr::null_mut()
842 }
843 }
844 });
845 result.unwrap_or_else(|_| {
846 error!("FFI ffi_encrypt_data: panic occurred");
847 ptr::null_mut()
848 })
849}
850
851#[unsafe(no_mangle)]
862pub unsafe extern "C" fn ffi_decrypt_data(
863 passphrase: *const c_char,
864 input_ptr: *const u8,
865 input_len: usize,
866 out_len: *mut usize,
867) -> *mut u8 {
868 let result = panic::catch_unwind(|| {
869 if input_ptr.is_null() || out_len.is_null() {
870 if !out_len.is_null() {
871 unsafe { *out_len = 0 };
872 }
873 error!("FFI decrypt_data failed: Null pointer argument.");
874 return ptr::null_mut();
875 }
876 let pass = match unsafe { c_str_to_str_safe(passphrase) } {
877 Ok(s) => s,
878 Err(_) => return ptr::null_mut(),
879 };
880 let input = unsafe { slice::from_raw_parts(input_ptr, input_len) };
881 unsafe { *out_len = 0 };
882
883 match decrypt_bytes(input, pass) {
884 Ok(decrypted) => unsafe { malloc_and_copy_bytes(&decrypted, out_len) },
885 Err(e) => {
886 if !matches!(e, AgentError::IncorrectPassphrase) {
887 error!("FFI decrypt_data failed: {}", e);
888 } else {
889 debug!("FFI decrypt_data: Incorrect passphrase provided.");
890 }
891 ptr::null_mut()
892 }
893 }
894 });
895 result.unwrap_or_else(|_| {
896 error!("FFI ffi_decrypt_data: panic occurred");
897 ptr::null_mut()
898 })
899}
900
901#[unsafe(no_mangle)]
910pub unsafe extern "C" fn ffi_free_str(ptr: *mut c_char) {
911 let _ = panic::catch_unwind(|| {
912 if !ptr.is_null() {
913 let _ = unsafe { CString::from_raw(ptr) };
916 }
917 });
918 }
920
921#[unsafe(no_mangle)]
931pub unsafe extern "C" fn ffi_free_bytes(ptr: *mut u8, _len: usize) {
932 let _ = panic::catch_unwind(|| {
933 if !ptr.is_null() {
934 unsafe { libc::free(ptr as *mut libc::c_void) };
935 }
936 });
937 }
939
940#[unsafe(no_mangle)]
947pub unsafe extern "C" fn ffi_set_encryption_algorithm(level: c_int) {
948 let _ = panic::catch_unwind(|| {
949 let algo = match level {
950 1 => EncryptionAlgorithm::AesGcm256,
951 2 => EncryptionAlgorithm::ChaCha20Poly1305,
952 _ => {
953 warn!(
954 "FFI: Unknown encryption level {}, defaulting to AES-GCM.",
955 level
956 );
957 EncryptionAlgorithm::AesGcm256
958 }
959 };
960 info!("FFI: Setting global encryption algorithm to {:?}", algo);
961 set_encryption_algorithm(algo);
962 });
963 }
965
966