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/// Human-readable text for a status code. The returned string is static and
143/// must not be freed.
144#[unsafe(no_mangle)]
145pub extern "C" fn hide_error_message(code: i32) -> *const c_char {
146    let text: &[u8] = match code {
147        HIDE_OK => b"success\0",
148        HIDE_ERR_INVALID_ARGUMENT => b"invalid argument\0",
149        HIDE_ERR_WRONG_PASSPHRASE => b"incorrect passphrase, or the key file was modified\0",
150        HIDE_ERR_NOT_A_KEY => b"not a HIDE key file\0",
151        HIDE_ERR_AUTHENTICATION => b"authentication failed; the data was altered\0",
152        HIDE_ERR_NO_MATCHING_RECIPIENT => b"no matching recipient for this key\0",
153        HIDE_ERR_MALFORMED => b"malformed or corrupt input\0",
154        HIDE_ERR_TOO_LARGE => b"input is too large\0",
155        HIDE_ERR_CHALLENGE_EXPIRED => b"the challenge expired before it was answered\0",
156        HIDE_ERR_CHALLENGE_REPLAYED => b"this challenge was already answered\0",
157        HIDE_ERR_PANIC => b"internal error (panic)\0",
158        _ => b"internal error\0",
159    };
160    text.as_ptr().cast()
161}
162
163/// The library version, as a static NUL-terminated string.
164#[unsafe(no_mangle)]
165pub extern "C" fn hide_version() -> *const c_char {
166    concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr().cast()
167}
168
169/// Frees a buffer produced by this library.
170///
171/// # Safety
172/// `buffer` must come from this library and must not be freed twice.
173#[unsafe(no_mangle)]
174pub unsafe extern "C" fn hide_buffer_free(buffer: *mut HideBuffer) {
175    if buffer.is_null() {
176        return;
177    }
178    let buffer = unsafe { &mut *buffer };
179    if buffer.data.is_null() {
180        return;
181    }
182    // Reconstruct the exact allocation, then zeroize: a freed buffer may still
183    // hold plaintext.
184    let mut bytes = unsafe { Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity) };
185    bytes.iter_mut().for_each(|byte| *byte = 0);
186    drop(bytes);
187    buffer.data = ptr::null_mut();
188    buffer.len = 0;
189    buffer.capacity = 0;
190}
191
192/// Generates a key pair. The secret is returned as an opaque handle; the public
193/// key is returned as bytes, which are safe to share.
194///
195/// # Safety
196/// Both out-pointers must be valid and non-null.
197#[unsafe(no_mangle)]
198pub unsafe extern "C" fn hide_keypair_generate(
199    out_secret: *mut *mut HideSecretKey,
200    out_public: *mut HideBuffer,
201) -> i32 {
202    guard(|| {
203        if out_secret.is_null() || out_public.is_null() {
204            return HIDE_ERR_INVALID_ARGUMENT;
205        }
206        let Ok(secret) = RecipientSecret::generate() else {
207            return HIDE_ERR_INTERNAL;
208        };
209        let Ok(public) = secret.public_key() else {
210            return HIDE_ERR_INTERNAL;
211        };
212        unsafe {
213            *out_public = HideBuffer::from_vec(public.to_bytes());
214            *out_secret = Box::into_raw(Box::new(HideSecretKey(secret)));
215        }
216        HIDE_OK
217    })
218}
219
220/// Reports whether a key file is raw or passphrase-protected, without needing
221/// the passphrase.
222///
223/// # Safety
224/// `data` must be valid for `len` bytes.
225#[unsafe(no_mangle)]
226pub unsafe extern "C" fn hide_inspect_key(data: *const u8, len: usize, out_kind: *mut i32) -> i32 {
227    guard(|| {
228        if out_kind.is_null() {
229            return HIDE_ERR_INVALID_ARGUMENT;
230        }
231        let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
232            return HIDE_ERR_INVALID_ARGUMENT;
233        };
234        let kind = match hide_keyring::inspect(bytes) {
235            KeyFormat::Raw => HIDE_KEY_RAW,
236            KeyFormat::Protected => HIDE_KEY_PROTECTED,
237        };
238        unsafe { *out_kind = kind };
239        HIDE_OK
240    })
241}
242
243/// Loads a secret key from a file's bytes. Pass `passphrase = NULL` for a raw
244/// key; a protected key without a passphrase fails rather than guessing.
245///
246/// # Safety
247/// `data` must be valid for `len` bytes; `passphrase` must be null or a valid
248/// C string.
249#[unsafe(no_mangle)]
250pub unsafe extern "C" fn hide_secret_key_open(
251    data: *const u8,
252    len: usize,
253    passphrase: *const c_char,
254    out_secret: *mut *mut HideSecretKey,
255) -> i32 {
256    guard(|| {
257        if out_secret.is_null() {
258            return HIDE_ERR_INVALID_ARGUMENT;
259        }
260        let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
261            return HIDE_ERR_INVALID_ARGUMENT;
262        };
263        let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
264            return HIDE_ERR_INVALID_ARGUMENT;
265        };
266        match hide_keyring::open(bytes, passphrase) {
267            Ok(secret) => {
268                unsafe { *out_secret = Box::into_raw(Box::new(HideSecretKey(secret))) };
269                HIDE_OK
270            }
271            Err(error) => keyring_error_code(&error),
272        }
273    })
274}
275
276/// Seals a secret key with a passphrase, producing the bytes to store on disk.
277///
278/// # Safety
279/// `secret` must come from this library; `passphrase` must be a valid C string.
280#[unsafe(no_mangle)]
281pub unsafe extern "C" fn hide_secret_key_protect(
282    secret: *const HideSecretKey,
283    passphrase: *const c_char,
284    out: *mut HideBuffer,
285) -> i32 {
286    guard(|| {
287        if secret.is_null() || out.is_null() {
288            return HIDE_ERR_INVALID_ARGUMENT;
289        }
290        let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
291            return HIDE_ERR_INVALID_ARGUMENT;
292        };
293        let secret = unsafe { &*secret };
294        match hide_keyring::protect(&secret.0, passphrase) {
295            Ok(sealed) => {
296                unsafe { *out = HideBuffer::from_vec(sealed) };
297                HIDE_OK
298            }
299            Err(error) => keyring_error_code(&error),
300        }
301    })
302}
303
304/// Derives the public key belonging to a secret key.
305///
306/// # Safety
307/// `secret` must come from this library.
308#[unsafe(no_mangle)]
309pub unsafe extern "C" fn hide_secret_key_public(
310    secret: *const HideSecretKey,
311    out: *mut HideBuffer,
312) -> i32 {
313    guard(|| {
314        if secret.is_null() || out.is_null() {
315            return HIDE_ERR_INVALID_ARGUMENT;
316        }
317        let secret = unsafe { &*secret };
318        match secret.0.public_key() {
319            Ok(public) => {
320                unsafe { *out = HideBuffer::from_vec(public.to_bytes()) };
321                HIDE_OK
322            }
323            Err(_) => HIDE_ERR_INTERNAL,
324        }
325    })
326}
327
328/// Releases a secret key. The key material is zeroized.
329///
330/// # Safety
331/// `secret` must come from this library and must not be freed twice.
332#[unsafe(no_mangle)]
333pub unsafe extern "C" fn hide_secret_key_free(secret: *mut HideSecretKey) {
334    if secret.is_null() {
335        return;
336    }
337    drop(unsafe { Box::from_raw(secret) });
338}
339
340/// Encrypts a buffer for one or more recipients.
341///
342/// `recipients` is a flat array of `recipient_count` public keys, each exactly
343/// `HIDE_PUBLIC_KEY_LEN` bytes.
344///
345/// # Safety
346/// All pointers must be valid for the stated lengths.
347#[unsafe(no_mangle)]
348pub unsafe extern "C" fn hide_encrypt(
349    plaintext: *const u8,
350    plaintext_len: usize,
351    recipients: *const u8,
352    recipient_count: usize,
353    filename: *const c_char,
354    media_type: *const c_char,
355    out: *mut HideBuffer,
356) -> i32 {
357    guard(|| {
358        if out.is_null() {
359            return HIDE_ERR_INVALID_ARGUMENT;
360        }
361        if recipient_count == 0 || recipient_count > 64 {
362            return HIDE_ERR_INVALID_ARGUMENT;
363        }
364        let Some(plaintext) = (unsafe { borrow(plaintext, plaintext_len, MAX_INPUT) }) else {
365            return HIDE_ERR_INVALID_ARGUMENT;
366        };
367        let Some(keys) =
368            (unsafe { borrow(recipients, recipient_count * HIDE_PUBLIC_KEY_LEN, MAX_INPUT) })
369        else {
370            return HIDE_ERR_INVALID_ARGUMENT;
371        };
372
373        let mut parsed = Vec::with_capacity(recipient_count);
374        for chunk in keys.chunks_exact(HIDE_PUBLIC_KEY_LEN) {
375            match RecipientPublic::from_bytes(chunk) {
376                Ok(key) => parsed.push(key),
377                Err(_) => return HIDE_ERR_INVALID_ARGUMENT,
378            }
379        }
380
381        let (Some(filename), Some(media_type)) = (unsafe { borrow_str(filename) }, unsafe {
382            borrow_str(media_type)
383        }) else {
384            return HIDE_ERR_INVALID_ARGUMENT;
385        };
386        let metadata = Metadata {
387            filename: filename.map(str::to_owned),
388            media_type: media_type.map(str::to_owned),
389            signature: None,
390        };
391
392        let mut container = Vec::new();
393        match hide_object::encrypt(&mut &*plaintext, &mut container, &parsed, &metadata) {
394            Ok(_) => {
395                unsafe { *out = HideBuffer::from_vec(container) };
396                HIDE_OK
397            }
398            Err(error) => object_error_code(&error),
399        }
400    })
401}
402
403/// Decrypts a container. Nothing is written to `out` unless the whole payload
404/// authenticates, so a caller cannot act on unverified plaintext.
405///
406/// Metadata is returned as length-prefixed buffers rather than C strings:
407/// the values are attacker-controlled, and a length is not something a caller
408/// can get wrong by scanning for a terminator. Free them like any buffer.
409///
410/// # Safety
411/// All pointers must be valid for the stated lengths.
412#[unsafe(no_mangle)]
413pub unsafe extern "C" fn hide_decrypt(
414    container: *const u8,
415    container_len: usize,
416    secret: *const HideSecretKey,
417    out: *mut HideBuffer,
418    out_filename: *mut HideBuffer,
419    out_media_type: *mut HideBuffer,
420) -> i32 {
421    guard(|| {
422        if secret.is_null() || out.is_null() {
423            return HIDE_ERR_INVALID_ARGUMENT;
424        }
425        let Some(container) = (unsafe { borrow(container, container_len, MAX_INPUT) }) else {
426            return HIDE_ERR_INVALID_ARGUMENT;
427        };
428        let secret = unsafe { &*secret };
429
430        // Held in a zeroizing buffer so a failed decryption leaves no plaintext.
431        let mut plaintext = Zeroizing::new(Vec::new());
432        let verified =
433            match hide_object::decrypt_to_staging(&mut &*container, &mut *plaintext, &secret.0) {
434                Ok(verified) => verified,
435                Err(error) => return object_error_code(&error),
436            };
437
438        if !out_filename.is_null() {
439            let value = verified.metadata.filename.unwrap_or_default();
440            unsafe { *out_filename = HideBuffer::from_vec(value.into_bytes()) };
441        }
442        if !out_media_type.is_null() {
443            let value = verified.metadata.media_type.unwrap_or_default();
444            unsafe { *out_media_type = HideBuffer::from_vec(value.into_bytes()) };
445        }
446
447        unsafe { *out = HideBuffer::from_vec(std::mem::take(&mut *plaintext)) };
448        HIDE_OK
449    })
450}
451
452/// Encodes a public key as pasteable armored text, returned as UTF-8 bytes.
453///
454/// # Safety
455/// `data` must be valid for `len` bytes.
456#[unsafe(no_mangle)]
457pub unsafe extern "C" fn hide_public_key_armor(
458    data: *const u8,
459    len: usize,
460    out: *mut HideBuffer,
461) -> i32 {
462    guard(|| {
463        if out.is_null() {
464            return HIDE_ERR_INVALID_ARGUMENT;
465        }
466        let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
467            return HIDE_ERR_INVALID_ARGUMENT;
468        };
469        if bytes.len() != HIDE_PUBLIC_KEY_LEN {
470            return HIDE_ERR_INVALID_ARGUMENT;
471        }
472        let armored = hide_keyring::encode_public(bytes);
473        unsafe { *out = HideBuffer::from_vec(armored.into_bytes()) };
474        HIDE_OK
475    })
476}
477
478/// Decodes armored public-key text back into bytes.
479///
480/// # Safety
481/// `text` must be a valid NUL-terminated C string.
482#[unsafe(no_mangle)]
483pub unsafe extern "C" fn hide_public_key_dearmor(text: *const c_char, out: *mut HideBuffer) -> i32 {
484    guard(|| {
485        if out.is_null() {
486            return HIDE_ERR_INVALID_ARGUMENT;
487        }
488        let Some(Some(text)) = (unsafe { borrow_str(text) }) else {
489            return HIDE_ERR_INVALID_ARGUMENT;
490        };
491        match hide_keyring::decode_public(text) {
492            Ok(bytes) if bytes.len() == HIDE_PUBLIC_KEY_LEN => {
493                unsafe { *out = HideBuffer::from_vec(bytes) };
494                HIDE_OK
495            }
496            Ok(_) => HIDE_ERR_INVALID_ARGUMENT,
497            Err(error) => keyring_error_code(&error),
498        }
499    })
500}
501
502/// A hybrid signature: Ed25519 followed by ML-DSA-65.
503pub const HIDE_SIGNATURE_LEN: usize = hide_sign::SIGNATURE_LENGTH;
504/// A hybrid verifying key.
505pub const HIDE_VERIFYING_KEY_LEN: usize = hide_sign::VERIFYING_KEY_LENGTH;
506/// A challenge nonce.
507pub const HIDE_NONCE_LEN: usize = hide_sign::NONCE_LENGTH;
508
509/// Reasons a challenge answer was refused, beyond the generic codes above.
510pub const HIDE_ERR_CHALLENGE_EXPIRED: i32 = 8;
511pub const HIDE_ERR_CHALLENGE_REPLAYED: i32 = 9;
512
513/// An opaque signing identity. As with secret keys, no function exports the
514/// seed: a binding can sign, and cannot leak.
515pub struct HideSigningIdentity(hide_sign::SigningIdentity);
516
517/// Creates an identity and returns it sealed under `passphrase`, ready to
518/// write to disk. One seed backs both encryption and signing, so a caller has
519/// a single thing to back up; the seed itself never crosses the boundary.
520///
521/// # Safety
522/// `passphrase` must be a valid C string and `out` must be non-null.
523#[unsafe(no_mangle)]
524pub unsafe extern "C" fn hide_identity_generate(
525    passphrase: *const c_char,
526    out_key_file: *mut HideBuffer,
527) -> i32 {
528    guard(|| {
529        if out_key_file.is_null() {
530            return HIDE_ERR_INVALID_ARGUMENT;
531        }
532        let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
533            return HIDE_ERR_INVALID_ARGUMENT;
534        };
535        let identity = match hide_keyring::Identity::generate() {
536            Ok(identity) => identity,
537            Err(_) => return HIDE_ERR_INTERNAL,
538        };
539        match hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase) {
540            Ok(sealed) => {
541                unsafe { *out_key_file = HideBuffer::from_vec(sealed) };
542                HIDE_OK
543            }
544            Err(error) => keyring_error_code(&error),
545        }
546    })
547}
548
549/// The verifier's record of answered challenges. Replay can only be detected
550/// by the verifier, so this must outlive a single request.
551pub struct HideSpentNonces(hide_sign::SpentNonces);
552
553/// Loads a signing identity from a key file's bytes.
554///
555/// See [`hide_identity_generate`] for creating one in the first place.
556///
557/// A key file written before signatures existed carries no signing seed, and
558/// fails here rather than being silently downgraded.
559///
560/// # Safety
561/// `data` must be valid for `len` bytes; `passphrase` must be null or a valid
562/// C string.
563#[unsafe(no_mangle)]
564pub unsafe extern "C" fn hide_signing_identity_open(
565    data: *const u8,
566    len: usize,
567    passphrase: *const c_char,
568    out_identity: *mut *mut HideSigningIdentity,
569) -> i32 {
570    guard(|| {
571        if out_identity.is_null() {
572            return HIDE_ERR_INVALID_ARGUMENT;
573        }
574        let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
575            return HIDE_ERR_INVALID_ARGUMENT;
576        };
577        let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
578            return HIDE_ERR_INVALID_ARGUMENT;
579        };
580
581        let seed = match hide_keyring::inspect(bytes) {
582            KeyFormat::Raw => {
583                if bytes.len() != 32 {
584                    return HIDE_ERR_NOT_A_KEY;
585                }
586                let mut seed = Zeroizing::new([0_u8; 32]);
587                seed.copy_from_slice(bytes);
588                hide_keyring::Identity::from_seed(seed).signing_seed()
589            }
590            KeyFormat::Protected => {
591                let Some(passphrase) = passphrase else {
592                    return HIDE_ERR_INVALID_ARGUMENT;
593                };
594                match hide_keyring::unprotect_seed(bytes, passphrase) {
595                    Ok((seed, hide_keyring::KeyPurpose::Identity)) => {
596                        hide_keyring::Identity::from_seed(seed).signing_seed()
597                    }
598                    // Encryption-only: predates signatures, so there is no
599                    // signing seed to derive and inventing one is not an option.
600                    Ok(_) => return HIDE_ERR_NOT_A_KEY,
601                    Err(error) => return keyring_error_code(&error),
602                }
603            }
604        };
605
606        match hide_sign::SigningIdentity::from_bytes(&seed[..]) {
607            Ok(identity) => {
608                unsafe { *out_identity = Box::into_raw(Box::new(HideSigningIdentity(identity))) };
609                HIDE_OK
610            }
611            Err(_) => HIDE_ERR_NOT_A_KEY,
612        }
613    })
614}
615
616/// The shareable verifying key for a signing identity.
617///
618/// # Safety
619/// `identity` must come from this library.
620#[unsafe(no_mangle)]
621pub unsafe extern "C" fn hide_signing_identity_public(
622    identity: *const HideSigningIdentity,
623    out: *mut HideBuffer,
624) -> i32 {
625    guard(|| {
626        if identity.is_null() || out.is_null() {
627            return HIDE_ERR_INVALID_ARGUMENT;
628        }
629        let identity = unsafe { &*identity };
630        let public = identity.0.verifying_key().to_bytes();
631        unsafe { *out = HideBuffer::from_vec(public.to_vec()) };
632        HIDE_OK
633    })
634}
635
636/// Releases a signing identity, zeroizing the seed.
637///
638/// # Safety
639/// `identity` must come from this library and must not be freed twice.
640#[unsafe(no_mangle)]
641pub unsafe extern "C" fn hide_signing_identity_free(identity: *mut HideSigningIdentity) {
642    if identity.is_null() {
643        return;
644    }
645    drop(unsafe { Box::from_raw(identity) });
646}
647
648/// Signs a message under a caller-chosen context.
649///
650/// The context separates uses of one identity: a signature made for one
651/// purpose must not verify as another. Bindings should pass the same context
652/// they will verify with, and never let a remote party choose it.
653///
654/// # Safety
655/// All pointers must be valid for the stated lengths.
656#[unsafe(no_mangle)]
657pub unsafe extern "C" fn hide_sign_message(
658    identity: *const HideSigningIdentity,
659    context: *const u8,
660    context_len: usize,
661    message: *const u8,
662    message_len: usize,
663    out: *mut HideBuffer,
664) -> i32 {
665    guard(|| {
666        if identity.is_null() || out.is_null() {
667            return HIDE_ERR_INVALID_ARGUMENT;
668        }
669        let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
670            return HIDE_ERR_INVALID_ARGUMENT;
671        };
672        let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
673            return HIDE_ERR_INVALID_ARGUMENT;
674        };
675        let identity = unsafe { &*identity };
676        let signature = identity.0.sign(context, message);
677        unsafe { *out = HideBuffer::from_vec(signature.to_vec()) };
678        HIDE_OK
679    })
680}
681
682/// Verifies a signature. Returns `HIDE_OK` only if **both** halves verify.
683///
684/// # Safety
685/// All pointers must be valid for the stated lengths.
686#[unsafe(no_mangle)]
687pub unsafe extern "C" fn hide_verify_message(
688    public_key: *const u8,
689    public_key_len: usize,
690    context: *const u8,
691    context_len: usize,
692    message: *const u8,
693    message_len: usize,
694    signature: *const u8,
695    signature_len: usize,
696) -> i32 {
697    guard(|| {
698        let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
699            return HIDE_ERR_INVALID_ARGUMENT;
700        };
701        let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
702            return HIDE_ERR_INVALID_ARGUMENT;
703        };
704        let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
705            return HIDE_ERR_INVALID_ARGUMENT;
706        };
707        let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
708        else {
709            return HIDE_ERR_INVALID_ARGUMENT;
710        };
711        let Ok(verifying) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
712            return HIDE_ERR_NOT_A_KEY;
713        };
714        match verifying.verify(context, message, signature) {
715            Ok(()) => HIDE_OK,
716            Err(_) => HIDE_ERR_AUTHENTICATION,
717        }
718    })
719}
720
721/// Creates a challenge for a prover to answer. The encoded challenge is not
722/// secret and is handed to the prover as-is.
723///
724/// # Safety
725/// `audience` must be a valid C string; `out` must be non-null.
726#[unsafe(no_mangle)]
727pub unsafe extern "C" fn hide_challenge_new(
728    audience: *const c_char,
729    now: u64,
730    valid_for: u64,
731    out: *mut HideBuffer,
732) -> i32 {
733    guard(|| {
734        if out.is_null() {
735            return HIDE_ERR_INVALID_ARGUMENT;
736        }
737        let Some(Some(audience)) = (unsafe { borrow_str(audience) }) else {
738            return HIDE_ERR_INVALID_ARGUMENT;
739        };
740        match hide_sign::Challenge::new(audience, now, valid_for) {
741            Ok(challenge) => {
742                unsafe { *out = HideBuffer::from_vec(challenge.encode()) };
743                HIDE_OK
744            }
745            Err(_) => HIDE_ERR_INTERNAL,
746        }
747    })
748}
749
750/// Answers a challenge, producing a signature over it.
751///
752/// # Safety
753/// All pointers must be valid for the stated lengths.
754#[unsafe(no_mangle)]
755pub unsafe extern "C" fn hide_challenge_answer(
756    identity: *const HideSigningIdentity,
757    challenge: *const u8,
758    challenge_len: usize,
759    out: *mut HideBuffer,
760) -> i32 {
761    guard(|| {
762        if identity.is_null() || out.is_null() {
763            return HIDE_ERR_INVALID_ARGUMENT;
764        }
765        let Some(bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) }) else {
766            return HIDE_ERR_INVALID_ARGUMENT;
767        };
768        let Ok(challenge) = hide_sign::Challenge::decode(bytes) else {
769            return HIDE_ERR_MALFORMED;
770        };
771        let identity = unsafe { &*identity };
772        unsafe { *out = HideBuffer::from_vec(challenge.answer(&identity.0).to_vec()) };
773        HIDE_OK
774    })
775}
776
777/// Creates the verifier's record of spent nonces.
778#[unsafe(no_mangle)]
779pub extern "C" fn hide_spent_nonces_new() -> *mut HideSpentNonces {
780    Box::into_raw(Box::new(HideSpentNonces(hide_sign::SpentNonces::new())))
781}
782
783/// Releases the record of spent nonces.
784///
785/// # Safety
786/// `spent` must come from this library and must not be freed twice.
787#[unsafe(no_mangle)]
788pub unsafe extern "C" fn hide_spent_nonces_free(spent: *mut HideSpentNonces) {
789    if spent.is_null() {
790        return;
791    }
792    drop(unsafe { Box::from_raw(spent) });
793}
794
795/// Accepts a challenge answer exactly once. A valid signature replayed a
796/// second time returns `HIDE_ERR_CHALLENGE_REPLAYED`, which is the entire
797/// reason this call takes a `spent` record rather than being a pure function.
798///
799/// # Safety
800/// All pointers must be valid for the stated lengths.
801#[unsafe(no_mangle)]
802pub unsafe extern "C" fn hide_challenge_accept(
803    spent: *mut HideSpentNonces,
804    challenge: *const u8,
805    challenge_len: usize,
806    signature: *const u8,
807    signature_len: usize,
808    public_key: *const u8,
809    public_key_len: usize,
810    now: u64,
811) -> i32 {
812    guard(|| {
813        if spent.is_null() {
814            return HIDE_ERR_INVALID_ARGUMENT;
815        }
816        let Some(challenge_bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) })
817        else {
818            return HIDE_ERR_INVALID_ARGUMENT;
819        };
820        let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
821        else {
822            return HIDE_ERR_INVALID_ARGUMENT;
823        };
824        let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
825            return HIDE_ERR_INVALID_ARGUMENT;
826        };
827        let Ok(challenge) = hide_sign::Challenge::decode(challenge_bytes) else {
828            return HIDE_ERR_MALFORMED;
829        };
830        let Ok(prover) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
831            return HIDE_ERR_NOT_A_KEY;
832        };
833        let spent = unsafe { &mut *spent };
834        match spent.0.accept(&challenge, signature, &prover, now) {
835            Ok(()) => HIDE_OK,
836            Err(hide_sign::ChallengeError::Expired) => HIDE_ERR_CHALLENGE_EXPIRED,
837            Err(hide_sign::ChallengeError::Replayed) => HIDE_ERR_CHALLENGE_REPLAYED,
838            Err(hide_sign::ChallengeError::NotSigned) => HIDE_ERR_AUTHENTICATION,
839        }
840    })
841}
842
843// ---------------------------------------------------------------------------
844// Identity logs
845// ---------------------------------------------------------------------------
846
847/// A log or chain is public and can be large; this only stops a hostile length
848/// from exhausting memory.
849const MAX_LOG_BYTES: usize = 16 * 1024 * 1024;
850
851/// Replays an identity log and reports how many devices it trusts now.
852///
853/// Returns `HIDE_ERR_MALFORMED` for a log that does not decode, and
854/// `HIDE_ERR_AUTHENTICATION` for one that decodes but does not verify — the
855/// distinction a caller needs to tell corruption from forgery.
856///
857/// # Safety
858/// All pointers must be valid for the stated lengths.
859#[unsafe(no_mangle)]
860pub unsafe extern "C" fn hide_identity_verify(
861    log: *const u8,
862    log_len: usize,
863    recovery: *const u8,
864    recovery_len: usize,
865    out_devices: *mut usize,
866) -> i32 {
867    guard(|| {
868        if out_devices.is_null() {
869            return HIDE_ERR_INVALID_ARGUMENT;
870        }
871        let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
872            return HIDE_ERR_INVALID_ARGUMENT;
873        };
874        let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
875            return HIDE_ERR_INVALID_ARGUMENT;
876        };
877        let Ok(recovery_key) = hide_sign::VerifyingIdentity::from_bytes(recovery_bytes) else {
878            return HIDE_ERR_NOT_A_KEY;
879        };
880        let Ok(entries) = hide_identity::decode(log_bytes) else {
881            return HIDE_ERR_MALFORMED;
882        };
883        let Ok(membership) = hide_identity::IdentityLog::verify(&entries, &recovery_key) else {
884            return HIDE_ERR_AUTHENTICATION;
885        };
886        unsafe { *out_devices = membership.len() };
887        HIDE_OK
888    })
889}
890
891/// Whether a log trusts a device right now. `out_trusted` is set to 1 or 0.
892///
893/// # Safety
894/// All pointers must be valid for the stated lengths.
895#[unsafe(no_mangle)]
896pub unsafe extern "C" fn hide_identity_trusts_device(
897    log: *const u8,
898    log_len: usize,
899    recovery: *const u8,
900    recovery_len: usize,
901    device_public: *const u8,
902    device_public_len: usize,
903    out_trusted: *mut i32,
904) -> i32 {
905    guard(|| {
906        if out_trusted.is_null() {
907            return HIDE_ERR_INVALID_ARGUMENT;
908        }
909        let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
910            return HIDE_ERR_INVALID_ARGUMENT;
911        };
912        let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
913            return HIDE_ERR_INVALID_ARGUMENT;
914        };
915        let Some(device_bytes) =
916            (unsafe { borrow(device_public, device_public_len, MAX_KEY_FILE) })
917        else {
918            return HIDE_ERR_INVALID_ARGUMENT;
919        };
920        let (Ok(recovery_key), Ok(device)) = (
921            hide_sign::VerifyingIdentity::from_bytes(recovery_bytes),
922            hide_sign::VerifyingIdentity::from_bytes(device_bytes),
923        ) else {
924            return HIDE_ERR_NOT_A_KEY;
925        };
926        let Ok(entries) = hide_identity::decode(log_bytes) else {
927            return HIDE_ERR_MALFORMED;
928        };
929        let Ok(membership) = hide_identity::IdentityLog::verify(&entries, &recovery_key) else {
930            return HIDE_ERR_AUTHENTICATION;
931        };
932        let trusted = membership.contains(&hide_identity::device_id(&device));
933        unsafe { *out_trusted = i32::from(trusted) };
934        HIDE_OK
935    })
936}
937
938/// The head of an identity log: one 32-byte value naming this exact history.
939///
940/// # Safety
941/// All pointers must be valid for the stated lengths.
942#[unsafe(no_mangle)]
943pub unsafe extern "C" fn hide_identity_head(
944    log: *const u8,
945    log_len: usize,
946    recovery: *const u8,
947    recovery_len: usize,
948    out: *mut HideBuffer,
949) -> i32 {
950    guard(|| {
951        if out.is_null() {
952            return HIDE_ERR_INVALID_ARGUMENT;
953        }
954        let Some(log_bytes) = (unsafe { borrow(log, log_len, MAX_LOG_BYTES) }) else {
955            return HIDE_ERR_INVALID_ARGUMENT;
956        };
957        let Some(recovery_bytes) = (unsafe { borrow(recovery, recovery_len, MAX_KEY_FILE) }) else {
958            return HIDE_ERR_INVALID_ARGUMENT;
959        };
960        let Ok(recovery_key) = hide_sign::VerifyingIdentity::from_bytes(recovery_bytes) else {
961            return HIDE_ERR_NOT_A_KEY;
962        };
963        let Ok(entries) = hide_identity::decode(log_bytes) else {
964            return HIDE_ERR_MALFORMED;
965        };
966        let Ok(log) = hide_identity::IdentityLog::from_entries(entries, recovery_key) else {
967            return HIDE_ERR_AUTHENTICATION;
968        };
969        unsafe { *out = HideBuffer::from_vec(log.head().to_vec()) };
970        HIDE_OK
971    })
972}
973
974// ---------------------------------------------------------------------------
975// Epoch chains
976// ---------------------------------------------------------------------------
977
978/// Verifies a published epoch history and reports how many epochs it holds.
979///
980/// # Safety
981/// All pointers must be valid for the stated lengths.
982#[unsafe(no_mangle)]
983pub unsafe extern "C" fn hide_epoch_verify(
984    chain: *const u8,
985    chain_len: usize,
986    out_epochs: *mut usize,
987) -> i32 {
988    guard(|| {
989        if out_epochs.is_null() {
990            return HIDE_ERR_INVALID_ARGUMENT;
991        }
992        let Some(bytes) = (unsafe { borrow(chain, chain_len, MAX_LOG_BYTES) }) else {
993            return HIDE_ERR_INVALID_ARGUMENT;
994        };
995        let Ok(records) = hide_epoch::decode_records(bytes) else {
996            return HIDE_ERR_MALFORMED;
997        };
998        if hide_epoch::EpochChain::verify(&records).is_err() {
999            return HIDE_ERR_AUTHENTICATION;
1000        }
1001        unsafe { *out_epochs = records.len() };
1002        HIDE_OK
1003    })
1004}
1005
1006/// The public key a sender should encrypt to for a given epoch.
1007///
1008/// # Safety
1009/// All pointers must be valid for the stated lengths.
1010#[unsafe(no_mangle)]
1011pub unsafe extern "C" fn hide_epoch_public_key(
1012    chain: *const u8,
1013    chain_len: usize,
1014    epoch: u64,
1015    out: *mut HideBuffer,
1016) -> i32 {
1017    guard(|| {
1018        if out.is_null() {
1019            return HIDE_ERR_INVALID_ARGUMENT;
1020        }
1021        let Some(bytes) = (unsafe { borrow(chain, chain_len, MAX_LOG_BYTES) }) else {
1022            return HIDE_ERR_INVALID_ARGUMENT;
1023        };
1024        let Ok(records) = hide_epoch::decode_records(bytes) else {
1025            return HIDE_ERR_MALFORMED;
1026        };
1027        if hide_epoch::EpochChain::verify(&records).is_err() {
1028            return HIDE_ERR_AUTHENTICATION;
1029        }
1030        let Some(record) = records.get(epoch as usize) else {
1031            return HIDE_ERR_INVALID_ARGUMENT;
1032        };
1033        unsafe { *out = HideBuffer::from_vec(record.public_key.clone()) };
1034        HIDE_OK
1035    })
1036}
1037
1038// ---------------------------------------------------------------------------
1039// Transparency proofs
1040// ---------------------------------------------------------------------------
1041
1042/// Checks an inclusion proof. `path` is the concatenated 32-byte hashes.
1043///
1044/// # Safety
1045/// All pointers must be valid for the stated lengths.
1046#[unsafe(no_mangle)]
1047pub unsafe extern "C" fn hide_transparency_verify_inclusion(
1048    leaf: *const u8,
1049    leaf_len: usize,
1050    index: u64,
1051    size: u64,
1052    path: *const u8,
1053    path_len: usize,
1054    root: *const u8,
1055    root_len: usize,
1056) -> i32 {
1057    guard(|| {
1058        let (Some(leaf), Some(path_bytes), Some(root)) = (
1059            unsafe { borrow(leaf, leaf_len, 32) },
1060            unsafe { borrow(path, path_len, 64 * 32) },
1061            unsafe { borrow(root, root_len, 32) },
1062        ) else {
1063            return HIDE_ERR_INVALID_ARGUMENT;
1064        };
1065        if leaf.len() != 32 || root.len() != 32 || path_bytes.len() % 32 != 0 {
1066            return HIDE_ERR_INVALID_ARGUMENT;
1067        }
1068        let proof = hide_transparency::InclusionProof {
1069            index,
1070            size,
1071            path: path_bytes
1072                .chunks_exact(32)
1073                .map(|chunk| {
1074                    let mut hash = [0u8; 32];
1075                    hash.copy_from_slice(chunk);
1076                    hash
1077                })
1078                .collect(),
1079        };
1080        let (mut leaf_hash, mut root_hash) = ([0u8; 32], [0u8; 32]);
1081        leaf_hash.copy_from_slice(leaf);
1082        root_hash.copy_from_slice(root);
1083
1084        match hide_transparency::verify_inclusion(&proof, &leaf_hash, &root_hash) {
1085            Ok(()) => HIDE_OK,
1086            Err(_) => HIDE_ERR_AUTHENTICATION,
1087        }
1088    })
1089}
1090
1091/// Checks a consistency proof: that `old_root` is the root the log had before
1092/// it grew to `new_root`.
1093///
1094/// # Safety
1095/// All pointers must be valid for the stated lengths.
1096#[unsafe(no_mangle)]
1097pub unsafe extern "C" fn hide_transparency_verify_consistency(
1098    old_size: u64,
1099    new_size: u64,
1100    path: *const u8,
1101    path_len: usize,
1102    old_root: *const u8,
1103    old_root_len: usize,
1104    new_root: *const u8,
1105    new_root_len: usize,
1106) -> i32 {
1107    guard(|| {
1108        let (Some(path_bytes), Some(old), Some(new)) = (
1109            unsafe { borrow(path, path_len, 64 * 32) },
1110            unsafe { borrow(old_root, old_root_len, 32) },
1111            unsafe { borrow(new_root, new_root_len, 32) },
1112        ) else {
1113            return HIDE_ERR_INVALID_ARGUMENT;
1114        };
1115        if old.len() != 32 || new.len() != 32 || path_bytes.len() % 32 != 0 {
1116            return HIDE_ERR_INVALID_ARGUMENT;
1117        }
1118        let proof = hide_transparency::ConsistencyProof {
1119            old_size,
1120            new_size,
1121            path: path_bytes
1122                .chunks_exact(32)
1123                .map(|chunk| {
1124                    let mut hash = [0u8; 32];
1125                    hash.copy_from_slice(chunk);
1126                    hash
1127                })
1128                .collect(),
1129        };
1130        let (mut old_hash, mut new_hash) = ([0u8; 32], [0u8; 32]);
1131        old_hash.copy_from_slice(old);
1132        new_hash.copy_from_slice(new);
1133
1134        match hide_transparency::verify_consistency(&proof, &old_hash, &new_hash) {
1135            Ok(()) => HIDE_OK,
1136            Err(_) => HIDE_ERR_AUTHENTICATION,
1137        }
1138    })
1139}
1140#[cfg(test)]
1141mod tests {
1142    use std::ffi::CString;
1143
1144    use super::*;
1145
1146    /// Drives the API exactly as a C caller would, pointers and all.
1147    unsafe fn keypair() -> (*mut HideSecretKey, HideBuffer) {
1148        let mut secret = ptr::null_mut();
1149        let mut public = hide_buffer_empty();
1150        assert_eq!(
1151            unsafe { hide_keypair_generate(&mut secret, &mut public) },
1152            HIDE_OK
1153        );
1154        assert_eq!(public.len, HIDE_PUBLIC_KEY_LEN);
1155        (secret, public)
1156    }
1157
1158    #[test]
1159    fn round_trips_through_the_c_api() {
1160        unsafe {
1161            let (secret, public) = keypair();
1162            let message = b"nume,suma\nAna,9000\n";
1163
1164            let mut container = hide_buffer_empty();
1165            assert_eq!(
1166                hide_encrypt(
1167                    message.as_ptr(),
1168                    message.len(),
1169                    public.data,
1170                    1,
1171                    c"salarii.csv".as_ptr(),
1172                    ptr::null(),
1173                    &mut container,
1174                ),
1175                HIDE_OK
1176            );
1177            assert!(container.len > message.len());
1178
1179            let mut plaintext = hide_buffer_empty();
1180            let mut filename = hide_buffer_empty();
1181            assert_eq!(
1182                hide_decrypt(
1183                    container.data,
1184                    container.len,
1185                    secret,
1186                    &mut plaintext,
1187                    &mut filename,
1188                    ptr::null_mut(),
1189                ),
1190                HIDE_OK
1191            );
1192            assert_eq!(
1193                slice::from_raw_parts(plaintext.data, plaintext.len),
1194                message
1195            );
1196            assert_eq!(
1197                slice::from_raw_parts(filename.data, filename.len),
1198                b"salarii.csv"
1199            );
1200
1201            hide_buffer_free(&mut filename);
1202            hide_buffer_free(&mut plaintext);
1203            hide_buffer_free(&mut container);
1204            hide_buffer_free(&mut { public });
1205            hide_secret_key_free(secret);
1206        }
1207    }
1208
1209    #[test]
1210    fn a_freed_buffer_is_cleared_and_safe_to_free_twice() {
1211        unsafe {
1212            let (secret, mut public) = keypair();
1213            let mut container = hide_buffer_empty();
1214            hide_encrypt(
1215                b"x".as_ptr(),
1216                1,
1217                public.data,
1218                1,
1219                ptr::null(),
1220                ptr::null(),
1221                &mut container,
1222            );
1223            hide_buffer_free(&mut container);
1224            assert!(container.data.is_null(), "freed buffer kept its pointer");
1225            // A second free must be a no-op rather than a double free.
1226            hide_buffer_free(&mut container);
1227            hide_buffer_free(&mut public);
1228            hide_secret_key_free(secret);
1229        }
1230    }
1231
1232    #[test]
1233    fn tampering_is_refused_and_yields_no_plaintext() {
1234        unsafe {
1235            let (secret, mut public) = keypair();
1236            let mut container = hide_buffer_empty();
1237            hide_encrypt(
1238                b"confidential".as_ptr(),
1239                12,
1240                public.data,
1241                1,
1242                ptr::null(),
1243                ptr::null(),
1244                &mut container,
1245            );
1246
1247            let mut damaged = slice::from_raw_parts(container.data, container.len).to_vec();
1248            let last = damaged.len() - 1;
1249            damaged[last] ^= 1;
1250
1251            let mut plaintext = hide_buffer_empty();
1252            let code = hide_decrypt(
1253                damaged.as_ptr(),
1254                damaged.len(),
1255                secret,
1256                &mut plaintext,
1257                ptr::null_mut(),
1258                ptr::null_mut(),
1259            );
1260            assert_ne!(code, HIDE_OK);
1261            assert!(plaintext.data.is_null(), "published unverified plaintext");
1262
1263            hide_buffer_free(&mut container);
1264            hide_buffer_free(&mut public);
1265            hide_secret_key_free(secret);
1266        }
1267    }
1268
1269    #[test]
1270    fn the_wrong_key_cannot_decrypt() {
1271        unsafe {
1272            let (secret, mut public) = keypair();
1273            let (other, mut other_public) = keypair();
1274            let mut container = hide_buffer_empty();
1275            hide_encrypt(
1276                b"secret".as_ptr(),
1277                6,
1278                public.data,
1279                1,
1280                ptr::null(),
1281                ptr::null(),
1282                &mut container,
1283            );
1284
1285            let mut plaintext = hide_buffer_empty();
1286            assert_eq!(
1287                hide_decrypt(
1288                    container.data,
1289                    container.len,
1290                    other,
1291                    &mut plaintext,
1292                    ptr::null_mut(),
1293                    ptr::null_mut(),
1294                ),
1295                HIDE_ERR_NO_MATCHING_RECIPIENT
1296            );
1297
1298            hide_buffer_free(&mut container);
1299            hide_buffer_free(&mut public);
1300            hide_buffer_free(&mut other_public);
1301            hide_secret_key_free(secret);
1302            hide_secret_key_free(other);
1303        }
1304    }
1305
1306    #[test]
1307    fn null_and_absurd_arguments_are_rejected_rather_than_crashing() {
1308        unsafe {
1309            let mut out = hide_buffer_empty();
1310            let mut secret = ptr::null_mut();
1311
1312            assert_eq!(
1313                hide_keypair_generate(ptr::null_mut(), &mut out),
1314                HIDE_ERR_INVALID_ARGUMENT
1315            );
1316            assert_eq!(
1317                hide_keypair_generate(&mut secret, ptr::null_mut()),
1318                HIDE_ERR_INVALID_ARGUMENT
1319            );
1320            // A non-null length with a null pointer must not be dereferenced.
1321            assert_eq!(
1322                hide_encrypt(
1323                    ptr::null(),
1324                    10,
1325                    ptr::null(),
1326                    1,
1327                    ptr::null(),
1328                    ptr::null(),
1329                    &mut out
1330                ),
1331                HIDE_ERR_INVALID_ARGUMENT
1332            );
1333            // A length that would over-allocate must be refused up front.
1334            assert_eq!(
1335                hide_encrypt(
1336                    b"x".as_ptr(),
1337                    usize::MAX,
1338                    ptr::null(),
1339                    1,
1340                    ptr::null(),
1341                    ptr::null(),
1342                    &mut out
1343                ),
1344                HIDE_ERR_INVALID_ARGUMENT
1345            );
1346            assert_eq!(
1347                hide_decrypt(
1348                    ptr::null(),
1349                    0,
1350                    ptr::null(),
1351                    &mut out,
1352                    ptr::null_mut(),
1353                    ptr::null_mut()
1354                ),
1355                HIDE_ERR_INVALID_ARGUMENT
1356            );
1357            let mut kind = -1;
1358            assert_eq!(
1359                hide_inspect_key(ptr::null(), 32, &mut kind),
1360                HIDE_ERR_INVALID_ARGUMENT
1361            );
1362            // Freeing null is a no-op, not a crash.
1363            hide_buffer_free(ptr::null_mut());
1364            hide_secret_key_free(ptr::null_mut());
1365        }
1366    }
1367
1368    #[test]
1369    fn protected_keys_round_trip_and_reject_a_wrong_passphrase() {
1370        unsafe {
1371            let (secret, mut public) = keypair();
1372            let mut sealed = hide_buffer_empty();
1373            assert_eq!(
1374                hide_secret_key_protect(secret, c"correct horse".as_ptr(), &mut sealed),
1375                HIDE_OK
1376            );
1377
1378            let mut kind = -1;
1379            assert_eq!(
1380                hide_inspect_key(sealed.data, sealed.len, &mut kind),
1381                HIDE_OK
1382            );
1383            assert_eq!(kind, HIDE_KEY_PROTECTED);
1384
1385            let mut reopened = ptr::null_mut();
1386            assert_eq!(
1387                hide_secret_key_open(sealed.data, sealed.len, c"wrong".as_ptr(), &mut reopened),
1388                HIDE_ERR_WRONG_PASSPHRASE
1389            );
1390            assert_eq!(
1391                hide_secret_key_open(sealed.data, sealed.len, ptr::null(), &mut reopened),
1392                HIDE_ERR_WRONG_PASSPHRASE
1393            );
1394            assert_eq!(
1395                hide_secret_key_open(
1396                    sealed.data,
1397                    sealed.len,
1398                    c"correct horse".as_ptr(),
1399                    &mut reopened
1400                ),
1401                HIDE_OK
1402            );
1403
1404            // The reopened key must be the same one.
1405            let mut derived = hide_buffer_empty();
1406            assert_eq!(hide_secret_key_public(reopened, &mut derived), HIDE_OK);
1407            assert_eq!(
1408                slice::from_raw_parts(derived.data, derived.len),
1409                slice::from_raw_parts(public.data, public.len)
1410            );
1411
1412            hide_buffer_free(&mut derived);
1413            hide_buffer_free(&mut sealed);
1414            hide_buffer_free(&mut public);
1415            hide_secret_key_free(reopened);
1416            hide_secret_key_free(secret);
1417        }
1418    }
1419
1420    #[test]
1421    fn armor_round_trips_and_rejects_rubbish() {
1422        unsafe {
1423            let (secret, mut public) = keypair();
1424            let mut armored = hide_buffer_empty();
1425            assert_eq!(
1426                hide_public_key_armor(public.data, public.len, &mut armored),
1427                HIDE_OK
1428            );
1429
1430            // The armored form must survive a trip through a C string.
1431            let text =
1432                CString::new(slice::from_raw_parts(armored.data, armored.len)).expect("no NUL");
1433            let mut decoded = hide_buffer_empty();
1434            assert_eq!(
1435                hide_public_key_dearmor(text.as_ptr(), &mut decoded),
1436                HIDE_OK
1437            );
1438            assert_eq!(
1439                slice::from_raw_parts(decoded.data, decoded.len),
1440                slice::from_raw_parts(public.data, public.len)
1441            );
1442            assert_ne!(
1443                hide_public_key_dearmor(c"not a key".as_ptr(), &mut decoded),
1444                HIDE_OK
1445            );
1446
1447            hide_buffer_free(&mut decoded);
1448            hide_buffer_free(&mut armored);
1449            hide_buffer_free(&mut public);
1450            hide_secret_key_free(secret);
1451        }
1452    }
1453
1454    /// A sealed identity file, the way a real caller would have one on disk.
1455    fn identity_file(passphrase: &str) -> Vec<u8> {
1456        let identity = hide_keyring::Identity::generate().expect("randomness");
1457        hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase)
1458            .expect("sealing works")
1459    }
1460
1461    unsafe fn open_identity(file: &[u8], passphrase: &CString) -> *mut HideSigningIdentity {
1462        let mut identity = ptr::null_mut();
1463        assert_eq!(
1464            unsafe {
1465                hide_signing_identity_open(
1466                    file.as_ptr(),
1467                    file.len(),
1468                    passphrase.as_ptr(),
1469                    &mut identity,
1470                )
1471            },
1472            HIDE_OK
1473        );
1474        identity
1475    }
1476
1477    #[test]
1478    fn signs_and_verifies_across_the_boundary() {
1479        unsafe {
1480            let passphrase = CString::new("correct horse battery staple").unwrap();
1481            let file = identity_file(passphrase.to_str().unwrap());
1482            let identity = open_identity(&file, &passphrase);
1483
1484            let mut public = hide_buffer_empty();
1485            assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1486            assert_eq!(public.len, HIDE_VERIFYING_KEY_LEN);
1487
1488            let context = b"HIDE/0.5 ffi test";
1489            let message = b"the message that crossed the boundary";
1490            let mut signature = hide_buffer_empty();
1491            assert_eq!(
1492                hide_sign_message(
1493                    identity,
1494                    context.as_ptr(),
1495                    context.len(),
1496                    message.as_ptr(),
1497                    message.len(),
1498                    &mut signature,
1499                ),
1500                HIDE_OK
1501            );
1502            assert_eq!(signature.len, HIDE_SIGNATURE_LEN);
1503
1504            assert_eq!(
1505                hide_verify_message(
1506                    public.data,
1507                    public.len,
1508                    context.as_ptr(),
1509                    context.len(),
1510                    message.as_ptr(),
1511                    message.len(),
1512                    signature.data,
1513                    signature.len,
1514                ),
1515                HIDE_OK
1516            );
1517
1518            // A different context must not verify, or the separation the C API
1519            // advertises would be decorative.
1520            let other = b"HIDE/0.5 something else";
1521            assert_eq!(
1522                hide_verify_message(
1523                    public.data,
1524                    public.len,
1525                    other.as_ptr(),
1526                    other.len(),
1527                    message.as_ptr(),
1528                    message.len(),
1529                    signature.data,
1530                    signature.len,
1531                ),
1532                HIDE_ERR_AUTHENTICATION
1533            );
1534
1535            let altered = b"the message that crossed the boundaryX";
1536            assert_eq!(
1537                hide_verify_message(
1538                    public.data,
1539                    public.len,
1540                    context.as_ptr(),
1541                    context.len(),
1542                    altered.as_ptr(),
1543                    altered.len(),
1544                    signature.data,
1545                    signature.len,
1546                ),
1547                HIDE_ERR_AUTHENTICATION
1548            );
1549
1550            hide_buffer_free(&mut signature);
1551            hide_buffer_free(&mut public);
1552            hide_signing_identity_free(identity);
1553        }
1554    }
1555
1556    /// A key file that predates signatures has no signing seed, and inventing
1557    /// one would silently sign under a key nobody expects.
1558    #[test]
1559    fn an_encryption_only_key_cannot_sign() {
1560        unsafe {
1561            let passphrase = CString::new("correct horse battery staple").unwrap();
1562            let secret = hide_crypto::RecipientSecret::generate().expect("randomness");
1563            let file =
1564                hide_keyring::protect(&secret, passphrase.to_str().unwrap()).expect("sealing");
1565
1566            let mut identity = ptr::null_mut();
1567            assert_eq!(
1568                hide_signing_identity_open(
1569                    file.as_ptr(),
1570                    file.len(),
1571                    passphrase.as_ptr(),
1572                    &mut identity,
1573                ),
1574                HIDE_ERR_NOT_A_KEY
1575            );
1576            assert!(identity.is_null());
1577        }
1578    }
1579
1580    #[test]
1581    fn a_wrong_passphrase_is_named_as_such() {
1582        unsafe {
1583            let file = identity_file("correct horse battery staple");
1584            let wrong = CString::new("not the passphrase at all").unwrap();
1585            let mut identity = ptr::null_mut();
1586            assert_eq!(
1587                hide_signing_identity_open(
1588                    file.as_ptr(),
1589                    file.len(),
1590                    wrong.as_ptr(),
1591                    &mut identity
1592                ),
1593                HIDE_ERR_WRONG_PASSPHRASE
1594            );
1595        }
1596    }
1597
1598    #[test]
1599    fn a_challenge_is_answered_once_and_then_refused() {
1600        unsafe {
1601            let passphrase = CString::new("correct horse battery staple").unwrap();
1602            let file = identity_file(passphrase.to_str().unwrap());
1603            let identity = open_identity(&file, &passphrase);
1604
1605            let mut public = hide_buffer_empty();
1606            assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1607
1608            let audience = CString::new("ssh://host.example").unwrap();
1609            let mut challenge = hide_buffer_empty();
1610            assert_eq!(
1611                hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1612                HIDE_OK
1613            );
1614
1615            let mut answer = hide_buffer_empty();
1616            assert_eq!(
1617                hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1618                HIDE_OK
1619            );
1620
1621            let spent = hide_spent_nonces_new();
1622            assert_eq!(
1623                hide_challenge_accept(
1624                    spent,
1625                    challenge.data,
1626                    challenge.len,
1627                    answer.data,
1628                    answer.len,
1629                    public.data,
1630                    public.len,
1631                    1_000,
1632                ),
1633                HIDE_OK
1634            );
1635            // The same valid answer, a second time.
1636            assert_eq!(
1637                hide_challenge_accept(
1638                    spent,
1639                    challenge.data,
1640                    challenge.len,
1641                    answer.data,
1642                    answer.len,
1643                    public.data,
1644                    public.len,
1645                    1_000,
1646                ),
1647                HIDE_ERR_CHALLENGE_REPLAYED
1648            );
1649
1650            hide_spent_nonces_free(spent);
1651            hide_buffer_free(&mut answer);
1652            hide_buffer_free(&mut challenge);
1653            hide_buffer_free(&mut public);
1654            hide_signing_identity_free(identity);
1655        }
1656    }
1657
1658    #[test]
1659    fn an_answer_after_the_window_is_refused_across_the_boundary() {
1660        unsafe {
1661            let passphrase = CString::new("correct horse battery staple").unwrap();
1662            let file = identity_file(passphrase.to_str().unwrap());
1663            let identity = open_identity(&file, &passphrase);
1664
1665            let mut public = hide_buffer_empty();
1666            assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
1667
1668            let audience = CString::new("ssh://host.example").unwrap();
1669            let mut challenge = hide_buffer_empty();
1670            assert_eq!(
1671                hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
1672                HIDE_OK
1673            );
1674            let mut answer = hide_buffer_empty();
1675            assert_eq!(
1676                hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
1677                HIDE_OK
1678            );
1679
1680            let spent = hide_spent_nonces_new();
1681            assert_eq!(
1682                hide_challenge_accept(
1683                    spent,
1684                    challenge.data,
1685                    challenge.len,
1686                    answer.data,
1687                    answer.len,
1688                    public.data,
1689                    public.len,
1690                    1_100,
1691                ),
1692                HIDE_ERR_CHALLENGE_EXPIRED
1693            );
1694
1695            hide_spent_nonces_free(spent);
1696            hide_buffer_free(&mut answer);
1697            hide_buffer_free(&mut challenge);
1698            hide_buffer_free(&mut public);
1699            hide_signing_identity_free(identity);
1700        }
1701    }
1702
1703    /// Null and absurd lengths must be refused, not dereferenced.
1704    #[test]
1705    fn the_signing_boundary_refuses_nulls_without_crashing() {
1706        unsafe {
1707            let mut out = hide_buffer_empty();
1708            assert_eq!(
1709                hide_sign_message(ptr::null(), ptr::null(), 0, ptr::null(), 0, &mut out),
1710                HIDE_ERR_INVALID_ARGUMENT
1711            );
1712            assert_eq!(
1713                hide_verify_message(
1714                    ptr::null(),
1715                    0,
1716                    ptr::null(),
1717                    0,
1718                    ptr::null(),
1719                    0,
1720                    ptr::null(),
1721                    0
1722                ),
1723                HIDE_ERR_NOT_A_KEY
1724            );
1725            assert_eq!(
1726                hide_challenge_answer(ptr::null(), ptr::null(), 0, &mut out),
1727                HIDE_ERR_INVALID_ARGUMENT
1728            );
1729            assert_eq!(
1730                hide_challenge_accept(
1731                    ptr::null_mut(),
1732                    ptr::null(),
1733                    0,
1734                    ptr::null(),
1735                    0,
1736                    ptr::null(),
1737                    0,
1738                    0
1739                ),
1740                HIDE_ERR_INVALID_ARGUMENT
1741            );
1742            // Freeing null is a no-op, not a crash.
1743            hide_signing_identity_free(ptr::null_mut());
1744            hide_spent_nonces_free(ptr::null_mut());
1745        }
1746    }
1747}