use std::{
ffi::CString,
hash::BuildHasher,
mem::MaybeUninit,
os::fd::{AsFd, BorrowedFd, RawFd},
sync::Arc,
};
use aes::Aes256;
#[expect(clippy::disallowed_types)]
use ahash::{AHasher, RandomState};
use base16ct::{lower, mixed, upper};
use bitflags::bitflags;
use blake2::{Blake2b, Blake2s};
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC, CRC_64_ECMA_182};
use ctr::{
cipher::{KeyIvInit, StreamCipher},
Ctr128BE,
};
use digest::{
consts::{U16, U20, U32, U48, U64},
Digest,
};
use gost94::Gost94CryptoPro;
use groestl::{Groestl256, Groestl512};
use hmac::{Hmac, KeyInit, Mac};
use lexis::ToName;
use libc::mode_t;
use md4::Md4;
use md5::Md5;
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::{OFlag, SealFlag},
sys::stat::Mode,
unistd::{lseek64, Whence},
};
use ripemd::{Ripemd128, Ripemd160, Ripemd256, Ripemd320};
use sha1::Sha1;
use sha2::{Sha224, Sha256, Sha384, Sha512};
use sha3::{Keccak256, Keccak512, Sha3_224, Sha3_256, Sha3_384, Sha3_512};
use sm3::Sm3;
use streebog::{Streebog256, Streebog512};
use subtle::ConstantTimeEq;
use tiger::{Tiger, Tiger2};
use whirlpool::Whirlpool;
use zeroize::Zeroizing;
use crate::{
compat::{fstatx, slice_assume_init, STATX_SIZE, STATX_TYPE},
config::*,
cookie::{safe_memfd_create, safe_read, safe_read2, safe_write},
fd::{seal_memfd, set_append, set_nonblock, SafeOwnedFd},
id::SydId,
io::read_chunk,
lookup::FileType,
retry::retry_on_eintr,
rng::{fillrandom, mkstempat},
sealbox::SecretBox,
try_to_vec,
};
pub const KEY_SIZE: usize = 32;
pub const IV_SIZE: usize = 16;
pub const BLOCK_SIZE: usize = 16;
pub const SHA256_DIGEST_SIZE: usize = 32;
pub const SHA256_BLOCK_SIZE: usize = 64;
pub const HMAC_TAG_SIZE: usize = SHA256_DIGEST_SIZE;
pub const SYD3_HDR_SIZE: u64 = (CRYPT_MAGIC.len() + HMAC_TAG_SIZE + IV_SIZE) as u64;
#[expect(clippy::cast_possible_wrap)]
pub const SYD3_HDR_OFFSET: libc::off64_t = SYD3_HDR_SIZE as libc::off64_t;
#[expect(clippy::cast_possible_wrap)]
pub const CRYPT_MAGIC_OFFSET: libc::off64_t = CRYPT_MAGIC.len() as libc::off64_t;
pub const SENDFILE_MAX: usize = 0x7ffff000;
pub struct Key(Zeroizing<[u8; KEY_SIZE]>);
impl Key {
pub fn new(key: [u8; KEY_SIZE]) -> Self {
Self(Zeroizing::new(key))
}
pub fn random() -> Result<Self, Errno> {
let mut bytes = Zeroizing::new([0u8; KEY_SIZE]);
fillrandom(bytes.as_mut())?;
Ok(Self(bytes))
}
pub fn from_hex(hex: &[u8]) -> Result<Self, Errno> {
let key = hex_decode(hex).map(Zeroizing::new)?;
let key = key.as_slice().try_into().or(Err(Errno::EINVAL))?;
Ok(Self::new(key))
}
pub fn as_hex(&self) -> Result<String, Errno> {
hex_encode_lower(self.as_ref())
}
pub fn is_zero(&self) -> bool {
self.as_ref().ct_eq(&[0u8; KEY_SIZE]).into()
}
}
impl AsRef<[u8]> for Key {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsMut<[u8]> for Key {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}
pub struct CryptKey {
enc: Key,
mac: Key,
}
pub type AesKey = Arc<SecretBox<CryptKey>>;
impl CryptKey {
pub fn enc(&self) -> &Key {
&self.enc
}
pub fn mac(&self) -> &Key {
&self.mac
}
pub fn load(enc_id: KeySerial, mac_id: KeySerial) -> Result<SecretBox<Self>, Errno> {
if enc_id == 0 || mac_id == 0 {
return Err(Errno::ENOKEY);
}
let seed = Self {
enc: Key::new([0u8; KEY_SIZE]),
mac: Key::new([0u8; KEY_SIZE]),
};
SecretBox::new_with(seed, |ck| {
let CryptKey { enc, mac } = ck;
if keyctl_read(enc_id, enc.as_mut())? != KEY_SIZE {
return Err(Errno::EINVAL);
}
if enc_id == mac_id {
mac.as_mut().copy_from_slice(enc.as_ref());
return Ok(());
}
if keyctl_read(mac_id, mac.as_mut())? != KEY_SIZE {
return Err(Errno::EINVAL);
}
Ok(())
})
}
}
pub struct IV([u8; IV_SIZE]);
impl IV {
pub fn new(iv: [u8; IV_SIZE]) -> Self {
Self(iv)
}
pub fn random() -> Result<Self, Errno> {
let mut bytes = [0u8; IV_SIZE];
fillrandom(&mut bytes)?;
Ok(Self(bytes))
}
pub fn from_hex(hex: &[u8]) -> Result<Self, Errno> {
let iv = hex_decode(hex)?;
let iv = iv.as_slice().try_into().or(Err(Errno::EINVAL))?;
Ok(Self::new(iv))
}
pub fn as_hex(&self) -> Result<String, Errno> {
hex_encode_lower(self.as_ref())
}
pub fn is_zero(&self) -> bool {
self.as_ref().ct_eq(&[0u8; IV_SIZE]).into()
}
#[expect(clippy::arithmetic_side_effects)]
pub fn add_counter(&mut self, ctr: u64) {
if ctr == 0 {
return;
}
let mut ctr = ctr / BLOCK_SIZE as u64;
let val = self.as_mut();
for i in (0..IV_SIZE).rev() {
let (new_byte, overflow) = val[i].overflowing_add((ctr & 0xFF) as u8);
val[i] = new_byte;
ctr = (ctr >> 8) + if overflow { 1 } else { 0 };
if ctr == 0 {
break;
}
}
}
}
impl Clone for IV {
fn clone(&self) -> Self {
IV(self.0)
}
}
impl AsRef<[u8]> for IV {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsMut<[u8]> for IV {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}
pub enum Secret {
Loaded(AesKey),
Serial(KeySerial, KeySerial),
}
impl Secret {
pub fn new(enc_key_id: KeySerial, mac_key_id: KeySerial) -> Self {
Self::Serial(enc_key_id, mac_key_id)
}
pub fn init(&mut self) -> Result<(), Errno> {
let (enc_key_id, mac_key_id) = match self {
Self::Serial(enc_key_id, mac_key_id) => (*enc_key_id, *mac_key_id),
Self::Loaded(_) => return Ok(()),
};
*self = Self::Loaded(Arc::new(CryptKey::load(enc_key_id, mac_key_id)?));
Ok(())
}
}
pub type KeySerial = i32;
pub const KEY_SPEC_THREAD_KEYRING: KeySerial = -1;
pub const KEY_SPEC_PROCESS_KEYRING: KeySerial = -2;
pub const KEY_SPEC_SESSION_KEYRING: KeySerial = -3;
pub const KEY_SPEC_USER_KEYRING: KeySerial = -4;
pub const KEY_SPEC_USER_SESSION_KEYRING: KeySerial = -5;
pub const KEY_SPEC_GROUP_KEYRING: KeySerial = -6;
pub const KEY_SPEC_REQKEY_AUTH_KEY: KeySerial = -7;
pub const KEY_SPEC_REQUESTOR_KEYRING: KeySerial = -8;
const KEYCTL_READ: libc::c_int = 11;
const KEYCTL_SETPERM: libc::c_int = 5;
bitflags! {
pub struct KeyPerms: u32 {
const POS_VIEW = 0x0100_0000;
const POS_READ = 0x0200_0000;
const POS_WRITE = 0x0400_0000;
const POS_SEARCH = 0x0800_0000;
const POS_LINK = 0x1000_0000;
const POS_SETATTR = 0x2000_0000;
const POS_ALL = 0x3f00_0000;
const USR_VIEW = 0x0001_0000;
const USR_READ = 0x0002_0000;
const USR_WRITE = 0x0004_0000;
const USR_SEARCH = 0x0008_0000;
const USR_LINK = 0x0010_0000;
const USR_SETATTR = 0x0020_0000;
const USR_ALL = 0x003f_0000;
const GRP_VIEW = 0x0000_0100;
const GRP_READ = 0x0000_0200;
const GRP_WRITE = 0x0000_0400;
const GRP_SEARCH = 0x0000_0800;
const GRP_LINK = 0x0000_1000;
const GRP_SETATTR = 0x0000_2000;
const GRP_ALL = 0x0000_3f00;
const OTH_VIEW = 0x0000_0001;
const OTH_READ = 0x0000_0002;
const OTH_WRITE = 0x0000_0004;
const OTH_SEARCH = 0x0000_0008;
const OTH_LINK = 0x0000_0010;
const OTH_SETATTR = 0x0000_0020;
const OTH_ALL = 0x0000_003f;
}
}
pub fn add_key(
key_type: &str,
key_desc: &str,
payload: &[u8],
keyring: KeySerial,
) -> Result<KeySerial, Errno> {
if key_type.is_empty() || key_desc.is_empty() || payload.is_empty() {
return Err(Errno::EINVAL);
}
let c_type = CString::new(key_type).map_err(|_| Errno::EINVAL)?;
let c_desc = CString::new(key_desc).map_err(|_| Errno::EINVAL)?;
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_add_key,
c_type.as_ptr() as *const libc::c_char,
c_desc.as_ptr() as *const libc::c_char,
payload.as_ptr() as *const libc::c_void,
payload.len() as libc::size_t,
keyring,
)
})
.map(|key_id| key_id as KeySerial)
}
pub fn keyctl_read(key: KeySerial, buf: &mut [u8]) -> Result<usize, Errno> {
let nread = Errno::result(unsafe {
libc::syscall(
libc::SYS_keyctl,
libc::c_long::from(KEYCTL_READ),
libc::c_long::from(key),
buf.as_mut_ptr().cast::<libc::c_void>(),
buf.len(),
)
})?;
let nread = usize::try_from(nread).or(Err(Errno::EINVAL))?;
if nread > buf.len() {
return Err(Errno::EOVERFLOW);
}
Ok(nread)
}
pub fn key_setperm(key: KeySerial, perms: KeyPerms) -> Result<(), Errno> {
#[expect(clippy::cast_lossless)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_keyctl,
libc::c_long::from(KEYCTL_SETPERM),
libc::c_long::from(key),
perms.bits() as libc::c_long,
)
})
.map(drop)
}
pub fn key_ring_new(name: &str, attach_to: KeySerial) -> Result<KeySerial, Errno> {
if name.is_empty() {
return Err(Errno::EINVAL);
}
let c_name = CString::new(name).map_err(|_| Errno::EINVAL)?;
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_add_key,
c"keyring".as_ptr() as *const libc::c_char,
c_name.as_ptr() as *const libc::c_char,
std::ptr::null::<libc::c_void>(),
0usize,
attach_to,
)
})
.map(|key_id| key_id as KeySerial)
}
pub fn key_ring_validate() -> Result<(), Errno> {
const KEYCTL_LINK: libc::c_int = 8;
Errno::result(unsafe {
libc::syscall(
libc::SYS_keyctl,
libc::c_long::from(KEYCTL_LINK),
libc::c_long::from(KEY_SPEC_USER_KEYRING),
libc::c_long::from(KEY_SPEC_SESSION_KEYRING),
)
})
.map(drop)
}
pub fn aes_ctr(key: &Key, iv: &IV) -> Result<Ctr128BE<Aes256>, Errno> {
Ctr128BE::<Aes256>::new_from_slices(key.as_ref(), iv.as_ref()).or(Err(Errno::EINVAL))
}
pub fn hmac_sha256(key: &Key) -> Result<Hmac<Sha256>, Errno> {
Hmac::<Sha256>::new_from_slice(key.as_ref()).or(Err(Errno::EINVAL))
}
pub unsafe fn hash_update_struct<H, T>(hasher: &mut H, value: &T)
where
H: digest::Update,
{
let bytes = unsafe {
std::slice::from_raw_parts((value as *const T).cast::<u8>(), std::mem::size_of::<T>())
};
hasher.update(bytes);
}
static CRC32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
static CRC32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
static CRC64: Crc<u64> = Crc::<u64>::new(&CRC_64_ECMA_182);
enum Hasher {
Md4(Md4),
Md5(Md5),
Sha1(Sha1),
Sha224(Sha224),
Sha256(Sha256),
Sha384(Sha384),
Sha512(Sha512),
Sha3_224(Sha3_224),
Sha3_256(Sha3_256),
Sha3_384(Sha3_384),
Sha3_512(Sha3_512),
Keccak256(Keccak256),
Keccak512(Keccak512),
Ripemd128(Ripemd128),
Ripemd160(Ripemd160),
Ripemd256(Ripemd256),
Ripemd320(Ripemd320),
Blake2b160(Blake2b<U20>),
Blake2b256(Blake2b<U32>),
Blake2b384(Blake2b<U48>),
Blake2b512(Blake2b<U64>),
Blake2s128(Blake2s<U16>),
Blake2s256(Blake2s<U32>),
Blake3(Box<blake3::Hasher>),
Sm3(Sm3),
Wp256(Whirlpool),
Wp384(Whirlpool),
Wp512(Whirlpool),
Streebog256(Streebog256),
Streebog512(Streebog512),
Gost94(Gost94CryptoPro),
Tiger(Tiger),
Tiger2(Tiger2),
Groestl256(Groestl256),
Groestl512(Groestl512),
Crc32(crc::Digest<'static, u32>),
Crc32c(crc::Digest<'static, u32>),
Crc64(crc::Digest<'static, u64>),
}
const HASH_REGISTRY: &[(&str, usize)] = &[
("blake2b-160", 20),
("blake2b-256", 32),
("blake2b-384", 48),
("blake2b-512", 64),
("blake2s-128", 16),
("blake2s-256", 32),
("blake3", 32),
("crc32", 4),
("crc32c", 4),
("crc64", 8),
("gost94", 32),
("groestl256", 32),
("groestl512", 64),
("keccak256", 32),
("keccak512", 64),
("md4", 16),
("md5", 16),
("rmd128", 16),
("rmd160", 20),
("rmd256", 32),
("rmd320", 40),
("sha1", 20),
("sha224", 28),
("sha256", 32),
("sha3-224", 28),
("sha3-256", 32),
("sha3-384", 48),
("sha3-512", 64),
("sha384", 48),
("sha512", 64),
("sm3", 32),
("streebog256", 32),
("streebog512", 64),
("tiger", 24),
("tiger2", 24),
("wp256", 32),
("wp384", 48),
("wp512", 64),
];
impl Hasher {
fn from_name(func: &str) -> Result<Self, Errno> {
Ok(match func {
"md4" => Self::Md4(Md4::new()),
"md5" => Self::Md5(Md5::new()),
"sha1" => Self::Sha1(Sha1::new()),
"sha224" => Self::Sha224(Sha224::new()),
"sha256" => Self::Sha256(Sha256::new()),
"sha384" => Self::Sha384(Sha384::new()),
"sha512" => Self::Sha512(Sha512::new()),
"sha3-224" => Self::Sha3_224(Sha3_224::new()),
"sha3-256" => Self::Sha3_256(Sha3_256::new()),
"sha3-384" => Self::Sha3_384(Sha3_384::new()),
"sha3-512" => Self::Sha3_512(Sha3_512::new()),
"keccak256" => Self::Keccak256(Keccak256::new()),
"keccak512" => Self::Keccak512(Keccak512::new()),
"rmd128" => Self::Ripemd128(Ripemd128::new()),
"rmd160" => Self::Ripemd160(Ripemd160::new()),
"rmd256" => Self::Ripemd256(Ripemd256::new()),
"rmd320" => Self::Ripemd320(Ripemd320::new()),
"blake2b-160" => Self::Blake2b160(Blake2b::<U20>::new()),
"blake2b-256" => Self::Blake2b256(Blake2b::<U32>::new()),
"blake2b-384" => Self::Blake2b384(Blake2b::<U48>::new()),
"blake2b-512" => Self::Blake2b512(Blake2b::<U64>::new()),
"blake2s-128" => Self::Blake2s128(Blake2s::<U16>::new()),
"blake2s-256" => Self::Blake2s256(Blake2s::<U32>::new()),
"blake3" => Self::Blake3(Box::new(blake3::Hasher::new())),
"sm3" => Self::Sm3(Sm3::new()),
"wp256" => Self::Wp256(Whirlpool::new()),
"wp384" => Self::Wp384(Whirlpool::new()),
"wp512" => Self::Wp512(Whirlpool::new()),
"streebog256" => Self::Streebog256(Streebog256::new()),
"streebog512" => Self::Streebog512(Streebog512::new()),
"gost94" => Self::Gost94(Gost94CryptoPro::new()),
"tiger" => Self::Tiger(Tiger::new()),
"tiger2" => Self::Tiger2(Tiger2::new()),
"groestl256" => Self::Groestl256(Groestl256::new()),
"groestl512" => Self::Groestl512(Groestl512::new()),
"crc32" => Self::Crc32(CRC32.digest()),
"crc32c" => Self::Crc32c(CRC32C.digest()),
"crc64" => Self::Crc64(CRC64.digest()),
_ => return Err(Errno::EOPNOTSUPP),
})
}
fn update(&mut self, data: &[u8]) {
match self {
Self::Md4(h) => h.update(data),
Self::Md5(h) => h.update(data),
Self::Sha1(h) => h.update(data),
Self::Sha224(h) => h.update(data),
Self::Sha256(h) => h.update(data),
Self::Sha384(h) => h.update(data),
Self::Sha512(h) => h.update(data),
Self::Sha3_224(h) => h.update(data),
Self::Sha3_256(h) => h.update(data),
Self::Sha3_384(h) => h.update(data),
Self::Sha3_512(h) => h.update(data),
Self::Keccak256(h) => h.update(data),
Self::Keccak512(h) => h.update(data),
Self::Ripemd128(h) => h.update(data),
Self::Ripemd160(h) => h.update(data),
Self::Ripemd256(h) => h.update(data),
Self::Ripemd320(h) => h.update(data),
Self::Blake2b160(h) => h.update(data),
Self::Blake2b256(h) => h.update(data),
Self::Blake2b384(h) => h.update(data),
Self::Blake2b512(h) => h.update(data),
Self::Blake2s128(h) => h.update(data),
Self::Blake2s256(h) => h.update(data),
Self::Blake3(h) => {
h.update(data);
}
Self::Sm3(h) => h.update(data),
Self::Wp256(h) | Self::Wp384(h) | Self::Wp512(h) => h.update(data),
Self::Streebog256(h) => h.update(data),
Self::Streebog512(h) => h.update(data),
Self::Gost94(h) => h.update(data),
Self::Tiger(h) => h.update(data),
Self::Tiger2(h) => h.update(data),
Self::Groestl256(h) => h.update(data),
Self::Groestl512(h) => h.update(data),
Self::Crc32(d) | Self::Crc32c(d) => d.update(data),
Self::Crc64(d) => d.update(data),
}
}
fn finalize(self) -> Result<Vec<u8>, Errno> {
match self {
Self::Md4(h) => try_to_vec(&h.finalize()),
Self::Md5(h) => try_to_vec(&h.finalize()),
Self::Sha1(h) => try_to_vec(&h.finalize()),
Self::Sha224(h) => try_to_vec(&h.finalize()),
Self::Sha256(h) => try_to_vec(&h.finalize()),
Self::Sha384(h) => try_to_vec(&h.finalize()),
Self::Sha512(h) => try_to_vec(&h.finalize()),
Self::Sha3_224(h) => try_to_vec(&h.finalize()),
Self::Sha3_256(h) => try_to_vec(&h.finalize()),
Self::Sha3_384(h) => try_to_vec(&h.finalize()),
Self::Sha3_512(h) => try_to_vec(&h.finalize()),
Self::Keccak256(h) => try_to_vec(&h.finalize()),
Self::Keccak512(h) => try_to_vec(&h.finalize()),
Self::Ripemd128(h) => try_to_vec(&h.finalize()),
Self::Ripemd160(h) => try_to_vec(&h.finalize()),
Self::Ripemd256(h) => try_to_vec(&h.finalize()),
Self::Ripemd320(h) => try_to_vec(&h.finalize()),
Self::Blake2b160(h) => try_to_vec(&h.finalize()),
Self::Blake2b256(h) => try_to_vec(&h.finalize()),
Self::Blake2b384(h) => try_to_vec(&h.finalize()),
Self::Blake2b512(h) => try_to_vec(&h.finalize()),
Self::Blake2s128(h) => try_to_vec(&h.finalize()),
Self::Blake2s256(h) => try_to_vec(&h.finalize()),
Self::Blake3(h) => try_to_vec(h.finalize().as_bytes()),
Self::Sm3(h) => try_to_vec(&h.finalize()),
Self::Wp256(h) => try_to_vec(&h.finalize()[..32]),
Self::Wp384(h) => try_to_vec(&h.finalize()[..48]),
Self::Wp512(h) => try_to_vec(&h.finalize()),
Self::Streebog256(h) => try_to_vec(&h.finalize()),
Self::Streebog512(h) => try_to_vec(&h.finalize()),
Self::Gost94(h) => try_to_vec(&h.finalize()),
Self::Tiger(h) => try_to_vec(&h.finalize()),
Self::Tiger2(h) => try_to_vec(&h.finalize()),
Self::Groestl256(h) => try_to_vec(&h.finalize()),
Self::Groestl512(h) => try_to_vec(&h.finalize()),
Self::Crc32(d) | Self::Crc32c(d) => try_to_vec(&d.finalize().to_be_bytes()),
Self::Crc64(d) => try_to_vec(&d.finalize().to_be_bytes()),
}
}
}
fn hash_feed<Fd: AsFd>(
hasher: &mut Hasher,
input: Fd,
size_hint: Option<u64>,
) -> Result<(), Errno> {
if size_hint == Some(0) {
return Err(Errno::EINVAL);
}
let mut buf = [MaybeUninit::<u8>::uninit(); IO_BUF_SIZE];
let mut remaining = size_hint;
loop {
let len = match remaining {
Some(0) => break,
Some(rem) => usize::try_from(rem).unwrap_or(IO_BUF_SIZE).min(IO_BUF_SIZE),
None => IO_BUF_SIZE,
};
let n = retry_on_eintr(|| safe_read2(&input, &mut buf[..len]))?;
if n == 0 {
break;
}
hasher.update(unsafe { slice_assume_init(&buf[..n]) });
if let Some(rem) = remaining {
let n = u64::try_from(n).or(Err(Errno::EOVERFLOW))?;
remaining = Some(rem.checked_sub(n).ok_or(Errno::EOVERFLOW)?);
}
}
if matches!(remaining, Some(left) if left > 0) {
return Err(Errno::EBADMSG);
}
Ok(())
}
pub fn hash_pipe<Fd: AsFd>(func: &str, input: Option<Fd>) -> Result<Vec<u8>, Errno> {
let mut hasher = Hasher::from_name(func)?;
if let Some(input) = input {
hash_feed(&mut hasher, input, None)?;
}
hasher.finalize()
}
pub fn hash_file<Fd: AsFd>(
func: &str,
input: Fd,
size_hint: Option<u64>,
) -> Result<Vec<u8>, Errno> {
if size_hint == Some(0) {
return Err(Errno::EINVAL);
}
let mut hasher = Hasher::from_name(func)?;
hash_feed(&mut hasher, input, size_hint)?;
hasher.finalize()
}
pub fn hash<Fd: AsFd>(func: &str, input: Fd) -> Result<Vec<u8>, Errno> {
if let Ok(stx) = fstatx(&input, STATX_TYPE | STATX_SIZE) {
let ftype = FileType::from(mode_t::from(stx.stx_mode));
if !matches!(ftype, FileType::Reg | FileType::Fifo) {
return Err(Errno::EBADFD);
}
if ftype.is_file() && stx.stx_size > 0 {
return hash_file(func, input, Some(stx.stx_size));
}
}
hash_pipe(func, Some(input))
}
pub fn hash_list() -> &'static [(&'static str, usize)] {
HASH_REGISTRY
}
pub fn hash_auto() -> Option<&'static str> {
Some("blake3")
}
pub fn hash_256(data: &[u8]) -> Result<[u8; 32], Errno> {
let func = hash_auto().ok_or(Errno::ENOSYS)?;
let mut hasher = Hasher::from_name(func)?;
hasher.update(data);
let digest = hasher.finalize().map(Zeroizing::new)?;
digest
.get(..32)
.and_then(|chunk| <[u8; 32]>::try_from(chunk).ok())
.ok_or(Errno::EINVAL)
}
pub fn validate_checksum(alg: &str, key: &[u8]) -> Result<(), Errno> {
let empty = hash_pipe::<SafeOwnedFd>(alg, None)?;
if key.len() != empty.len() {
return Err(Errno::EINVAL);
}
if is_equal(key, empty.as_slice()) {
return Err(Errno::EINVAL);
}
Ok(())
}
#[expect(clippy::type_complexity)]
pub fn aes_ctr_tmp<Fd: AsFd>(
keys: &CryptKey,
fd: Fd,
flags: OFlag,
tmp: Option<RawFd>,
) -> Result<Option<(SafeOwnedFd, IV)>, Errno> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let size = lseek64(&fd, 0, Whence::SeekEnd)? as usize;
#[expect(clippy::arithmetic_side_effects)]
let (iv, tag) = if size == 0 {
(IV::random()?, None)
} else if size <= CRYPT_MAGIC.len() + HMAC_TAG_SIZE + IV_SIZE {
return Ok(None);
} else {
lseek64(&fd, 0, Whence::SeekSet)?;
let mut magic = [0u8; CRYPT_MAGIC.len()];
let mut nread = 0;
while nread < magic.len() {
#[expect(clippy::arithmetic_side_effects)]
match safe_read(&fd, &mut magic[nread..]) {
Ok(0) => {
return Ok(None);
}
Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
if !is_equal(&magic, CRYPT_MAGIC) {
return Ok(None);
}
let mut hdr = [MaybeUninit::<u8>::uninit(); HMAC_TAG_SIZE + IV_SIZE];
let hdr = read_chunk(&fd, &mut hdr)?;
let hmac_tag =
<[u8; HMAC_TAG_SIZE]>::try_from(&hdr[..HMAC_TAG_SIZE]).or(Err(Errno::EBADMSG))?;
let iv = IV::new(<[u8; IV_SIZE]>::try_from(&hdr[HMAC_TAG_SIZE..]).or(Err(Errno::EBADMSG))?);
(iv, Some(hmac_tag))
};
let new_iv = if !flags.contains(OFlag::O_APPEND) && size > 0 {
Some(IV::random()?)
} else {
None
};
let dst_fd = if let Some(tmp) = tmp {
let tmp = unsafe { BorrowedFd::borrow_raw(tmp) };
mkstempat(tmp, b"syd-aes-", Mode::from_bits_truncate(0o600))
} else {
safe_memfd_create(SydId::get_memfd_aes(), *SAFE_MFD_FLAGS)
}?;
if let Some(hmac_tag) = tag {
let mut cipher = aes_ctr(keys.enc(), &iv)?;
let mut mac = hmac_sha256(keys.mac())?;
mac.update(CRYPT_MAGIC);
mac.update(iv.as_ref());
#[expect(clippy::arithmetic_side_effects)]
let mut datasz = size - CRYPT_MAGIC.len() - HMAC_TAG_SIZE - IV_SIZE;
let mut buf = Zeroizing::new([MaybeUninit::<u8>::uninit(); IO_BUF_SIZE]);
while datasz > 0 {
let len = datasz.min(IO_BUF_SIZE);
let chunk = read_chunk(&fd, &mut buf[..len])?;
mac.update(&*chunk);
cipher.apply_keystream(chunk);
let mut nwrite = 0;
while nwrite < len {
match safe_write(&dst_fd, &chunk[nwrite..len]) {
Ok(0) => return Err(Errno::EINVAL),
Ok(n) => nwrite = nwrite.checked_add(n).ok_or(Errno::EOVERFLOW)?,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
datasz = datasz.checked_sub(len).ok_or(Errno::EOVERFLOW)?;
}
mac.verify_slice(&hmac_tag[..]).or(Err(Errno::EBADMSG))?;
}
if flags.contains(OFlag::O_APPEND) {
set_append(&dst_fd, true)?;
if tmp.is_none() {
seal_memfd(&dst_fd, SealFlag::F_SEAL_SHRINK)?;
}
} else if size > 0 {
lseek64(&dst_fd, 0, Whence::SeekSet)?;
}
if flags.contains(OFlag::O_NONBLOCK) || flags.contains(OFlag::O_NDELAY) {
set_nonblock(&dst_fd, true)?;
}
Ok(Some((dst_fd, new_iv.unwrap_or(iv))))
}
pub fn get_at_random() -> &'static [u8; 16] {
unsafe {
let ptr = libc::getauxval(libc::AT_RANDOM) as *const u8;
assert!(!ptr.is_null(), "AT_RANDOM not found");
&*(ptr as *const [u8; 16])
}
}
pub fn get_at_random_u64() -> (u64, u64) {
let rnd = get_at_random();
#[expect(clippy::disallowed_methods)]
(
u64::from_ne_bytes(rnd[..8].try_into().unwrap()),
u64::from_ne_bytes(rnd[8..].try_into().unwrap()),
)
}
pub fn get_at_random_hex(upper: bool) -> Result<String, Errno> {
let rnd = get_at_random();
if upper {
hex_encode_upper(rnd)
} else {
hex_encode_lower(rnd)
}
}
pub fn get_at_random_name(idx: usize) -> String {
assert!(idx == 0 || idx == 1, "BUG: invalid AT_RANDOM index!");
let (rnd0, rnd1) = get_at_random_u64();
match idx {
0 => rnd0.to_name(),
1 => rnd1.to_name(),
_ => unreachable!("BUG: invalid AT_RANDOM index"),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SydRandomState {
k0: u64,
k1: u64,
k2: u64,
k3: u64,
}
impl SydRandomState {
#[inline]
#[expect(clippy::disallowed_methods)]
pub fn new() -> Self {
let mut buf = [0u8; 32];
fillrandom(&mut buf).expect("SydRandomState: failed to acquire 32 bytes of entropy");
let k0 = u64::from_ne_bytes(buf[0..8].try_into().unwrap());
let k1 = u64::from_ne_bytes(buf[8..16].try_into().unwrap());
let k2 = u64::from_ne_bytes(buf[16..24].try_into().unwrap());
let k3 = u64::from_ne_bytes(buf[24..32].try_into().unwrap());
SydRandomState { k0, k1, k2, k3 }
}
}
impl Default for SydRandomState {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl BuildHasher for SydRandomState {
type Hasher = AHasher;
#[inline]
#[expect(clippy::disallowed_types)]
fn build_hasher(&self) -> Self::Hasher {
RandomState::with_seeds(self.k0, self.k1, self.k2, self.k3).build_hasher()
}
}
#[expect(clippy::disallowed_types)]
pub type SydHashMap<K, V> = std::collections::HashMap<K, V, SydRandomState>;
#[expect(clippy::disallowed_types)]
pub type SydHashSet<K> = std::collections::HashSet<K, SydRandomState>;
#[expect(clippy::disallowed_types)]
pub type SydIndexMap<K, V> = indexmap::IndexMap<K, V, SydRandomState>;
#[expect(clippy::disallowed_types)]
pub type SydIndexSet<K> = indexmap::IndexSet<K, SydRandomState>;
pub fn hex_encode_lower(data: &[u8]) -> Result<String, Errno> {
let len = data.len().checked_mul(2).ok_or(Errno::EOVERFLOW)?;
let mut buf = Vec::new();
buf.try_reserve_exact(len).map_err(|_| Errno::ENOMEM)?;
buf.resize(len, 0);
lower::encode(data, &mut buf).map_err(|e| match e {
base16ct::Error::InvalidLength => Errno::EINVAL,
base16ct::Error::InvalidEncoding => Errno::EILSEQ,
})?;
Ok(unsafe { String::from_utf8_unchecked(buf) })
}
pub fn hex_encode_upper(data: &[u8]) -> Result<String, Errno> {
let len = data.len().checked_mul(2).ok_or(Errno::EOVERFLOW)?;
let mut buf = Vec::new();
buf.try_reserve_exact(len).map_err(|_| Errno::ENOMEM)?;
buf.resize(len, 0);
upper::encode(data, &mut buf).map_err(|e| match e {
base16ct::Error::InvalidLength => Errno::EINVAL,
base16ct::Error::InvalidEncoding => Errno::EILSEQ,
})?;
Ok(unsafe { String::from_utf8_unchecked(buf) })
}
pub fn hex_decode(hex: &[u8]) -> Result<Vec<u8>, Errno> {
let len = (hex.len() / 2)
.checked_add(hex.len() % 2)
.ok_or(Errno::EOVERFLOW)?;
let mut buf = Vec::new();
buf.try_reserve_exact(len).map_err(|_| Errno::ENOMEM)?;
buf.resize(len, 0);
mixed::decode(hex, &mut buf).map_err(|e| match e {
base16ct::Error::InvalidLength => Errno::EINVAL,
base16ct::Error::InvalidEncoding => Errno::EILSEQ,
})?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use std::io::Write;
use ctr::cipher::{StreamCipher, StreamCipherSeek};
use hmac::Mac;
use nix::unistd::{read, write};
use super::*;
use crate::{compat::MFdFlags, cookie::safe_memfd_create};
#[test]
fn test_key_1() {
let bytes = [0u8; KEY_SIZE];
let key = Key::new(bytes);
assert!(key.is_zero());
}
#[test]
fn test_key_2() {
let mut bytes = [0u8; KEY_SIZE];
bytes[0] = 1;
let key = Key::new(bytes);
assert!(!key.is_zero());
}
#[test]
fn test_key_3() {
let key = Key::random().unwrap();
assert!(!key.is_zero());
}
#[test]
fn test_key_4() {
let hex = b"0000000000000000000000000000000000000000000000000000000000000000";
let key = Key::from_hex(hex).unwrap();
assert!(key.is_zero());
}
#[test]
fn test_key_5() {
let hex = b"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
let key = Key::from_hex(hex).unwrap();
assert_eq!(key.as_ref()[0], 0x01);
assert_eq!(key.as_ref()[31], 0x20);
}
#[test]
fn test_key_6() {
let result = Key::from_hex(b"not_hex");
assert!(result.is_err());
}
#[test]
fn test_key_7() {
let result = Key::from_hex(b"0102");
assert!(result.is_err());
}
#[test]
fn test_key_8() {
let hex_str = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
let key = Key::from_hex(hex_str.as_bytes()).unwrap();
assert_eq!(key.as_hex().unwrap(), hex_str);
}
#[test]
fn test_key_9() {
let bytes = [42u8; KEY_SIZE];
let key = Key::new(bytes);
assert_eq!(key.as_ref(), &bytes);
}
#[test]
fn test_key_10() {
let mut key = Key::new([0u8; KEY_SIZE]);
key.as_mut()[0] = 0xFF;
assert_eq!(key.as_ref()[0], 0xFF);
}
#[test]
fn test_iv_1() {
let bytes = [0u8; IV_SIZE];
let iv = IV::new(bytes);
assert!(iv.is_zero());
}
#[test]
fn test_iv_2() {
let mut bytes = [0u8; IV_SIZE];
bytes[0] = 1;
let iv = IV::new(bytes);
assert!(!iv.is_zero());
}
#[test]
fn test_iv_3() {
let iv = IV::random().unwrap();
assert!(!iv.is_zero());
}
#[test]
fn test_iv_4() {
let hex = b"00000000000000000000000000000000";
let iv = IV::from_hex(hex).unwrap();
assert!(iv.is_zero());
}
#[test]
fn test_iv_5() {
let result = IV::from_hex(b"not_hex");
assert!(result.is_err());
}
#[test]
fn test_iv_6() {
let result = IV::from_hex(b"0102");
assert!(result.is_err());
}
#[test]
fn test_iv_7() {
let hex_str = "0102030405060708090a0b0c0d0e0f10";
let iv = IV::from_hex(hex_str.as_bytes()).unwrap();
assert_eq!(iv.as_hex().unwrap(), hex_str);
}
#[test]
fn test_iv_8() {
let mut iv = IV::new([0u8; IV_SIZE]);
iv.add_counter(0);
assert!(iv.is_zero());
}
#[test]
fn test_iv_9() {
let mut iv = IV::new([0u8; IV_SIZE]);
iv.add_counter(BLOCK_SIZE as u64);
assert_eq!(iv.as_ref()[IV_SIZE - 1], 1);
}
#[test]
fn test_iv_10() {
let mut iv = IV::new([0u8; IV_SIZE]);
iv.add_counter(256 * BLOCK_SIZE as u64);
assert_eq!(iv.as_ref()[IV_SIZE - 2], 1);
assert_eq!(iv.as_ref()[IV_SIZE - 1], 0);
}
#[test]
fn test_iv_11() {
let mut iv = IV::new([0u8; IV_SIZE]);
iv.add_counter(15); assert!(iv.is_zero());
}
#[test]
fn test_iv_12() {
let iv = IV::random().unwrap();
let cloned = iv.clone();
assert_eq!(iv.as_ref(), cloned.as_ref());
}
#[test]
fn test_iv_13() {
let bytes = [42u8; IV_SIZE];
let iv = IV::new(bytes);
assert_eq!(iv.as_ref(), &bytes);
}
#[test]
fn test_iv_14() {
let mut iv = IV::new([0u8; IV_SIZE]);
iv.as_mut()[0] = 0xFF;
assert_eq!(iv.as_ref()[0], 0xFF);
}
#[test]
fn test_iv_15() {
let mut iv = IV::new([0xFF; IV_SIZE]);
iv.add_counter(BLOCK_SIZE as u64);
assert_eq!(
iv.as_ref(),
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn test_syd_random_state_1() {
let state = SydRandomState::default();
let hash1 = state.hash_one("test");
let hash2 = state.hash_one("test");
assert_eq!(hash1, hash2);
}
#[test]
fn test_syd_random_state_2() {
let state = SydRandomState::default();
let hash1 = state.hash_one("foo");
let hash2 = state.hash_one("bar");
assert_ne!(hash1, hash2);
}
#[test]
fn test_syd_random_state_3() {
let s1 = SydRandomState::new();
let s2 = SydRandomState::new();
assert_ne!(s1, s2);
}
#[test]
fn test_syd_random_state_4() {
let state = SydRandomState::new();
let cloned = state;
assert_eq!(state, cloned);
}
#[test]
fn test_syd_random_state_5() {
let state = SydRandomState::new();
let dbg = format!("{state:?}");
assert!(dbg.contains("SydRandomState"));
}
#[test]
fn test_syd_hashmap_1() {
let mut map: SydHashMap<String, i32> = SydHashMap::with_hasher(SydRandomState::new());
map.insert("key".to_string(), 42);
assert_eq!(map.get("key"), Some(&42));
}
#[test]
fn test_syd_hashmap_2() {
let mut map: SydHashMap<i32, i32> = SydHashMap::with_hasher(SydRandomState::new());
for i in 0..100 {
map.insert(i, i * 2);
}
assert_eq!(map.len(), 100);
assert_eq!(map.get(&50), Some(&100));
}
#[test]
fn test_syd_hashset_1() {
let mut set: SydHashSet<i32> = SydHashSet::with_hasher(SydRandomState::new());
set.insert(1);
set.insert(2);
set.insert(1);
assert_eq!(set.len(), 2);
assert!(set.contains(&1));
}
#[test]
fn test_keyperms_1() {
let perms = KeyPerms::POS_VIEW | KeyPerms::POS_READ;
assert!(perms.contains(KeyPerms::POS_VIEW));
assert!(perms.contains(KeyPerms::POS_READ));
assert!(!perms.contains(KeyPerms::POS_WRITE));
}
#[test]
fn test_keyperms_2() {
let perms = KeyPerms::POS_ALL;
assert!(perms.contains(KeyPerms::POS_VIEW));
assert!(perms.contains(KeyPerms::POS_READ));
assert!(perms.contains(KeyPerms::POS_WRITE));
assert!(perms.contains(KeyPerms::POS_SEARCH));
assert!(perms.contains(KeyPerms::POS_LINK));
assert!(perms.contains(KeyPerms::POS_SETATTR));
}
#[test]
fn test_keyperms_3() {
let perms = KeyPerms::USR_ALL;
assert!(perms.contains(KeyPerms::USR_VIEW));
assert!(perms.contains(KeyPerms::USR_READ));
assert!(perms.contains(KeyPerms::USR_WRITE));
assert!(perms.contains(KeyPerms::USR_SEARCH));
assert!(perms.contains(KeyPerms::USR_LINK));
assert!(perms.contains(KeyPerms::USR_SETATTR));
}
#[test]
fn test_keyperms_4() {
let perms = KeyPerms::GRP_ALL;
assert!(perms.contains(KeyPerms::GRP_VIEW));
assert!(perms.contains(KeyPerms::GRP_READ));
assert!(perms.contains(KeyPerms::GRP_WRITE));
assert!(perms.contains(KeyPerms::GRP_SEARCH));
assert!(perms.contains(KeyPerms::GRP_LINK));
assert!(perms.contains(KeyPerms::GRP_SETATTR));
}
#[test]
fn test_keyperms_5() {
let perms = KeyPerms::OTH_ALL;
assert!(perms.contains(KeyPerms::OTH_VIEW));
assert!(perms.contains(KeyPerms::OTH_READ));
assert!(perms.contains(KeyPerms::OTH_WRITE));
assert!(perms.contains(KeyPerms::OTH_SEARCH));
assert!(perms.contains(KeyPerms::OTH_LINK));
assert!(perms.contains(KeyPerms::OTH_SETATTR));
}
#[test]
fn test_keyperms_6() {
let empty = KeyPerms::empty();
assert!(empty.is_empty());
assert!(!empty.contains(KeyPerms::POS_VIEW));
}
#[test]
fn test_keyperms_7() {
let perms = KeyPerms::POS_ALL | KeyPerms::USR_ALL | KeyPerms::GRP_ALL | KeyPerms::OTH_ALL;
assert_eq!(perms.bits(), 0x3f3f_3f3f);
}
#[test]
fn test_keyperms_8() {
let perms = KeyPerms::from_bits_truncate(0x0100_0000);
assert!(perms.contains(KeyPerms::POS_VIEW));
assert_eq!(perms.bits(), KeyPerms::POS_VIEW.bits());
}
#[test]
fn test_add_key_1() {
let result = add_key("", "desc", b"payload", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_add_key_2() {
let result = add_key("user", "", b"payload", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_add_key_3() {
let result = add_key("user", "desc", b"", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_add_key_4() {
let result = add_key("user\0nul", "desc", b"payload", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_add_key_5() {
let result = add_key("user", "desc\0nul", b"payload", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_key_ring_new_1() {
let result = key_ring_new("", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_key_ring_new_2() {
let result = key_ring_new("name\0nul", KEY_SPEC_USER_KEYRING);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_keyctl_read_1() {
let payload = [0x5au8; KEY_SIZE];
let id = add_key(
"user",
"syd-test-keyctl-read-1",
&payload,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mut buf = [0u8; KEY_SIZE];
let n = keyctl_read(id, &mut buf).unwrap();
assert_eq!(n, KEY_SIZE);
assert_eq!(buf, payload);
}
#[test]
fn test_keyctl_read_2() {
let mut buf = [0u8; KEY_SIZE];
assert!(keyctl_read(KeySerial::MAX, &mut buf).is_err());
}
#[test]
fn test_keyctl_read_3() {
let payload = [0u8; KEY_SIZE + 1];
let id = add_key(
"user",
"syd-test-keyctl-read-3",
&payload,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mut buf = [0u8; KEY_SIZE];
assert_eq!(keyctl_read(id, &mut buf).err(), Some(Errno::EOVERFLOW));
}
#[test]
fn test_keyctl_read_4() {
let payload = [0x3cu8; 16];
let id = add_key(
"user",
"syd-test-keyctl-read-4",
&payload,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mut buf = [0u8; KEY_SIZE];
let n = keyctl_read(id, &mut buf).unwrap();
assert_eq!(n, 16);
assert_eq!(&buf[..n], &payload[..]);
}
#[test]
fn test_keyctl_read_5() {
let payload = [0u8; KEY_SIZE];
let id = add_key(
"user",
"syd-test-keyctl-read-5",
&payload,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(id, KeyPerms::POS_VIEW | KeyPerms::POS_READ).unwrap();
let mut buf = [0u8; KEY_SIZE];
assert_eq!(keyctl_read(id, &mut buf).err(), Some(Errno::EACCES));
}
#[test]
fn test_keyctl_read_6() {
let payload = [0x77u8; KEY_SIZE];
let id = add_key(
"user",
"syd-test-keyctl-read-6",
&payload,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(id, KeyPerms::USR_VIEW | KeyPerms::USR_READ).unwrap();
let mut buf = [0u8; KEY_SIZE];
let n = keyctl_read(id, &mut buf).unwrap();
assert_eq!(n, KEY_SIZE);
assert_eq!(buf, payload);
}
#[test]
fn test_crypt_key_load_1() {
let enc = [0x11u8; KEY_SIZE];
let mac = [0x22u8; KEY_SIZE];
let enc_id = add_key(
"user",
"syd-test-load-crypt-key-1-enc",
&enc,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
enc_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mac_id = add_key(
"user",
"syd-test-load-crypt-key-1-mac",
&mac,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
mac_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let ck = CryptKey::load(enc_id, mac_id).unwrap();
assert_eq!(ck.enc().as_ref(), &enc[..]);
assert_eq!(ck.mac().as_ref(), &mac[..]);
}
#[test]
fn test_crypt_key_load_2() {
assert_eq!(CryptKey::load(0, 0).err(), Some(Errno::ENOKEY));
}
#[test]
fn test_crypt_key_load_3() {
let enc = [0x11u8; KEY_SIZE];
let mac = [0x22u8; 16];
let enc_id = add_key(
"user",
"syd-test-load-crypt-key-3-enc",
&enc,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
enc_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mac_id = add_key(
"user",
"syd-test-load-crypt-key-3-mac",
&mac,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
mac_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
assert_eq!(CryptKey::load(enc_id, mac_id).err(), Some(Errno::EINVAL));
}
#[test]
fn test_crypt_key_load_4() {
assert_eq!(
CryptKey::load(KeySerial::MAX, KeySerial::MAX).err(),
Some(Errno::ENOKEY)
);
}
#[test]
fn test_crypt_key_load_5() {
let key = [0x99u8; KEY_SIZE];
let id = add_key(
"user",
"syd-test-crypt-key-load-5",
&key,
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let ck = CryptKey::load(id, id).unwrap();
assert_eq!(ck.enc().as_ref(), &key[..]);
assert_eq!(ck.mac().as_ref(), &key[..]);
}
#[test]
fn test_secret_1() {
let secret = Secret::new(0, 0);
assert!(matches!(secret, Secret::Serial(0, 0)));
}
#[test]
fn test_secret_2() {
let mut secret = Secret::new(0, 0);
assert_eq!(secret.init(), Err(Errno::ENOKEY));
}
#[test]
fn test_secret_3() {
let mut secret = Secret::new(1, 0);
assert_eq!(secret.init(), Err(Errno::ENOKEY));
}
#[test]
fn test_secret_4() {
let mut secret = Secret::new(0, 1);
assert_eq!(secret.init(), Err(Errno::ENOKEY));
}
#[test]
fn test_aes_ctr_1() {
let key =
Key::from_hex(b"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
.unwrap();
let iv = IV::from_hex(b"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
let mut data = hex_decode(
b"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
)
.unwrap();
aes_ctr(&key, &iv).unwrap().apply_keystream(&mut data);
assert_eq!(
hex_encode_lower(&data).unwrap(),
"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
);
}
#[test]
fn test_aes_ctr_2() {
let key = Key::random().unwrap();
let iv = IV::random().unwrap();
let plain = [0x42u8; 80];
let mut buf = plain;
aes_ctr(&key, &iv).unwrap().apply_keystream(&mut buf);
assert_ne!(buf, plain);
aes_ctr(&key, &iv).unwrap().apply_keystream(&mut buf);
assert_eq!(buf, plain);
}
#[test]
fn test_aes_ctr_3() {
let key = Key::random().unwrap();
let iv = IV::random().unwrap();
let plain = [0x37u8; 64];
let mut full = plain;
aes_ctr(&key, &iv).unwrap().apply_keystream(&mut full);
let mut tail = [0x37u8; 32];
let mut cipher = aes_ctr(&key, &iv).unwrap();
cipher.seek(32u64);
cipher.apply_keystream(&mut tail);
assert_eq!(&tail[..], &full[32..]);
}
#[test]
fn test_hmac_sha256_1() {
for case in HMAC_TEST_CASES {
let mut mac = Hmac::<Sha256>::new_from_slice(case.0).unwrap();
mac.update(case.1);
let tag = mac.finalize().into_bytes();
let len = case.2.len() / 2;
assert_eq!(hex_encode_lower(&tag[..len]).unwrap(), case.2);
}
}
#[test]
fn test_hmac_sha256_2() {
let key = Key::random().unwrap();
let mut mac = hmac_sha256(&key).unwrap();
mac.update(b"syd crypt sandboxing");
let tag = mac.finalize().into_bytes();
let mut check = hmac_sha256(&key).unwrap();
check.update(b"syd crypt sandboxing");
assert!(check.verify_slice(&tag[..]).is_ok());
}
#[test]
fn test_hmac_sha256_3() {
let key = Key::random().unwrap();
let mut mac = hmac_sha256(&key).unwrap();
mac.update(b"syd crypt sandboxing");
let mut tag = mac.finalize().into_bytes();
tag[0] ^= 0x01;
let mut check = hmac_sha256(&key).unwrap();
check.update(b"syd crypt sandboxing");
assert!(check.verify_slice(&tag[..]).is_err());
}
#[test]
fn test_get_at_random_1() {
let rnd = get_at_random();
assert_eq!(rnd.len(), 16);
}
#[test]
fn test_get_at_random_2() {
let r1 = get_at_random();
let r2 = get_at_random();
assert_eq!(r1, r2);
}
#[test]
fn test_get_at_random_u64_1() {
let (a, b) = get_at_random_u64();
let _ = a;
let _ = b;
}
#[test]
fn test_get_at_random_hex_1() {
let hex = get_at_random_hex(false).unwrap();
assert_eq!(hex.len(), 32);
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_get_at_random_hex_2() {
let hex = get_at_random_hex(true).unwrap();
assert_eq!(hex.len(), 32);
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_get_at_random_hex_3() {
let lower = get_at_random_hex(false).unwrap();
assert!(lower.chars().all(|c| !c.is_ascii_uppercase()));
}
#[test]
fn test_get_at_random_hex_4() {
let upper = get_at_random_hex(true).unwrap();
assert!(upper.chars().all(|c| !c.is_ascii_lowercase()));
}
#[test]
fn test_get_at_random_name_1() {
let name = get_at_random_name(0);
assert!(!name.is_empty());
}
#[test]
fn test_get_at_random_name_2() {
let name = get_at_random_name(1);
assert!(!name.is_empty());
}
#[test]
#[should_panic]
fn test_get_at_random_name_3() {
let _ = get_at_random_name(2);
}
#[test]
fn test_hash_list_1() {
let algs = hash_list();
assert!(!algs.is_empty());
for w in algs.windows(2) {
assert!(
w[0].0 < w[1].0,
"HASH_REGISTRY not sorted/unique: {} !< {}",
w[0].0,
w[1].0
);
}
for &(name, size) in algs {
let digest =
hash_pipe::<SafeOwnedFd>(name, None).unwrap_or_else(|e| panic!("{name}: {e}"));
assert_eq!(
digest.len(),
size,
"{name}: digest len {} != declared {size}",
digest.len()
);
}
}
#[test]
fn test_hash_auto_1() {
match hash_auto() {
Some(alg) => assert!(!alg.is_empty()),
None => {}
}
}
struct HashTestCase(&'static [u8], &'static str, &'static str);
struct HmacTestCase(&'static [u8], &'static [u8], &'static str);
const HMAC_TEST_CASES: &[HmacTestCase] = &[
HmacTestCase(
&[0x0b; 20], b"Hi There", "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
),
HmacTestCase(
b"Jefe", b"what do ya want for nothing?", "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
),
HmacTestCase(
&[0xaa; 20], &[0xdd; 50], "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
),
HmacTestCase(
&[
0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0a,
0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14,
0x15, 0x16, 0x17, 0x18, 0x19,
], &[0xcd; 50], "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
),
HmacTestCase(
&[0x0c; 20], b"Test With Truncation", "a3b6167473100ee06e0c796c2955552b", ),
HmacTestCase(
&[0xaa; 131], b"Test Using Larger Than Block-Size Key - Hash Key First", "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
),
HmacTestCase(
&[0xaa; 131], b"This is a test using a larger than block-size key and a larger than block-size data. \
The key needs to be hashed before being used by the HMAC algorithm.", "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
),
];
fn tmpfile_with_data(data: &[u8]) -> std::fs::File {
let mut f = tempfile::tempfile().unwrap();
f.write_all(data).unwrap();
lseek64(&f, 0, Whence::SeekSet).unwrap();
f
}
#[rustfmt::skip]
const HASH_TEST_CASES: &[HashTestCase] = &[
HashTestCase(b"", "3345524ABF6BBE1809449224B5972C41790B6CF2", "blake2b-160"),
HashTestCase(b"abc", "384264F676F39536840523F284921CDC68B6846B", "blake2b-160"),
HashTestCase(b"", "0E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A8", "blake2b-256"),
HashTestCase(b"abc", "BDDD813C634239723171EF3FEE98579B94964E3BB1CB3E427262C8C068D52319", "blake2b-256"),
HashTestCase(b"", "B32811423377F52D7862286EE1A72EE540524380FDA1724A6F25D7978C6FD3244A6CAF0498812673C5E05EF583825100", "blake2b-384"),
HashTestCase(b"abc", "6F56A82C8E7EF526DFE182EB5212F7DB9DF1317E57815DBDA46083FC30F54EE6C66BA83BE64B302D7CBA6CE15BB556F4", "blake2b-384"),
HashTestCase(b"", "786A02F742015903C6C6FD852552D272912F4740E15847618A86E217F71F5419D25E1031AFEE585313896444934EB04B903A685B1448B755D56F701AFE9BE2CE", "blake2b-512"),
HashTestCase(b"abc", "BA80A53F981C4D0D6A2797B69F12F6E94C212F14685AC4B74B12BB6FDBFFA2D17D87C5392AAB792DC252D5DE4533CC9518D38AA8DBF1925AB92386EDD4009923", "blake2b-512"),
HashTestCase(b"", "64550D6FFE2C0A01A14ABA1EADE0200C", "blake2s-128"),
HashTestCase(b"abc", "AA4938119B1DC7B87CBAD0FFD200D0AE", "blake2s-128"),
HashTestCase(b"", "69217A3079908094E11121D042354A7C1F55B6482CA1A51E1B250DFD1ED0EEF9", "blake2s-256"),
HashTestCase(b"abc", "508C5E8C327C14E2E1A72BA34EEB452F37458B209ED63A294D999B4C86675982", "blake2s-256"),
HashTestCase(b"", "AF1349B9F5F9A1A6A0404DEA36DCC9499BCB25C9ADC112B7CC9A93CAE41F3262", "blake3"),
HashTestCase(b"abc", "6437B3AC38465133FFB63B75273A8DB548C558465D79DB03FD359C6CD5BD9D85", "blake3"),
HashTestCase(b"", "00000000", "crc32"),
HashTestCase(b"abc", "352441C2", "crc32"),
HashTestCase(b"", "00000000", "crc32c"),
HashTestCase(b"abc", "364B3FB7", "crc32c"),
HashTestCase(b"", "0000000000000000", "crc64"),
HashTestCase(b"abc", "66501A349A0E0855", "crc64"),
HashTestCase(b"", "981E5F3CA30C841487830F84FB433E13AC1101569B9C13584AC483234CD656C0", "gost94"),
HashTestCase(b"abc", "B285056DBF18D7392D7677369524DD14747459ED8143997E163B2986F92FD42C", "gost94"),
HashTestCase(b"", "1A52D11D550039BE16107F9C58DB9EBCC417F16F736ADB2502567119F0083467", "groestl256"),
HashTestCase(b"abc", "F3C1BB19C048801326A7EFBCF16E3D7887446249829C379E1840D1A3A1E7D4D2", "groestl256"),
HashTestCase(b"", "6D3AD29D279110EEF3ADBD66DE2A0345A77BAEDE1557F5D099FCE0C03D6DC2BA8E6D4A6633DFBD66053C20FAA87D1A11F39A7FBE4A6C2F009801370308FC4AD8", "groestl512"),
HashTestCase(b"abc", "70E1C68C60DF3B655339D67DC291CC3F1DDE4EF343F11B23FDD44957693815A75A8339C682FC28322513FD1F283C18E53CFF2B264E06BF83A2F0AC8C1F6FBFF6", "groestl512"),
HashTestCase(b"", "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470", "keccak256"),
HashTestCase(b"abc", "4E03657AEA45A94FC7D47BA826C8D667C0D1E6E33A64A036EC44F58FA12D6C45", "keccak256"),
HashTestCase(b"", "0EAB42DE4C3CEB9235FC91ACFFE746B29C29A8C366B7C60E4E67C466F36A4304C00FA9CAF9D87976BA469BCBE06713B435F091EF2769FB160CDAB33D3670680E", "keccak512"),
HashTestCase(b"abc", "18587DC2EA106B9A1563E32B3312421CA164C7F1F07BC922A9C83D77CEA3A1E5D0C69910739025372DC14AC9642629379540C17E2A65B19D77AA511A9D00BB96", "keccak512"),
HashTestCase(b"", "31D6CFE0D16AE931B73C59D7E0C089C0", "md4"),
HashTestCase(b"abc", "A448017AAF21D8525FC10AE87AA6729D", "md4"),
HashTestCase(b"", "D41D8CD98F00B204E9800998ECF8427E", "md5"),
HashTestCase(b"abc", "900150983CD24FB0D6963F7D28E17F72", "md5"),
HashTestCase(b"", "CDF26213A150DC3ECB610F18F6B38B46", "rmd128"),
HashTestCase(b"abc", "C14A12199C66E4BA84636B0F69144C77", "rmd128"),
HashTestCase(b"", "9C1185A5C5E9FC54612808977EE8F548B2258D31", "rmd160"),
HashTestCase(b"abc", "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC", "rmd160"),
HashTestCase(b"", "02BA4C4E5F8ECD1877FC52D64D30E37A2D9774FB1E5D026380AE0168E3C5522D", "rmd256"),
HashTestCase(b"abc", "AFBD6E228B9D8CBBCEF5CA2D03E6DBA10AC0BC7DCBE4680E1E42D2E975459B65", "rmd256"),
HashTestCase(b"", "22D65D5661536CDC75C1FDF5C6DE7B41B9F27325EBC61E8557177D705A0EC880151C3A32A00899B8", "rmd320"),
HashTestCase(b"abc", "DE4C01B3054F8930A79D09AE738E92301E5A17085BEFFDC1B8D116713E74F82FA942D64CDBC4682D", "rmd320"),
HashTestCase(b"", "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", "sha1"),
HashTestCase(b"abc", "A9993E364706816ABA3E25717850C26C9CD0D89D", "sha1"),
HashTestCase(b"", "D14A028C2A3A2BC9476102BB288234C415A2B01F828EA62AC5B3E42F", "sha224"),
HashTestCase(b"abc", "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7", "sha224"),
HashTestCase(b"", "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", "sha256"),
HashTestCase(b"abc", "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD", "sha256"),
HashTestCase(b"", "6B4E03423667DBB73B6E15454F0EB1ABD4597F9A1B078E3F5B5A6BC7", "sha3-224"),
HashTestCase(b"abc", "E642824C3F8CF24AD09234EE7D3C766FC9A3A5168D0C94AD73B46FDF", "sha3-224"),
HashTestCase(b"", "A7FFC6F8BF1ED76651C14756A061D662F580FF4DE43B49FA82D80A4B80F8434A", "sha3-256"),
HashTestCase(b"abc", "3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532", "sha3-256"),
HashTestCase(b"", "0C63A75B845E4F7D01107D852E4C2485C51A50AAAA94FC61995E71BBEE983A2AC3713831264ADB47FB6BD1E058D5F004", "sha3-384"),
HashTestCase(b"abc", "EC01498288516FC926459F58E2C6AD8DF9B473CB0FC08C2596DA7CF0E49BE4B298D88CEA927AC7F539F1EDF228376D25", "sha3-384"),
HashTestCase(b"", "A69F73CCA23A9AC5C8B567DC185A756E97C982164FE25859E0D1DCC1475C80A615B2123AF1F5F94C11E3E9402C3AC558F500199D95B6D3E301758586281DCD26", "sha3-512"),
HashTestCase(b"abc", "B751850B1A57168A5693CD924B6B096E08F621827444F70D884F5D0240D2712E10E116E9192AF3C91A7EC57647E3934057340B4CF408D5A56592F8274EEC53F0", "sha3-512"),
HashTestCase(b"", "38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B", "sha384"),
HashTestCase(b"abc", "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7", "sha384"),
HashTestCase(b"", "CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715DC83F4A921D36CE9CE47D0D13C5D85F2B0FF8318D2877EEC2F63B931BD47417A81A538327AF927DA3E", "sha512"),
HashTestCase(b"abc", "DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F", "sha512"),
HashTestCase(b"", "1AB21D8355CFA17F8E61194831E81A8F22BEC8C728FEFB747ED035EB5082AA2B", "sm3"),
HashTestCase(b"abc", "66C7F0F462EEEDD9D1F2D46BDC10E4E24167C4875CF2F7A2297DA02B8F4BA8E0", "sm3"),
HashTestCase(b"", "3F539A213E97C802CC229D474C6AA32A825A360B2A933A949FD925208D9CE1BB", "streebog256"),
HashTestCase(b"abc", "4E2919CF137ED41EC4FB6270C61826CC4FFFB660341E0AF3688CD0626D23B481", "streebog256"),
HashTestCase(b"", "8E945DA209AA869F0455928529BCAE4679E9873AB707B55315F56CEB98BEF0A7362F715528356EE83CDA5F2AAC4C6AD2BA3A715C1BCD81CB8E9F90BF4C1C1A8A", "streebog512"),
HashTestCase(b"abc", "28156E28317DA7C98F4FE2BED6B542D0DAB85BB224445FCEDAF75D46E26D7EB8D5997F3E0915DD6B7F0AAB08D9C8BEB0D8C64BAE2AB8B3C8C6BC53B3BF0DB728", "streebog512"),
HashTestCase(b"", "3293AC630C13F0245F92BBB1766E16167A4E58492DDE73F3", "tiger"),
HashTestCase(b"abc", "2AAB1484E8C158F2BFB8C5FF41B57A525129131C957B5F93", "tiger"),
HashTestCase(b"", "4441BE75F6018773C206C22745374B924AA8313FEF919F41", "tiger2"),
HashTestCase(b"abc", "F68D7BC5AF4B43A06E048D7829560D4A9415658BB0B1F3BF", "tiger2"),
HashTestCase(b"", "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A7", "wp256"),
HashTestCase(b"abc", "4E2448A4C6F486BB16B6562C73B4020BF3043E3A731BCE721AE1B303D97E6D4C", "wp256"),
HashTestCase(b"", "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757", "wp384"),
HashTestCase(b"abc", "4E2448A4C6F486BB16B6562C73B4020BF3043E3A731BCE721AE1B303D97E6D4C7181EEBDB6C57E277D0E34957114CBD6", "wp384"),
HashTestCase(b"", "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3", "wp512"),
HashTestCase(b"abc", "4E2448A4C6F486BB16B6562C73B4020BF3043E3A731BCE721AE1B303D97E6D4C7181EEBDB6C57E277D0E34957114CBD6C797FC9D95D8B582D225292076D4EEF5", "wp512"),
];
#[rustfmt::skip]
const HASH_1M_CASES: &[(&str, &str)] = &[
("blake2b-160", "9B512A5ED7D52DDEB8D8762E4B6DD880B25EA54D"),
("blake2b-256", "0741850F36CBA4259628355D1073E24DDB9CA0E1BFAC36FD39AE5DC2101E23A4"),
("blake2b-384", "92650B7746765A98701EC2077C3603127C62525C8543477C8519D6CC53AC5A9F0098ED56EB7AAF03CA50BFE046E7BBA3"),
("blake2b-512", "98FB3EFB7206FD19EBF69B6F312CF7B64E3B94DBE1A17107913975A793F177E1D077609D7FBA363CBBA00D05F7AA4E4FA8715D6428104C0A75643B0FF3FD3EAF"),
("blake2s-128", "1D7E5CADCA3075D31D56EF6752B31307"),
("blake2s-256", "BEC0C0E6CDE5B67ACB73B81F79A67A4079AE1C60DAC9D2661AF18E9F8B50DFA5"),
("blake3", "616F575A1B58D4C9797D4217B9730AE5E6EB319D76EDEF6549B46F4EFE31FF8B"),
("crc32", "DC25BFBC"),
("crc32c", "436FE240"),
("crc64", "3377CEC7A585E11F"),
("gost94", "8693287AA62F9478F7CB312EC0866B6C4E4A0F11160441E8F4FFCD2715DD554F"),
("groestl256", "A43CB4311FB1B53E2B207B1345E4E81C4279CF7AFC9531EF10FB9EDF4E705DAF"),
("groestl512", "44E2C56D41EDB735438C652572533E41FEC7DC06567DEA9406D50B4E665F92E95F218D2540333632C75369ED5D5CEFCB6C4835BC8AB16DD85E614E7926FDECFB"),
("keccak256", "FADAE6B49F129BBB812BE8407B7B2894F34AECF6DBD1F9B0F0C7E9853098FC96"),
("keccak512", "5CF53F2E556BE5A624425EDE23D0E8B2C7814B4BA0E4E09CBBF3C2FAC7056F61E048FC341262875EBC58A5183FEA651447124370C1EBF4D6C89BC9A7731063BB"),
("md4", "BBCE80CC6BB65E5C6745E30D4EECA9A4"),
("md5", "7707D6AE4E027C70EEA2A935C2296F21"),
("rmd128", "4A7F5723F954EBA1216C9D8F6320431F"),
("rmd160", "52783243C1697BDBE16D37F97F68F08325DC1528"),
("rmd256", "AC953744E10E31514C150D4D8D7B677342E33399788296E43AE4850CE4F97978"),
("rmd320", "BDEE37F4371E20646B8B0D862DDA16292AE36F40965E8C8509E63D1DBDDECC503E2B63EB9245BB66"),
("sha1", "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"),
("sha224", "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2EE4EE7AD67"),
("sha256", "CDC76E5C9914FB9281A1C7E284D73E67F1809A48A497200E046D39CCC7112CD0"),
("sha3-224", "D69335B93325192E516A912E6D19A15CB51C6ED5C15243E7A7FD653C"),
("sha3-256", "5C8875AE474A3634BA4FD55EC85BFFD661F32ACA75C6D699D0CDCB6C115891C1"),
("sha3-384", "EEE9E24D78C1855337983451DF97C8AD9EEDF256C6334F8E948D252D5E0E76847AA0774DDB90A842190D2C558B4B8340"),
("sha3-512", "3C3A876DA14034AB60627C077BB98F7E120A2A5370212DFFB3385A18D4F38859ED311D0A9D5141CE9CC5C66EE689B266A8AA18ACE8282A0E0DB596C90B0A7B87"),
("sha384", "9D0E1809716474CB086E834E310A4A1CED149E9C00F248527972CEC5704C2A5B07B8B3DC38ECC4EBAE97DDD87F3D8985"),
("sha512", "E718483D0CE769644E2E42C7BC15B4638E1F98B13B2044285632A803AFA973EBDE0FF244877EA60A4CB0432CE577C31BEB009C5C2C49AA2E4EADB217AD8CC09B"),
("sm3", "C8AAF89429554029E231941A2ACC0AD61FF2A5ACD8FADD25847A3A732B3B02C3"),
("streebog256", "841AF1A0B2F92A800FB1B7E4AABC8E48763153C448A0FC57C90BA830E130F152"),
("streebog512", "D396A40B126B1F324465BFA7AA159859AB33FAC02DCDD4515AD231206396A266D0102367E4C544EF47D2294064E1A25342D0CD25AE3D904B45ABB1425AE41095"),
("tiger", "6DB0E2729CBEAD93D715C6A7D36302E9B3CEE0D2BC314B41"),
("tiger2", "E068281F060F551628CC5715B9D0226796914D45F7717CF4"),
("wp256", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF5"),
("wp384", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD"),
("wp512", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD8C6008AD677F77126953B226E4ED8B01"),
];
#[test]
fn test_hash_pipe_1() {
let mut errors = Vec::new();
for (i, case) in HASH_TEST_CASES.iter().enumerate() {
let fd = if case.0.is_empty() {
None
} else {
Some(tmpfile_with_data(case.0))
};
let result = match hash_pipe(case.2, fd.as_ref()) {
Ok(digest) => hex_encode_upper(&digest).unwrap(),
Err(errno) => {
errors.push(format!(
"Case {i} ({}, input_len={}): hash_pipe failed: {errno}",
case.2,
case.0.len()
));
continue;
}
};
if result != case.1 {
errors.push(format!(
"Case {i} ({}): mismatch\n expected: {}\n got: {result}",
case.2, case.1
));
}
}
assert!(
errors.is_empty(),
"hash_pipe test failures:\n{}",
errors.join("\n")
);
}
#[test]
fn test_hash_pipe_2() {
let input = b"a".repeat(1_000_000);
let fd = tmpfile_with_data(&input);
let cases = HASH_1M_CASES;
let mut errors = Vec::new();
for &(alg, expected) in cases {
lseek64(fd.as_fd(), 0, Whence::SeekSet).unwrap();
let result = match hash_pipe(alg, Some(&fd)) {
Ok(digest) => hex_encode_upper(&digest).unwrap(),
Err(errno) => {
errors.push(format!("{alg}: hash_pipe 1M failed: {errno}"));
continue;
}
};
if result != expected {
errors.push(format!("{alg}: expected {expected}, got {result}"));
}
}
assert!(errors.is_empty(), "hash_pipe 1M errors: {errors:?}");
}
#[test]
fn test_hash_pipe_3() {
let result = hash_pipe::<SafeOwnedFd>("Pink Floyd", None);
assert!(matches!(result, Err(Errno::EOPNOTSUPP)), "{result:?}");
}
#[test]
fn test_hash_file_1() {
let mut errors = Vec::new();
for (i, case) in HASH_TEST_CASES.iter().enumerate() {
let fd = tmpfile_with_data(case.0);
let result = match hash_file(case.2, &fd, None) {
Ok(digest) => hex_encode_upper(&digest).unwrap(),
Err(errno) => {
errors.push(format!(
"Case {i} ({}, input_len={}): hash_file failed: {errno}",
case.2,
case.0.len()
));
continue;
}
};
if result != case.1 {
errors.push(format!(
"Case {i} ({}): mismatch\n expected: {}\n got: {result}",
case.2, case.1
));
}
}
assert!(
errors.is_empty(),
"hash_file test failures:\n{}",
errors.join("\n")
);
}
#[test]
fn test_hash_file_2() {
let input = b"a".repeat(1_000_000);
let fd = tmpfile_with_data(&input);
let cases = HASH_1M_CASES;
let mut errors = Vec::new();
for &(alg, expected) in cases {
lseek64(fd.as_fd(), 0, Whence::SeekSet).unwrap();
let result = match hash_file(alg, &fd, None) {
Ok(digest) => hex_encode_upper(&digest).unwrap(),
Err(errno) => {
errors.push(format!("{alg}: hash_file 1M failed: {errno}"));
continue;
}
};
if result != expected {
errors.push(format!("{alg}: expected {expected}, got {result}"));
}
}
assert!(errors.is_empty(), "hash_file 1M errors: {errors:?}");
}
#[test]
fn test_hash_file_3() {
let fd = tmpfile_with_data(b"test");
let result = hash_file("Pink Floyd", &fd, None);
assert!(matches!(result, Err(Errno::EOPNOTSUPP)), "{result:?}");
}
#[test]
fn test_validate_checksum_1() {
let good = hash_pipe("sha256", Some(&tmpfile_with_data(b"abc"))).unwrap();
assert_eq!(good.len(), 32);
assert!(validate_checksum("sha256", &good).is_ok());
assert_eq!(
validate_checksum("sha256", &good[..16]).err(),
Some(Errno::EINVAL)
);
assert_eq!(
validate_checksum("Pink Floyd", &good).err(),
Some(Errno::EOPNOTSUPP)
);
let empty = hash_pipe::<SafeOwnedFd>("sha256", None).unwrap();
assert_eq!(
validate_checksum("sha256", &empty).err(),
Some(Errno::EINVAL)
);
}
#[test]
fn test_aes_ctr_tmp_1() {
let enc = Key::random().unwrap();
let mac = Key::random().unwrap();
let enc_id = add_key(
"user",
"syd-test-aes-ctr-tmp-enc",
enc.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
enc_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mac_id = add_key(
"user",
"syd-test-aes-ctr-tmp-mac",
mac.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
mac_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let keys = CryptKey::load(enc_id, mac_id).unwrap();
let plain =
b"Change return success. Going and coming without error. Action brings good fortune.";
let iv = IV::random().unwrap();
let mut ct = plain.to_vec();
aes_ctr(keys.enc(), &iv).unwrap().apply_keystream(&mut ct);
let mut m = hmac_sha256(keys.mac()).unwrap();
m.update(CRYPT_MAGIC);
m.update(iv.as_ref());
m.update(&ct);
let tag = m.finalize().into_bytes();
let enc_memfd = safe_memfd_create(c"syd", MFdFlags::empty()).unwrap();
write(enc_memfd.as_fd(), CRYPT_MAGIC).unwrap();
write(enc_memfd.as_fd(), &tag[..]).unwrap();
write(enc_memfd.as_fd(), iv.as_ref()).unwrap();
write(enc_memfd.as_fd(), &ct).unwrap();
let (dec_memfd, _) = aes_ctr_tmp(&keys, &enc_memfd, OFlag::empty(), None)
.unwrap()
.unwrap();
let mut got = vec![0u8; plain.len()];
lseek64(&dec_memfd, 0, Whence::SeekSet).unwrap();
read(dec_memfd, &mut got).unwrap();
assert_eq!(got.as_slice(), plain.as_slice());
}
#[test]
fn test_aes_ctr_tmp_2() {
let enc = Key::random().unwrap();
let mac = Key::random().unwrap();
let enc_id = add_key(
"user",
"syd-test-aes-ctr-tmp-2-enc",
enc.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
enc_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mac_id = add_key(
"user",
"syd-test-aes-ctr-tmp-2-mac",
mac.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
mac_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let keys = CryptKey::load(enc_id, mac_id).unwrap();
let plain = b"tamper detection must reject a flipped ciphertext byte";
let iv = IV::random().unwrap();
let mut ct = plain.to_vec();
aes_ctr(keys.enc(), &iv).unwrap().apply_keystream(&mut ct);
let mut m = hmac_sha256(keys.mac()).unwrap();
m.update(CRYPT_MAGIC);
m.update(iv.as_ref());
m.update(&ct);
let tag = m.finalize().into_bytes();
ct[0] ^= 0x01;
let enc_memfd = safe_memfd_create(c"syd", MFdFlags::empty()).unwrap();
write(enc_memfd.as_fd(), CRYPT_MAGIC).unwrap();
write(enc_memfd.as_fd(), &tag[..]).unwrap();
write(enc_memfd.as_fd(), iv.as_ref()).unwrap();
write(enc_memfd.as_fd(), &ct).unwrap();
assert_eq!(
aes_ctr_tmp(&keys, &enc_memfd, OFlag::empty(), None).err(),
Some(Errno::EBADMSG)
);
}
#[test]
fn test_aes_ctr_tmp_3() {
let enc = Key::random().unwrap();
let mac = Key::random().unwrap();
let enc_id = add_key(
"user",
"syd-test-aes-ctr-tmp-3-enc",
enc.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
enc_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mac_id = add_key(
"user",
"syd-test-aes-ctr-tmp-3-mac",
mac.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
mac_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let keys = CryptKey::load(enc_id, mac_id).unwrap();
let mut plain = Vec::new();
while plain.len() <= IO_BUF_SIZE {
plain.extend_from_slice(b"syd crypt multi-chunk roundtrip 0123456789 ");
}
let iv = IV::random().unwrap();
let mut ct = plain.clone();
aes_ctr(keys.enc(), &iv).unwrap().apply_keystream(&mut ct);
let mut m = hmac_sha256(keys.mac()).unwrap();
m.update(CRYPT_MAGIC);
m.update(iv.as_ref());
m.update(&ct);
let tag = m.finalize().into_bytes();
let mut file = Vec::new();
file.extend_from_slice(CRYPT_MAGIC);
file.extend_from_slice(&tag[..]);
file.extend_from_slice(iv.as_ref());
file.extend_from_slice(&ct);
let enc_memfd = safe_memfd_create(c"syd", MFdFlags::empty()).unwrap();
let mut off = 0;
while off < file.len() {
off += write(enc_memfd.as_fd(), &file[off..]).unwrap();
}
let (dec, _) = aes_ctr_tmp(&keys, &enc_memfd, OFlag::empty(), None)
.unwrap()
.unwrap();
lseek64(&dec, 0, Whence::SeekSet).unwrap();
let mut out = vec![0u8; plain.len()];
let mut off = 0;
while off < out.len() {
let n = read(&dec, &mut out[off..]).unwrap();
assert_ne!(n, 0);
off += n;
}
assert_eq!(out, plain);
}
#[test]
fn test_aes_ctr_tmp_4() {
let enc =
Key::from_hex(b"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
.unwrap();
let mac =
Key::from_hex(b"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
.unwrap();
let iv = IV::from_hex(b"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
let plain = hex_decode(
b"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
)
.unwrap();
let mut ct = plain.clone();
aes_ctr(&enc, &iv).unwrap().apply_keystream(&mut ct);
let mut m = hmac_sha256(&mac).unwrap();
m.update(CRYPT_MAGIC);
m.update(iv.as_ref());
m.update(&ct);
let tag = m.finalize().into_bytes();
let mut file = Vec::new();
file.extend_from_slice(CRYPT_MAGIC);
file.extend_from_slice(&tag);
file.extend_from_slice(iv.as_ref());
file.extend_from_slice(&ct);
assert_eq!(
hex_encode_lower(&file).unwrap(),
"7f53594403fa107398edb4539f547ce4583ae3bbd863ee81ef9db9374fe39555b016dffb87f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
);
let enc_id = add_key(
"user",
"syd-test-aes-ctr-tmp-4-enc",
enc.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
enc_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let mac_id = add_key(
"user",
"syd-test-aes-ctr-tmp-4-mac",
mac.as_ref(),
KEY_SPEC_PROCESS_KEYRING,
)
.unwrap();
key_setperm(
mac_id,
KeyPerms::POS_VIEW | KeyPerms::POS_READ | KeyPerms::POS_SEARCH,
)
.unwrap();
let keys = CryptKey::load(enc_id, mac_id).unwrap();
let enc_memfd = safe_memfd_create(c"syd", MFdFlags::empty()).unwrap();
let mut off = 0;
while off < file.len() {
off += write(enc_memfd.as_fd(), &file[off..]).unwrap();
}
let (dec, _) = aes_ctr_tmp(&keys, &enc_memfd, OFlag::empty(), None)
.unwrap()
.unwrap();
lseek64(&dec, 0, Whence::SeekSet).unwrap();
let mut out = vec![0u8; plain.len()];
let mut off = 0;
while off < out.len() {
let n = read(&dec, &mut out[off..]).unwrap();
assert_ne!(n, 0);
off += n;
}
assert_eq!(out, plain);
}
}