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#[doc(hidden)]
147#[unsafe(no_mangle)]
148pub extern "C" fn hide_test_panic() -> i32 {
149 guard(|| panic!("deliberate panic for the ABI guard test"))
150}
151
152#[unsafe(no_mangle)]
155pub extern "C" fn hide_error_message(code: i32) -> *const c_char {
156 let text: &[u8] = match code {
157 HIDE_OK => b"success\0",
158 HIDE_ERR_INVALID_ARGUMENT => b"invalid argument\0",
159 HIDE_ERR_WRONG_PASSPHRASE => b"incorrect passphrase, or the key file was modified\0",
160 HIDE_ERR_NOT_A_KEY => b"not a HIDE key file\0",
161 HIDE_ERR_AUTHENTICATION => b"authentication failed; the data was altered\0",
162 HIDE_ERR_NO_MATCHING_RECIPIENT => b"no matching recipient for this key\0",
163 HIDE_ERR_MALFORMED => b"malformed or corrupt input\0",
164 HIDE_ERR_TOO_LARGE => b"input is too large\0",
165 HIDE_ERR_CHALLENGE_EXPIRED => b"the challenge expired before it was answered\0",
166 HIDE_ERR_CHALLENGE_REPLAYED => b"this challenge was already answered\0",
167 HIDE_ERR_PANIC => b"internal error (panic)\0",
168 _ => b"internal error\0",
169 };
170 text.as_ptr().cast()
171}
172
173#[unsafe(no_mangle)]
175pub extern "C" fn hide_version() -> *const c_char {
176 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr().cast()
177}
178
179#[unsafe(no_mangle)]
184pub unsafe extern "C" fn hide_buffer_free(buffer: *mut HideBuffer) {
185 if buffer.is_null() {
186 return;
187 }
188 let buffer = unsafe { &mut *buffer };
189 if buffer.data.is_null() {
190 return;
191 }
192 let mut bytes = unsafe { Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity) };
195 bytes.iter_mut().for_each(|byte| *byte = 0);
196 drop(bytes);
197 buffer.data = ptr::null_mut();
198 buffer.len = 0;
199 buffer.capacity = 0;
200}
201
202#[unsafe(no_mangle)]
208pub unsafe extern "C" fn hide_keypair_generate(
209 out_secret: *mut *mut HideSecretKey,
210 out_public: *mut HideBuffer,
211) -> i32 {
212 guard(|| {
213 if out_secret.is_null() || out_public.is_null() {
214 return HIDE_ERR_INVALID_ARGUMENT;
215 }
216 let Ok(secret) = RecipientSecret::generate() else {
217 return HIDE_ERR_INTERNAL;
218 };
219 let Ok(public) = secret.public_key() else {
220 return HIDE_ERR_INTERNAL;
221 };
222 unsafe {
223 *out_public = HideBuffer::from_vec(public.to_bytes());
224 *out_secret = Box::into_raw(Box::new(HideSecretKey(secret)));
225 }
226 HIDE_OK
227 })
228}
229
230#[unsafe(no_mangle)]
236pub unsafe extern "C" fn hide_inspect_key(data: *const u8, len: usize, out_kind: *mut i32) -> i32 {
237 guard(|| {
238 if out_kind.is_null() {
239 return HIDE_ERR_INVALID_ARGUMENT;
240 }
241 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
242 return HIDE_ERR_INVALID_ARGUMENT;
243 };
244 let kind = match hide_keyring::inspect(bytes) {
245 KeyFormat::Raw => HIDE_KEY_RAW,
246 KeyFormat::Protected => HIDE_KEY_PROTECTED,
247 };
248 unsafe { *out_kind = kind };
249 HIDE_OK
250 })
251}
252
253#[unsafe(no_mangle)]
260pub unsafe extern "C" fn hide_secret_key_open(
261 data: *const u8,
262 len: usize,
263 passphrase: *const c_char,
264 out_secret: *mut *mut HideSecretKey,
265) -> i32 {
266 guard(|| {
267 if out_secret.is_null() {
268 return HIDE_ERR_INVALID_ARGUMENT;
269 }
270 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
271 return HIDE_ERR_INVALID_ARGUMENT;
272 };
273 let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
274 return HIDE_ERR_INVALID_ARGUMENT;
275 };
276 match hide_keyring::open(bytes, passphrase) {
277 Ok(secret) => {
278 unsafe { *out_secret = Box::into_raw(Box::new(HideSecretKey(secret))) };
279 HIDE_OK
280 }
281 Err(error) => keyring_error_code(&error),
282 }
283 })
284}
285
286#[unsafe(no_mangle)]
291pub unsafe extern "C" fn hide_secret_key_protect(
292 secret: *const HideSecretKey,
293 passphrase: *const c_char,
294 out: *mut HideBuffer,
295) -> i32 {
296 guard(|| {
297 if secret.is_null() || out.is_null() {
298 return HIDE_ERR_INVALID_ARGUMENT;
299 }
300 let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
301 return HIDE_ERR_INVALID_ARGUMENT;
302 };
303 let secret = unsafe { &*secret };
304 match hide_keyring::protect(&secret.0, passphrase) {
305 Ok(sealed) => {
306 unsafe { *out = HideBuffer::from_vec(sealed) };
307 HIDE_OK
308 }
309 Err(error) => keyring_error_code(&error),
310 }
311 })
312}
313
314#[unsafe(no_mangle)]
319pub unsafe extern "C" fn hide_secret_key_public(
320 secret: *const HideSecretKey,
321 out: *mut HideBuffer,
322) -> i32 {
323 guard(|| {
324 if secret.is_null() || out.is_null() {
325 return HIDE_ERR_INVALID_ARGUMENT;
326 }
327 let secret = unsafe { &*secret };
328 match secret.0.public_key() {
329 Ok(public) => {
330 unsafe { *out = HideBuffer::from_vec(public.to_bytes()) };
331 HIDE_OK
332 }
333 Err(_) => HIDE_ERR_INTERNAL,
334 }
335 })
336}
337
338#[unsafe(no_mangle)]
343pub unsafe extern "C" fn hide_secret_key_free(secret: *mut HideSecretKey) {
344 if secret.is_null() {
345 return;
346 }
347 drop(unsafe { Box::from_raw(secret) });
348}
349
350#[unsafe(no_mangle)]
358pub unsafe extern "C" fn hide_encrypt(
359 plaintext: *const u8,
360 plaintext_len: usize,
361 recipients: *const u8,
362 recipient_count: usize,
363 filename: *const c_char,
364 media_type: *const c_char,
365 out: *mut HideBuffer,
366) -> i32 {
367 guard(|| {
368 if out.is_null() {
369 return HIDE_ERR_INVALID_ARGUMENT;
370 }
371 if recipient_count == 0 || recipient_count > 64 {
372 return HIDE_ERR_INVALID_ARGUMENT;
373 }
374 let Some(plaintext) = (unsafe { borrow(plaintext, plaintext_len, MAX_INPUT) }) else {
375 return HIDE_ERR_INVALID_ARGUMENT;
376 };
377 let Some(keys) =
378 (unsafe { borrow(recipients, recipient_count * HIDE_PUBLIC_KEY_LEN, MAX_INPUT) })
379 else {
380 return HIDE_ERR_INVALID_ARGUMENT;
381 };
382
383 let mut parsed = Vec::with_capacity(recipient_count);
384 for chunk in keys.chunks_exact(HIDE_PUBLIC_KEY_LEN) {
385 match RecipientPublic::from_bytes(chunk) {
386 Ok(key) => parsed.push(key),
387 Err(_) => return HIDE_ERR_INVALID_ARGUMENT,
388 }
389 }
390
391 let (Some(filename), Some(media_type)) = (unsafe { borrow_str(filename) }, unsafe {
392 borrow_str(media_type)
393 }) else {
394 return HIDE_ERR_INVALID_ARGUMENT;
395 };
396 let metadata = Metadata {
397 filename: filename.map(str::to_owned),
398 media_type: media_type.map(str::to_owned),
399 signature: None,
400 };
401
402 let mut container = Vec::new();
403 match hide_object::encrypt(&mut &*plaintext, &mut container, &parsed, &metadata) {
404 Ok(_) => {
405 unsafe { *out = HideBuffer::from_vec(container) };
406 HIDE_OK
407 }
408 Err(error) => object_error_code(&error),
409 }
410 })
411}
412
413#[unsafe(no_mangle)]
423pub unsafe extern "C" fn hide_decrypt(
424 container: *const u8,
425 container_len: usize,
426 secret: *const HideSecretKey,
427 out: *mut HideBuffer,
428 out_filename: *mut HideBuffer,
429 out_media_type: *mut HideBuffer,
430) -> i32 {
431 guard(|| {
432 if secret.is_null() || out.is_null() {
433 return HIDE_ERR_INVALID_ARGUMENT;
434 }
435 let Some(container) = (unsafe { borrow(container, container_len, MAX_INPUT) }) else {
436 return HIDE_ERR_INVALID_ARGUMENT;
437 };
438 let secret = unsafe { &*secret };
439
440 let mut plaintext = Zeroizing::new(Vec::new());
442 let verified =
443 match hide_object::decrypt_to_staging(&mut &*container, &mut *plaintext, &secret.0) {
444 Ok(verified) => verified,
445 Err(error) => return object_error_code(&error),
446 };
447
448 if !out_filename.is_null() {
449 let value = verified.metadata.filename.unwrap_or_default();
450 unsafe { *out_filename = HideBuffer::from_vec(value.into_bytes()) };
451 }
452 if !out_media_type.is_null() {
453 let value = verified.metadata.media_type.unwrap_or_default();
454 unsafe { *out_media_type = HideBuffer::from_vec(value.into_bytes()) };
455 }
456
457 unsafe { *out = HideBuffer::from_vec(std::mem::take(&mut *plaintext)) };
458 HIDE_OK
459 })
460}
461
462#[unsafe(no_mangle)]
467pub unsafe extern "C" fn hide_public_key_armor(
468 data: *const u8,
469 len: usize,
470 out: *mut HideBuffer,
471) -> i32 {
472 guard(|| {
473 if out.is_null() {
474 return HIDE_ERR_INVALID_ARGUMENT;
475 }
476 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
477 return HIDE_ERR_INVALID_ARGUMENT;
478 };
479 if bytes.len() != HIDE_PUBLIC_KEY_LEN {
480 return HIDE_ERR_INVALID_ARGUMENT;
481 }
482 let armored = hide_keyring::encode_public(bytes);
483 unsafe { *out = HideBuffer::from_vec(armored.into_bytes()) };
484 HIDE_OK
485 })
486}
487
488#[unsafe(no_mangle)]
493pub unsafe extern "C" fn hide_public_key_dearmor(text: *const c_char, out: *mut HideBuffer) -> i32 {
494 guard(|| {
495 if out.is_null() {
496 return HIDE_ERR_INVALID_ARGUMENT;
497 }
498 let Some(Some(text)) = (unsafe { borrow_str(text) }) else {
499 return HIDE_ERR_INVALID_ARGUMENT;
500 };
501 match hide_keyring::decode_public(text) {
502 Ok(bytes) if bytes.len() == HIDE_PUBLIC_KEY_LEN => {
503 unsafe { *out = HideBuffer::from_vec(bytes) };
504 HIDE_OK
505 }
506 Ok(_) => HIDE_ERR_INVALID_ARGUMENT,
507 Err(error) => keyring_error_code(&error),
508 }
509 })
510}
511
512pub const HIDE_SIGNATURE_LEN: usize = hide_sign::SIGNATURE_LENGTH;
514pub const HIDE_VERIFYING_KEY_LEN: usize = hide_sign::VERIFYING_KEY_LENGTH;
516pub const HIDE_NONCE_LEN: usize = hide_sign::NONCE_LENGTH;
518
519pub const HIDE_ERR_CHALLENGE_EXPIRED: i32 = 8;
521pub const HIDE_ERR_CHALLENGE_REPLAYED: i32 = 9;
522
523pub struct HideSigningIdentity(hide_sign::SigningIdentity);
526
527#[unsafe(no_mangle)]
534pub unsafe extern "C" fn hide_identity_generate(
535 passphrase: *const c_char,
536 out_key_file: *mut HideBuffer,
537) -> i32 {
538 guard(|| {
539 if out_key_file.is_null() {
540 return HIDE_ERR_INVALID_ARGUMENT;
541 }
542 let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
543 return HIDE_ERR_INVALID_ARGUMENT;
544 };
545 let identity = match hide_keyring::Identity::generate() {
546 Ok(identity) => identity,
547 Err(_) => return HIDE_ERR_INTERNAL,
548 };
549 match hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase) {
550 Ok(sealed) => {
551 unsafe { *out_key_file = HideBuffer::from_vec(sealed) };
552 HIDE_OK
553 }
554 Err(error) => keyring_error_code(&error),
555 }
556 })
557}
558
559pub struct HideSpentNonces(hide_sign::SpentNonces);
562
563#[unsafe(no_mangle)]
574pub unsafe extern "C" fn hide_signing_identity_open(
575 data: *const u8,
576 len: usize,
577 passphrase: *const c_char,
578 out_identity: *mut *mut HideSigningIdentity,
579) -> i32 {
580 guard(|| {
581 if out_identity.is_null() {
582 return HIDE_ERR_INVALID_ARGUMENT;
583 }
584 let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
585 return HIDE_ERR_INVALID_ARGUMENT;
586 };
587 let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
588 return HIDE_ERR_INVALID_ARGUMENT;
589 };
590
591 let seed = match hide_keyring::inspect(bytes) {
592 KeyFormat::Raw => {
593 if bytes.len() != 32 {
594 return HIDE_ERR_NOT_A_KEY;
595 }
596 let mut seed = Zeroizing::new([0_u8; 32]);
597 seed.copy_from_slice(bytes);
598 hide_keyring::Identity::from_seed(seed).signing_seed()
599 }
600 KeyFormat::Protected => {
601 let Some(passphrase) = passphrase else {
602 return HIDE_ERR_INVALID_ARGUMENT;
603 };
604 match hide_keyring::unprotect_seed(bytes, passphrase) {
605 Ok((seed, hide_keyring::KeyPurpose::Identity)) => {
606 hide_keyring::Identity::from_seed(seed).signing_seed()
607 }
608 Ok(_) => return HIDE_ERR_NOT_A_KEY,
611 Err(error) => return keyring_error_code(&error),
612 }
613 }
614 };
615
616 match hide_sign::SigningIdentity::from_bytes(&seed[..]) {
617 Ok(identity) => {
618 unsafe { *out_identity = Box::into_raw(Box::new(HideSigningIdentity(identity))) };
619 HIDE_OK
620 }
621 Err(_) => HIDE_ERR_NOT_A_KEY,
622 }
623 })
624}
625
626#[unsafe(no_mangle)]
631pub unsafe extern "C" fn hide_signing_identity_public(
632 identity: *const HideSigningIdentity,
633 out: *mut HideBuffer,
634) -> i32 {
635 guard(|| {
636 if identity.is_null() || out.is_null() {
637 return HIDE_ERR_INVALID_ARGUMENT;
638 }
639 let identity = unsafe { &*identity };
640 let public = identity.0.verifying_key().to_bytes();
641 unsafe { *out = HideBuffer::from_vec(public.to_vec()) };
642 HIDE_OK
643 })
644}
645
646#[unsafe(no_mangle)]
651pub unsafe extern "C" fn hide_signing_identity_free(identity: *mut HideSigningIdentity) {
652 if identity.is_null() {
653 return;
654 }
655 drop(unsafe { Box::from_raw(identity) });
656}
657
658#[unsafe(no_mangle)]
667pub unsafe extern "C" fn hide_sign_message(
668 identity: *const HideSigningIdentity,
669 context: *const u8,
670 context_len: usize,
671 message: *const u8,
672 message_len: usize,
673 out: *mut HideBuffer,
674) -> i32 {
675 guard(|| {
676 if identity.is_null() || out.is_null() {
677 return HIDE_ERR_INVALID_ARGUMENT;
678 }
679 let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
680 return HIDE_ERR_INVALID_ARGUMENT;
681 };
682 let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
683 return HIDE_ERR_INVALID_ARGUMENT;
684 };
685 let identity = unsafe { &*identity };
686 let signature = identity.0.sign(context, message);
687 unsafe { *out = HideBuffer::from_vec(signature.to_vec()) };
688 HIDE_OK
689 })
690}
691
692#[unsafe(no_mangle)]
697pub unsafe extern "C" fn hide_verify_message(
698 public_key: *const u8,
699 public_key_len: usize,
700 context: *const u8,
701 context_len: usize,
702 message: *const u8,
703 message_len: usize,
704 signature: *const u8,
705 signature_len: usize,
706) -> i32 {
707 guard(|| {
708 let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
709 return HIDE_ERR_INVALID_ARGUMENT;
710 };
711 let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
712 return HIDE_ERR_INVALID_ARGUMENT;
713 };
714 let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
715 return HIDE_ERR_INVALID_ARGUMENT;
716 };
717 if signature_len != HIDE_SIGNATURE_LEN || public_key_len != HIDE_VERIFYING_KEY_LEN {
720 return HIDE_ERR_INVALID_ARGUMENT;
721 }
722 let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
723 else {
724 return HIDE_ERR_INVALID_ARGUMENT;
725 };
726 let Ok(verifying) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
727 return HIDE_ERR_NOT_A_KEY;
728 };
729 match verifying.verify(context, message, signature) {
730 Ok(()) => HIDE_OK,
731 Err(_) => HIDE_ERR_AUTHENTICATION,
732 }
733 })
734}
735
736#[unsafe(no_mangle)]
742pub unsafe extern "C" fn hide_challenge_new(
743 audience: *const c_char,
744 now: u64,
745 valid_for: u64,
746 out: *mut HideBuffer,
747) -> i32 {
748 guard(|| {
749 if out.is_null() {
750 return HIDE_ERR_INVALID_ARGUMENT;
751 }
752 let Some(Some(audience)) = (unsafe { borrow_str(audience) }) else {
753 return HIDE_ERR_INVALID_ARGUMENT;
754 };
755 match hide_sign::Challenge::new(audience, now, valid_for) {
756 Ok(challenge) => {
757 unsafe { *out = HideBuffer::from_vec(challenge.encode()) };
758 HIDE_OK
759 }
760 Err(_) => HIDE_ERR_INTERNAL,
761 }
762 })
763}
764
765#[unsafe(no_mangle)]
770pub unsafe extern "C" fn hide_challenge_answer(
771 identity: *const HideSigningIdentity,
772 challenge: *const u8,
773 challenge_len: usize,
774 out: *mut HideBuffer,
775) -> i32 {
776 guard(|| {
777 if identity.is_null() || out.is_null() {
778 return HIDE_ERR_INVALID_ARGUMENT;
779 }
780 let Some(bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) }) else {
781 return HIDE_ERR_INVALID_ARGUMENT;
782 };
783 let Ok(challenge) = hide_sign::Challenge::decode(bytes) else {
784 return HIDE_ERR_MALFORMED;
785 };
786 let identity = unsafe { &*identity };
787 unsafe { *out = HideBuffer::from_vec(challenge.answer(&identity.0).to_vec()) };
788 HIDE_OK
789 })
790}
791
792#[unsafe(no_mangle)]
794pub extern "C" fn hide_spent_nonces_new() -> *mut HideSpentNonces {
795 Box::into_raw(Box::new(HideSpentNonces(hide_sign::SpentNonces::new())))
796}
797
798#[unsafe(no_mangle)]
803pub unsafe extern "C" fn hide_spent_nonces_free(spent: *mut HideSpentNonces) {
804 if spent.is_null() {
805 return;
806 }
807 drop(unsafe { Box::from_raw(spent) });
808}
809
810#[unsafe(no_mangle)]
817pub unsafe extern "C" fn hide_challenge_accept(
818 spent: *mut HideSpentNonces,
819 challenge: *const u8,
820 challenge_len: usize,
821 signature: *const u8,
822 signature_len: usize,
823 public_key: *const u8,
824 public_key_len: usize,
825 now: u64,
826) -> i32 {
827 guard(|| {
828 if spent.is_null() {
829 return HIDE_ERR_INVALID_ARGUMENT;
830 }
831 let Some(challenge_bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) })
832 else {
833 return HIDE_ERR_INVALID_ARGUMENT;
834 };
835 if signature_len != HIDE_SIGNATURE_LEN || public_key_len != HIDE_VERIFYING_KEY_LEN {
836 return HIDE_ERR_INVALID_ARGUMENT;
837 }
838 let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
839 else {
840 return HIDE_ERR_INVALID_ARGUMENT;
841 };
842 let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
843 return HIDE_ERR_INVALID_ARGUMENT;
844 };
845 let Ok(challenge) = hide_sign::Challenge::decode(challenge_bytes) else {
846 return HIDE_ERR_MALFORMED;
847 };
848 let Ok(prover) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
849 return HIDE_ERR_NOT_A_KEY;
850 };
851 let spent = unsafe { &mut *spent };
852 match spent.0.accept(&challenge, signature, &prover, now) {
853 Ok(()) => HIDE_OK,
854 Err(hide_sign::ChallengeError::Expired) => HIDE_ERR_CHALLENGE_EXPIRED,
855 Err(hide_sign::ChallengeError::Replayed) => HIDE_ERR_CHALLENGE_REPLAYED,
856 Err(hide_sign::ChallengeError::NotSigned) => HIDE_ERR_AUTHENTICATION,
857 }
858 })
859}
860
861const MAX_LOG_BYTES: usize = 16 * 1024 * 1024;
868
869#[unsafe(no_mangle)]
878pub unsafe extern "C" fn hide_identity_verify(
879 log: *const u8,
880 log_len: usize,
881 recovery: *const u8,
882 recovery_len: usize,
883 out_devices: *mut usize,
884) -> i32 {
885 guard(|| {
886 if out_devices.is_null() {
887 return HIDE_ERR_INVALID_ARGUMENT;
888 }
889 let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
890 return HIDE_ERR_INVALID_ARGUMENT;
891 };
892 let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
893 return HIDE_ERR_INVALID_ARGUMENT;
894 };
895 let Ok(recovery_key) = hide_sign::VerifyingIdentity::from_bytes(recovery_bytes) else {
896 return HIDE_ERR_NOT_A_KEY;
897 };
898 let Ok(entries) = hide_identity::decode(log_bytes) else {
899 return HIDE_ERR_MALFORMED;
900 };
901 let Ok(membership) = hide_identity::IdentityLog::verify(&entries, &recovery_key) else {
902 return HIDE_ERR_AUTHENTICATION;
903 };
904 unsafe { *out_devices = membership.len() };
905 HIDE_OK
906 })
907}
908
909#[unsafe(no_mangle)]
914pub unsafe extern "C" fn hide_identity_trusts_device(
915 log: *const u8,
916 log_len: usize,
917 recovery: *const u8,
918 recovery_len: usize,
919 device_public: *const u8,
920 device_public_len: usize,
921 out_trusted: *mut i32,
922) -> i32 {
923 guard(|| {
924 if out_trusted.is_null() {
925 return HIDE_ERR_INVALID_ARGUMENT;
926 }
927 let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
928 return HIDE_ERR_INVALID_ARGUMENT;
929 };
930 let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
931 return HIDE_ERR_INVALID_ARGUMENT;
932 };
933 let Some(device_bytes) =
934 (unsafe { borrow(device_public, device_public_len, MAX_KEY_FILE) })
935 else {
936 return HIDE_ERR_INVALID_ARGUMENT;
937 };
938 let (Ok(recovery_key), Ok(device)) = (
939 hide_sign::VerifyingIdentity::from_bytes(recovery_bytes),
940 hide_sign::VerifyingIdentity::from_bytes(device_bytes),
941 ) else {
942 return HIDE_ERR_NOT_A_KEY;
943 };
944 let Ok(entries) = hide_identity::decode(log_bytes) else {
945 return HIDE_ERR_MALFORMED;
946 };
947 let Ok(membership) = hide_identity::IdentityLog::verify(&entries, &recovery_key) else {
948 return HIDE_ERR_AUTHENTICATION;
949 };
950 let trusted = membership.contains(&hide_identity::device_id(&device));
951 unsafe { *out_trusted = i32::from(trusted) };
952 HIDE_OK
953 })
954}
955
956#[unsafe(no_mangle)]
961pub unsafe extern "C" fn hide_identity_head(
962 log: *const u8,
963 log_len: usize,
964 recovery: *const u8,
965 recovery_len: usize,
966 out: *mut HideBuffer,
967) -> i32 {
968 guard(|| {
969 if out.is_null() {
970 return HIDE_ERR_INVALID_ARGUMENT;
971 }
972 let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
973 return HIDE_ERR_INVALID_ARGUMENT;
974 };
975 let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
976 return HIDE_ERR_INVALID_ARGUMENT;
977 };
978 let Ok(recovery_key) = hide_sign::VerifyingIdentity::from_bytes(recovery_bytes) else {
979 return HIDE_ERR_NOT_A_KEY;
980 };
981 let Ok(entries) = hide_identity::decode(log_bytes) else {
982 return HIDE_ERR_MALFORMED;
983 };
984 let Ok(log) = hide_identity::IdentityLog::from_entries(entries, recovery_key) else {
985 return HIDE_ERR_AUTHENTICATION;
986 };
987 unsafe { *out = HideBuffer::from_vec(log.head().to_vec()) };
988 HIDE_OK
989 })
990}
991
992#[unsafe(no_mangle)]
1001pub unsafe extern "C" fn hide_epoch_verify(
1002 chain: *const u8,
1003 chain_len: usize,
1004 out_epochs: *mut usize,
1005) -> i32 {
1006 guard(|| {
1007 if out_epochs.is_null() {
1008 return HIDE_ERR_INVALID_ARGUMENT;
1009 }
1010 let Some(bytes) = (unsafe { borrow(chain, chain_len, MAX_LOG_BYTES) }) else {
1011 return HIDE_ERR_INVALID_ARGUMENT;
1012 };
1013 let Ok(records) = hide_epoch::decode_records(bytes) else {
1014 return HIDE_ERR_MALFORMED;
1015 };
1016 if hide_epoch::EpochChain::verify(&records).is_err() {
1017 return HIDE_ERR_AUTHENTICATION;
1018 }
1019 unsafe { *out_epochs = records.len() };
1020 HIDE_OK
1021 })
1022}
1023
1024#[unsafe(no_mangle)]
1029pub unsafe extern "C" fn hide_epoch_public_key(
1030 chain: *const u8,
1031 chain_len: usize,
1032 epoch: u64,
1033 out: *mut HideBuffer,
1034) -> i32 {
1035 guard(|| {
1036 if out.is_null() {
1037 return HIDE_ERR_INVALID_ARGUMENT;
1038 }
1039 let Some(bytes) = (unsafe { borrow(chain, chain_len, MAX_LOG_BYTES) }) else {
1040 return HIDE_ERR_INVALID_ARGUMENT;
1041 };
1042 let Ok(records) = hide_epoch::decode_records(bytes) else {
1043 return HIDE_ERR_MALFORMED;
1044 };
1045 if hide_epoch::EpochChain::verify(&records).is_err() {
1046 return HIDE_ERR_AUTHENTICATION;
1047 }
1048 let Ok(index) = usize::try_from(epoch) else {
1050 return HIDE_ERR_INVALID_ARGUMENT;
1051 };
1052 let Some(record) = records.get(index) else {
1053 return HIDE_ERR_INVALID_ARGUMENT;
1054 };
1055 unsafe { *out = HideBuffer::from_vec(record.public_key.clone()) };
1056 HIDE_OK
1057 })
1058}
1059
1060#[unsafe(no_mangle)]
1069pub unsafe extern "C" fn hide_transparency_verify_inclusion(
1070 leaf: *const u8,
1071 leaf_len: usize,
1072 index: u64,
1073 size: u64,
1074 path: *const u8,
1075 path_len: usize,
1076 root: *const u8,
1077 root_len: usize,
1078) -> i32 {
1079 guard(|| {
1080 let (Some(leaf), Some(path_bytes), Some(root)) = (
1081 unsafe { borrow(leaf, leaf_len, 32) },
1082 unsafe { borrow(path, path_len, 64 * 32) },
1083 unsafe { borrow(root, root_len, 32) },
1084 ) else {
1085 return HIDE_ERR_INVALID_ARGUMENT;
1086 };
1087 if leaf.len() != 32 || root.len() != 32 || path_bytes.len() % 32 != 0 {
1088 return HIDE_ERR_INVALID_ARGUMENT;
1089 }
1090 let proof = hide_transparency::InclusionProof {
1091 index,
1092 size,
1093 path: path_bytes
1094 .chunks_exact(32)
1095 .map(|chunk| {
1096 let mut hash = [0u8; 32];
1097 hash.copy_from_slice(chunk);
1098 hash
1099 })
1100 .collect(),
1101 };
1102 let (mut leaf_hash, mut root_hash) = ([0u8; 32], [0u8; 32]);
1103 leaf_hash.copy_from_slice(leaf);
1104 root_hash.copy_from_slice(root);
1105
1106 match hide_transparency::verify_inclusion(&proof, &leaf_hash, &root_hash) {
1107 Ok(()) => HIDE_OK,
1108 Err(_) => HIDE_ERR_AUTHENTICATION,
1109 }
1110 })
1111}
1112
1113#[unsafe(no_mangle)]
1119pub unsafe extern "C" fn hide_transparency_verify_consistency(
1120 old_size: u64,
1121 new_size: u64,
1122 path: *const u8,
1123 path_len: usize,
1124 old_root: *const u8,
1125 old_root_len: usize,
1126 new_root: *const u8,
1127 new_root_len: usize,
1128) -> i32 {
1129 guard(|| {
1130 let (Some(path_bytes), Some(old), Some(new)) = (
1131 unsafe { borrow(path, path_len, 64 * 32) },
1132 unsafe { borrow(old_root, old_root_len, 32) },
1133 unsafe { borrow(new_root, new_root_len, 32) },
1134 ) else {
1135 return HIDE_ERR_INVALID_ARGUMENT;
1136 };
1137 if old.len() != 32 || new.len() != 32 || path_bytes.len() % 32 != 0 {
1138 return HIDE_ERR_INVALID_ARGUMENT;
1139 }
1140 let proof = hide_transparency::ConsistencyProof {
1141 old_size,
1142 new_size,
1143 path: path_bytes
1144 .chunks_exact(32)
1145 .map(|chunk| {
1146 let mut hash = [0u8; 32];
1147 hash.copy_from_slice(chunk);
1148 hash
1149 })
1150 .collect(),
1151 };
1152 let (mut old_hash, mut new_hash) = ([0u8; 32], [0u8; 32]);
1153 old_hash.copy_from_slice(old);
1154 new_hash.copy_from_slice(new);
1155
1156 match hide_transparency::verify_consistency(&proof, &old_hash, &new_hash) {
1157 Ok(()) => HIDE_OK,
1158 Err(_) => HIDE_ERR_AUTHENTICATION,
1159 }
1160 })
1161}
1162#[cfg(test)]
1163mod tests {
1164 use std::ffi::CString;
1165
1166 use super::*;
1167
1168 unsafe fn keypair() -> (*mut HideSecretKey, HideBuffer) {
1170 let mut secret = ptr::null_mut();
1171 let mut public = hide_buffer_empty();
1172 assert_eq!(
1173 unsafe { hide_keypair_generate(&mut secret, &mut public) },
1174 HIDE_OK
1175 );
1176 assert_eq!(public.len, HIDE_PUBLIC_KEY_LEN);
1177 (secret, public)
1178 }
1179
1180 #[test]
1181 fn round_trips_through_the_c_api() {
1182 unsafe {
1183 let (secret, public) = keypair();
1184 let message = b"nume,suma\nAna,9000\n";
1185
1186 let mut container = hide_buffer_empty();
1187 assert_eq!(
1188 hide_encrypt(
1189 message.as_ptr(),
1190 message.len(),
1191 public.data,
1192 1,
1193 c"salarii.csv".as_ptr(),
1194 ptr::null(),
1195 &mut container,
1196 ),
1197 HIDE_OK
1198 );
1199 assert!(container.len > message.len());
1200
1201 let mut plaintext = hide_buffer_empty();
1202 let mut filename = hide_buffer_empty();
1203 assert_eq!(
1204 hide_decrypt(
1205 container.data,
1206 container.len,
1207 secret,
1208 &mut plaintext,
1209 &mut filename,
1210 ptr::null_mut(),
1211 ),
1212 HIDE_OK
1213 );
1214 assert_eq!(
1215 slice::from_raw_parts(plaintext.data, plaintext.len),
1216 message
1217 );
1218 assert_eq!(
1219 slice::from_raw_parts(filename.data, filename.len),
1220 b"salarii.csv"
1221 );
1222
1223 hide_buffer_free(&mut filename);
1224 hide_buffer_free(&mut plaintext);
1225 hide_buffer_free(&mut container);
1226 hide_buffer_free(&mut { public });
1227 hide_secret_key_free(secret);
1228 }
1229 }
1230
1231 #[test]
1232 fn a_freed_buffer_is_cleared_and_safe_to_free_twice() {
1233 unsafe {
1234 let (secret, mut public) = keypair();
1235 let mut container = hide_buffer_empty();
1236 hide_encrypt(
1237 b"x".as_ptr(),
1238 1,
1239 public.data,
1240 1,
1241 ptr::null(),
1242 ptr::null(),
1243 &mut container,
1244 );
1245 hide_buffer_free(&mut container);
1246 assert!(container.data.is_null(), "freed buffer kept its pointer");
1247 hide_buffer_free(&mut container);
1249 hide_buffer_free(&mut public);
1250 hide_secret_key_free(secret);
1251 }
1252 }
1253
1254 #[test]
1255 fn tampering_is_refused_and_yields_no_plaintext() {
1256 unsafe {
1257 let (secret, mut public) = keypair();
1258 let mut container = hide_buffer_empty();
1259 hide_encrypt(
1260 b"confidential".as_ptr(),
1261 12,
1262 public.data,
1263 1,
1264 ptr::null(),
1265 ptr::null(),
1266 &mut container,
1267 );
1268
1269 let mut damaged = slice::from_raw_parts(container.data, container.len).to_vec();
1270 let last = damaged.len() - 1;
1271 damaged[last] ^= 1;
1272
1273 let mut plaintext = hide_buffer_empty();
1274 let code = hide_decrypt(
1275 damaged.as_ptr(),
1276 damaged.len(),
1277 secret,
1278 &mut plaintext,
1279 ptr::null_mut(),
1280 ptr::null_mut(),
1281 );
1282 assert_ne!(code, HIDE_OK);
1283 assert!(plaintext.data.is_null(), "published unverified plaintext");
1284
1285 hide_buffer_free(&mut container);
1286 hide_buffer_free(&mut public);
1287 hide_secret_key_free(secret);
1288 }
1289 }
1290
1291 #[test]
1292 fn the_wrong_key_cannot_decrypt() {
1293 unsafe {
1294 let (secret, mut public) = keypair();
1295 let (other, mut other_public) = keypair();
1296 let mut container = hide_buffer_empty();
1297 hide_encrypt(
1298 b"secret".as_ptr(),
1299 6,
1300 public.data,
1301 1,
1302 ptr::null(),
1303 ptr::null(),
1304 &mut container,
1305 );
1306
1307 let mut plaintext = hide_buffer_empty();
1308 assert_eq!(
1309 hide_decrypt(
1310 container.data,
1311 container.len,
1312 other,
1313 &mut plaintext,
1314 ptr::null_mut(),
1315 ptr::null_mut(),
1316 ),
1317 HIDE_ERR_NO_MATCHING_RECIPIENT
1318 );
1319
1320 hide_buffer_free(&mut container);
1321 hide_buffer_free(&mut public);
1322 hide_buffer_free(&mut other_public);
1323 hide_secret_key_free(secret);
1324 hide_secret_key_free(other);
1325 }
1326 }
1327
1328 #[test]
1329 fn null_and_absurd_arguments_are_rejected_rather_than_crashing() {
1330 unsafe {
1331 let mut out = hide_buffer_empty();
1332 let mut secret = ptr::null_mut();
1333
1334 assert_eq!(
1335 hide_keypair_generate(ptr::null_mut(), &mut out),
1336 HIDE_ERR_INVALID_ARGUMENT
1337 );
1338 assert_eq!(
1339 hide_keypair_generate(&mut secret, ptr::null_mut()),
1340 HIDE_ERR_INVALID_ARGUMENT
1341 );
1342 assert_eq!(
1344 hide_encrypt(
1345 ptr::null(),
1346 10,
1347 ptr::null(),
1348 1,
1349 ptr::null(),
1350 ptr::null(),
1351 &mut out
1352 ),
1353 HIDE_ERR_INVALID_ARGUMENT
1354 );
1355 assert_eq!(
1357 hide_encrypt(
1358 b"x".as_ptr(),
1359 usize::MAX,
1360 ptr::null(),
1361 1,
1362 ptr::null(),
1363 ptr::null(),
1364 &mut out
1365 ),
1366 HIDE_ERR_INVALID_ARGUMENT
1367 );
1368 assert_eq!(
1369 hide_decrypt(
1370 ptr::null(),
1371 0,
1372 ptr::null(),
1373 &mut out,
1374 ptr::null_mut(),
1375 ptr::null_mut()
1376 ),
1377 HIDE_ERR_INVALID_ARGUMENT
1378 );
1379 let mut kind = -1;
1380 assert_eq!(
1381 hide_inspect_key(ptr::null(), 32, &mut kind),
1382 HIDE_ERR_INVALID_ARGUMENT
1383 );
1384 hide_buffer_free(ptr::null_mut());
1386 hide_secret_key_free(ptr::null_mut());
1387 }
1388 }
1389
1390 #[test]
1391 fn protected_keys_round_trip_and_reject_a_wrong_passphrase() {
1392 unsafe {
1393 let (secret, mut public) = keypair();
1394 let mut sealed = hide_buffer_empty();
1395 assert_eq!(
1396 hide_secret_key_protect(secret, c"correct horse".as_ptr(), &mut sealed),
1397 HIDE_OK
1398 );
1399
1400 let mut kind = -1;
1401 assert_eq!(
1402 hide_inspect_key(sealed.data, sealed.len, &mut kind),
1403 HIDE_OK
1404 );
1405 assert_eq!(kind, HIDE_KEY_PROTECTED);
1406
1407 let mut reopened = ptr::null_mut();
1408 assert_eq!(
1409 hide_secret_key_open(sealed.data, sealed.len, c"wrong".as_ptr(), &mut reopened),
1410 HIDE_ERR_WRONG_PASSPHRASE
1411 );
1412 assert_eq!(
1413 hide_secret_key_open(sealed.data, sealed.len, ptr::null(), &mut reopened),
1414 HIDE_ERR_WRONG_PASSPHRASE
1415 );
1416 assert_eq!(
1417 hide_secret_key_open(
1418 sealed.data,
1419 sealed.len,
1420 c"correct horse".as_ptr(),
1421 &mut reopened
1422 ),
1423 HIDE_OK
1424 );
1425
1426 let mut derived = hide_buffer_empty();
1428 assert_eq!(hide_secret_key_public(reopened, &mut derived), HIDE_OK);
1429 assert_eq!(
1430 slice::from_raw_parts(derived.data, derived.len),
1431 slice::from_raw_parts(public.data, public.len)
1432 );
1433
1434 hide_buffer_free(&mut derived);
1435 hide_buffer_free(&mut sealed);
1436 hide_buffer_free(&mut public);
1437 hide_secret_key_free(reopened);
1438 hide_secret_key_free(secret);
1439 }
1440 }
1441
1442 #[test]
1443 fn armor_round_trips_and_rejects_rubbish() {
1444 unsafe {
1445 let (secret, mut public) = keypair();
1446 let mut armored = hide_buffer_empty();
1447 assert_eq!(
1448 hide_public_key_armor(public.data, public.len, &mut armored),
1449 HIDE_OK
1450 );
1451
1452 let text =
1454 CString::new(slice::from_raw_parts(armored.data, armored.len)).expect("no NUL");
1455 let mut decoded = hide_buffer_empty();
1456 assert_eq!(
1457 hide_public_key_dearmor(text.as_ptr(), &mut decoded),
1458 HIDE_OK
1459 );
1460 assert_eq!(
1461 slice::from_raw_parts(decoded.data, decoded.len),
1462 slice::from_raw_parts(public.data, public.len)
1463 );
1464 assert_ne!(
1465 hide_public_key_dearmor(c"not a key".as_ptr(), &mut decoded),
1466 HIDE_OK
1467 );
1468
1469 hide_buffer_free(&mut decoded);
1470 hide_buffer_free(&mut armored);
1471 hide_buffer_free(&mut public);
1472 hide_secret_key_free(secret);
1473 }
1474 }
1475
1476 fn identity_file(passphrase: &str) -> Vec<u8> {
1478 let identity = hide_keyring::Identity::generate().expect("randomness");
1479 hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase)
1480 .expect("sealing works")
1481 }
1482
1483 unsafe fn open_identity(file: &[u8], passphrase: &CString) -> *mut HideSigningIdentity {
1484 let mut identity = ptr::null_mut();
1485 assert_eq!(
1486 unsafe {
1487 hide_signing_identity_open(
1488 file.as_ptr(),
1489 file.len(),
1490 passphrase.as_ptr(),
1491 &mut identity,
1492 )
1493 },
1494 HIDE_OK
1495 );
1496 identity
1497 }
1498
1499 #[test]
1500 fn signs_and_verifies_across_the_boundary() {
1501 unsafe {
1502 let passphrase = CString::new("correct horse battery staple").unwrap();
1503 let file = identity_file(passphrase.to_str().unwrap());
1504 let identity = open_identity(&file, &passphrase);
1505
1506 let mut public = hide_buffer_empty();
1507 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1508 assert_eq!(public.len, HIDE_VERIFYING_KEY_LEN);
1509
1510 let context = b"HIDE/0.5 ffi test";
1511 let message = b"the message that crossed the boundary";
1512 let mut signature = hide_buffer_empty();
1513 assert_eq!(
1514 hide_sign_message(
1515 identity,
1516 context.as_ptr(),
1517 context.len(),
1518 message.as_ptr(),
1519 message.len(),
1520 &mut signature,
1521 ),
1522 HIDE_OK
1523 );
1524 assert_eq!(signature.len, HIDE_SIGNATURE_LEN);
1525
1526 assert_eq!(
1527 hide_verify_message(
1528 public.data,
1529 public.len,
1530 context.as_ptr(),
1531 context.len(),
1532 message.as_ptr(),
1533 message.len(),
1534 signature.data,
1535 signature.len,
1536 ),
1537 HIDE_OK
1538 );
1539
1540 let other = b"HIDE/0.5 something else";
1543 assert_eq!(
1544 hide_verify_message(
1545 public.data,
1546 public.len,
1547 other.as_ptr(),
1548 other.len(),
1549 message.as_ptr(),
1550 message.len(),
1551 signature.data,
1552 signature.len,
1553 ),
1554 HIDE_ERR_AUTHENTICATION
1555 );
1556
1557 let altered = b"the message that crossed the boundaryX";
1558 assert_eq!(
1559 hide_verify_message(
1560 public.data,
1561 public.len,
1562 context.as_ptr(),
1563 context.len(),
1564 altered.as_ptr(),
1565 altered.len(),
1566 signature.data,
1567 signature.len,
1568 ),
1569 HIDE_ERR_AUTHENTICATION
1570 );
1571
1572 hide_buffer_free(&mut signature);
1573 hide_buffer_free(&mut public);
1574 hide_signing_identity_free(identity);
1575 }
1576 }
1577
1578 #[test]
1581 fn an_encryption_only_key_cannot_sign() {
1582 unsafe {
1583 let passphrase = CString::new("correct horse battery staple").unwrap();
1584 let secret = hide_crypto::RecipientSecret::generate().expect("randomness");
1585 let file =
1586 hide_keyring::protect(&secret, passphrase.to_str().unwrap()).expect("sealing");
1587
1588 let mut identity = ptr::null_mut();
1589 assert_eq!(
1590 hide_signing_identity_open(
1591 file.as_ptr(),
1592 file.len(),
1593 passphrase.as_ptr(),
1594 &mut identity,
1595 ),
1596 HIDE_ERR_NOT_A_KEY
1597 );
1598 assert!(identity.is_null());
1599 }
1600 }
1601
1602 #[test]
1603 fn a_wrong_passphrase_is_named_as_such() {
1604 unsafe {
1605 let file = identity_file("correct horse battery staple");
1606 let wrong = CString::new("not the passphrase at all").unwrap();
1607 let mut identity = ptr::null_mut();
1608 assert_eq!(
1609 hide_signing_identity_open(
1610 file.as_ptr(),
1611 file.len(),
1612 wrong.as_ptr(),
1613 &mut identity
1614 ),
1615 HIDE_ERR_WRONG_PASSPHRASE
1616 );
1617 }
1618 }
1619
1620 #[test]
1621 fn a_challenge_is_answered_once_and_then_refused() {
1622 unsafe {
1623 let passphrase = CString::new("correct horse battery staple").unwrap();
1624 let file = identity_file(passphrase.to_str().unwrap());
1625 let identity = open_identity(&file, &passphrase);
1626
1627 let mut public = hide_buffer_empty();
1628 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1629
1630 let audience = CString::new("ssh://host.example").unwrap();
1631 let mut challenge = hide_buffer_empty();
1632 assert_eq!(
1633 hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1634 HIDE_OK
1635 );
1636
1637 let mut answer = hide_buffer_empty();
1638 assert_eq!(
1639 hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1640 HIDE_OK
1641 );
1642
1643 let spent = hide_spent_nonces_new();
1644 assert_eq!(
1645 hide_challenge_accept(
1646 spent,
1647 challenge.data,
1648 challenge.len,
1649 answer.data,
1650 answer.len,
1651 public.data,
1652 public.len,
1653 1_000,
1654 ),
1655 HIDE_OK
1656 );
1657 assert_eq!(
1659 hide_challenge_accept(
1660 spent,
1661 challenge.data,
1662 challenge.len,
1663 answer.data,
1664 answer.len,
1665 public.data,
1666 public.len,
1667 1_000,
1668 ),
1669 HIDE_ERR_CHALLENGE_REPLAYED
1670 );
1671
1672 hide_spent_nonces_free(spent);
1673 hide_buffer_free(&mut answer);
1674 hide_buffer_free(&mut challenge);
1675 hide_buffer_free(&mut public);
1676 hide_signing_identity_free(identity);
1677 }
1678 }
1679
1680 #[test]
1681 fn an_answer_after_the_window_is_refused_across_the_boundary() {
1682 unsafe {
1683 let passphrase = CString::new("correct horse battery staple").unwrap();
1684 let file = identity_file(passphrase.to_str().unwrap());
1685 let identity = open_identity(&file, &passphrase);
1686
1687 let mut public = hide_buffer_empty();
1688 assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1689
1690 let audience = CString::new("ssh://host.example").unwrap();
1691 let mut challenge = hide_buffer_empty();
1692 assert_eq!(
1693 hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1694 HIDE_OK
1695 );
1696 let mut answer = hide_buffer_empty();
1697 assert_eq!(
1698 hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1699 HIDE_OK
1700 );
1701
1702 let spent = hide_spent_nonces_new();
1703 assert_eq!(
1704 hide_challenge_accept(
1705 spent,
1706 challenge.data,
1707 challenge.len,
1708 answer.data,
1709 answer.len,
1710 public.data,
1711 public.len,
1712 1_100,
1713 ),
1714 HIDE_ERR_CHALLENGE_EXPIRED
1715 );
1716
1717 hide_spent_nonces_free(spent);
1718 hide_buffer_free(&mut answer);
1719 hide_buffer_free(&mut challenge);
1720 hide_buffer_free(&mut public);
1721 hide_signing_identity_free(identity);
1722 }
1723 }
1724
1725 #[test]
1727 fn the_signing_boundary_refuses_nulls_without_crashing() {
1728 unsafe {
1729 let mut out = hide_buffer_empty();
1730 assert_eq!(
1731 hide_sign_message(ptr::null(), ptr::null(), 0, ptr::null(), 0, &mut out),
1732 HIDE_ERR_INVALID_ARGUMENT
1733 );
1734 assert_eq!(
1735 hide_verify_message(
1736 ptr::null(),
1737 0,
1738 ptr::null(),
1739 0,
1740 ptr::null(),
1741 0,
1742 ptr::null(),
1743 0
1744 ),
1745 HIDE_ERR_INVALID_ARGUMENT
1748 );
1749 assert_eq!(
1750 hide_challenge_answer(ptr::null(), ptr::null(), 0, &mut out),
1751 HIDE_ERR_INVALID_ARGUMENT
1752 );
1753 assert_eq!(
1754 hide_challenge_accept(
1755 ptr::null_mut(),
1756 ptr::null(),
1757 0,
1758 ptr::null(),
1759 0,
1760 ptr::null(),
1761 0,
1762 0
1763 ),
1764 HIDE_ERR_INVALID_ARGUMENT
1765 );
1766 hide_signing_identity_free(ptr::null_mut());
1768 hide_spent_nonces_free(ptr::null_mut());
1769 }
1770 }
1771}