Skip to main content

hide_ffi/
lib.rs

1//! C ABI for HIDE. Every language binding calls this; none reimplements the
2//! cryptography, so there is exactly one implementation to review.
3//!
4//! Rules this boundary keeps:
5//!
6//! - Nothing crossing the boundary is trusted: every pointer is checked for
7//!   null and every length is validated before use.
8//! - Secret keys never leave Rust. Callers hold an opaque handle; there is no
9//!   function that exports key material.
10//! - Buffers allocated here are freed here (`hide_buffer_free`), because a
11//!   caller freeing Rust memory with libc `free` is undefined behaviour.
12//! - No function unwinds across the boundary. A panic in Rust crossing into C
13//!   is undefined behaviour, so every entry point catches it.
14
15use 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
26/// Status codes. Zero is success; everything else is a failure the caller must
27/// handle. Values are stable across versions: bindings switch on them.
28pub 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
39/// Key file kinds reported by [`hide_inspect_key`].
40pub 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
46/// Bounds every allocation driven by a caller-supplied length.
47const MAX_INPUT: usize = 1 << 30;
48const MAX_KEY_FILE: usize = 4096;
49
50/// An owned buffer handed to the caller. Free it with [`hide_buffer_free`].
51#[repr(C)]
52pub struct HideBuffer {
53    pub data: *mut u8,
54    pub len: usize,
55    /// Kept so the exact original allocation can be reconstructed on free.
56    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/// An empty buffer, for initialising a local before passing its address in.
72/// Callers must start from this rather than from uninitialised memory, because
73/// [`hide_buffer_free`] reads the pointer it is given.
74#[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
83/// An opaque secret key. The caller only ever holds this pointer; there is no
84/// accessor that returns the underlying bytes.
85pub struct HideSecretKey(RecipientSecret);
86
87/// Reads a caller-provided slice, refusing null and absurd lengths.
88///
89/// # Safety
90/// `data` must be valid for `len` bytes, or null when `len` is zero.
91unsafe 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
104/// # Safety
105/// `text` must be null or a valid NUL-terminated C string.
106unsafe 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
136/// Runs `body`, converting a panic into a status code rather than unwinding
137/// into C, which would be undefined behaviour.
138fn guard(body: impl FnOnce() -> i32) -> i32 {
139    catch_unwind(AssertUnwindSafe(body)).unwrap_or(HIDE_ERR_PANIC)
140}
141
142/// Panics on purpose, so a test can prove the guard converts a panic into
143/// `HIDE_ERR_PANIC` in the same build profile that gets published. With
144/// `panic = "abort"` this would kill the test process instead, which is the
145/// exact failure this exists to catch. Not in the header; not for callers.
146#[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/// Human-readable text for a status code. The returned string is static and
153/// must not be freed.
154#[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/// The library version, as a static NUL-terminated string.
174#[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/// Frees a buffer produced by this library.
180///
181/// # Safety
182/// `buffer` must come from this library and must not be freed twice.
183#[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    // Reconstruct the exact allocation, then zeroize: a freed buffer may still
193    // hold plaintext.
194    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/// Generates a key pair. The secret is returned as an opaque handle; the public
203/// key is returned as bytes, which are safe to share.
204///
205/// # Safety
206/// Both out-pointers must be valid and non-null.
207#[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/// Reports whether a key file is raw or passphrase-protected, without needing
231/// the passphrase.
232///
233/// # Safety
234/// `data` must be valid for `len` bytes.
235#[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/// Loads a secret key from a file's bytes. Pass `passphrase = NULL` for a raw
254/// key; a protected key without a passphrase fails rather than guessing.
255///
256/// # Safety
257/// `data` must be valid for `len` bytes; `passphrase` must be null or a valid
258/// C string.
259#[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/// Seals a secret key with a passphrase, producing the bytes to store on disk.
287///
288/// # Safety
289/// `secret` must come from this library; `passphrase` must be a valid C string.
290#[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/// Derives the public key belonging to a secret key.
315///
316/// # Safety
317/// `secret` must come from this library.
318#[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/// Releases a secret key. The key material is zeroized.
339///
340/// # Safety
341/// `secret` must come from this library and must not be freed twice.
342#[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/// Encrypts a buffer for one or more recipients.
351///
352/// `recipients` is a flat array of `recipient_count` public keys, each exactly
353/// `HIDE_PUBLIC_KEY_LEN` bytes.
354///
355/// # Safety
356/// All pointers must be valid for the stated lengths.
357#[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/// Decrypts a container. Nothing is written to `out` unless the whole payload
414/// authenticates, so a caller cannot act on unverified plaintext.
415///
416/// Metadata is returned as length-prefixed buffers rather than C strings:
417/// the values are attacker-controlled, and a length is not something a caller
418/// can get wrong by scanning for a terminator. Free them like any buffer.
419///
420/// # Safety
421/// All pointers must be valid for the stated lengths.
422#[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        // Held in a zeroizing buffer so a failed decryption leaves no plaintext.
441        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/// Encodes a public key as pasteable armored text, returned as UTF-8 bytes.
463///
464/// # Safety
465/// `data` must be valid for `len` bytes.
466#[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/// Decodes armored public-key text back into bytes.
489///
490/// # Safety
491/// `text` must be a valid NUL-terminated C string.
492#[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
512/// A hybrid signature: Ed25519 followed by ML-DSA-65.
513pub const HIDE_SIGNATURE_LEN: usize = hide_sign::SIGNATURE_LENGTH;
514/// A hybrid verifying key.
515pub const HIDE_VERIFYING_KEY_LEN: usize = hide_sign::VERIFYING_KEY_LENGTH;
516/// A challenge nonce.
517pub const HIDE_NONCE_LEN: usize = hide_sign::NONCE_LENGTH;
518
519/// Reasons a challenge answer was refused, beyond the generic codes above.
520pub const HIDE_ERR_CHALLENGE_EXPIRED: i32 = 8;
521pub const HIDE_ERR_CHALLENGE_REPLAYED: i32 = 9;
522
523/// An opaque signing identity. As with secret keys, no function exports the
524/// seed: a binding can sign, and cannot leak.
525pub struct HideSigningIdentity(hide_sign::SigningIdentity);
526
527/// Creates an identity and returns it sealed under `passphrase`, ready to
528/// write to disk. One seed backs both encryption and signing, so a caller has
529/// a single thing to back up; the seed itself never crosses the boundary.
530///
531/// # Safety
532/// `passphrase` must be a valid C string and `out` must be non-null.
533#[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
559/// The verifier's record of answered challenges. Replay can only be detected
560/// by the verifier, so this must outlive a single request.
561pub struct HideSpentNonces(hide_sign::SpentNonces);
562
563/// Loads a signing identity from a key file's bytes.
564///
565/// See [`hide_identity_generate`] for creating one in the first place.
566///
567/// A key file written before signatures existed carries no signing seed, and
568/// fails here rather than being silently downgraded.
569///
570/// # Safety
571/// `data` must be valid for `len` bytes; `passphrase` must be null or a valid
572/// C string.
573#[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                    // Encryption-only: predates signatures, so there is no
609                    // signing seed to derive and inventing one is not an option.
610                    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/// The shareable verifying key for a signing identity.
627///
628/// # Safety
629/// `identity` must come from this library.
630#[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/// Releases a signing identity, zeroizing the seed.
647///
648/// # Safety
649/// `identity` must come from this library and must not be freed twice.
650#[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/// Signs a message under a caller-chosen context.
659///
660/// The context separates uses of one identity: a signature made for one
661/// purpose must not verify as another. Bindings should pass the same context
662/// they will verify with, and never let a remote party choose it.
663///
664/// # Safety
665/// All pointers must be valid for the stated lengths.
666#[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/// Verifies a signature. Returns `HIDE_OK` only if **both** halves verify.
693///
694/// # Safety
695/// All pointers must be valid for the stated lengths.
696#[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        // Fixed-width inputs are an argument error at the boundary, so a caller
718        // that passed the wrong buffer learns that, not "authentication failed".
719        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/// Creates a challenge for a prover to answer. The encoded challenge is not
737/// secret and is handed to the prover as-is.
738///
739/// # Safety
740/// `audience` must be a valid C string; `out` must be non-null.
741#[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/// Answers a challenge, producing a signature over it.
766///
767/// # Safety
768/// All pointers must be valid for the stated lengths.
769#[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/// Creates the verifier's record of spent nonces.
793#[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/// Releases the record of spent nonces.
799///
800/// # Safety
801/// `spent` must come from this library and must not be freed twice.
802#[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/// Accepts a challenge answer exactly once. A valid signature replayed a
811/// second time returns `HIDE_ERR_CHALLENGE_REPLAYED`, which is the entire
812/// reason this call takes a `spent` record rather than being a pure function.
813///
814/// # Safety
815/// All pointers must be valid for the stated lengths.
816#[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
861// ---------------------------------------------------------------------------
862// Identity logs
863// ---------------------------------------------------------------------------
864
865/// A log or chain is public and can be large; this only stops a hostile length
866/// from exhausting memory.
867const MAX_LOG_BYTES: usize = 16 * 1024 * 1024;
868
869/// Replays an identity log and reports how many devices it trusts now.
870///
871/// Returns `HIDE_ERR_MALFORMED` for a log that does not decode, and
872/// `HIDE_ERR_AUTHENTICATION` for one that decodes but does not verify — the
873/// distinction a caller needs to tell corruption from forgery.
874///
875/// # Safety
876/// All pointers must be valid for the stated lengths.
877#[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/// Whether a log trusts a device right now. `out_trusted` is set to 1 or 0.
910///
911/// # Safety
912/// All pointers must be valid for the stated lengths.
913#[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/// The head of an identity log: one 32-byte value naming this exact history.
957///
958/// # Safety
959/// All pointers must be valid for the stated lengths.
960#[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// ---------------------------------------------------------------------------
993// Epoch chains
994// ---------------------------------------------------------------------------
995
996/// Verifies a published epoch history and reports how many epochs it holds.
997///
998/// # Safety
999/// All pointers must be valid for the stated lengths.
1000#[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/// The public key a sender should encrypt to for a given epoch.
1025///
1026/// # Safety
1027/// All pointers must be valid for the stated lengths.
1028#[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        // `as usize` would wrap on a 32-bit target and select the wrong epoch.
1049        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// ---------------------------------------------------------------------------
1061// Transparency proofs
1062// ---------------------------------------------------------------------------
1063
1064/// Checks an inclusion proof. `path` is the concatenated 32-byte hashes.
1065///
1066/// # Safety
1067/// All pointers must be valid for the stated lengths.
1068#[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/// Checks a consistency proof: that `old_root` is the root the log had before
1114/// it grew to `new_root`.
1115///
1116/// # Safety
1117/// All pointers must be valid for the stated lengths.
1118#[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    /// Drives the API exactly as a C caller would, pointers and all.
1169    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            // A second free must be a no-op rather than a double free.
1248            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            // A non-null length with a null pointer must not be dereferenced.
1343            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            // A length that would over-allocate must be refused up front.
1356            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            // Freeing null is a no-op, not a crash.
1385            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            // The reopened key must be the same one.
1427            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            // The armored form must survive a trip through a C string.
1453            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    /// A sealed identity file, the way a real caller would have one on disk.
1477    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            // A different context must not verify, or the separation the C API
1541            // advertises would be decorative.
1542            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    /// A key file that predates signatures has no signing seed, and inventing
1579    /// one would silently sign under a key nobody expects.
1580    #[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            // The same valid answer, a second time.
1658            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    /// Null and absurd lengths must be refused, not dereferenced.
1726    #[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                // A key or signature of the wrong width is an argument error,
1746                // decided at the boundary before any parsing.
1747                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            // Freeing null is a no-op, not a crash.
1767            hide_signing_identity_free(ptr::null_mut());
1768            hide_spent_nonces_free(ptr::null_mut());
1769        }
1770    }
1771}