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
843const MAX_LOG_BYTES: usize = 16 * 1024 * 1024;
850
851#[unsafe(no_mangle)]
860pub unsafe extern "C" fn hide_identity_verify(
861 log: *const u8,
862 log_len: usize,
863 recovery: *const u8,
864 recovery_len: usize,
865 out_devices: *mut usize,
866) -> i32 {
867 guard(|| {
868 if out_devices.is_null() {
869 return HIDE_ERR_INVALID_ARGUMENT;
870 }
871 let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
872 return HIDE_ERR_INVALID_ARGUMENT;
873 };
874 let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
875 return HIDE_ERR_INVALID_ARGUMENT;
876 };
877 let Ok(recovery_key) = hide_sign::VerifyingIdentity::from_bytes(recovery_bytes) else {
878 return HIDE_ERR_NOT_A_KEY;
879 };
880 let Ok(entries) = hide_identity::decode(log_bytes) else {
881 return HIDE_ERR_MALFORMED;
882 };
883 let Ok(membership) = hide_identity::IdentityLog::verify(&entries, &recovery_key) else {
884 return HIDE_ERR_AUTHENTICATION;
885 };
886 unsafe { *out_devices = membership.len() };
887 HIDE_OK
888 })
889}
890
891#[unsafe(no_mangle)]
896pub unsafe extern "C" fn hide_identity_trusts_device(
897 log: *const u8,
898 log_len: usize,
899 recovery: *const u8,
900 recovery_len: usize,
901 device_public: *const u8,
902 device_public_len: usize,
903 out_trusted: *mut i32,
904) -> i32 {
905 guard(|| {
906 if out_trusted.is_null() {
907 return HIDE_ERR_INVALID_ARGUMENT;
908 }
909 let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
910 return HIDE_ERR_INVALID_ARGUMENT;
911 };
912 let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
913 return HIDE_ERR_INVALID_ARGUMENT;
914 };
915 let Some(device_bytes) =
916 (unsafe { borrow(device_public, device_public_len, MAX_KEY_FILE) })
917 else {
918 return HIDE_ERR_INVALID_ARGUMENT;
919 };
920 let (Ok(recovery_key), Ok(device)) = (
921 hide_sign::VerifyingIdentity::from_bytes(recovery_bytes),
922 hide_sign::VerifyingIdentity::from_bytes(device_bytes),
923 ) else {
924 return HIDE_ERR_NOT_A_KEY;
925 };
926 let Ok(entries) = hide_identity::decode(log_bytes) else {
927 return HIDE_ERR_MALFORMED;
928 };
929 let Ok(membership) = hide_identity::IdentityLog::verify(&entries, &recovery_key) else {
930 return HIDE_ERR_AUTHENTICATION;
931 };
932 let trusted = membership.contains(&hide_identity::device_id(&device));
933 unsafe { *out_trusted = i32::from(trusted) };
934 HIDE_OK
935 })
936}
937
938#[unsafe(no_mangle)]
943pub unsafe extern "C" fn hide_identity_head(
944 log: *const u8,
945 log_len: usize,
946 recovery: *const u8,
947 recovery_len: usize,
948 out: *mut HideBuffer,
949) -> i32 {
950 guard(|| {
951 if out.is_null() {
952 return HIDE_ERR_INVALID_ARGUMENT;
953 }
954 let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
955 return HIDE_ERR_INVALID_ARGUMENT;
956 };
957 let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
958 return HIDE_ERR_INVALID_ARGUMENT;
959 };
960 let Ok(recovery_key) = hide_sign::VerifyingIdentity::from_bytes(recovery_bytes) else {
961 return HIDE_ERR_NOT_A_KEY;
962 };
963 let Ok(entries) = hide_identity::decode(log_bytes) else {
964 return HIDE_ERR_MALFORMED;
965 };
966 let Ok(log) = hide_identity::IdentityLog::from_entries(entries, recovery_key) else {
967 return HIDE_ERR_AUTHENTICATION;
968 };
969 unsafe { *out = HideBuffer::from_vec(log.head().to_vec()) };
970 HIDE_OK
971 })
972}
973
974#[unsafe(no_mangle)]
983pub unsafe extern "C" fn hide_epoch_verify(
984 chain: *const u8,
985 chain_len: usize,
986 out_epochs: *mut usize,
987) -> i32 {
988 guard(|| {
989 if out_epochs.is_null() {
990 return HIDE_ERR_INVALID_ARGUMENT;
991 }
992 let Some(bytes) = (unsafe { borrow(chain, chain_len, MAX_LOG_BYTES) }) else {
993 return HIDE_ERR_INVALID_ARGUMENT;
994 };
995 let Ok(records) = hide_epoch::decode_records(bytes) else {
996 return HIDE_ERR_MALFORMED;
997 };
998 if hide_epoch::EpochChain::verify(&records).is_err() {
999 return HIDE_ERR_AUTHENTICATION;
1000 }
1001 unsafe { *out_epochs = records.len() };
1002 HIDE_OK
1003 })
1004}
1005
1006#[unsafe(no_mangle)]
1011pub unsafe extern "C" fn hide_epoch_public_key(
1012 chain: *const u8,
1013 chain_len: usize,
1014 epoch: u64,
1015 out: *mut HideBuffer,
1016) -> i32 {
1017 guard(|| {
1018 if out.is_null() {
1019 return HIDE_ERR_INVALID_ARGUMENT;
1020 }
1021 let Some(bytes) = (unsafe { borrow(chain, chain_len, MAX_LOG_BYTES) }) else {
1022 return HIDE_ERR_INVALID_ARGUMENT;
1023 };
1024 let Ok(records) = hide_epoch::decode_records(bytes) else {
1025 return HIDE_ERR_MALFORMED;
1026 };
1027 if hide_epoch::EpochChain::verify(&records).is_err() {
1028 return HIDE_ERR_AUTHENTICATION;
1029 }
1030 let Some(record) = records.get(epoch as usize) else {
1031 return HIDE_ERR_INVALID_ARGUMENT;
1032 };
1033 unsafe { *out = HideBuffer::from_vec(record.public_key.clone()) };
1034 HIDE_OK
1035 })
1036}
1037
1038#[unsafe(no_mangle)]
1047pub unsafe extern "C" fn hide_transparency_verify_inclusion(
1048 leaf: *const u8,
1049 leaf_len: usize,
1050 index: u64,
1051 size: u64,
1052 path: *const u8,
1053 path_len: usize,
1054 root: *const u8,
1055 root_len: usize,
1056) -> i32 {
1057 guard(|| {
1058 let (Some(leaf), Some(path_bytes), Some(root)) = (
1059 unsafe { borrow(leaf, leaf_len, 32) },
1060 unsafe { borrow(path, path_len, 64 * 32) },
1061 unsafe { borrow(root, root_len, 32) },
1062 ) else {
1063 return HIDE_ERR_INVALID_ARGUMENT;
1064 };
1065 if leaf.len() != 32 || root.len() != 32 || path_bytes.len() % 32 != 0 {
1066 return HIDE_ERR_INVALID_ARGUMENT;
1067 }
1068 let proof = hide_transparency::InclusionProof {
1069 index,
1070 size,
1071 path: path_bytes
1072 .chunks_exact(32)
1073 .map(|chunk| {
1074 let mut hash = [0u8; 32];
1075 hash.copy_from_slice(chunk);
1076 hash
1077 })
1078 .collect(),
1079 };
1080 let (mut leaf_hash, mut root_hash) = ([0u8; 32], [0u8; 32]);
1081 leaf_hash.copy_from_slice(leaf);
1082 root_hash.copy_from_slice(root);
1083
1084 match hide_transparency::verify_inclusion(&proof, &leaf_hash, &root_hash) {
1085 Ok(()) => HIDE_OK,
1086 Err(_) => HIDE_ERR_AUTHENTICATION,
1087 }
1088 })
1089}
1090
1091#[unsafe(no_mangle)]
1097pub unsafe extern "C" fn hide_transparency_verify_consistency(
1098 old_size: u64,
1099 new_size: u64,
1100 path: *const u8,
1101 path_len: usize,
1102 old_root: *const u8,
1103 old_root_len: usize,
1104 new_root: *const u8,
1105 new_root_len: usize,
1106) -> i32 {
1107 guard(|| {
1108 let (Some(path_bytes), Some(old), Some(new)) = (
1109 unsafe { borrow(path, path_len, 64 * 32) },
1110 unsafe { borrow(old_root, old_root_len, 32) },
1111 unsafe { borrow(new_root, new_root_len, 32) },
1112 ) else {
1113 return HIDE_ERR_INVALID_ARGUMENT;
1114 };
1115 if old.len() != 32 || new.len() != 32 || path_bytes.len() % 32 != 0 {
1116 return HIDE_ERR_INVALID_ARGUMENT;
1117 }
1118 let proof = hide_transparency::ConsistencyProof {
1119 old_size,
1120 new_size,
1121 path: path_bytes
1122 .chunks_exact(32)
1123 .map(|chunk| {
1124 let mut hash = [0u8; 32];
1125 hash.copy_from_slice(chunk);
1126 hash
1127 })
1128 .collect(),
1129 };
1130 let (mut old_hash, mut new_hash) = ([0u8; 32], [0u8; 32]);
1131 old_hash.copy_from_slice(old);
1132 new_hash.copy_from_slice(new);
1133
1134 match hide_transparency::verify_consistency(&proof, &old_hash, &new_hash) {
1135 Ok(()) => HIDE_OK,
1136 Err(_) => HIDE_ERR_AUTHENTICATION,
1137 }
1138 })
1139}
1140#[cfg(test)]
1141mod tests {
1142 use std::ffi::CString;
1143
1144 use super::*;
1145
1146 unsafe fn keypair() -> (*mut HideSecretKey, HideBuffer) {
1148 let mut secret = ptr::null_mut();
1149 let mut public = hide_buffer_empty();
1150 assert_eq!(
1151 unsafe { hide_keypair_generate(&mut secret, &mut public) },
1152 HIDE_OK
1153 );
1154 assert_eq!(public.len, HIDE_PUBLIC_KEY_LEN);
1155 (secret, public)
1156 }
1157
1158 #[test]
1159 fn round_trips_through_the_c_api() {
1160 unsafe {
1161 let (secret, public) = keypair();
1162 let message = b"nume,suma\nAna,9000\n";
1163
1164 let mut container = hide_buffer_empty();
1165 assert_eq!(
1166 hide_encrypt(
1167 message.as_ptr(),
1168 message.len(),
1169 public.data,
1170 1,
1171 c"salarii.csv".as_ptr(),
1172 ptr::null(),
1173 &mut container,
1174 ),
1175 HIDE_OK
1176 );
1177 assert!(container.len > message.len());
1178
1179 let mut plaintext = hide_buffer_empty();
1180 let mut filename = hide_buffer_empty();
1181 assert_eq!(
1182 hide_decrypt(
1183 container.data,
1184 container.len,
1185 secret,
1186 &mut plaintext,
1187 &mut filename,
1188 ptr::null_mut(),
1189 ),
1190 HIDE_OK
1191 );
1192 assert_eq!(
1193 slice::from_raw_parts(plaintext.data, plaintext.len),
1194 message
1195 );
1196 assert_eq!(
1197 slice::from_raw_parts(filename.data, filename.len),
1198 b"salarii.csv"
1199 );
1200
1201 hide_buffer_free(&mut filename);
1202 hide_buffer_free(&mut plaintext);
1203 hide_buffer_free(&mut container);
1204 hide_buffer_free(&mut { public });
1205 hide_secret_key_free(secret);
1206 }
1207 }
1208
1209 #[test]
1210 fn a_freed_buffer_is_cleared_and_safe_to_free_twice() {
1211 unsafe {
1212 let (secret, mut public) = keypair();
1213 let mut container = hide_buffer_empty();
1214 hide_encrypt(
1215 b"x".as_ptr(),
1216 1,
1217 public.data,
1218 1,
1219 ptr::null(),
1220 ptr::null(),
1221 &mut container,
1222 );
1223 hide_buffer_free(&mut container);
1224 assert!(container.data.is_null(), "freed buffer kept its pointer");
1225 hide_buffer_free(&mut container);
1227 hide_buffer_free(&mut public);
1228 hide_secret_key_free(secret);
1229 }
1230 }
1231
1232 #[test]
1233 fn tampering_is_refused_and_yields_no_plaintext() {
1234 unsafe {
1235 let (secret, mut public) = keypair();
1236 let mut container = hide_buffer_empty();
1237 hide_encrypt(
1238 b"confidential".as_ptr(),
1239 12,
1240 public.data,
1241 1,
1242 ptr::null(),
1243 ptr::null(),
1244 &mut container,
1245 );
1246
1247 let mut damaged = slice::from_raw_parts(container.data, container.len).to_vec();
1248 let last = damaged.len() - 1;
1249 damaged[last] ^= 1;
1250
1251 let mut plaintext = hide_buffer_empty();
1252 let code = hide_decrypt(
1253 damaged.as_ptr(),
1254 damaged.len(),
1255 secret,
1256 &mut plaintext,
1257 ptr::null_mut(),
1258 ptr::null_mut(),
1259 );
1260 assert_ne!(code, HIDE_OK);
1261 assert!(plaintext.data.is_null(), "published unverified plaintext");
1262
1263 hide_buffer_free(&mut container);
1264 hide_buffer_free(&mut public);
1265 hide_secret_key_free(secret);
1266 }
1267 }
1268
1269 #[test]
1270 fn the_wrong_key_cannot_decrypt() {
1271 unsafe {
1272 let (secret, mut public) = keypair();
1273 let (other, mut other_public) = keypair();
1274 let mut container = hide_buffer_empty();
1275 hide_encrypt(
1276 b"secret".as_ptr(),
1277 6,
1278 public.data,
1279 1,
1280 ptr::null(),
1281 ptr::null(),
1282 &mut container,
1283 );
1284
1285 let mut plaintext = hide_buffer_empty();
1286 assert_eq!(
1287 hide_decrypt(
1288 container.data,
1289 container.len,
1290 other,
1291 &mut plaintext,
1292 ptr::null_mut(),
1293 ptr::null_mut(),
1294 ),
1295 HIDE_ERR_NO_MATCHING_RECIPIENT
1296 );
1297
1298 hide_buffer_free(&mut container);
1299 hide_buffer_free(&mut public);
1300 hide_buffer_free(&mut other_public);
1301 hide_secret_key_free(secret);
1302 hide_secret_key_free(other);
1303 }
1304 }
1305
1306 #[test]
1307 fn null_and_absurd_arguments_are_rejected_rather_than_crashing() {
1308 unsafe {
1309 let mut out = hide_buffer_empty();
1310 let mut secret = ptr::null_mut();
1311
1312 assert_eq!(
1313 hide_keypair_generate(ptr::null_mut(), &mut out),
1314 HIDE_ERR_INVALID_ARGUMENT
1315 );
1316 assert_eq!(
1317 hide_keypair_generate(&mut secret, ptr::null_mut()),
1318 HIDE_ERR_INVALID_ARGUMENT
1319 );
1320 assert_eq!(
1322 hide_encrypt(
1323 ptr::null(),
1324 10,
1325 ptr::null(),
1326 1,
1327 ptr::null(),
1328 ptr::null(),
1329 &mut out
1330 ),
1331 HIDE_ERR_INVALID_ARGUMENT
1332 );
1333 assert_eq!(
1335 hide_encrypt(
1336 b"x".as_ptr(),
1337 usize::MAX,
1338 ptr::null(),
1339 1,
1340 ptr::null(),
1341 ptr::null(),
1342 &mut out
1343 ),
1344 HIDE_ERR_INVALID_ARGUMENT
1345 );
1346 assert_eq!(
1347 hide_decrypt(
1348 ptr::null(),
1349 0,
1350 ptr::null(),
1351 &mut out,
1352 ptr::null_mut(),
1353 ptr::null_mut()
1354 ),
1355 HIDE_ERR_INVALID_ARGUMENT
1356 );
1357 let mut kind = -1;
1358 assert_eq!(
1359 hide_inspect_key(ptr::null(), 32, &mut kind),
1360 HIDE_ERR_INVALID_ARGUMENT
1361 );
1362 hide_buffer_free(ptr::null_mut());
1364 hide_secret_key_free(ptr::null_mut());
1365 }
1366 }
1367
1368 #[test]
1369 fn protected_keys_round_trip_and_reject_a_wrong_passphrase() {
1370 unsafe {
1371 let (secret, mut public) = keypair();
1372 let mut sealed = hide_buffer_empty();
1373 assert_eq!(
1374 hide_secret_key_protect(secret, c"correct horse".as_ptr(), &mut sealed),
1375 HIDE_OK
1376 );
1377
1378 let mut kind = -1;
1379 assert_eq!(
1380 hide_inspect_key(sealed.data, sealed.len, &mut kind),
1381 HIDE_OK
1382 );
1383 assert_eq!(kind, HIDE_KEY_PROTECTED);
1384
1385 let mut reopened = ptr::null_mut();
1386 assert_eq!(
1387 hide_secret_key_open(sealed.data, sealed.len, c"wrong".as_ptr(), &mut reopened),
1388 HIDE_ERR_WRONG_PASSPHRASE
1389 );
1390 assert_eq!(
1391 hide_secret_key_open(sealed.data, sealed.len, ptr::null(), &mut reopened),
1392 HIDE_ERR_WRONG_PASSPHRASE
1393 );
1394 assert_eq!(
1395 hide_secret_key_open(
1396 sealed.data,
1397 sealed.len,
1398 c"correct horse".as_ptr(),
1399 &mut reopened
1400 ),
1401 HIDE_OK
1402 );
1403
1404 let mut derived = hide_buffer_empty();
1406 assert_eq!(hide_secret_key_public(reopened, &mut derived), HIDE_OK);
1407 assert_eq!(
1408 slice::from_raw_parts(derived.data, derived.len),
1409 slice::from_raw_parts(public.data, public.len)
1410 );
1411
1412 hide_buffer_free(&mut derived);
1413 hide_buffer_free(&mut sealed);
1414 hide_buffer_free(&mut public);
1415 hide_secret_key_free(reopened);
1416 hide_secret_key_free(secret);
1417 }
1418 }
1419
1420 #[test]
1421 fn armor_round_trips_and_rejects_rubbish() {
1422 unsafe {
1423 let (secret, mut public) = keypair();
1424 let mut armored = hide_buffer_empty();
1425 assert_eq!(
1426 hide_public_key_armor(public.data, public.len, &mut armored),
1427 HIDE_OK
1428 );
1429
1430 let text =
1432 CString::new(slice::from_raw_parts(armored.data, armored.len)).expect("no NUL");
1433 let mut decoded = hide_buffer_empty();
1434 assert_eq!(
1435 hide_public_key_dearmor(text.as_ptr(), &mut decoded),
1436 HIDE_OK
1437 );
1438 assert_eq!(
1439 slice::from_raw_parts(decoded.data, decoded.len),
1440 slice::from_raw_parts(public.data, public.len)
1441 );
1442 assert_ne!(
1443 hide_public_key_dearmor(c"not a key".as_ptr(), &mut decoded),
1444 HIDE_OK
1445 );
1446
1447 hide_buffer_free(&mut decoded);
1448 hide_buffer_free(&mut armored);
1449 hide_buffer_free(&mut public);
1450 hide_secret_key_free(secret);
1451 }
1452 }
1453
1454 fn identity_file(passphrase: &str) -> Vec<u8> {
1456 let identity = hide_keyring::Identity::generate().expect("randomness");
1457 hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase)
1458 .expect("sealing works")
1459 }
1460
1461 unsafe fn open_identity(file: &[u8], passphrase: &CString) -> *mut HideSigningIdentity {
1462 let mut identity = ptr::null_mut();
1463 assert_eq!(
1464 unsafe {
1465 hide_signing_identity_open(
1466 file.as_ptr(),
1467 file.len(),
1468 passphrase.as_ptr(),
1469 &mut identity,
1470 )
1471 },
1472 HIDE_OK
1473 );
1474 identity
1475 }
1476
1477 #[test]
1478 fn signs_and_verifies_across_the_boundary() {
1479 unsafe {
1480 let passphrase = CString::new("correct horse battery staple").unwrap();
1481 let file = identity_file(passphrase.to_str().unwrap());
1482 let identity = open_identity(&file, &passphrase);
1483
1484 let mut public = hide_buffer_empty();
1485 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1486 assert_eq!(public.len, HIDE_VERIFYING_KEY_LEN);
1487
1488 let context = b"HIDE/0.5 ffi test";
1489 let message = b"the message that crossed the boundary";
1490 let mut signature = hide_buffer_empty();
1491 assert_eq!(
1492 hide_sign_message(
1493 identity,
1494 context.as_ptr(),
1495 context.len(),
1496 message.as_ptr(),
1497 message.len(),
1498 &mut signature,
1499 ),
1500 HIDE_OK
1501 );
1502 assert_eq!(signature.len, HIDE_SIGNATURE_LEN);
1503
1504 assert_eq!(
1505 hide_verify_message(
1506 public.data,
1507 public.len,
1508 context.as_ptr(),
1509 context.len(),
1510 message.as_ptr(),
1511 message.len(),
1512 signature.data,
1513 signature.len,
1514 ),
1515 HIDE_OK
1516 );
1517
1518 let other = b"HIDE/0.5 something else";
1521 assert_eq!(
1522 hide_verify_message(
1523 public.data,
1524 public.len,
1525 other.as_ptr(),
1526 other.len(),
1527 message.as_ptr(),
1528 message.len(),
1529 signature.data,
1530 signature.len,
1531 ),
1532 HIDE_ERR_AUTHENTICATION
1533 );
1534
1535 let altered = b"the message that crossed the boundaryX";
1536 assert_eq!(
1537 hide_verify_message(
1538 public.data,
1539 public.len,
1540 context.as_ptr(),
1541 context.len(),
1542 altered.as_ptr(),
1543 altered.len(),
1544 signature.data,
1545 signature.len,
1546 ),
1547 HIDE_ERR_AUTHENTICATION
1548 );
1549
1550 hide_buffer_free(&mut signature);
1551 hide_buffer_free(&mut public);
1552 hide_signing_identity_free(identity);
1553 }
1554 }
1555
1556 #[test]
1559 fn an_encryption_only_key_cannot_sign() {
1560 unsafe {
1561 let passphrase = CString::new("correct horse battery staple").unwrap();
1562 let secret = hide_crypto::RecipientSecret::generate().expect("randomness");
1563 let file =
1564 hide_keyring::protect(&secret, passphrase.to_str().unwrap()).expect("sealing");
1565
1566 let mut identity = ptr::null_mut();
1567 assert_eq!(
1568 hide_signing_identity_open(
1569 file.as_ptr(),
1570 file.len(),
1571 passphrase.as_ptr(),
1572 &mut identity,
1573 ),
1574 HIDE_ERR_NOT_A_KEY
1575 );
1576 assert!(identity.is_null());
1577 }
1578 }
1579
1580 #[test]
1581 fn a_wrong_passphrase_is_named_as_such() {
1582 unsafe {
1583 let file = identity_file("correct horse battery staple");
1584 let wrong = CString::new("not the passphrase at all").unwrap();
1585 let mut identity = ptr::null_mut();
1586 assert_eq!(
1587 hide_signing_identity_open(
1588 file.as_ptr(),
1589 file.len(),
1590 wrong.as_ptr(),
1591 &mut identity
1592 ),
1593 HIDE_ERR_WRONG_PASSPHRASE
1594 );
1595 }
1596 }
1597
1598 #[test]
1599 fn a_challenge_is_answered_once_and_then_refused() {
1600 unsafe {
1601 let passphrase = CString::new("correct horse battery staple").unwrap();
1602 let file = identity_file(passphrase.to_str().unwrap());
1603 let identity = open_identity(&file, &passphrase);
1604
1605 let mut public = hide_buffer_empty();
1606 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1607
1608 let audience = CString::new("ssh://host.example").unwrap();
1609 let mut challenge = hide_buffer_empty();
1610 assert_eq!(
1611 hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1612 HIDE_OK
1613 );
1614
1615 let mut answer = hide_buffer_empty();
1616 assert_eq!(
1617 hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1618 HIDE_OK
1619 );
1620
1621 let spent = hide_spent_nonces_new();
1622 assert_eq!(
1623 hide_challenge_accept(
1624 spent,
1625 challenge.data,
1626 challenge.len,
1627 answer.data,
1628 answer.len,
1629 public.data,
1630 public.len,
1631 1_000,
1632 ),
1633 HIDE_OK
1634 );
1635 assert_eq!(
1637 hide_challenge_accept(
1638 spent,
1639 challenge.data,
1640 challenge.len,
1641 answer.data,
1642 answer.len,
1643 public.data,
1644 public.len,
1645 1_000,
1646 ),
1647 HIDE_ERR_CHALLENGE_REPLAYED
1648 );
1649
1650 hide_spent_nonces_free(spent);
1651 hide_buffer_free(&mut answer);
1652 hide_buffer_free(&mut challenge);
1653 hide_buffer_free(&mut public);
1654 hide_signing_identity_free(identity);
1655 }
1656 }
1657
1658 #[test]
1659 fn an_answer_after_the_window_is_refused_across_the_boundary() {
1660 unsafe {
1661 let passphrase = CString::new("correct horse battery staple").unwrap();
1662 let file = identity_file(passphrase.to_str().unwrap());
1663 let identity = open_identity(&file, &passphrase);
1664
1665 let mut public = hide_buffer_empty();
1666 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1667
1668 let audience = CString::new("ssh://host.example").unwrap();
1669 let mut challenge = hide_buffer_empty();
1670 assert_eq!(
1671 hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1672 HIDE_OK
1673 );
1674 let mut answer = hide_buffer_empty();
1675 assert_eq!(
1676 hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1677 HIDE_OK
1678 );
1679
1680 let spent = hide_spent_nonces_new();
1681 assert_eq!(
1682 hide_challenge_accept(
1683 spent,
1684 challenge.data,
1685 challenge.len,
1686 answer.data,
1687 answer.len,
1688 public.data,
1689 public.len,
1690 1_100,
1691 ),
1692 HIDE_ERR_CHALLENGE_EXPIRED
1693 );
1694
1695 hide_spent_nonces_free(spent);
1696 hide_buffer_free(&mut answer);
1697 hide_buffer_free(&mut challenge);
1698 hide_buffer_free(&mut public);
1699 hide_signing_identity_free(identity);
1700 }
1701 }
1702
1703 #[test]
1705 fn the_signing_boundary_refuses_nulls_without_crashing() {
1706 unsafe {
1707 let mut out = hide_buffer_empty();
1708 assert_eq!(
1709 hide_sign_message(ptr::null(), ptr::null(), 0, ptr::null(), 0, &mut out),
1710 HIDE_ERR_INVALID_ARGUMENT
1711 );
1712 assert_eq!(
1713 hide_verify_message(
1714 ptr::null(),
1715 0,
1716 ptr::null(),
1717 0,
1718 ptr::null(),
1719 0,
1720 ptr::null(),
1721 0
1722 ),
1723 HIDE_ERR_NOT_A_KEY
1724 );
1725 assert_eq!(
1726 hide_challenge_answer(ptr::null(), ptr::null(), 0, &mut out),
1727 HIDE_ERR_INVALID_ARGUMENT
1728 );
1729 assert_eq!(
1730 hide_challenge_accept(
1731 ptr::null_mut(),
1732 ptr::null(),
1733 0,
1734 ptr::null(),
1735 0,
1736 ptr::null(),
1737 0,
1738 0
1739 ),
1740 HIDE_ERR_INVALID_ARGUMENT
1741 );
1742 hide_signing_identity_free(ptr::null_mut());
1744 hide_spent_nonces_free(ptr::null_mut());
1745 }
1746 }
1747}