1use std::{
16 ffi::{CStr, c_char},
17 panic::{AssertUnwindSafe, catch_unwind},
18 ptr, slice,
19};
20
21use hide_crypto::{RecipientPublic, RecipientSecret};
22use hide_keyring::KeyFormat;
23use hide_object::Metadata;
24use zeroize::Zeroizing;
25
26pub const HIDE_OK: i32 = 0;
29pub const HIDE_ERR_INVALID_ARGUMENT: i32 = 1;
30pub const HIDE_ERR_WRONG_PASSPHRASE: i32 = 2;
31pub const HIDE_ERR_NOT_A_KEY: i32 = 3;
32pub const HIDE_ERR_AUTHENTICATION: i32 = 4;
33pub const HIDE_ERR_NO_MATCHING_RECIPIENT: i32 = 5;
34pub const HIDE_ERR_MALFORMED: i32 = 6;
35pub const HIDE_ERR_TOO_LARGE: i32 = 7;
36pub const HIDE_ERR_PANIC: i32 = 98;
37pub const HIDE_ERR_INTERNAL: i32 = 99;
38
39pub const HIDE_KEY_RAW: i32 = 0;
41pub const HIDE_KEY_PROTECTED: i32 = 1;
42
43pub const HIDE_PUBLIC_KEY_LEN: usize = 1216;
44pub const HIDE_MIN_PASSPHRASE_LEN: usize = hide_keyring::MIN_PASSPHRASE_LEN;
45
46const MAX_INPUT: usize = 1 << 30;
48const MAX_KEY_FILE: usize = 4096;
49
50#[repr(C)]
52pub struct HideBuffer {
53 pub data: *mut u8,
54 pub len: usize,
55 capacity: usize,
57}
58
59impl HideBuffer {
60 fn from_vec(mut bytes: Vec<u8>) -> Self {
61 let buffer = Self {
62 data: bytes.as_mut_ptr(),
63 len: bytes.len(),
64 capacity: bytes.capacity(),
65 };
66 std::mem::forget(bytes);
67 buffer
68 }
69}
70
71#[unsafe(no_mangle)]
75pub extern "C" fn hide_buffer_empty() -> HideBuffer {
76 HideBuffer {
77 data: ptr::null_mut(),
78 len: 0,
79 capacity: 0,
80 }
81}
82
83pub struct HideSecretKey(RecipientSecret);
86
87unsafe fn borrow<'a>(data: *const u8, len: usize, limit: usize) -> Option<&'a [u8]> {
92 if len > limit {
93 return None;
94 }
95 if len == 0 {
96 return Some(&[]);
97 }
98 if data.is_null() {
99 return None;
100 }
101 Some(unsafe { slice::from_raw_parts(data, len) })
102}
103
104unsafe fn borrow_str<'a>(text: *const c_char) -> Option<Option<&'a str>> {
107 if text.is_null() {
108 return Some(None);
109 }
110 match unsafe { CStr::from_ptr(text) }.to_str() {
111 Ok(value) => Some(Some(value)),
112 Err(_) => None,
113 }
114}
115
116fn object_error_code(error: &hide_object::ObjectError) -> i32 {
117 use hide_object::ObjectError;
118 match error {
119 ObjectError::NoMatchingRecipient => HIDE_ERR_NO_MATCHING_RECIPIENT,
120 ObjectError::Crypto(_) => HIDE_ERR_AUTHENTICATION,
121 ObjectError::ObjectTooLarge => HIDE_ERR_TOO_LARGE,
122 _ => HIDE_ERR_MALFORMED,
123 }
124}
125
126fn keyring_error_code(error: &hide_keyring::KeyringError) -> i32 {
127 use hide_keyring::KeyringError;
128 match error {
129 KeyringError::WrongPassphrase => HIDE_ERR_WRONG_PASSPHRASE,
130 KeyringError::NotAKeyFile | KeyringError::UnsupportedVersion(_) => HIDE_ERR_NOT_A_KEY,
131 KeyringError::PassphraseTooShort(_) => HIDE_ERR_INVALID_ARGUMENT,
132 _ => HIDE_ERR_MALFORMED,
133 }
134}
135
136fn guard(body: impl FnOnce() -> i32) -> i32 {
139 catch_unwind(AssertUnwindSafe(body)).unwrap_or(HIDE_ERR_PANIC)
140}
141
142#[unsafe(no_mangle)]
145pub extern "C" fn hide_error_message(code: i32) -> *const c_char {
146 let text: &[u8] = match code {
147 HIDE_OK => b"success\0",
148 HIDE_ERR_INVALID_ARGUMENT => b"invalid argument\0",
149 HIDE_ERR_WRONG_PASSPHRASE => b"incorrect passphrase, or the key file was modified\0",
150 HIDE_ERR_NOT_A_KEY => b"not a HIDE key file\0",
151 HIDE_ERR_AUTHENTICATION => b"authentication failed; the data was altered\0",
152 HIDE_ERR_NO_MATCHING_RECIPIENT => b"no matching recipient for this key\0",
153 HIDE_ERR_MALFORMED => b"malformed or corrupt input\0",
154 HIDE_ERR_TOO_LARGE => b"input is too large\0",
155 HIDE_ERR_CHALLENGE_EXPIRED => b"the challenge expired before it was answered\0",
156 HIDE_ERR_CHALLENGE_REPLAYED => b"this challenge was already answered\0",
157 HIDE_ERR_PANIC => b"internal error (panic)\0",
158 _ => b"internal error\0",
159 };
160 text.as_ptr().cast()
161}
162
163#[unsafe(no_mangle)]
165pub extern "C" fn hide_version() -> *const c_char {
166 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr().cast()
167}
168
169#[unsafe(no_mangle)]
174pub unsafe extern "C" fn hide_buffer_free(buffer: *mut HideBuffer) {
175 if buffer.is_null() {
176 return;
177 }
178 let buffer = unsafe { &mut *buffer };
179 if buffer.data.is_null() {
180 return;
181 }
182 let mut bytes = unsafe { Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity) };
185 bytes.iter_mut().for_each(|byte| *byte = 0);
186 drop(bytes);
187 buffer.data = ptr::null_mut();
188 buffer.len = 0;
189 buffer.capacity = 0;
190}
191
192#[unsafe(no_mangle)]
198pub unsafe extern "C" fn hide_keypair_generate(
199 out_secret: *mut *mut HideSecretKey,
200 out_public: *mut HideBuffer,
201) -> i32 {
202 guard(|| {
203 if out_secret.is_null() || out_public.is_null() {
204 return HIDE_ERR_INVALID_ARGUMENT;
205 }
206 let Ok(secret) = RecipientSecret::generate() else {
207 return HIDE_ERR_INTERNAL;
208 };
209 let Ok(public) = secret.public_key() else {
210 return HIDE_ERR_INTERNAL;
211 };
212 unsafe {
213 *out_public = HideBuffer::from_vec(public.to_bytes());
214 *out_secret = Box::into_raw(Box::new(HideSecretKey(secret)));
215 }
216 HIDE_OK
217 })
218}
219
220#[unsafe(no_mangle)]
226pub unsafe extern "C" fn hide_inspect_key(data: *const u8, len: usize, out_kind: *mut i32) -> i32 {
227 guard(|| {
228 if out_kind.is_null() {
229 return HIDE_ERR_INVALID_ARGUMENT;
230 }
231 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
232 return HIDE_ERR_INVALID_ARGUMENT;
233 };
234 let kind = match hide_keyring::inspect(bytes) {
235 KeyFormat::Raw => HIDE_KEY_RAW,
236 KeyFormat::Protected => HIDE_KEY_PROTECTED,
237 };
238 unsafe { *out_kind = kind };
239 HIDE_OK
240 })
241}
242
243#[unsafe(no_mangle)]
250pub unsafe extern "C" fn hide_secret_key_open(
251 data: *const u8,
252 len: usize,
253 passphrase: *const c_char,
254 out_secret: *mut *mut HideSecretKey,
255) -> i32 {
256 guard(|| {
257 if out_secret.is_null() {
258 return HIDE_ERR_INVALID_ARGUMENT;
259 }
260 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
261 return HIDE_ERR_INVALID_ARGUMENT;
262 };
263 let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
264 return HIDE_ERR_INVALID_ARGUMENT;
265 };
266 match hide_keyring::open(bytes, passphrase) {
267 Ok(secret) => {
268 unsafe { *out_secret = Box::into_raw(Box::new(HideSecretKey(secret))) };
269 HIDE_OK
270 }
271 Err(error) => keyring_error_code(&error),
272 }
273 })
274}
275
276#[unsafe(no_mangle)]
281pub unsafe extern "C" fn hide_secret_key_protect(
282 secret: *const HideSecretKey,
283 passphrase: *const c_char,
284 out: *mut HideBuffer,
285) -> i32 {
286 guard(|| {
287 if secret.is_null() || out.is_null() {
288 return HIDE_ERR_INVALID_ARGUMENT;
289 }
290 let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
291 return HIDE_ERR_INVALID_ARGUMENT;
292 };
293 let secret = unsafe { &*secret };
294 match hide_keyring::protect(&secret.0, passphrase) {
295 Ok(sealed) => {
296 unsafe { *out = HideBuffer::from_vec(sealed) };
297 HIDE_OK
298 }
299 Err(error) => keyring_error_code(&error),
300 }
301 })
302}
303
304#[unsafe(no_mangle)]
309pub unsafe extern "C" fn hide_secret_key_public(
310 secret: *const HideSecretKey,
311 out: *mut HideBuffer,
312) -> i32 {
313 guard(|| {
314 if secret.is_null() || out.is_null() {
315 return HIDE_ERR_INVALID_ARGUMENT;
316 }
317 let secret = unsafe { &*secret };
318 match secret.0.public_key() {
319 Ok(public) => {
320 unsafe { *out = HideBuffer::from_vec(public.to_bytes()) };
321 HIDE_OK
322 }
323 Err(_) => HIDE_ERR_INTERNAL,
324 }
325 })
326}
327
328#[unsafe(no_mangle)]
333pub unsafe extern "C" fn hide_secret_key_free(secret: *mut HideSecretKey) {
334 if secret.is_null() {
335 return;
336 }
337 drop(unsafe { Box::from_raw(secret) });
338}
339
340#[unsafe(no_mangle)]
348pub unsafe extern "C" fn hide_encrypt(
349 plaintext: *const u8,
350 plaintext_len: usize,
351 recipients: *const u8,
352 recipient_count: usize,
353 filename: *const c_char,
354 media_type: *const c_char,
355 out: *mut HideBuffer,
356) -> i32 {
357 guard(|| {
358 if out.is_null() {
359 return HIDE_ERR_INVALID_ARGUMENT;
360 }
361 if recipient_count == 0 || recipient_count > 64 {
362 return HIDE_ERR_INVALID_ARGUMENT;
363 }
364 let Some(plaintext) = (unsafe { borrow(plaintext, plaintext_len, MAX_INPUT) }) else {
365 return HIDE_ERR_INVALID_ARGUMENT;
366 };
367 let Some(keys) =
368 (unsafe { borrow(recipients, recipient_count * HIDE_PUBLIC_KEY_LEN, MAX_INPUT) })
369 else {
370 return HIDE_ERR_INVALID_ARGUMENT;
371 };
372
373 let mut parsed = Vec::with_capacity(recipient_count);
374 for chunk in keys.chunks_exact(HIDE_PUBLIC_KEY_LEN) {
375 match RecipientPublic::from_bytes(chunk) {
376 Ok(key) => parsed.push(key),
377 Err(_) => return HIDE_ERR_INVALID_ARGUMENT,
378 }
379 }
380
381 let (Some(filename), Some(media_type)) = (unsafe { borrow_str(filename) }, unsafe {
382 borrow_str(media_type)
383 }) else {
384 return HIDE_ERR_INVALID_ARGUMENT;
385 };
386 let metadata = Metadata {
387 filename: filename.map(str::to_owned),
388 media_type: media_type.map(str::to_owned),
389 signature: None,
390 };
391
392 let mut container = Vec::new();
393 match hide_object::encrypt(&mut &*plaintext, &mut container, &parsed, &metadata) {
394 Ok(_) => {
395 unsafe { *out = HideBuffer::from_vec(container) };
396 HIDE_OK
397 }
398 Err(error) => object_error_code(&error),
399 }
400 })
401}
402
403#[unsafe(no_mangle)]
413pub unsafe extern "C" fn hide_decrypt(
414 container: *const u8,
415 container_len: usize,
416 secret: *const HideSecretKey,
417 out: *mut HideBuffer,
418 out_filename: *mut HideBuffer,
419 out_media_type: *mut HideBuffer,
420) -> i32 {
421 guard(|| {
422 if secret.is_null() || out.is_null() {
423 return HIDE_ERR_INVALID_ARGUMENT;
424 }
425 let Some(container) = (unsafe { borrow(container, container_len, MAX_INPUT) }) else {
426 return HIDE_ERR_INVALID_ARGUMENT;
427 };
428 let secret = unsafe { &*secret };
429
430 let mut plaintext = Zeroizing::new(Vec::new());
432 let verified =
433 match hide_object::decrypt_to_staging(&mut &*container, &mut *plaintext, &secret.0) {
434 Ok(verified) => verified,
435 Err(error) => return object_error_code(&error),
436 };
437
438 if !out_filename.is_null() {
439 let value = verified.metadata.filename.unwrap_or_default();
440 unsafe { *out_filename = HideBuffer::from_vec(value.into_bytes()) };
441 }
442 if !out_media_type.is_null() {
443 let value = verified.metadata.media_type.unwrap_or_default();
444 unsafe { *out_media_type = HideBuffer::from_vec(value.into_bytes()) };
445 }
446
447 unsafe { *out = HideBuffer::from_vec(std::mem::take(&mut *plaintext)) };
448 HIDE_OK
449 })
450}
451
452#[unsafe(no_mangle)]
457pub unsafe extern "C" fn hide_public_key_armor(
458 data: *const u8,
459 len: usize,
460 out: *mut HideBuffer,
461) -> i32 {
462 guard(|| {
463 if out.is_null() {
464 return HIDE_ERR_INVALID_ARGUMENT;
465 }
466 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
467 return HIDE_ERR_INVALID_ARGUMENT;
468 };
469 if bytes.len() != HIDE_PUBLIC_KEY_LEN {
470 return HIDE_ERR_INVALID_ARGUMENT;
471 }
472 let armored = hide_keyring::encode_public(bytes);
473 unsafe { *out = HideBuffer::from_vec(armored.into_bytes()) };
474 HIDE_OK
475 })
476}
477
478#[unsafe(no_mangle)]
483pub unsafe extern "C" fn hide_public_key_dearmor(text: *const c_char, out: *mut HideBuffer) -> i32 {
484 guard(|| {
485 if out.is_null() {
486 return HIDE_ERR_INVALID_ARGUMENT;
487 }
488 let Some(Some(text)) = (unsafe { borrow_str(text) }) else {
489 return HIDE_ERR_INVALID_ARGUMENT;
490 };
491 match hide_keyring::decode_public(text) {
492 Ok(bytes) if bytes.len() == HIDE_PUBLIC_KEY_LEN => {
493 unsafe { *out = HideBuffer::from_vec(bytes) };
494 HIDE_OK
495 }
496 Ok(_) => HIDE_ERR_INVALID_ARGUMENT,
497 Err(error) => keyring_error_code(&error),
498 }
499 })
500}
501
502pub const HIDE_SIGNATURE_LEN: usize = hide_sign::SIGNATURE_LENGTH;
504pub const HIDE_VERIFYING_KEY_LEN: usize = hide_sign::VERIFYING_KEY_LENGTH;
506pub const HIDE_NONCE_LEN: usize = hide_sign::NONCE_LENGTH;
508
509pub const HIDE_ERR_CHALLENGE_EXPIRED: i32 = 8;
511pub const HIDE_ERR_CHALLENGE_REPLAYED: i32 = 9;
512
513pub struct HideSigningIdentity(hide_sign::SigningIdentity);
516
517#[unsafe(no_mangle)]
524pub unsafe extern "C" fn hide_identity_generate(
525 passphrase: *const c_char,
526 out_key_file: *mut HideBuffer,
527) -> i32 {
528 guard(|| {
529 if out_key_file.is_null() {
530 return HIDE_ERR_INVALID_ARGUMENT;
531 }
532 let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
533 return HIDE_ERR_INVALID_ARGUMENT;
534 };
535 let identity = match hide_keyring::Identity::generate() {
536 Ok(identity) => identity,
537 Err(_) => return HIDE_ERR_INTERNAL,
538 };
539 match hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase) {
540 Ok(sealed) => {
541 unsafe { *out_key_file = HideBuffer::from_vec(sealed) };
542 HIDE_OK
543 }
544 Err(error) => keyring_error_code(&error),
545 }
546 })
547}
548
549pub struct HideSpentNonces(hide_sign::SpentNonces);
552
553#[unsafe(no_mangle)]
564pub unsafe extern "C" fn hide_signing_identity_open(
565 data: *const u8,
566 len: usize,
567 passphrase: *const c_char,
568 out_identity: *mut *mut HideSigningIdentity,
569) -> i32 {
570 guard(|| {
571 if out_identity.is_null() {
572 return HIDE_ERR_INVALID_ARGUMENT;
573 }
574 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
575 return HIDE_ERR_INVALID_ARGUMENT;
576 };
577 let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
578 return HIDE_ERR_INVALID_ARGUMENT;
579 };
580
581 let seed = match hide_keyring::inspect(bytes) {
582 KeyFormat::Raw => {
583 if bytes.len() != 32 {
584 return HIDE_ERR_NOT_A_KEY;
585 }
586 let mut seed = Zeroizing::new([0_u8; 32]);
587 seed.copy_from_slice(bytes);
588 hide_keyring::Identity::from_seed(seed).signing_seed()
589 }
590 KeyFormat::Protected => {
591 let Some(passphrase) = passphrase else {
592 return HIDE_ERR_INVALID_ARGUMENT;
593 };
594 match hide_keyring::unprotect_seed(bytes, passphrase) {
595 Ok((seed, hide_keyring::KeyPurpose::Identity)) => {
596 hide_keyring::Identity::from_seed(seed).signing_seed()
597 }
598 Ok(_) => return HIDE_ERR_NOT_A_KEY,
601 Err(error) => return keyring_error_code(&error),
602 }
603 }
604 };
605
606 match hide_sign::SigningIdentity::from_bytes(&seed[..]) {
607 Ok(identity) => {
608 unsafe { *out_identity = Box::into_raw(Box::new(HideSigningIdentity(identity))) };
609 HIDE_OK
610 }
611 Err(_) => HIDE_ERR_NOT_A_KEY,
612 }
613 })
614}
615
616#[unsafe(no_mangle)]
621pub unsafe extern "C" fn hide_signing_identity_public(
622 identity: *const HideSigningIdentity,
623 out: *mut HideBuffer,
624) -> i32 {
625 guard(|| {
626 if identity.is_null() || out.is_null() {
627 return HIDE_ERR_INVALID_ARGUMENT;
628 }
629 let identity = unsafe { &*identity };
630 let public = identity.0.verifying_key().to_bytes();
631 unsafe { *out = HideBuffer::from_vec(public.to_vec()) };
632 HIDE_OK
633 })
634}
635
636#[unsafe(no_mangle)]
641pub unsafe extern "C" fn hide_signing_identity_free(identity: *mut HideSigningIdentity) {
642 if identity.is_null() {
643 return;
644 }
645 drop(unsafe { Box::from_raw(identity) });
646}
647
648#[unsafe(no_mangle)]
657pub unsafe extern "C" fn hide_sign_message(
658 identity: *const HideSigningIdentity,
659 context: *const u8,
660 context_len: usize,
661 message: *const u8,
662 message_len: usize,
663 out: *mut HideBuffer,
664) -> i32 {
665 guard(|| {
666 if identity.is_null() || out.is_null() {
667 return HIDE_ERR_INVALID_ARGUMENT;
668 }
669 let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
670 return HIDE_ERR_INVALID_ARGUMENT;
671 };
672 let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
673 return HIDE_ERR_INVALID_ARGUMENT;
674 };
675 let identity = unsafe { &*identity };
676 let signature = identity.0.sign(context, message);
677 unsafe { *out = HideBuffer::from_vec(signature.to_vec()) };
678 HIDE_OK
679 })
680}
681
682#[unsafe(no_mangle)]
687pub unsafe extern "C" fn hide_verify_message(
688 public_key: *const u8,
689 public_key_len: usize,
690 context: *const u8,
691 context_len: usize,
692 message: *const u8,
693 message_len: usize,
694 signature: *const u8,
695 signature_len: usize,
696) -> i32 {
697 guard(|| {
698 let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
699 return HIDE_ERR_INVALID_ARGUMENT;
700 };
701 let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
702 return HIDE_ERR_INVALID_ARGUMENT;
703 };
704 let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
705 return HIDE_ERR_INVALID_ARGUMENT;
706 };
707 let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
708 else {
709 return HIDE_ERR_INVALID_ARGUMENT;
710 };
711 let Ok(verifying) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
712 return HIDE_ERR_NOT_A_KEY;
713 };
714 match verifying.verify(context, message, signature) {
715 Ok(()) => HIDE_OK,
716 Err(_) => HIDE_ERR_AUTHENTICATION,
717 }
718 })
719}
720
721#[unsafe(no_mangle)]
727pub unsafe extern "C" fn hide_challenge_new(
728 audience: *const c_char,
729 now: u64,
730 valid_for: u64,
731 out: *mut HideBuffer,
732) -> i32 {
733 guard(|| {
734 if out.is_null() {
735 return HIDE_ERR_INVALID_ARGUMENT;
736 }
737 let Some(Some(audience)) = (unsafe { borrow_str(audience) }) else {
738 return HIDE_ERR_INVALID_ARGUMENT;
739 };
740 match hide_sign::Challenge::new(audience, now, valid_for) {
741 Ok(challenge) => {
742 unsafe { *out = HideBuffer::from_vec(challenge.encode()) };
743 HIDE_OK
744 }
745 Err(_) => HIDE_ERR_INTERNAL,
746 }
747 })
748}
749
750#[unsafe(no_mangle)]
755pub unsafe extern "C" fn hide_challenge_answer(
756 identity: *const HideSigningIdentity,
757 challenge: *const u8,
758 challenge_len: usize,
759 out: *mut HideBuffer,
760) -> i32 {
761 guard(|| {
762 if identity.is_null() || out.is_null() {
763 return HIDE_ERR_INVALID_ARGUMENT;
764 }
765 let Some(bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) }) else {
766 return HIDE_ERR_INVALID_ARGUMENT;
767 };
768 let Ok(challenge) = hide_sign::Challenge::decode(bytes) else {
769 return HIDE_ERR_MALFORMED;
770 };
771 let identity = unsafe { &*identity };
772 unsafe { *out = HideBuffer::from_vec(challenge.answer(&identity.0).to_vec()) };
773 HIDE_OK
774 })
775}
776
777#[unsafe(no_mangle)]
779pub extern "C" fn hide_spent_nonces_new() -> *mut HideSpentNonces {
780 Box::into_raw(Box::new(HideSpentNonces(hide_sign::SpentNonces::new())))
781}
782
783#[unsafe(no_mangle)]
788pub unsafe extern "C" fn hide_spent_nonces_free(spent: *mut HideSpentNonces) {
789 if spent.is_null() {
790 return;
791 }
792 drop(unsafe { Box::from_raw(spent) });
793}
794
795#[unsafe(no_mangle)]
802pub unsafe extern "C" fn hide_challenge_accept(
803 spent: *mut HideSpentNonces,
804 challenge: *const u8,
805 challenge_len: usize,
806 signature: *const u8,
807 signature_len: usize,
808 public_key: *const u8,
809 public_key_len: usize,
810 now: u64,
811) -> i32 {
812 guard(|| {
813 if spent.is_null() {
814 return HIDE_ERR_INVALID_ARGUMENT;
815 }
816 let Some(challenge_bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) })
817 else {
818 return HIDE_ERR_INVALID_ARGUMENT;
819 };
820 let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
821 else {
822 return HIDE_ERR_INVALID_ARGUMENT;
823 };
824 let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
825 return HIDE_ERR_INVALID_ARGUMENT;
826 };
827 let Ok(challenge) = hide_sign::Challenge::decode(challenge_bytes) else {
828 return HIDE_ERR_MALFORMED;
829 };
830 let Ok(prover) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
831 return HIDE_ERR_NOT_A_KEY;
832 };
833 let spent = unsafe { &mut *spent };
834 match spent.0.accept(&challenge, signature, &prover, now) {
835 Ok(()) => HIDE_OK,
836 Err(hide_sign::ChallengeError::Expired) => HIDE_ERR_CHALLENGE_EXPIRED,
837 Err(hide_sign::ChallengeError::Replayed) => HIDE_ERR_CHALLENGE_REPLAYED,
838 Err(hide_sign::ChallengeError::NotSigned) => HIDE_ERR_AUTHENTICATION,
839 }
840 })
841}
842
843#[cfg(test)]
844mod tests {
845 use std::ffi::CString;
846
847 use super::*;
848
849 unsafe fn keypair() -> (*mut HideSecretKey, HideBuffer) {
851 let mut secret = ptr::null_mut();
852 let mut public = hide_buffer_empty();
853 assert_eq!(
854 unsafe { hide_keypair_generate(&mut secret, &mut public) },
855 HIDE_OK
856 );
857 assert_eq!(public.len, HIDE_PUBLIC_KEY_LEN);
858 (secret, public)
859 }
860
861 #[test]
862 fn round_trips_through_the_c_api() {
863 unsafe {
864 let (secret, public) = keypair();
865 let message = b"nume,suma\nAna,9000\n";
866
867 let mut container = hide_buffer_empty();
868 assert_eq!(
869 hide_encrypt(
870 message.as_ptr(),
871 message.len(),
872 public.data,
873 1,
874 c"salarii.csv".as_ptr(),
875 ptr::null(),
876 &mut container,
877 ),
878 HIDE_OK
879 );
880 assert!(container.len > message.len());
881
882 let mut plaintext = hide_buffer_empty();
883 let mut filename = hide_buffer_empty();
884 assert_eq!(
885 hide_decrypt(
886 container.data,
887 container.len,
888 secret,
889 &mut plaintext,
890 &mut filename,
891 ptr::null_mut(),
892 ),
893 HIDE_OK
894 );
895 assert_eq!(
896 slice::from_raw_parts(plaintext.data, plaintext.len),
897 message
898 );
899 assert_eq!(
900 slice::from_raw_parts(filename.data, filename.len),
901 b"salarii.csv"
902 );
903
904 hide_buffer_free(&mut filename);
905 hide_buffer_free(&mut plaintext);
906 hide_buffer_free(&mut container);
907 hide_buffer_free(&mut { public });
908 hide_secret_key_free(secret);
909 }
910 }
911
912 #[test]
913 fn a_freed_buffer_is_cleared_and_safe_to_free_twice() {
914 unsafe {
915 let (secret, mut public) = keypair();
916 let mut container = hide_buffer_empty();
917 hide_encrypt(
918 b"x".as_ptr(),
919 1,
920 public.data,
921 1,
922 ptr::null(),
923 ptr::null(),
924 &mut container,
925 );
926 hide_buffer_free(&mut container);
927 assert!(container.data.is_null(), "freed buffer kept its pointer");
928 hide_buffer_free(&mut container);
930 hide_buffer_free(&mut public);
931 hide_secret_key_free(secret);
932 }
933 }
934
935 #[test]
936 fn tampering_is_refused_and_yields_no_plaintext() {
937 unsafe {
938 let (secret, mut public) = keypair();
939 let mut container = hide_buffer_empty();
940 hide_encrypt(
941 b"confidential".as_ptr(),
942 12,
943 public.data,
944 1,
945 ptr::null(),
946 ptr::null(),
947 &mut container,
948 );
949
950 let mut damaged = slice::from_raw_parts(container.data, container.len).to_vec();
951 let last = damaged.len() - 1;
952 damaged[last] ^= 1;
953
954 let mut plaintext = hide_buffer_empty();
955 let code = hide_decrypt(
956 damaged.as_ptr(),
957 damaged.len(),
958 secret,
959 &mut plaintext,
960 ptr::null_mut(),
961 ptr::null_mut(),
962 );
963 assert_ne!(code, HIDE_OK);
964 assert!(plaintext.data.is_null(), "published unverified plaintext");
965
966 hide_buffer_free(&mut container);
967 hide_buffer_free(&mut public);
968 hide_secret_key_free(secret);
969 }
970 }
971
972 #[test]
973 fn the_wrong_key_cannot_decrypt() {
974 unsafe {
975 let (secret, mut public) = keypair();
976 let (other, mut other_public) = keypair();
977 let mut container = hide_buffer_empty();
978 hide_encrypt(
979 b"secret".as_ptr(),
980 6,
981 public.data,
982 1,
983 ptr::null(),
984 ptr::null(),
985 &mut container,
986 );
987
988 let mut plaintext = hide_buffer_empty();
989 assert_eq!(
990 hide_decrypt(
991 container.data,
992 container.len,
993 other,
994 &mut plaintext,
995 ptr::null_mut(),
996 ptr::null_mut(),
997 ),
998 HIDE_ERR_NO_MATCHING_RECIPIENT
999 );
1000
1001 hide_buffer_free(&mut container);
1002 hide_buffer_free(&mut public);
1003 hide_buffer_free(&mut other_public);
1004 hide_secret_key_free(secret);
1005 hide_secret_key_free(other);
1006 }
1007 }
1008
1009 #[test]
1010 fn null_and_absurd_arguments_are_rejected_rather_than_crashing() {
1011 unsafe {
1012 let mut out = hide_buffer_empty();
1013 let mut secret = ptr::null_mut();
1014
1015 assert_eq!(
1016 hide_keypair_generate(ptr::null_mut(), &mut out),
1017 HIDE_ERR_INVALID_ARGUMENT
1018 );
1019 assert_eq!(
1020 hide_keypair_generate(&mut secret, ptr::null_mut()),
1021 HIDE_ERR_INVALID_ARGUMENT
1022 );
1023 assert_eq!(
1025 hide_encrypt(
1026 ptr::null(),
1027 10,
1028 ptr::null(),
1029 1,
1030 ptr::null(),
1031 ptr::null(),
1032 &mut out
1033 ),
1034 HIDE_ERR_INVALID_ARGUMENT
1035 );
1036 assert_eq!(
1038 hide_encrypt(
1039 b"x".as_ptr(),
1040 usize::MAX,
1041 ptr::null(),
1042 1,
1043 ptr::null(),
1044 ptr::null(),
1045 &mut out
1046 ),
1047 HIDE_ERR_INVALID_ARGUMENT
1048 );
1049 assert_eq!(
1050 hide_decrypt(
1051 ptr::null(),
1052 0,
1053 ptr::null(),
1054 &mut out,
1055 ptr::null_mut(),
1056 ptr::null_mut()
1057 ),
1058 HIDE_ERR_INVALID_ARGUMENT
1059 );
1060 let mut kind = -1;
1061 assert_eq!(
1062 hide_inspect_key(ptr::null(), 32, &mut kind),
1063 HIDE_ERR_INVALID_ARGUMENT
1064 );
1065 hide_buffer_free(ptr::null_mut());
1067 hide_secret_key_free(ptr::null_mut());
1068 }
1069 }
1070
1071 #[test]
1072 fn protected_keys_round_trip_and_reject_a_wrong_passphrase() {
1073 unsafe {
1074 let (secret, mut public) = keypair();
1075 let mut sealed = hide_buffer_empty();
1076 assert_eq!(
1077 hide_secret_key_protect(secret, c"correct horse".as_ptr(), &mut sealed),
1078 HIDE_OK
1079 );
1080
1081 let mut kind = -1;
1082 assert_eq!(
1083 hide_inspect_key(sealed.data, sealed.len, &mut kind),
1084 HIDE_OK
1085 );
1086 assert_eq!(kind, HIDE_KEY_PROTECTED);
1087
1088 let mut reopened = ptr::null_mut();
1089 assert_eq!(
1090 hide_secret_key_open(sealed.data, sealed.len, c"wrong".as_ptr(), &mut reopened),
1091 HIDE_ERR_WRONG_PASSPHRASE
1092 );
1093 assert_eq!(
1094 hide_secret_key_open(sealed.data, sealed.len, ptr::null(), &mut reopened),
1095 HIDE_ERR_WRONG_PASSPHRASE
1096 );
1097 assert_eq!(
1098 hide_secret_key_open(
1099 sealed.data,
1100 sealed.len,
1101 c"correct horse".as_ptr(),
1102 &mut reopened
1103 ),
1104 HIDE_OK
1105 );
1106
1107 let mut derived = hide_buffer_empty();
1109 assert_eq!(hide_secret_key_public(reopened, &mut derived), HIDE_OK);
1110 assert_eq!(
1111 slice::from_raw_parts(derived.data, derived.len),
1112 slice::from_raw_parts(public.data, public.len)
1113 );
1114
1115 hide_buffer_free(&mut derived);
1116 hide_buffer_free(&mut sealed);
1117 hide_buffer_free(&mut public);
1118 hide_secret_key_free(reopened);
1119 hide_secret_key_free(secret);
1120 }
1121 }
1122
1123 #[test]
1124 fn armor_round_trips_and_rejects_rubbish() {
1125 unsafe {
1126 let (secret, mut public) = keypair();
1127 let mut armored = hide_buffer_empty();
1128 assert_eq!(
1129 hide_public_key_armor(public.data, public.len, &mut armored),
1130 HIDE_OK
1131 );
1132
1133 let text =
1135 CString::new(slice::from_raw_parts(armored.data, armored.len)).expect("no NUL");
1136 let mut decoded = hide_buffer_empty();
1137 assert_eq!(
1138 hide_public_key_dearmor(text.as_ptr(), &mut decoded),
1139 HIDE_OK
1140 );
1141 assert_eq!(
1142 slice::from_raw_parts(decoded.data, decoded.len),
1143 slice::from_raw_parts(public.data, public.len)
1144 );
1145 assert_ne!(
1146 hide_public_key_dearmor(c"not a key".as_ptr(), &mut decoded),
1147 HIDE_OK
1148 );
1149
1150 hide_buffer_free(&mut decoded);
1151 hide_buffer_free(&mut armored);
1152 hide_buffer_free(&mut public);
1153 hide_secret_key_free(secret);
1154 }
1155 }
1156
1157 fn identity_file(passphrase: &str) -> Vec<u8> {
1159 let identity = hide_keyring::Identity::generate().expect("randomness");
1160 hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase)
1161 .expect("sealing works")
1162 }
1163
1164 unsafe fn open_identity(file: &[u8], passphrase: &CString) -> *mut HideSigningIdentity {
1165 let mut identity = ptr::null_mut();
1166 assert_eq!(
1167 unsafe {
1168 hide_signing_identity_open(
1169 file.as_ptr(),
1170 file.len(),
1171 passphrase.as_ptr(),
1172 &mut identity,
1173 )
1174 },
1175 HIDE_OK
1176 );
1177 identity
1178 }
1179
1180 #[test]
1181 fn signs_and_verifies_across_the_boundary() {
1182 unsafe {
1183 let passphrase = CString::new("correct horse battery staple").unwrap();
1184 let file = identity_file(passphrase.to_str().unwrap());
1185 let identity = open_identity(&file, &passphrase);
1186
1187 let mut public = hide_buffer_empty();
1188 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1189 assert_eq!(public.len, HIDE_VERIFYING_KEY_LEN);
1190
1191 let context = b"HIDE/0.5 ffi test";
1192 let message = b"the message that crossed the boundary";
1193 let mut signature = hide_buffer_empty();
1194 assert_eq!(
1195 hide_sign_message(
1196 identity,
1197 context.as_ptr(),
1198 context.len(),
1199 message.as_ptr(),
1200 message.len(),
1201 &mut signature,
1202 ),
1203 HIDE_OK
1204 );
1205 assert_eq!(signature.len, HIDE_SIGNATURE_LEN);
1206
1207 assert_eq!(
1208 hide_verify_message(
1209 public.data,
1210 public.len,
1211 context.as_ptr(),
1212 context.len(),
1213 message.as_ptr(),
1214 message.len(),
1215 signature.data,
1216 signature.len,
1217 ),
1218 HIDE_OK
1219 );
1220
1221 let other = b"HIDE/0.5 something else";
1224 assert_eq!(
1225 hide_verify_message(
1226 public.data,
1227 public.len,
1228 other.as_ptr(),
1229 other.len(),
1230 message.as_ptr(),
1231 message.len(),
1232 signature.data,
1233 signature.len,
1234 ),
1235 HIDE_ERR_AUTHENTICATION
1236 );
1237
1238 let altered = b"the message that crossed the boundaryX";
1239 assert_eq!(
1240 hide_verify_message(
1241 public.data,
1242 public.len,
1243 context.as_ptr(),
1244 context.len(),
1245 altered.as_ptr(),
1246 altered.len(),
1247 signature.data,
1248 signature.len,
1249 ),
1250 HIDE_ERR_AUTHENTICATION
1251 );
1252
1253 hide_buffer_free(&mut signature);
1254 hide_buffer_free(&mut public);
1255 hide_signing_identity_free(identity);
1256 }
1257 }
1258
1259 #[test]
1262 fn an_encryption_only_key_cannot_sign() {
1263 unsafe {
1264 let passphrase = CString::new("correct horse battery staple").unwrap();
1265 let secret = hide_crypto::RecipientSecret::generate().expect("randomness");
1266 let file =
1267 hide_keyring::protect(&secret, passphrase.to_str().unwrap()).expect("sealing");
1268
1269 let mut identity = ptr::null_mut();
1270 assert_eq!(
1271 hide_signing_identity_open(
1272 file.as_ptr(),
1273 file.len(),
1274 passphrase.as_ptr(),
1275 &mut identity,
1276 ),
1277 HIDE_ERR_NOT_A_KEY
1278 );
1279 assert!(identity.is_null());
1280 }
1281 }
1282
1283 #[test]
1284 fn a_wrong_passphrase_is_named_as_such() {
1285 unsafe {
1286 let file = identity_file("correct horse battery staple");
1287 let wrong = CString::new("not the passphrase at all").unwrap();
1288 let mut identity = ptr::null_mut();
1289 assert_eq!(
1290 hide_signing_identity_open(
1291 file.as_ptr(),
1292 file.len(),
1293 wrong.as_ptr(),
1294 &mut identity
1295 ),
1296 HIDE_ERR_WRONG_PASSPHRASE
1297 );
1298 }
1299 }
1300
1301 #[test]
1302 fn a_challenge_is_answered_once_and_then_refused() {
1303 unsafe {
1304 let passphrase = CString::new("correct horse battery staple").unwrap();
1305 let file = identity_file(passphrase.to_str().unwrap());
1306 let identity = open_identity(&file, &passphrase);
1307
1308 let mut public = hide_buffer_empty();
1309 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1310
1311 let audience = CString::new("ssh://host.example").unwrap();
1312 let mut challenge = hide_buffer_empty();
1313 assert_eq!(
1314 hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1315 HIDE_OK
1316 );
1317
1318 let mut answer = hide_buffer_empty();
1319 assert_eq!(
1320 hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1321 HIDE_OK
1322 );
1323
1324 let spent = hide_spent_nonces_new();
1325 assert_eq!(
1326 hide_challenge_accept(
1327 spent,
1328 challenge.data,
1329 challenge.len,
1330 answer.data,
1331 answer.len,
1332 public.data,
1333 public.len,
1334 1_000,
1335 ),
1336 HIDE_OK
1337 );
1338 assert_eq!(
1340 hide_challenge_accept(
1341 spent,
1342 challenge.data,
1343 challenge.len,
1344 answer.data,
1345 answer.len,
1346 public.data,
1347 public.len,
1348 1_000,
1349 ),
1350 HIDE_ERR_CHALLENGE_REPLAYED
1351 );
1352
1353 hide_spent_nonces_free(spent);
1354 hide_buffer_free(&mut answer);
1355 hide_buffer_free(&mut challenge);
1356 hide_buffer_free(&mut public);
1357 hide_signing_identity_free(identity);
1358 }
1359 }
1360
1361 #[test]
1362 fn an_answer_after_the_window_is_refused_across_the_boundary() {
1363 unsafe {
1364 let passphrase = CString::new("correct horse battery staple").unwrap();
1365 let file = identity_file(passphrase.to_str().unwrap());
1366 let identity = open_identity(&file, &passphrase);
1367
1368 let mut public = hide_buffer_empty();
1369 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1370
1371 let audience = CString::new("ssh://host.example").unwrap();
1372 let mut challenge = hide_buffer_empty();
1373 assert_eq!(
1374 hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1375 HIDE_OK
1376 );
1377 let mut answer = hide_buffer_empty();
1378 assert_eq!(
1379 hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1380 HIDE_OK
1381 );
1382
1383 let spent = hide_spent_nonces_new();
1384 assert_eq!(
1385 hide_challenge_accept(
1386 spent,
1387 challenge.data,
1388 challenge.len,
1389 answer.data,
1390 answer.len,
1391 public.data,
1392 public.len,
1393 1_100,
1394 ),
1395 HIDE_ERR_CHALLENGE_EXPIRED
1396 );
1397
1398 hide_spent_nonces_free(spent);
1399 hide_buffer_free(&mut answer);
1400 hide_buffer_free(&mut challenge);
1401 hide_buffer_free(&mut public);
1402 hide_signing_identity_free(identity);
1403 }
1404 }
1405
1406 #[test]
1408 fn the_signing_boundary_refuses_nulls_without_crashing() {
1409 unsafe {
1410 let mut out = hide_buffer_empty();
1411 assert_eq!(
1412 hide_sign_message(ptr::null(), ptr::null(), 0, ptr::null(), 0, &mut out),
1413 HIDE_ERR_INVALID_ARGUMENT
1414 );
1415 assert_eq!(
1416 hide_verify_message(
1417 ptr::null(),
1418 0,
1419 ptr::null(),
1420 0,
1421 ptr::null(),
1422 0,
1423 ptr::null(),
1424 0
1425 ),
1426 HIDE_ERR_NOT_A_KEY
1427 );
1428 assert_eq!(
1429 hide_challenge_answer(ptr::null(), ptr::null(), 0, &mut out),
1430 HIDE_ERR_INVALID_ARGUMENT
1431 );
1432 assert_eq!(
1433 hide_challenge_accept(
1434 ptr::null_mut(),
1435 ptr::null(),
1436 0,
1437 ptr::null(),
1438 0,
1439 ptr::null(),
1440 0,
1441 0
1442 ),
1443 HIDE_ERR_INVALID_ARGUMENT
1444 );
1445 hide_signing_identity_free(ptr::null_mut());
1447 hide_spent_nonces_free(ptr::null_mut());
1448 }
1449 }
1450}