use std::{
ffi::{CStr, c_char},
panic::{AssertUnwindSafe, catch_unwind},
ptr, slice,
};
use hide_crypto::{RecipientPublic, RecipientSecret};
use hide_keyring::KeyFormat;
use hide_object::Metadata;
use zeroize::Zeroizing;
pub const HIDE_OK: i32 = 0;
pub const HIDE_ERR_INVALID_ARGUMENT: i32 = 1;
pub const HIDE_ERR_WRONG_PASSPHRASE: i32 = 2;
pub const HIDE_ERR_NOT_A_KEY: i32 = 3;
pub const HIDE_ERR_AUTHENTICATION: i32 = 4;
pub const HIDE_ERR_NO_MATCHING_RECIPIENT: i32 = 5;
pub const HIDE_ERR_MALFORMED: i32 = 6;
pub const HIDE_ERR_TOO_LARGE: i32 = 7;
pub const HIDE_ERR_PANIC: i32 = 98;
pub const HIDE_ERR_INTERNAL: i32 = 99;
pub const HIDE_KEY_RAW: i32 = 0;
pub const HIDE_KEY_PROTECTED: i32 = 1;
pub const HIDE_PUBLIC_KEY_LEN: usize = 1216;
pub const HIDE_MIN_PASSPHRASE_LEN: usize = hide_keyring::MIN_PASSPHRASE_LEN;
const MAX_INPUT: usize = 1 << 30;
const MAX_KEY_FILE: usize = 4096;
#[repr(C)]
pub struct HideBuffer {
pub data: *mut u8,
pub len: usize,
capacity: usize,
}
impl HideBuffer {
fn from_vec(mut bytes: Vec<u8>) -> Self {
let buffer = Self {
data: bytes.as_mut_ptr(),
len: bytes.len(),
capacity: bytes.capacity(),
};
std::mem::forget(bytes);
buffer
}
}
#[unsafe(no_mangle)]
pub extern "C" fn hide_buffer_empty() -> HideBuffer {
HideBuffer {
data: ptr::null_mut(),
len: 0,
capacity: 0,
}
}
pub struct HideSecretKey(RecipientSecret);
unsafe fn borrow<'a>(data: *const u8, len: usize, limit: usize) -> Option<&'a [u8]> {
if len > limit {
return None;
}
if len == 0 {
return Some(&[]);
}
if data.is_null() {
return None;
}
Some(unsafe { slice::from_raw_parts(data, len) })
}
unsafe fn borrow_str<'a>(text: *const c_char) -> Option<Option<&'a str>> {
if text.is_null() {
return Some(None);
}
match unsafe { CStr::from_ptr(text) }.to_str() {
Ok(value) => Some(Some(value)),
Err(_) => None,
}
}
fn object_error_code(error: &hide_object::ObjectError) -> i32 {
use hide_object::ObjectError;
match error {
ObjectError::NoMatchingRecipient => HIDE_ERR_NO_MATCHING_RECIPIENT,
ObjectError::Crypto(_) => HIDE_ERR_AUTHENTICATION,
ObjectError::ObjectTooLarge => HIDE_ERR_TOO_LARGE,
_ => HIDE_ERR_MALFORMED,
}
}
fn keyring_error_code(error: &hide_keyring::KeyringError) -> i32 {
use hide_keyring::KeyringError;
match error {
KeyringError::WrongPassphrase => HIDE_ERR_WRONG_PASSPHRASE,
KeyringError::NotAKeyFile | KeyringError::UnsupportedVersion(_) => HIDE_ERR_NOT_A_KEY,
KeyringError::PassphraseTooShort(_) => HIDE_ERR_INVALID_ARGUMENT,
_ => HIDE_ERR_MALFORMED,
}
}
fn guard(body: impl FnOnce() -> i32) -> i32 {
catch_unwind(AssertUnwindSafe(body)).unwrap_or(HIDE_ERR_PANIC)
}
#[unsafe(no_mangle)]
pub extern "C" fn hide_error_message(code: i32) -> *const c_char {
let text: &[u8] = match code {
HIDE_OK => b"success\0",
HIDE_ERR_INVALID_ARGUMENT => b"invalid argument\0",
HIDE_ERR_WRONG_PASSPHRASE => b"incorrect passphrase, or the key file was modified\0",
HIDE_ERR_NOT_A_KEY => b"not a HIDE key file\0",
HIDE_ERR_AUTHENTICATION => b"authentication failed; the data was altered\0",
HIDE_ERR_NO_MATCHING_RECIPIENT => b"no matching recipient for this key\0",
HIDE_ERR_MALFORMED => b"malformed or corrupt input\0",
HIDE_ERR_TOO_LARGE => b"input is too large\0",
HIDE_ERR_CHALLENGE_EXPIRED => b"the challenge expired before it was answered\0",
HIDE_ERR_CHALLENGE_REPLAYED => b"this challenge was already answered\0",
HIDE_ERR_PANIC => b"internal error (panic)\0",
_ => b"internal error\0",
};
text.as_ptr().cast()
}
#[unsafe(no_mangle)]
pub extern "C" fn hide_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr().cast()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_buffer_free(buffer: *mut HideBuffer) {
if buffer.is_null() {
return;
}
let buffer = unsafe { &mut *buffer };
if buffer.data.is_null() {
return;
}
let mut bytes = unsafe { Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity) };
bytes.iter_mut().for_each(|byte| *byte = 0);
drop(bytes);
buffer.data = ptr::null_mut();
buffer.len = 0;
buffer.capacity = 0;
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_keypair_generate(
out_secret: *mut *mut HideSecretKey,
out_public: *mut HideBuffer,
) -> i32 {
guard(|| {
if out_secret.is_null() || out_public.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Ok(secret) = RecipientSecret::generate() else {
return HIDE_ERR_INTERNAL;
};
let Ok(public) = secret.public_key() else {
return HIDE_ERR_INTERNAL;
};
unsafe {
*out_public = HideBuffer::from_vec(public.to_bytes());
*out_secret = Box::into_raw(Box::new(HideSecretKey(secret)));
}
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_inspect_key(data: *const u8, len: usize, out_kind: *mut i32) -> i32 {
guard(|| {
if out_kind.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let kind = match hide_keyring::inspect(bytes) {
KeyFormat::Raw => HIDE_KEY_RAW,
KeyFormat::Protected => HIDE_KEY_PROTECTED,
};
unsafe { *out_kind = kind };
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_secret_key_open(
data: *const u8,
len: usize,
passphrase: *const c_char,
out_secret: *mut *mut HideSecretKey,
) -> i32 {
guard(|| {
if out_secret.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
match hide_keyring::open(bytes, passphrase) {
Ok(secret) => {
unsafe { *out_secret = Box::into_raw(Box::new(HideSecretKey(secret))) };
HIDE_OK
}
Err(error) => keyring_error_code(&error),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_secret_key_protect(
secret: *const HideSecretKey,
passphrase: *const c_char,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if secret.is_null() || out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let secret = unsafe { &*secret };
match hide_keyring::protect(&secret.0, passphrase) {
Ok(sealed) => {
unsafe { *out = HideBuffer::from_vec(sealed) };
HIDE_OK
}
Err(error) => keyring_error_code(&error),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_secret_key_public(
secret: *const HideSecretKey,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if secret.is_null() || out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let secret = unsafe { &*secret };
match secret.0.public_key() {
Ok(public) => {
unsafe { *out = HideBuffer::from_vec(public.to_bytes()) };
HIDE_OK
}
Err(_) => HIDE_ERR_INTERNAL,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_secret_key_free(secret: *mut HideSecretKey) {
if secret.is_null() {
return;
}
drop(unsafe { Box::from_raw(secret) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_encrypt(
plaintext: *const u8,
plaintext_len: usize,
recipients: *const u8,
recipient_count: usize,
filename: *const c_char,
media_type: *const c_char,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
if recipient_count == 0 || recipient_count > 64 {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(plaintext) = (unsafe { borrow(plaintext, plaintext_len, MAX_INPUT) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(keys) =
(unsafe { borrow(recipients, recipient_count * HIDE_PUBLIC_KEY_LEN, MAX_INPUT) })
else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let mut parsed = Vec::with_capacity(recipient_count);
for chunk in keys.chunks_exact(HIDE_PUBLIC_KEY_LEN) {
match RecipientPublic::from_bytes(chunk) {
Ok(key) => parsed.push(key),
Err(_) => return HIDE_ERR_INVALID_ARGUMENT,
}
}
let (Some(filename), Some(media_type)) = (unsafe { borrow_str(filename) }, unsafe {
borrow_str(media_type)
}) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let metadata = Metadata {
filename: filename.map(str::to_owned),
media_type: media_type.map(str::to_owned),
signature: None,
};
let mut container = Vec::new();
match hide_object::encrypt(&mut &*plaintext, &mut container, &parsed, &metadata) {
Ok(_) => {
unsafe { *out = HideBuffer::from_vec(container) };
HIDE_OK
}
Err(error) => object_error_code(&error),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_decrypt(
container: *const u8,
container_len: usize,
secret: *const HideSecretKey,
out: *mut HideBuffer,
out_filename: *mut HideBuffer,
out_media_type: *mut HideBuffer,
) -> i32 {
guard(|| {
if secret.is_null() || out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(container) = (unsafe { borrow(container, container_len, MAX_INPUT) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let secret = unsafe { &*secret };
let mut plaintext = Zeroizing::new(Vec::new());
let verified =
match hide_object::decrypt_to_staging(&mut &*container, &mut *plaintext, &secret.0) {
Ok(verified) => verified,
Err(error) => return object_error_code(&error),
};
if !out_filename.is_null() {
let value = verified.metadata.filename.unwrap_or_default();
unsafe { *out_filename = HideBuffer::from_vec(value.into_bytes()) };
}
if !out_media_type.is_null() {
let value = verified.metadata.media_type.unwrap_or_default();
unsafe { *out_media_type = HideBuffer::from_vec(value.into_bytes()) };
}
unsafe { *out = HideBuffer::from_vec(std::mem::take(&mut *plaintext)) };
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_public_key_armor(
data: *const u8,
len: usize,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
if bytes.len() != HIDE_PUBLIC_KEY_LEN {
return HIDE_ERR_INVALID_ARGUMENT;
}
let armored = hide_keyring::encode_public(bytes);
unsafe { *out = HideBuffer::from_vec(armored.into_bytes()) };
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_public_key_dearmor(text: *const c_char, out: *mut HideBuffer) -> i32 {
guard(|| {
if out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(Some(text)) = (unsafe { borrow_str(text) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
match hide_keyring::decode_public(text) {
Ok(bytes) if bytes.len() == HIDE_PUBLIC_KEY_LEN => {
unsafe { *out = HideBuffer::from_vec(bytes) };
HIDE_OK
}
Ok(_) => HIDE_ERR_INVALID_ARGUMENT,
Err(error) => keyring_error_code(&error),
}
})
}
pub const HIDE_SIGNATURE_LEN: usize = hide_sign::SIGNATURE_LENGTH;
pub const HIDE_VERIFYING_KEY_LEN: usize = hide_sign::VERIFYING_KEY_LENGTH;
pub const HIDE_NONCE_LEN: usize = hide_sign::NONCE_LENGTH;
pub const HIDE_ERR_CHALLENGE_EXPIRED: i32 = 8;
pub const HIDE_ERR_CHALLENGE_REPLAYED: i32 = 9;
pub struct HideSigningIdentity(hide_sign::SigningIdentity);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_identity_generate(
passphrase: *const c_char,
out_key_file: *mut HideBuffer,
) -> i32 {
guard(|| {
if out_key_file.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(Some(passphrase)) = (unsafe { borrow_str(passphrase) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let identity = match hide_keyring::Identity::generate() {
Ok(identity) => identity,
Err(_) => return HIDE_ERR_INTERNAL,
};
match hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase) {
Ok(sealed) => {
unsafe { *out_key_file = HideBuffer::from_vec(sealed) };
HIDE_OK
}
Err(error) => keyring_error_code(&error),
}
})
}
pub struct HideSpentNonces(hide_sign::SpentNonces);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_signing_identity_open(
data: *const u8,
len: usize,
passphrase: *const c_char,
out_identity: *mut *mut HideSigningIdentity,
) -> i32 {
guard(|| {
if out_identity.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(bytes) = (unsafe { borrow(data, len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(passphrase) = (unsafe { borrow_str(passphrase) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let seed = match hide_keyring::inspect(bytes) {
KeyFormat::Raw => {
if bytes.len() != 32 {
return HIDE_ERR_NOT_A_KEY;
}
let mut seed = Zeroizing::new([0_u8; 32]);
seed.copy_from_slice(bytes);
hide_keyring::Identity::from_seed(seed).signing_seed()
}
KeyFormat::Protected => {
let Some(passphrase) = passphrase else {
return HIDE_ERR_INVALID_ARGUMENT;
};
match hide_keyring::unprotect_seed(bytes, passphrase) {
Ok((seed, hide_keyring::KeyPurpose::Identity)) => {
hide_keyring::Identity::from_seed(seed).signing_seed()
}
Ok(_) => return HIDE_ERR_NOT_A_KEY,
Err(error) => return keyring_error_code(&error),
}
}
};
match hide_sign::SigningIdentity::from_bytes(&seed[..]) {
Ok(identity) => {
unsafe { *out_identity = Box::into_raw(Box::new(HideSigningIdentity(identity))) };
HIDE_OK
}
Err(_) => HIDE_ERR_NOT_A_KEY,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_signing_identity_public(
identity: *const HideSigningIdentity,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if identity.is_null() || out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let identity = unsafe { &*identity };
let public = identity.0.verifying_key().to_bytes();
unsafe { *out = HideBuffer::from_vec(public.to_vec()) };
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_signing_identity_free(identity: *mut HideSigningIdentity) {
if identity.is_null() {
return;
}
drop(unsafe { Box::from_raw(identity) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_sign_message(
identity: *const HideSigningIdentity,
context: *const u8,
context_len: usize,
message: *const u8,
message_len: usize,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if identity.is_null() || out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let identity = unsafe { &*identity };
let signature = identity.0.sign(context, message);
unsafe { *out = HideBuffer::from_vec(signature.to_vec()) };
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_verify_message(
public_key: *const u8,
public_key_len: usize,
context: *const u8,
context_len: usize,
message: *const u8,
message_len: usize,
signature: *const u8,
signature_len: usize,
) -> i32 {
guard(|| {
let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(context) = (unsafe { borrow(context, context_len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(message) = (unsafe { borrow(message, message_len, MAX_INPUT) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Ok(verifying) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
return HIDE_ERR_NOT_A_KEY;
};
match verifying.verify(context, message, signature) {
Ok(()) => HIDE_OK,
Err(_) => HIDE_ERR_AUTHENTICATION,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_challenge_new(
audience: *const c_char,
now: u64,
valid_for: u64,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(Some(audience)) = (unsafe { borrow_str(audience) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
match hide_sign::Challenge::new(audience, now, valid_for) {
Ok(challenge) => {
unsafe { *out = HideBuffer::from_vec(challenge.encode()) };
HIDE_OK
}
Err(_) => HIDE_ERR_INTERNAL,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_challenge_answer(
identity: *const HideSigningIdentity,
challenge: *const u8,
challenge_len: usize,
out: *mut HideBuffer,
) -> i32 {
guard(|| {
if identity.is_null() || out.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Ok(challenge) = hide_sign::Challenge::decode(bytes) else {
return HIDE_ERR_MALFORMED;
};
let identity = unsafe { &*identity };
unsafe { *out = HideBuffer::from_vec(challenge.answer(&identity.0).to_vec()) };
HIDE_OK
})
}
#[unsafe(no_mangle)]
pub extern "C" fn hide_spent_nonces_new() -> *mut HideSpentNonces {
Box::into_raw(Box::new(HideSpentNonces(hide_sign::SpentNonces::new())))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_spent_nonces_free(spent: *mut HideSpentNonces) {
if spent.is_null() {
return;
}
drop(unsafe { Box::from_raw(spent) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hide_challenge_accept(
spent: *mut HideSpentNonces,
challenge: *const u8,
challenge_len: usize,
signature: *const u8,
signature_len: usize,
public_key: *const u8,
public_key_len: usize,
now: u64,
) -> i32 {
guard(|| {
if spent.is_null() {
return HIDE_ERR_INVALID_ARGUMENT;
}
let Some(challenge_bytes) = (unsafe { borrow(challenge, challenge_len, MAX_KEY_FILE) })
else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(signature) = (unsafe { borrow(signature, signature_len, MAX_KEY_FILE * 4) })
else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Some(public_key) = (unsafe { borrow(public_key, public_key_len, MAX_KEY_FILE) }) else {
return HIDE_ERR_INVALID_ARGUMENT;
};
let Ok(challenge) = hide_sign::Challenge::decode(challenge_bytes) else {
return HIDE_ERR_MALFORMED;
};
let Ok(prover) = hide_sign::VerifyingIdentity::from_bytes(public_key) else {
return HIDE_ERR_NOT_A_KEY;
};
let spent = unsafe { &mut *spent };
match spent.0.accept(&challenge, signature, &prover, now) {
Ok(()) => HIDE_OK,
Err(hide_sign::ChallengeError::Expired) => HIDE_ERR_CHALLENGE_EXPIRED,
Err(hide_sign::ChallengeError::Replayed) => HIDE_ERR_CHALLENGE_REPLAYED,
Err(hide_sign::ChallengeError::NotSigned) => HIDE_ERR_AUTHENTICATION,
}
})
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use super::*;
unsafe fn keypair() -> (*mut HideSecretKey, HideBuffer) {
let mut secret = ptr::null_mut();
let mut public = hide_buffer_empty();
assert_eq!(
unsafe { hide_keypair_generate(&mut secret, &mut public) },
HIDE_OK
);
assert_eq!(public.len, HIDE_PUBLIC_KEY_LEN);
(secret, public)
}
#[test]
fn round_trips_through_the_c_api() {
unsafe {
let (secret, public) = keypair();
let message = b"nume,suma\nAna,9000\n";
let mut container = hide_buffer_empty();
assert_eq!(
hide_encrypt(
message.as_ptr(),
message.len(),
public.data,
1,
c"salarii.csv".as_ptr(),
ptr::null(),
&mut container,
),
HIDE_OK
);
assert!(container.len > message.len());
let mut plaintext = hide_buffer_empty();
let mut filename = hide_buffer_empty();
assert_eq!(
hide_decrypt(
container.data,
container.len,
secret,
&mut plaintext,
&mut filename,
ptr::null_mut(),
),
HIDE_OK
);
assert_eq!(
slice::from_raw_parts(plaintext.data, plaintext.len),
message
);
assert_eq!(
slice::from_raw_parts(filename.data, filename.len),
b"salarii.csv"
);
hide_buffer_free(&mut filename);
hide_buffer_free(&mut plaintext);
hide_buffer_free(&mut container);
hide_buffer_free(&mut { public });
hide_secret_key_free(secret);
}
}
#[test]
fn a_freed_buffer_is_cleared_and_safe_to_free_twice() {
unsafe {
let (secret, mut public) = keypair();
let mut container = hide_buffer_empty();
hide_encrypt(
b"x".as_ptr(),
1,
public.data,
1,
ptr::null(),
ptr::null(),
&mut container,
);
hide_buffer_free(&mut container);
assert!(container.data.is_null(), "freed buffer kept its pointer");
hide_buffer_free(&mut container);
hide_buffer_free(&mut public);
hide_secret_key_free(secret);
}
}
#[test]
fn tampering_is_refused_and_yields_no_plaintext() {
unsafe {
let (secret, mut public) = keypair();
let mut container = hide_buffer_empty();
hide_encrypt(
b"confidential".as_ptr(),
12,
public.data,
1,
ptr::null(),
ptr::null(),
&mut container,
);
let mut damaged = slice::from_raw_parts(container.data, container.len).to_vec();
let last = damaged.len() - 1;
damaged[last] ^= 1;
let mut plaintext = hide_buffer_empty();
let code = hide_decrypt(
damaged.as_ptr(),
damaged.len(),
secret,
&mut plaintext,
ptr::null_mut(),
ptr::null_mut(),
);
assert_ne!(code, HIDE_OK);
assert!(plaintext.data.is_null(), "published unverified plaintext");
hide_buffer_free(&mut container);
hide_buffer_free(&mut public);
hide_secret_key_free(secret);
}
}
#[test]
fn the_wrong_key_cannot_decrypt() {
unsafe {
let (secret, mut public) = keypair();
let (other, mut other_public) = keypair();
let mut container = hide_buffer_empty();
hide_encrypt(
b"secret".as_ptr(),
6,
public.data,
1,
ptr::null(),
ptr::null(),
&mut container,
);
let mut plaintext = hide_buffer_empty();
assert_eq!(
hide_decrypt(
container.data,
container.len,
other,
&mut plaintext,
ptr::null_mut(),
ptr::null_mut(),
),
HIDE_ERR_NO_MATCHING_RECIPIENT
);
hide_buffer_free(&mut container);
hide_buffer_free(&mut public);
hide_buffer_free(&mut other_public);
hide_secret_key_free(secret);
hide_secret_key_free(other);
}
}
#[test]
fn null_and_absurd_arguments_are_rejected_rather_than_crashing() {
unsafe {
let mut out = hide_buffer_empty();
let mut secret = ptr::null_mut();
assert_eq!(
hide_keypair_generate(ptr::null_mut(), &mut out),
HIDE_ERR_INVALID_ARGUMENT
);
assert_eq!(
hide_keypair_generate(&mut secret, ptr::null_mut()),
HIDE_ERR_INVALID_ARGUMENT
);
assert_eq!(
hide_encrypt(
ptr::null(),
10,
ptr::null(),
1,
ptr::null(),
ptr::null(),
&mut out
),
HIDE_ERR_INVALID_ARGUMENT
);
assert_eq!(
hide_encrypt(
b"x".as_ptr(),
usize::MAX,
ptr::null(),
1,
ptr::null(),
ptr::null(),
&mut out
),
HIDE_ERR_INVALID_ARGUMENT
);
assert_eq!(
hide_decrypt(
ptr::null(),
0,
ptr::null(),
&mut out,
ptr::null_mut(),
ptr::null_mut()
),
HIDE_ERR_INVALID_ARGUMENT
);
let mut kind = -1;
assert_eq!(
hide_inspect_key(ptr::null(), 32, &mut kind),
HIDE_ERR_INVALID_ARGUMENT
);
hide_buffer_free(ptr::null_mut());
hide_secret_key_free(ptr::null_mut());
}
}
#[test]
fn protected_keys_round_trip_and_reject_a_wrong_passphrase() {
unsafe {
let (secret, mut public) = keypair();
let mut sealed = hide_buffer_empty();
assert_eq!(
hide_secret_key_protect(secret, c"correct horse".as_ptr(), &mut sealed),
HIDE_OK
);
let mut kind = -1;
assert_eq!(
hide_inspect_key(sealed.data, sealed.len, &mut kind),
HIDE_OK
);
assert_eq!(kind, HIDE_KEY_PROTECTED);
let mut reopened = ptr::null_mut();
assert_eq!(
hide_secret_key_open(sealed.data, sealed.len, c"wrong".as_ptr(), &mut reopened),
HIDE_ERR_WRONG_PASSPHRASE
);
assert_eq!(
hide_secret_key_open(sealed.data, sealed.len, ptr::null(), &mut reopened),
HIDE_ERR_WRONG_PASSPHRASE
);
assert_eq!(
hide_secret_key_open(
sealed.data,
sealed.len,
c"correct horse".as_ptr(),
&mut reopened
),
HIDE_OK
);
let mut derived = hide_buffer_empty();
assert_eq!(hide_secret_key_public(reopened, &mut derived), HIDE_OK);
assert_eq!(
slice::from_raw_parts(derived.data, derived.len),
slice::from_raw_parts(public.data, public.len)
);
hide_buffer_free(&mut derived);
hide_buffer_free(&mut sealed);
hide_buffer_free(&mut public);
hide_secret_key_free(reopened);
hide_secret_key_free(secret);
}
}
#[test]
fn armor_round_trips_and_rejects_rubbish() {
unsafe {
let (secret, mut public) = keypair();
let mut armored = hide_buffer_empty();
assert_eq!(
hide_public_key_armor(public.data, public.len, &mut armored),
HIDE_OK
);
let text =
CString::new(slice::from_raw_parts(armored.data, armored.len)).expect("no NUL");
let mut decoded = hide_buffer_empty();
assert_eq!(
hide_public_key_dearmor(text.as_ptr(), &mut decoded),
HIDE_OK
);
assert_eq!(
slice::from_raw_parts(decoded.data, decoded.len),
slice::from_raw_parts(public.data, public.len)
);
assert_ne!(
hide_public_key_dearmor(c"not a key".as_ptr(), &mut decoded),
HIDE_OK
);
hide_buffer_free(&mut decoded);
hide_buffer_free(&mut armored);
hide_buffer_free(&mut public);
hide_secret_key_free(secret);
}
}
fn identity_file(passphrase: &str) -> Vec<u8> {
let identity = hide_keyring::Identity::generate().expect("randomness");
hide_keyring::protect_identity(identity.expose_seed_for_sealing(), passphrase)
.expect("sealing works")
}
unsafe fn open_identity(file: &[u8], passphrase: &CString) -> *mut HideSigningIdentity {
let mut identity = ptr::null_mut();
assert_eq!(
unsafe {
hide_signing_identity_open(
file.as_ptr(),
file.len(),
passphrase.as_ptr(),
&mut identity,
)
},
HIDE_OK
);
identity
}
#[test]
fn signs_and_verifies_across_the_boundary() {
unsafe {
let passphrase = CString::new("correct horse battery staple").unwrap();
let file = identity_file(passphrase.to_str().unwrap());
let identity = open_identity(&file, &passphrase);
let mut public = hide_buffer_empty();
assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
assert_eq!(public.len, HIDE_VERIFYING_KEY_LEN);
let context = b"HIDE/0.5 ffi test";
let message = b"the message that crossed the boundary";
let mut signature = hide_buffer_empty();
assert_eq!(
hide_sign_message(
identity,
context.as_ptr(),
context.len(),
message.as_ptr(),
message.len(),
&mut signature,
),
HIDE_OK
);
assert_eq!(signature.len, HIDE_SIGNATURE_LEN);
assert_eq!(
hide_verify_message(
public.data,
public.len,
context.as_ptr(),
context.len(),
message.as_ptr(),
message.len(),
signature.data,
signature.len,
),
HIDE_OK
);
let other = b"HIDE/0.5 something else";
assert_eq!(
hide_verify_message(
public.data,
public.len,
other.as_ptr(),
other.len(),
message.as_ptr(),
message.len(),
signature.data,
signature.len,
),
HIDE_ERR_AUTHENTICATION
);
let altered = b"the message that crossed the boundaryX";
assert_eq!(
hide_verify_message(
public.data,
public.len,
context.as_ptr(),
context.len(),
altered.as_ptr(),
altered.len(),
signature.data,
signature.len,
),
HIDE_ERR_AUTHENTICATION
);
hide_buffer_free(&mut signature);
hide_buffer_free(&mut public);
hide_signing_identity_free(identity);
}
}
#[test]
fn an_encryption_only_key_cannot_sign() {
unsafe {
let passphrase = CString::new("correct horse battery staple").unwrap();
let secret = hide_crypto::RecipientSecret::generate().expect("randomness");
let file =
hide_keyring::protect(&secret, passphrase.to_str().unwrap()).expect("sealing");
let mut identity = ptr::null_mut();
assert_eq!(
hide_signing_identity_open(
file.as_ptr(),
file.len(),
passphrase.as_ptr(),
&mut identity,
),
HIDE_ERR_NOT_A_KEY
);
assert!(identity.is_null());
}
}
#[test]
fn a_wrong_passphrase_is_named_as_such() {
unsafe {
let file = identity_file("correct horse battery staple");
let wrong = CString::new("not the passphrase at all").unwrap();
let mut identity = ptr::null_mut();
assert_eq!(
hide_signing_identity_open(
file.as_ptr(),
file.len(),
wrong.as_ptr(),
&mut identity
),
HIDE_ERR_WRONG_PASSPHRASE
);
}
}
#[test]
fn a_challenge_is_answered_once_and_then_refused() {
unsafe {
let passphrase = CString::new("correct horse battery staple").unwrap();
let file = identity_file(passphrase.to_str().unwrap());
let identity = open_identity(&file, &passphrase);
let mut public = hide_buffer_empty();
assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
let audience = CString::new("ssh://host.example").unwrap();
let mut challenge = hide_buffer_empty();
assert_eq!(
hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
HIDE_OK
);
let mut answer = hide_buffer_empty();
assert_eq!(
hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
HIDE_OK
);
let spent = hide_spent_nonces_new();
assert_eq!(
hide_challenge_accept(
spent,
challenge.data,
challenge.len,
answer.data,
answer.len,
public.data,
public.len,
1_000,
),
HIDE_OK
);
assert_eq!(
hide_challenge_accept(
spent,
challenge.data,
challenge.len,
answer.data,
answer.len,
public.data,
public.len,
1_000,
),
HIDE_ERR_CHALLENGE_REPLAYED
);
hide_spent_nonces_free(spent);
hide_buffer_free(&mut answer);
hide_buffer_free(&mut challenge);
hide_buffer_free(&mut public);
hide_signing_identity_free(identity);
}
}
#[test]
fn an_answer_after_the_window_is_refused_across_the_boundary() {
unsafe {
let passphrase = CString::new("correct horse battery staple").unwrap();
let file = identity_file(passphrase.to_str().unwrap());
let identity = open_identity(&file, &passphrase);
let mut public = hide_buffer_empty();
assert_eq!(hide_signing_identity_public(identity, &mut public), HIDE_OK);
let audience = CString::new("ssh://host.example").unwrap();
let mut challenge = hide_buffer_empty();
assert_eq!(
hide_challenge_new(audience.as_ptr(), 1_000, 60, &mut challenge),
HIDE_OK
);
let mut answer = hide_buffer_empty();
assert_eq!(
hide_challenge_answer(identity, challenge.data, challenge.len, &mut answer),
HIDE_OK
);
let spent = hide_spent_nonces_new();
assert_eq!(
hide_challenge_accept(
spent,
challenge.data,
challenge.len,
answer.data,
answer.len,
public.data,
public.len,
1_100,
),
HIDE_ERR_CHALLENGE_EXPIRED
);
hide_spent_nonces_free(spent);
hide_buffer_free(&mut answer);
hide_buffer_free(&mut challenge);
hide_buffer_free(&mut public);
hide_signing_identity_free(identity);
}
}
#[test]
fn the_signing_boundary_refuses_nulls_without_crashing() {
unsafe {
let mut out = hide_buffer_empty();
assert_eq!(
hide_sign_message(ptr::null(), ptr::null(), 0, ptr::null(), 0, &mut out),
HIDE_ERR_INVALID_ARGUMENT
);
assert_eq!(
hide_verify_message(
ptr::null(),
0,
ptr::null(),
0,
ptr::null(),
0,
ptr::null(),
0
),
HIDE_ERR_NOT_A_KEY
);
assert_eq!(
hide_challenge_answer(ptr::null(), ptr::null(), 0, &mut out),
HIDE_ERR_INVALID_ARGUMENT
);
assert_eq!(
hide_challenge_accept(
ptr::null_mut(),
ptr::null(),
0,
ptr::null(),
0,
ptr::null(),
0,
0
),
HIDE_ERR_INVALID_ARGUMENT
);
hide_signing_identity_free(ptr::null_mut());
hide_spent_nonces_free(ptr::null_mut());
}
}
}