#[cfg(test)]
use crate::zeroization::Zeroize;
use std::io;
#[cfg_attr(not(any(feature = "log", test)), allow(dead_code))]
pub(crate) const NONCE_LEN: usize = crypto_bastion::NONCE_SIZE;
#[cfg_attr(not(any(feature = "log", test)), allow(dead_code))]
pub(crate) const TAG_LEN: usize = crypto_bastion::TAG_SIZE;
#[cfg(any(feature = "log", test))]
pub(crate) const SHA512_LEN: usize = 64;
#[cfg(test)]
const SHA512_BLOCK_LEN: usize = 128;
#[cfg_attr(not(feature = "log"), allow(dead_code))]
pub(crate) fn fill_random(out: &mut [u8]) -> io::Result<()> {
fill_random_inner(out)
}
#[inline]
#[cfg_attr(not(feature = "log"), allow(dead_code))]
pub(crate) fn fill_random_array<const N: usize>(out: &mut [u8; N]) -> io::Result<()> {
fill_random(out.as_mut_slice())
}
#[inline]
#[cfg(any(feature = "log", test))]
pub(crate) fn sha512(data: &[u8]) -> [u8; SHA512_LEN] {
crypto_bastion::hash(data)
}
#[cfg_attr(not(test), allow(dead_code))]
#[inline]
pub(crate) fn ct_eq(a: &[u8], b: &[u8]) -> bool {
crypto_bastion::compare(a, b)
}
#[cfg(test)]
pub(crate) fn hmac_sha512_parts(key: &[u8], parts: &[&[u8]]) -> [u8; SHA512_LEN] {
let mut key_block = [0u8; SHA512_BLOCK_LEN];
if key.len() > SHA512_BLOCK_LEN {
let mut digest = sha512(key);
key_block[..digest.len()].copy_from_slice(&digest);
digest.zeroize();
} else {
key_block[..key.len()].copy_from_slice(key);
}
let mut inner_pad = [0x36_u8; SHA512_BLOCK_LEN];
let mut outer_pad = [0x5C_u8; SHA512_BLOCK_LEN];
for (pad, key_byte) in inner_pad.iter_mut().zip(key_block.iter()) {
*pad ^= *key_byte;
}
for (pad, key_byte) in outer_pad.iter_mut().zip(key_block.iter()) {
*pad ^= *key_byte;
}
let mut inner_input = Vec::with_capacity(total_len(SHA512_BLOCK_LEN, parts));
inner_input.extend_from_slice(&inner_pad);
for part in parts {
inner_input.extend_from_slice(part);
}
let mut inner_hash = sha512(&inner_input);
let mut outer_input = Vec::with_capacity(SHA512_BLOCK_LEN + inner_hash.len());
outer_input.extend_from_slice(&outer_pad);
outer_input.extend_from_slice(&inner_hash);
let mac = sha512(&outer_input);
inner_input.zeroize();
outer_input.zeroize();
inner_hash.zeroize();
inner_pad.zeroize();
outer_pad.zeroize();
key_block.zeroize();
mac
}
#[cfg_attr(not(any(feature = "log", test)), allow(dead_code))]
pub(crate) fn aes256_gcm_encrypt_into(
key: &[u8; 32],
nonce: &[u8; NONCE_LEN],
aad: &[u8],
plaintext: &[u8],
ciphertext_out: &mut [u8],
tag_out: &mut [u8; TAG_LEN],
) -> io::Result<usize> {
crypto_bastion::encrypt(key, nonce, aad, plaintext, ciphertext_out, tag_out)
.map_err(io::Error::other)?;
Ok(plaintext.len())
}
#[cfg(test)]
pub(crate) fn aes256_gcm_decrypt_into(
key: &[u8; 32],
nonce: &[u8; NONCE_LEN],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8; TAG_LEN],
plaintext_out: &mut [u8],
) -> io::Result<usize> {
crypto_bastion::decrypt(key, nonce, aad, ciphertext, tag, plaintext_out)
.map_err(io::Error::other)?;
Ok(ciphertext.len())
}
#[cfg(test)]
fn total_len(base: usize, parts: &[&[u8]]) -> usize {
parts
.iter()
.fold(base, |len, part| len.saturating_add(part.len()))
}
#[cfg(unix)]
#[cfg_attr(not(feature = "log"), allow(dead_code))]
fn fill_random_inner(out: &mut [u8]) -> io::Result<()> {
use std::fs::File;
use std::io::Read;
File::open("/dev/urandom")?.read_exact(out)
}
#[cfg(windows)]
fn fill_random_inner(out: &mut [u8]) -> io::Result<()> {
use core::ffi::c_void;
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x0000_0002;
const STATUS_SUCCESS: i32 = 0;
#[link(name = "bcrypt")]
unsafe extern "system" {
fn BCryptGenRandom(
algorithm: *mut c_void,
buffer: *mut u8,
buffer_len: u32,
flags: u32,
) -> i32;
}
let len = u32::try_from(out.len()).map_err(|_| io::Error::other("random buffer too large"))?;
let status = unsafe {
BCryptGenRandom(
core::ptr::null_mut(),
out.as_mut_ptr(),
len,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
if status == STATUS_SUCCESS {
Ok(())
} else {
Err(io::Error::other("BCryptGenRandom failed"))
}
}
#[cfg(not(any(unix, windows)))]
fn fill_random_inner(_out: &mut [u8]) -> io::Result<()> {
Err(io::Error::other(
"OS random source is unsupported on this platform",
))
}
#[cfg(test)]
mod tests {
use super::{
NONCE_LEN, SHA512_LEN, TAG_LEN, aes256_gcm_decrypt_into, aes256_gcm_encrypt_into, ct_eq,
hmac_sha512_parts, sha512,
};
#[test]
fn sha512_matches_expected_length() {
assert_eq!(sha512(b"hello").len(), SHA512_LEN);
}
#[test]
fn hmac_depends_on_key() {
let mac_a = hmac_sha512_parts(b"key-a", &[b"payload"]);
let mac_b = hmac_sha512_parts(b"key-b", &[b"payload"]);
assert!(!ct_eq(&mac_a, &mac_b));
}
#[test]
fn aes_gcm_roundtrip() {
let key = [0xAB_u8; 32];
let nonce = [0x11_u8; NONCE_LEN];
let plaintext = b"palisade";
let mut ciphertext = [0u8; 8];
let mut tag = [0u8; TAG_LEN];
let len = aes256_gcm_encrypt_into(&key, &nonce, b"", plaintext, &mut ciphertext, &mut tag)
.unwrap();
assert_eq!(tag.len(), TAG_LEN);
let mut decrypted = [0u8; 8];
let decrypted_len =
aes256_gcm_decrypt_into(&key, &nonce, b"", &ciphertext[..len], &tag, &mut decrypted)
.unwrap();
assert_eq!(&decrypted[..decrypted_len], plaintext);
}
}