use std::{
ffi::CString,
hash::BuildHasher,
io::{IoSlice, IoSliceMut},
os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, RawFd},
sync::LazyLock,
};
#[expect(clippy::disallowed_types)]
use ahash::{AHasher, RandomState};
use bitflags::bitflags;
use data_encoding::{HEXLOWER, HEXLOWER_PERMISSIVE, HEXUPPER};
use lexis::ToName;
use libc::mode_t;
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::{open, splice, tee, OFlag, SpliceFFlags},
sys::{
socket::{AlgAddr, SockFlag, SockaddrStorage},
stat::Mode,
},
unistd::{lseek64, read, write, Whence},
};
use procfs_core::{SelfTest, Type};
use subtle::ConstantTimeEq;
use zeroize::Zeroizing;
use crate::{
compat::{
fstatx, recvmsg, send, sendmsg, AddressFamily, Cmsg, MsgFlags, MsgHdr, SockType,
STATX_SIZE, STATX_TYPE,
},
config::*,
cookie::{safe_accept4, safe_bind, safe_memfd_create, safe_pipe2, safe_sendfile, safe_socket},
fd::{set_append, set_nonblock, SafeOwnedFd},
lookup::FileType,
proc::{proc_crypto, proc_crypto_read},
retry::retry_on_eintr,
rng::{fillrandom, mkstempat},
};
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;
static AES_ADDR: LazyLock<AlgAddr> = LazyLock::new(|| AlgAddr::new("skcipher", "ctr(aes)"));
static HMAC_ADDR: LazyLock<AlgAddr> = LazyLock::new(|| AlgAddr::new("hash", "hmac(sha256)"));
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 = HEXLOWER_PERMISSIVE.decode(hex).or(Err(Errno::EINVAL))?;
let key = key.as_slice().try_into().or(Err(Errno::EINVAL))?;
Ok(Self::new(key))
}
pub fn as_hex(&self) -> String {
HEXLOWER.encode(self.as_ref())
}
pub fn is_zero(&self) -> bool {
self.as_ref().iter().all(|&byte| byte == 0)
}
}
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 IV(Zeroizing<[u8; IV_SIZE]>);
impl IV {
pub fn new(iv: [u8; IV_SIZE]) -> Self {
Self(Zeroizing::new(iv))
}
pub fn random() -> Result<Self, Errno> {
let mut bytes = Zeroizing::new([0u8; IV_SIZE]);
fillrandom(bytes.as_mut())?;
Ok(Self(bytes))
}
pub fn from_hex(hex: &[u8]) -> Result<Self, Errno> {
let iv = HEXLOWER_PERMISSIVE.decode(hex).or(Err(Errno::EINVAL))?;
let iv = iv.as_slice().try_into().or(Err(Errno::EINVAL))?;
Ok(Self::new(iv))
}
pub fn as_hex(&self) -> String {
HEXLOWER.encode(self.as_ref())
}
pub fn is_zero(&self) -> bool {
self.as_ref().iter().all(|&byte| byte == 0)
}
#[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.clone())
}
}
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 {
Alg(RawFd, RawFd),
Key(KeySerial, KeySerial),
}
impl Secret {
pub fn new(enc_key_id: KeySerial, mac_key_id: KeySerial) -> Self {
Self::Key(enc_key_id, mac_key_id)
}
pub fn init(&mut self) -> Result<(), Errno> {
let (enc_key_id, mac_key_id) = if let Secret::Key(enc_key_id, mac_key_id) = self {
(*enc_key_id, *mac_key_id)
} else {
return Ok(());
};
if enc_key_id == 0 || mac_key_id == 0 {
return Err(Errno::ENOKEY);
}
let enc_fd = aes_ctr_setup(enc_key_id)?;
let tag_fd = hmac_sha256_setup(mac_key_id)?;
*self = Self::Alg(enc_fd.into_raw_fd(), tag_fd.into_raw_fd());
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_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 check_setsockopt_serial_support() -> bool {
match aes_ctr_setup(KeySerial::MAX).map(drop) {
Ok(()) => true,
Err(Errno::ENOPROTOOPT) => false,
Err(Errno::ENOKEY)
| Err(Errno::ENOENT)
| Err(Errno::EACCES)
| Err(Errno::EPERM)
| Err(Errno::EBUSY)
| Err(Errno::EINVAL)
| Err(Errno::ENOTCONN)
| Err(Errno::EOPNOTSUPP) => true,
_ => false,
}
}
pub fn setsockopt_serial<Fd: AsFd>(fd: Fd, id: KeySerial) -> Result<(), Errno> {
const SOL_ALG: libc::c_int = 279;
const ALG_SET_KEY_BY_KEY_SERIAL: libc::c_int = 7;
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::setsockopt(
fd.as_fd().as_raw_fd(),
SOL_ALG,
ALG_SET_KEY_BY_KEY_SERIAL,
&raw const id as *const libc::c_void,
size_of::<KeySerial>() as libc::socklen_t,
)
})
.map(drop)
}
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 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);
}
pub fn hash_pipe<Fd: AsFd>(func: &str, input: Option<Fd>) -> Result<Vec<u8>, Errno> {
let addr = AlgAddr::new("hash", func);
let sock = retry_on_eintr(|| {
safe_socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::SOCK_CLOEXEC,
0,
)
})?;
retry_on_eintr(|| safe_bind(&sock, &addr))?;
let conn = retry_on_eintr(|| safe_accept4(sock.as_fd(), SockFlag::SOCK_CLOEXEC, false))?.0;
if let Some(input) = input {
let (pipe_rd, pipe_wr) = safe_pipe2(OFlag::O_CLOEXEC)?;
loop {
let n = retry_on_eintr(|| {
splice(
&input,
None,
&pipe_wr,
None,
PIPE_BUF_ALG,
SpliceFFlags::SPLICE_F_MORE,
)
})?;
if n == 0 {
break;
}
let mut remain = n;
while remain > 0 {
let m = retry_on_eintr(|| {
splice(
&pipe_rd,
None,
&conn,
None,
remain,
SpliceFFlags::SPLICE_F_MORE,
)
})?;
if m == 0 {
return Err(Errno::EBADMSG);
}
remain = remain.checked_sub(m).ok_or(Errno::EOVERFLOW)?;
}
}
}
retry_on_eintr(|| send(conn.as_fd(), &[], MsgFlags::empty()))?;
let mut buf = Vec::new();
buf.try_reserve(HASH_MAX_DIGESTSIZE)
.or(Err(Errno::ENOMEM))?;
buf.resize(HASH_MAX_DIGESTSIZE, 0);
let (bytes, trunc) = {
let mut hdr = MsgHdr::default();
let mut iov = [IoSliceMut::new(&mut buf)];
hdr.set_iov_mut(&mut iov);
let msg = loop {
match recvmsg(&conn, &mut hdr, MsgFlags::empty()) {
Ok(msg) => break msg,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
};
(msg.bytes, msg.flags & MsgFlags::MSG_TRUNC.bits() != 0)
};
assert!(
bytes > 0,
"BUG: AF_ALG socket returned zero byte for algorithm {func}, report a bug!"
);
assert!(!trunc,
"BUG: AF_ALG digest size exceeded {HASH_MAX_DIGESTSIZE} bytes for algorithm {func}, report a bug!");
buf.truncate(bytes);
buf.shrink_to_fit();
Ok(buf)
}
pub fn hash_file<Fd: AsFd>(
func: &str,
input: Fd,
size_hint: Option<u64>,
) -> Result<Vec<u8>, Errno> {
let addr = AlgAddr::new("hash", func);
let sock = retry_on_eintr(|| {
safe_socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::SOCK_CLOEXEC,
0,
)
})?;
retry_on_eintr(|| safe_bind(&sock, &addr))?;
let conn = retry_on_eintr(|| safe_accept4(sock.as_fd(), SockFlag::SOCK_CLOEXEC, false))?.0;
if let Some(mut remain) = size_hint {
while remain > 0 {
let chunk = remain
.min(SENDFILE_MAX as u64)
.try_into()
.or(Err(Errno::EOVERFLOW))?;
let nsent = retry_on_eintr(|| safe_sendfile(&conn, &input, chunk))?
.try_into()
.or(Err(Errno::EOVERFLOW))?;
if nsent == 0 {
return Err(Errno::EBADMSG);
}
remain = remain.checked_sub(nsent).ok_or(Errno::EOVERFLOW)?;
}
} else {
while retry_on_eintr(|| safe_sendfile(&conn, &input, SENDFILE_MAX))? > 0 {}
}
let mut buf = Vec::new();
buf.try_reserve(HASH_MAX_DIGESTSIZE)
.or(Err(Errno::ENOMEM))?;
buf.resize(HASH_MAX_DIGESTSIZE, 0);
let (bytes, trunc) = {
let mut hdr = MsgHdr::default();
let mut iov = [IoSliceMut::new(&mut buf)];
hdr.set_iov_mut(&mut iov);
let msg = loop {
match recvmsg(&conn, &mut hdr, MsgFlags::empty()) {
Ok(msg) => break msg,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
};
(msg.bytes, msg.flags & MsgFlags::MSG_TRUNC.bits() != 0)
};
assert!(
bytes > 0,
"BUG: AF_ALG socket returned zero byte for algorithm {func}, report a bug!"
);
assert!(!trunc,
"BUG: AF_ALG digest size exceeded {HASH_MAX_DIGESTSIZE} bytes for algorithm {func}, report a bug!");
buf.truncate(bytes);
buf.shrink_to_fit();
Ok(buf)
}
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 <= SENDFILE_MAX as u64 {
return hash_file(func, input, Some(stx.stx_size));
}
}
hash_pipe(func, Some(input))
}
pub fn hash_list() -> Result<Vec<String>, Errno> {
let table = proc_crypto()?;
let mut algs: Vec<String> = Vec::new();
for (name, blocks) in &table.crypto_blocks {
if name.contains('(') {
continue;
}
for block in blocks {
let is_hash = matches!(
&block.crypto_type,
procfs_core::Type::Ahash(_) | procfs_core::Type::Shash(_)
);
if is_hash {
algs.push(name.clone());
break;
}
}
}
algs.sort();
algs.dedup();
Ok(algs)
}
const HASH_PREFERRED: &[&str] = &[
"sha3-512",
"sha3-384",
"sha3-256",
"blake2b-512",
"blake2b-384",
"blake2b-256",
"streebog512",
"streebog256",
"sha512",
"sha384",
"sha256",
"sha224",
"sm3",
];
pub fn hash_auto() -> Option<String> {
for &alg in HASH_PREFERRED {
match hash_pipe::<SafeOwnedFd>(alg, None) {
Ok(_) => return Some(alg.to_string()),
Err(Errno::ENOENT) => continue,
Err(_) => return None,
}
}
None
}
pub const SAFE_HASH_SIZE: usize = 32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SafeHashAlgorithm {
Sha256,
Blake2b256,
Sm3,
Sha3_256,
Wp256,
Streebog256,
}
impl SafeHashAlgorithm {
pub const ALL: &'static [Self] = &[
Self::Sha256,
Self::Blake2b256,
Self::Sm3,
Self::Sha3_256,
Self::Wp256,
Self::Streebog256,
];
pub const fn name(self) -> &'static str {
match self {
Self::Sha256 => "sha256",
Self::Blake2b256 => "blake2b-256",
Self::Sm3 => "sm3",
Self::Sha3_256 => "sha3-256",
Self::Wp256 => "wp256",
Self::Streebog256 => "streebog256",
}
}
pub const fn cookie_digest(self) -> [u8; SAFE_HASH_SIZE] {
match self {
Self::Sha256 => [
0x58, 0xd8, 0x97, 0xa0, 0x61, 0x55, 0xe2, 0x1c, 0xd5, 0xc4, 0xd5, 0x3b, 0x8d, 0xfc,
0x80, 0xb5, 0x44, 0xe9, 0x0c, 0x2d, 0xa6, 0x1a, 0x07, 0xf5, 0xea, 0x96, 0x19, 0xe8,
0xd4, 0xce, 0x6f, 0x6e,
],
Self::Blake2b256 => [
0x15, 0xda, 0x4f, 0xd5, 0x21, 0x0c, 0xd8, 0xed, 0xf2, 0xe0, 0x7d, 0xdb, 0xed, 0x7a,
0x3f, 0xb8, 0xc4, 0xc6, 0xf3, 0x1f, 0xf3, 0xb8, 0xef, 0x07, 0x4e, 0x4e, 0xc7, 0xf3,
0x05, 0x9f, 0x88, 0x8d,
],
Self::Sm3 => [
0x7f, 0x40, 0x03, 0x43, 0x69, 0x9a, 0x11, 0x3c, 0x44, 0xc2, 0x01, 0x54, 0x99, 0x8b,
0xfc, 0x92, 0x77, 0xf3, 0x43, 0x4d, 0xbd, 0xcc, 0x57, 0xf1, 0xb0, 0xfc, 0x86, 0x08,
0x6b, 0x1f, 0xe1, 0xe4,
],
Self::Sha3_256 => [
0xea, 0xe3, 0xe2, 0x38, 0xeb, 0x4e, 0x29, 0x93, 0xc2, 0x61, 0xc6, 0xa6, 0x23, 0xfb,
0xe6, 0x0f, 0x0d, 0xe3, 0xc9, 0xe8, 0x08, 0xb9, 0x0f, 0x49, 0xb5, 0xbc, 0x55, 0x19,
0x21, 0x7b, 0xe9, 0x30,
],
Self::Wp256 => [
0xd5, 0xf0, 0xb4, 0x2b, 0x64, 0x10, 0x20, 0xb4, 0x0b, 0x49, 0x3e, 0x6b, 0x9d, 0xfd,
0xf7, 0x7e, 0xf3, 0x81, 0x0d, 0xad, 0x34, 0x97, 0x87, 0xc8, 0xb5, 0xf0, 0xc2, 0xe8,
0x47, 0xa9, 0xc2, 0x56,
],
Self::Streebog256 => [
0x56, 0x99, 0x7c, 0x36, 0x22, 0x87, 0xb1, 0xaf, 0x74, 0x7d, 0xd0, 0x30, 0x2d, 0x62,
0x93, 0x74, 0x3c, 0xac, 0x91, 0x73, 0xb1, 0x44, 0x02, 0x89, 0x9d, 0xff, 0x1b, 0x73,
0xcf, 0x75, 0x60, 0x9e,
],
}
}
}
static SAFE_HASH_ALG: LazyLock<Option<SafeHashAlgorithm>> = LazyLock::new(SafeHash::detect);
pub enum SafeHash {
Kernel(SafeOwnedFd),
Blake3(Box<blake3::Hasher>),
}
impl SafeHash {
const COOKIE: &'static str =
"Change return success. Going and coming without error. Action brings good fortune.";
pub fn new() -> Self {
if let Some(alg) = *SAFE_HASH_ALG {
if let Ok(hash) = Self::bind(alg) {
return hash;
}
}
Self::Blake3(Box::new(blake3::Hasher::new()))
}
pub fn backend() -> &'static str {
match *SAFE_HASH_ALG {
Some(alg) => alg.name(),
None => "blake3",
}
}
fn try_update(&mut self, data: &[u8]) -> Result<(), Errno> {
match self {
Self::Kernel(sock) => {
let mut off = 0;
while off < data.len() {
let n = retry_on_eintr(|| send(&sock, &data[off..], MsgFlags::MSG_MORE))?;
if n == 0 {
return Err(Errno::EIO);
}
off = off.checked_add(n).ok_or(Errno::EOVERFLOW)?;
}
Ok(())
}
Self::Blake3(hash) => {
hash.update(data);
Ok(())
}
}
}
fn try_finalize_into(self, out: &mut [u8; SAFE_HASH_SIZE]) -> Result<(), Errno> {
match self {
Self::Kernel(sock) => {
retry_on_eintr(|| send(&sock, &[], MsgFlags::empty()))?;
let mut hdr = MsgHdr::default();
let mut iov = [IoSliceMut::new(&mut out[..])];
hdr.set_iov_mut(&mut iov);
let msg = loop {
match recvmsg(&sock, &mut hdr, MsgFlags::empty()) {
Ok(msg) => break msg,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
};
if msg.bytes != SAFE_HASH_SIZE || msg.flags & MsgFlags::MSG_TRUNC.bits() != 0 {
return Err(Errno::EMSGSIZE);
}
Ok(())
}
Self::Blake3(hash) => {
out.copy_from_slice(hash.finalize().as_bytes());
Ok(())
}
}
}
fn detect() -> Option<SafeHashAlgorithm> {
for &alg in SafeHashAlgorithm::ALL {
let mut hash = if let Ok(hash) = Self::bind(alg) {
hash
} else {
continue;
};
if hash.try_update(Self::COOKIE.as_bytes()).is_err() {
continue;
}
let mut result = [0u8; SAFE_HASH_SIZE];
if hash.try_finalize_into(&mut result).is_err() {
continue;
}
if result[..].ct_eq(&alg.cookie_digest()[..]).into() {
return Some(alg);
}
}
None
}
fn bind(alg: SafeHashAlgorithm) -> Result<Self, Errno> {
let addr = AlgAddr::new("hash", alg.name());
let sock = retry_on_eintr(|| {
safe_socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::SOCK_CLOEXEC,
0,
)
})?;
retry_on_eintr(|| safe_bind(&sock, &addr))?;
let conn = retry_on_eintr(|| safe_accept4(sock.as_fd(), SockFlag::SOCK_CLOEXEC, false))?.0;
Ok(Self::Kernel(conn))
}
}
impl Default for SafeHash {
fn default() -> Self {
Self::new()
}
}
impl digest::HashMarker for SafeHash {}
impl digest::OutputSizeUser for SafeHash {
type OutputSize = digest::consts::U32;
}
impl digest::Update for SafeHash {
#[expect(clippy::disallowed_methods)]
fn update(&mut self, data: &[u8]) {
self.try_update(data).expect(
"BUG: SafeHash::update: AF_ALG send failed after successful detection, report a bug!",
);
}
}
impl digest::FixedOutput for SafeHash {
#[expect(clippy::disallowed_methods)]
fn finalize_into(self, out: &mut digest::Output<Self>) {
let buf: &mut [u8; SAFE_HASH_SIZE] = out
.as_mut_slice()
.try_into()
.expect("BUG: SafeHash::finalize_into: digest::Output length mismatches SAFE_HASH_SIZE, report a bug!");
self.try_finalize_into(buf)
.expect("BUG: SafeHash::finalize_into: AF_ALG finalize failed after successful detection, report a bug!");
}
}
pub fn hmac_sha256_info() -> String {
#[expect(clippy::disallowed_methods)]
let fd = match open("/proc/crypto", OFlag::O_RDONLY, Mode::empty()) {
Ok(fd) => fd.into(),
Err(errno) => return format!("HMAC-SHA256: failed to open /proc/crypto: {errno}!"),
};
match proc_crypto_read(fd) {
Err(errno) => format!("HMAC-SHA256: failed to read /proc/crypto: {errno}!"),
Ok(table) => {
if let Some(blocks) = table.crypto_blocks.get("hmac(sha256)") {
for block in blocks {
if let Type::Shash(sh) = &block.crypto_type {
let selftest = match block.self_test {
SelfTest::Passed => "passed",
SelfTest::Unknown => "unknown",
};
let internal = if block.internal {
"in-kernel"
} else {
"external"
};
let fips = if block.fips_enabled {
"FIPS"
} else {
"no-FIPS"
};
return format!(
"HMAC-SHA256: Secure hash is supported via '{}' driver; \
module '{}'; prio {}; refcnt {}; \
self-test: {}; {}; {}; \
blocksize {}B; digestsize {}B.",
block.driver,
block.module,
block.priority,
block.ref_count,
selftest,
internal,
fips,
sh.block_size,
sh.digest_size,
);
}
}
}
"HMAC-SHA256: Secure hash is unsupported!".to_string()
}
}
}
pub fn hmac_sha256_setup(key_id: KeySerial) -> Result<SafeOwnedFd, Errno> {
let sock = retry_on_eintr(|| {
safe_socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::SOCK_CLOEXEC,
0,
)
})?;
retry_on_eintr(|| safe_bind(&sock, &*HMAC_ADDR))?;
retry_on_eintr(|| setsockopt_serial(&sock, key_id))?;
Ok(sock)
}
pub fn hmac_sha256_init<F: AsRawFd>(fd: &F, nonblock: bool) -> Result<SafeOwnedFd, Errno> {
let mut flags = SockFlag::SOCK_CLOEXEC;
if nonblock {
flags |= SockFlag::SOCK_NONBLOCK;
}
let fd = unsafe { BorrowedFd::borrow_raw(fd.as_raw_fd()) };
retry_on_eintr(|| safe_accept4(fd, flags, false)).map(|(fd, _)| fd)
}
pub fn hmac_sha256_feed<Fd: AsFd>(sock: Fd, chunk: &[u8], more: bool) -> Result<usize, Errno> {
let iov = [IoSlice::new(chunk)];
let flags = if more {
MsgFlags::MSG_MORE
} else {
MsgFlags::empty()
};
retry_on_eintr(|| sendmsg::<_, SockaddrStorage>(&sock, &iov, &[], flags, None))
}
pub fn hmac_sha256_fini<Fd: AsFd>(sock: Fd) -> Result<Zeroizing<Vec<u8>>, Errno> {
let mut data = Vec::new();
data.try_reserve(SHA256_DIGEST_SIZE)
.or(Err(Errno::ENOMEM))?;
data.resize(SHA256_DIGEST_SIZE, 0);
let mut data = Zeroizing::new(data);
let buf: &mut [u8] = data.as_mut();
let mut nread = 0;
while nread < SHA256_DIGEST_SIZE {
#[expect(clippy::arithmetic_side_effects)]
match read(&sock, &mut buf[nread..]) {
Ok(0) => return Err(Errno::EINVAL),
Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
Ok(data)
}
pub fn aes_ctr_info() -> String {
#[expect(clippy::disallowed_methods)]
let fd = match open("/proc/crypto", OFlag::O_RDONLY, Mode::empty()) {
Ok(fd) => fd.into(),
Err(errno) => return format!("AES-CTR: failed to open /proc/crypto: {errno}!"),
};
match proc_crypto_read(fd) {
Err(errno) => format!("AES-CTR: failed to read /proc/crypto: {errno}!"),
Ok(table) => {
if let Some(blocks) = table.crypto_blocks.get("ctr(aes)") {
for block in blocks {
if let Type::Skcipher(sk) = &block.crypto_type {
let selftest = match block.self_test {
SelfTest::Passed => "passed",
SelfTest::Unknown => "unknown",
};
let internal = if block.internal {
"in-kernel"
} else {
"external"
};
let fips = if block.fips_enabled {
"FIPS"
} else {
"no-FIPS"
};
let async_cap = if sk.async_capable { "async" } else { "sync" };
return format!(
"AES-CTR: Symmetric-key cipher is supported via '{}' driver; \
module '{}'; prio {}; refcnt {}; \
self-test: {}; {}; {}; {}; \
key {}–{}B; iv {}B; chunk {}B; walk {}B.",
block.driver,
block.module,
block.priority,
block.ref_count,
selftest,
internal,
fips,
async_cap,
sk.min_key_size,
sk.max_key_size,
sk.iv_size,
sk.chunk_size,
sk.walk_size,
);
}
}
}
"AES-CTR: Symmetric-key cipher is unsupported!".to_string()
}
}
}
pub fn aes_ctr_setup(key_id: KeySerial) -> Result<SafeOwnedFd, Errno> {
let sock = retry_on_eintr(|| {
safe_socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::SOCK_CLOEXEC,
0,
)
})?;
retry_on_eintr(|| safe_bind(&sock, &*AES_ADDR))?;
retry_on_eintr(|| setsockopt_serial(&sock, key_id))?;
Ok(sock)
}
pub fn aes_ctr_init<F: AsRawFd>(fd: &F, nonblock: bool) -> Result<SafeOwnedFd, Errno> {
let mut flags = SockFlag::SOCK_CLOEXEC;
if nonblock {
flags |= SockFlag::SOCK_NONBLOCK;
}
let fd = unsafe { BorrowedFd::borrow_raw(fd.as_raw_fd()) };
retry_on_eintr(|| safe_accept4(fd, flags, false)).map(|(fd, _)| fd)
}
pub fn aes_ctr_enc<Fd: AsFd>(
sock: Fd,
chunk: &[u8],
iv: Option<&IV>,
more: bool,
) -> Result<usize, Errno> {
let flags = if more {
MsgFlags::MSG_MORE
} else {
MsgFlags::empty()
};
let iov = if chunk.is_empty() {
&[][..]
} else {
&[IoSlice::new(chunk)][..]
};
if let Some(iv) = iv {
let cmsgs = &[
Cmsg::AlgSetOp(&libc::ALG_OP_ENCRYPT),
Cmsg::AlgSetIv(iv.as_ref()),
][..];
retry_on_eintr(|| sendmsg::<_, SockaddrStorage>(&sock, iov, cmsgs, flags, None))
} else {
retry_on_eintr(|| sendmsg::<_, SockaddrStorage>(&sock, iov, &[], flags, None))
}
}
pub fn aes_ctr_dec<Fd: AsFd>(
sock: Fd,
chunk: &[u8],
iv: Option<&IV>,
more: bool,
) -> Result<usize, Errno> {
let flags = if more {
MsgFlags::MSG_MORE
} else {
MsgFlags::empty()
};
let iov = if chunk.is_empty() {
&[][..]
} else {
&[IoSlice::new(chunk)][..]
};
if let Some(iv) = iv {
let cmsgs = &[
Cmsg::AlgSetOp(&libc::ALG_OP_DECRYPT),
Cmsg::AlgSetIv(iv.as_ref()),
][..];
retry_on_eintr(|| sendmsg::<_, SockaddrStorage>(&sock, iov, cmsgs, flags, None))
} else {
retry_on_eintr(|| sendmsg::<_, SockaddrStorage>(&sock, iov, &[], flags, None))
}
}
pub fn aes_ctr_fini<Fd: AsFd>(sock: Fd, size: usize) -> Result<Zeroizing<Vec<u8>>, Errno> {
let mut data = Vec::new();
data.try_reserve(size).or(Err(Errno::ENOMEM))?;
data.resize(size, 0);
let mut data = Zeroizing::new(data);
let buf: &mut [u8] = data.as_mut();
let mut nread = 0;
while nread < size {
#[expect(clippy::arithmetic_side_effects)]
match read(&sock, &mut buf[nread..]) {
Ok(0) => return Err(Errno::EINVAL),
Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
Ok(data)
}
#[expect(clippy::cognitive_complexity)]
#[expect(clippy::type_complexity)]
pub fn aes_ctr_tmp<Fd: AsFd>(
setup_fds: (RawFd, RawFd),
fd: Fd,
flags: OFlag,
tmp: Option<RawFd>,
) -> Result<Option<(SafeOwnedFd, IV)>, Errno> {
let (aes_fd, mac_fd) = setup_fds;
#[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 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 hmac_tag = Zeroizing::new([0u8; HMAC_TAG_SIZE]);
let buf = hmac_tag.as_mut();
let mut nread = 0;
while nread < buf.len() {
#[expect(clippy::arithmetic_side_effects)]
match read(&fd, &mut buf[nread..]) {
Ok(0) => {
return Err(Errno::EBADMSG);
}
Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
let mut iv = IV::new([0u8; IV_SIZE]);
let buf = iv.as_mut();
let mut nread = 0;
while nread < buf.len() {
#[expect(clippy::arithmetic_side_effects)]
match read(&fd, &mut buf[nread..]) {
Ok(0) => {
return Err(Errno::EBADMSG);
}
Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
(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-")
} else {
safe_memfd_create(c"syd/aes", *SAFE_MFD_FLAGS)
}?;
if let Some(hmac_tag) = tag {
let sock_mac = hmac_sha256_init(&mac_fd, false)?;
hmac_sha256_feed(&sock_mac, CRYPT_MAGIC, true)?;
hmac_sha256_feed(&sock_mac, iv.as_ref(), true)?;
let sock_dec = aes_ctr_init(&aes_fd, false)?;
aes_ctr_dec(&sock_dec, &[], Some(&iv), true)?;
let (pipe_rd_dec, pipe_wr_dec) = safe_pipe2(OFlag::O_CLOEXEC)?;
let (pipe_rd_mac, pipe_wr_mac) = safe_pipe2(OFlag::O_CLOEXEC)?;
#[expect(clippy::arithmetic_side_effects)]
let mut datasz = size - CRYPT_MAGIC.len() - HMAC_TAG_SIZE - IV_SIZE;
let mut nflush = 0;
while datasz > 0 {
let len = datasz.min(PIPE_BUF_ALG);
let n = retry_on_eintr(|| {
splice(
&fd,
None,
&pipe_wr_dec,
None,
len,
SpliceFFlags::SPLICE_F_MORE,
)
})?;
if n == 0 {
break;
}
let mut ntee = n;
#[expect(clippy::arithmetic_side_effects)]
while ntee > 0 {
let n_tee = retry_on_eintr(|| {
tee(&pipe_rd_dec, &pipe_wr_mac, ntee, SpliceFFlags::empty())
})?;
if n_tee == 0 {
return Err(Errno::EBADMSG);
}
ntee -= n_tee;
}
let mut ncopy = n;
#[expect(clippy::arithmetic_side_effects)]
while ncopy > 0 {
let n = retry_on_eintr(|| {
splice(
&pipe_rd_dec,
None,
&sock_dec,
None,
ncopy,
SpliceFFlags::SPLICE_F_MORE,
)
})?;
if n == 0 {
return Err(Errno::EBADMSG);
}
ncopy -= n;
datasz -= n;
nflush += n;
}
let mut ncopy = n;
#[expect(clippy::arithmetic_side_effects)]
while ncopy > 0 {
let n = retry_on_eintr(|| {
splice(
&pipe_rd_mac,
None,
&sock_mac,
None,
ncopy,
SpliceFFlags::SPLICE_F_MORE,
)
})?;
if n == 0 {
return Err(Errno::EBADMSG);
}
ncopy -= n;
}
#[expect(clippy::arithmetic_side_effects)]
while nflush > BLOCK_SIZE {
let len = nflush - (nflush % BLOCK_SIZE);
let n = retry_on_eintr(|| {
splice(
&sock_dec,
None,
&pipe_wr_dec,
None,
len,
SpliceFFlags::empty(),
)
})?;
if n == 0 {
return Err(Errno::EBADMSG);
}
let mut ncopy = n;
while ncopy > 0 {
let n = retry_on_eintr(|| {
splice(
&pipe_rd_dec,
None,
&dst_fd,
None,
ncopy,
SpliceFFlags::empty(),
)
})?;
if n == 0 {
return Err(Errno::EBADMSG);
}
ncopy -= n;
nflush -= n;
}
}
}
while nflush > 0 {
match aes_ctr_dec(&sock_dec, &[], None, false) {
Ok(_) | Err(Errno::EINVAL) => {}
Err(errno) => return Err(errno),
}
let len = nflush.min(PIPE_BUF_ALG);
let n = retry_on_eintr(|| {
splice(
&sock_dec,
None,
&pipe_wr_dec,
None,
len,
SpliceFFlags::empty(),
)
})?;
if n == 0 {
return Err(Errno::EBADMSG);
}
let mut ncopy = n;
#[expect(clippy::arithmetic_side_effects)]
while ncopy > 0 {
let n = retry_on_eintr(|| {
splice(
&pipe_rd_dec,
None,
&dst_fd,
None,
ncopy,
SpliceFFlags::empty(),
)
})?;
if n == 0 {
return Err(Errno::EBADMSG);
}
ncopy -= n;
nflush -= n;
}
}
let computed_hmac = hmac_sha256_fini(&sock_mac)?;
if hmac_tag.ct_ne(&computed_hmac).into() {
return Err(Errno::EBADMSG);
}
}
if flags.contains(OFlag::O_APPEND) {
set_append(&dst_fd, true)?
} 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 aes_ctr_feed<S: AsFd, F: AsFd>(sock: S, fd: F, buf: &mut [u8]) -> Result<usize, Errno> {
let mut nread = 0;
while nread < buf.len() {
#[expect(clippy::arithmetic_side_effects)]
match read(&fd, &mut buf[nread..]) {
Ok(0) => break, Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
let mut nwrite = 0;
while nwrite < nread {
#[expect(clippy::arithmetic_side_effects)]
match send(sock.as_fd(), &buf[nwrite..nread], MsgFlags::MSG_MORE) {
Ok(0) => return Err(Errno::EINVAL),
Ok(n) => nwrite += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
Ok(nwrite)
}
pub fn aes_ctr_flush<S: AsFd, F: AsFd>(
sock: S,
fd: F,
buf: &mut [u8],
size: usize,
) -> Result<usize, Errno> {
assert!(buf.len() >= size);
let mut nread = 0;
while nread < size {
#[expect(clippy::arithmetic_side_effects)]
match read(&sock, &mut buf[nread..size]) {
Ok(0) => return Err(Errno::EINVAL),
Ok(n) => nread += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
let mut nwrite = 0;
while nwrite < nread {
#[expect(clippy::arithmetic_side_effects)]
match write(&fd, &buf[nwrite..nread]) {
Ok(0) => return Err(Errno::EINVAL),
Ok(n) => nwrite += n,
Err(Errno::EINTR) => continue,
Err(errno) => return Err(errno),
}
}
Ok(nwrite)
}
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) -> String {
let rnd = get_at_random();
if upper {
HEXUPPER.encode(rnd)
} else {
HEXLOWER.encode(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>;
#[cfg(test)]
mod tests {
use std::io::Write;
use digest::Digest;
use nix::{fcntl::open, sys::stat::Mode};
use super::*;
use crate::{compat::MFdFlags, cookie::safe_memfd_create, fd::open_static_proc};
#[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(), 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(), 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_secret_1() {
let secret = Secret::new(0, 0);
assert!(matches!(secret, Secret::Key(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_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);
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);
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);
assert!(lower.chars().all(|c| !c.is_ascii_uppercase()));
}
#[test]
fn test_get_at_random_hex_4() {
let upper = get_at_random_hex(true);
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() {
open_static_proc().expect("open_static_proc");
match hash_list() {
Ok(algs) => {
assert!(!algs.is_empty());
let mut sorted = algs.clone();
sorted.sort();
sorted.dedup();
assert_eq!(algs, sorted);
}
Err(Errno::ENOENT) => {}
Err(errno) => panic!("hash_list failed: {errno}"),
}
}
#[test]
fn test_hash_auto() {
match hash_auto() {
Some(alg) => assert!(!alg.is_empty()),
None => {}
}
}
#[test]
fn test_safe_hash_1() {
for &alg in SafeHashAlgorithm::ALL {
let mut hash = match SafeHash::bind(alg) {
Ok(hash) => hash,
Err(Errno::ENOENT) | Err(Errno::EAFNOSUPPORT) => continue,
Err(errno) => panic!("bind({}) failed: {errno}", alg.name()),
};
hash.try_update(SafeHash::COOKIE.as_bytes())
.unwrap_or_else(|e| panic!("update for {}: {e}", alg.name()));
let mut got = [0u8; SAFE_HASH_SIZE];
hash.try_finalize_into(&mut got)
.unwrap_or_else(|e| panic!("finalize for {}: {e}", alg.name()));
assert_eq!(
got,
alg.cookie_digest(),
"COOKIE digest mismatch for {}",
alg.name()
);
}
}
#[test]
fn test_safe_hash_2() {
let one = <SafeHash as Digest>::digest(b"hello world");
let mut hash = SafeHash::new();
digest::Update::update(&mut hash, b"hello ");
digest::Update::update(&mut hash, b"world");
let stream = hash.finalize();
assert_eq!(one, stream);
}
#[test]
fn test_safe_hash_3() {
let digest = <SafeHash as Digest>::digest(b"");
assert_eq!(digest.len(), SAFE_HASH_SIZE);
}
#[test]
fn test_safe_hash_4() {
let fresh = SafeHash::detect();
assert_eq!(fresh, *SAFE_HASH_ALG);
}
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 check_kernel_crypto_support() -> bool {
let key = Key::random().unwrap();
let key_id = match add_key(
"user",
"SYD-3-CRYPT-TEST",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
) {
Ok(key_id) => key_id,
Err(Errno::EAFNOSUPPORT | Errno::ENOSYS) => {
eprintln!("Test requires Linux keyrings(7) API, skipping!");
return false;
}
Err(Errno::EACCES) => {
eprintln!("Is your session keyring attached to your user keyring?");
eprintln!("Test requires Linux keyrings(7) API, skipping!");
return false;
}
Err(errno) => {
eprintln!("Failed to test for Linux keyrings(7) API: {errno}");
return false;
}
};
match aes_ctr_setup(key_id) {
Ok(fd) => drop(fd),
Err(Errno::EAFNOSUPPORT) => {
eprintln!("Test requires Linux Kernel Cryptography API, skipping!");
return false;
}
Err(Errno::EACCES) => {
eprintln!("Is your session keyring attached to your user keyring?");
eprintln!("Test requires Linux keyrings(7) API, skipping!");
return false;
}
Err(errno) => {
eprintln!("Failed to test for Linux Kernel Cryptography API: {errno}");
return false;
}
}
match hmac_sha256_setup(key_id) {
Ok(fd) => drop(fd),
Err(Errno::EAFNOSUPPORT) => {
eprintln!("Test requires Linux Kernel Cryptography API, skipping!");
return false;
}
Err(Errno::EACCES) => {
eprintln!("Is your session keyring attached to your user keyring?");
eprintln!("Test requires Linux keyrings(7) API, skipping!");
return false;
}
Err(errno) => {
eprintln!("Failed to test for Linux Kernel Cryptography API: {errno}");
return false;
}
}
true
}
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
}
const HASH_TEST_CASES: &[HashTestCase] = &[
HashTestCase(b"", "00000000", "crc32"),
HashTestCase(b"abc", "D09865CA", "crc32"),
HashTestCase(b"", "00000000", "crc32c"),
HashTestCase(b"abc", "B73F4B36", "crc32c"),
HashTestCase(b"", "31D6CFE0D16AE931B73C59D7E0C089C0", "md4"),
HashTestCase(b"abc", "A448017AAF21D8525FC10AE87AA6729D", "md4"),
HashTestCase(b"", "D41D8CD98F00B204E9800998ECF8427E", "md5"),
HashTestCase(b"abc", "900150983CD24FB0D6963F7D28E17F72", "md5"),
HashTestCase(b"", "9C1185A5C5E9FC54612808977EE8F548B2258D31", "rmd160"),
HashTestCase(b"abc", "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC", "rmd160"),
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"",
"38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B",
"sha384",
),
HashTestCase(
b"abc",
"CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7",
"sha384",
),
HashTestCase(
b"",
"CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715DC83F4A921D36CE9CE47D0D13C5D85F2B0FF8318D2877EEC2F63B931BD47417A81A538327AF927DA3E",
"sha512",
),
HashTestCase(
b"abc",
"DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F",
"sha512",
),
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"",
"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"", "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"", "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",
),
HashTestCase(b"", "99E9D85137DB46EF", "xxhash64"),
HashTestCase(b"abc", "990977ADF52CBC44", "xxhash64"),
];
#[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) => HEXUPPER.encode(&digest),
Err(Errno::EAFNOSUPPORT | Errno::ENOENT) => {
eprintln!(
"Kernel Crypto API not available for '{}', skipping!",
case.2
);
return;
}
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: &[(&str, &str)] = &[
("crc32", "22745CCE"),
("crc32c", "40E26F43"),
("md4", "BBCE80CC6BB65E5C6745E30D4EECA9A4"),
("md5", "7707D6AE4E027C70EEA2A935C2296F21"),
("rmd160", "52783243C1697BDBE16D37F97F68F08325DC1528"),
("sha1", "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"),
("sha224", "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2EE4EE7AD67"),
("sha256", "CDC76E5C9914FB9281A1C7E284D73E67F1809A48A497200E046D39CCC7112CD0"),
("sha384", "9D0E1809716474CB086E834E310A4A1CED149E9C00F248527972CEC5704C2A5B07B8B3DC38ECC4EBAE97DDD87F3D8985"),
("sha512", "E718483D0CE769644E2E42C7BC15B4638E1F98B13B2044285632A803AFA973EBDE0FF244877EA60A4CB0432CE577C31BEB009C5C2C49AA2E4EADB217AD8CC09B"),
("sha3-224", "D69335B93325192E516A912E6D19A15CB51C6ED5C15243E7A7FD653C"),
("sha3-256", "5C8875AE474A3634BA4FD55EC85BFFD661F32ACA75C6D699D0CDCB6C115891C1"),
("sha3-384", "EEE9E24D78C1855337983451DF97C8AD9EEDF256C6334F8E948D252D5E0E76847AA0774DDB90A842190D2C558B4B8340"),
("sha3-512", "3C3A876DA14034AB60627C077BB98F7E120A2A5370212DFFB3385A18D4F38859ED311D0A9D5141CE9CC5C66EE689B266A8AA18ACE8282A0E0DB596C90B0A7B87"),
("sm3", "C8AAF89429554029E231941A2ACC0AD61FF2A5ACD8FADD25847A3A732B3B02C3"),
("streebog256", "841AF1A0B2F92A800FB1B7E4AABC8E48763153C448A0FC57C90BA830E130F152"),
("streebog512", "D396A40B126B1F324465BFA7AA159859AB33FAC02DCDD4515AD231206396A266D0102367E4C544EF47D2294064E1A25342D0CD25AE3D904B45ABB1425AE41095"),
("blake2b-160", "9B512A5ED7D52DDEB8D8762E4B6DD880B25EA54D"),
("blake2b-256", "0741850F36CBA4259628355D1073E24DDB9CA0E1BFAC36FD39AE5DC2101E23A4"),
("blake2b-384", "92650B7746765A98701EC2077C3603127C62525C8543477C8519D6CC53AC5A9F0098ED56EB7AAF03CA50BFE046E7BBA3"),
("blake2b-512", "98FB3EFB7206FD19EBF69B6F312CF7B64E3B94DBE1A17107913975A793F177E1D077609D7FBA363CBBA00D05F7AA4E4FA8715D6428104C0A75643B0FF3FD3EAF"),
("wp256", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF5"),
("wp384", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD"),
("wp512", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD8C6008AD677F77126953B226E4ED8B01"),
("xxhash64", "40DC4F9BAA3A48DC"),
];
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) => HEXUPPER.encode(&digest),
Err(Errno::EAFNOSUPPORT | Errno::ENOENT) => {
eprintln!("{alg}: not supported by this kernel, skipping.");
continue;
}
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::EAFNOSUPPORT | Errno::ENOENT)),
"{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) => HEXUPPER.encode(&digest),
Err(Errno::EAFNOSUPPORT | Errno::ENOENT) => {
eprintln!(
"Kernel Crypto API not available for '{}', skipping!",
case.2
);
return;
}
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: &[(&str, &str)] = &[
("crc32", "22745CCE"),
("crc32c", "40E26F43"),
("md4", "BBCE80CC6BB65E5C6745E30D4EECA9A4"),
("md5", "7707D6AE4E027C70EEA2A935C2296F21"),
("rmd160", "52783243C1697BDBE16D37F97F68F08325DC1528"),
("sha1", "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"),
("sha224", "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2EE4EE7AD67"),
("sha256", "CDC76E5C9914FB9281A1C7E284D73E67F1809A48A497200E046D39CCC7112CD0"),
("sha384", "9D0E1809716474CB086E834E310A4A1CED149E9C00F248527972CEC5704C2A5B07B8B3DC38ECC4EBAE97DDD87F3D8985"),
("sha512", "E718483D0CE769644E2E42C7BC15B4638E1F98B13B2044285632A803AFA973EBDE0FF244877EA60A4CB0432CE577C31BEB009C5C2C49AA2E4EADB217AD8CC09B"),
("sha3-224", "D69335B93325192E516A912E6D19A15CB51C6ED5C15243E7A7FD653C"),
("sha3-256", "5C8875AE474A3634BA4FD55EC85BFFD661F32ACA75C6D699D0CDCB6C115891C1"),
("sha3-384", "EEE9E24D78C1855337983451DF97C8AD9EEDF256C6334F8E948D252D5E0E76847AA0774DDB90A842190D2C558B4B8340"),
("sha3-512", "3C3A876DA14034AB60627C077BB98F7E120A2A5370212DFFB3385A18D4F38859ED311D0A9D5141CE9CC5C66EE689B266A8AA18ACE8282A0E0DB596C90B0A7B87"),
("sm3", "C8AAF89429554029E231941A2ACC0AD61FF2A5ACD8FADD25847A3A732B3B02C3"),
("streebog256", "841AF1A0B2F92A800FB1B7E4AABC8E48763153C448A0FC57C90BA830E130F152"),
("streebog512", "D396A40B126B1F324465BFA7AA159859AB33FAC02DCDD4515AD231206396A266D0102367E4C544EF47D2294064E1A25342D0CD25AE3D904B45ABB1425AE41095"),
("blake2b-160", "9B512A5ED7D52DDEB8D8762E4B6DD880B25EA54D"),
("blake2b-256", "0741850F36CBA4259628355D1073E24DDB9CA0E1BFAC36FD39AE5DC2101E23A4"),
("blake2b-384", "92650B7746765A98701EC2077C3603127C62525C8543477C8519D6CC53AC5A9F0098ED56EB7AAF03CA50BFE046E7BBA3"),
("blake2b-512", "98FB3EFB7206FD19EBF69B6F312CF7B64E3B94DBE1A17107913975A793F177E1D077609D7FBA363CBBA00D05F7AA4E4FA8715D6428104C0A75643B0FF3FD3EAF"),
("wp256", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF5"),
("wp384", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD"),
("wp512", "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD8C6008AD677F77126953B226E4ED8B01"),
("xxhash64", "40DC4F9BAA3A48DC"),
];
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) => HEXUPPER.encode(&digest),
Err(Errno::EAFNOSUPPORT | Errno::ENOENT) => {
eprintln!("{alg}: not supported by this kernel, skipping.");
continue;
}
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::EAFNOSUPPORT | Errno::ENOENT)),
"{result:?}"
);
}
#[test]
fn test_hmac_sha256() {
if !check_kernel_crypto_support() {
return;
}
let mut errors = Vec::new();
for (i, test_case) in HMAC_TEST_CASES.iter().enumerate() {
let key = test_case.0;
let data = test_case.1;
let expected_hmac = test_case.2.to_lowercase();
let key_id = add_key("user", "SYD-3-CRYPT-TEST", &key, KEY_SPEC_USER_KEYRING).unwrap();
let setup_fd = match hmac_sha256_setup(key_id) {
Ok(fd) => fd,
Err(Errno::EAFNOSUPPORT) => {
eprintln!("KCAPI not supported, skipping!");
continue;
}
Err(Errno::EACCES) => {
eprintln!("Session keyring isn't linked to user keyring, skipping!");
continue;
}
Err(errno) => {
errors.push(format!(
"Test case {}: hmac_sha256_setup failed with error: {errno:?}",
i + 1,
));
continue;
}
};
let init_sock = match hmac_sha256_init(&setup_fd, false) {
Ok(sock) => sock,
Err(errno) => {
errors.push(format!(
"Test case {}: hmac_sha256_init failed with error: {errno:?}",
i + 1,
));
continue;
}
};
let feed_result = hmac_sha256_feed(&init_sock, data, false);
if let Err(errno) = feed_result {
errors.push(format!(
"Test case {}: hmac_sha256_feed failed with error: {errno:?}",
i + 1,
));
continue;
}
let hmac_result = match hmac_sha256_fini(&init_sock) {
Ok(hmac) => hmac,
Err(errno) => {
errors.push(format!(
"Test case {}: hmac_sha256_fini failed with error: {errno:?}",
i + 1,
));
continue;
}
};
let computed_hex = HEXLOWER.encode(hmac_result.as_slice());
if i == 5 {
} else if expected_hmac.len() < 64 {
if !computed_hex.starts_with(&expected_hmac) {
errors.push(format!(
"Test case {}: Mismatch.\nExpected (prefix): {}\nGot: {}",
i + 1,
expected_hmac,
&computed_hex[..expected_hmac.len()]
));
}
} else {
if computed_hex != expected_hmac {
errors.push(format!(
"Test case {}: Mismatch.\nExpected: {}\nGot: {}",
i + 1,
expected_hmac,
computed_hex
));
}
}
}
assert!(
errors.is_empty(),
"HMAC-SHA256 Test failures:\n{}",
errors.join("\n")
);
}
#[test]
fn test_aes_ctr_setup() {
if !check_kernel_crypto_support() {
return;
}
let key = Key::random().unwrap();
assert!(!key.is_zero(), "key is all zeros!");
let key_id = add_key(
"user",
"SYD-3-CRYPT-TEST",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
.unwrap();
match aes_ctr_setup(key_id).map(drop) {
Ok(()) => {}
Err(Errno::EAFNOSUPPORT) => {
eprintln!("KCAPI not supported, skipping!");
return;
}
Err(Errno::EACCES) => {
eprintln!("Session keyring isn't linked to user keyring, skipping!");
return;
}
Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
};
}
#[test]
fn test_aes_ctr_init() {
if !check_kernel_crypto_support() {
return;
}
let key = Key::random().unwrap();
assert!(!key.is_zero(), "key is all zeros!");
let key_id = add_key(
"user",
"SYD-3-CRYPT-TEST",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
.unwrap();
let setup_fd = match aes_ctr_setup(key_id) {
Ok(fd) => fd,
Err(Errno::EAFNOSUPPORT) => {
eprintln!("KCAPI not supported, skipping!");
return;
}
Err(Errno::EACCES) => {
eprintln!("Session keyring isn't linked to user keyring, skipping!");
return;
}
Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
};
let result = aes_ctr_init(&setup_fd, false);
assert!(result.is_ok());
}
#[test]
fn test_aes_ctr_enc_1() {
if !check_kernel_crypto_support() {
return;
}
let key = Key::random().unwrap();
assert!(!key.is_zero(), "key is all zeros!");
let key_id = add_key(
"user",
"SYD-3-CRYPT-TEST",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
.unwrap();
let iv = IV::random().unwrap();
assert!(!iv.is_zero(), "iv is all zeros!");
let setup_fd = match aes_ctr_setup(key_id) {
Ok(fd) => fd,
Err(Errno::EAFNOSUPPORT) => {
eprintln!("KCAPI not supported, skipping!");
return;
}
Err(Errno::EACCES) => {
eprintln!("Session keyring isn't linked to user keyring, skipping!");
return;
}
Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
};
let sock_enc = aes_ctr_init(&setup_fd, false).unwrap();
aes_ctr_enc(&sock_enc, &[], Some(&iv), true).unwrap();
let data =
b"Change return success. Going and coming without error. Action brings good fortune.";
let encrypted_size = aes_ctr_enc(&sock_enc, data, None, false).unwrap();
assert_eq!(encrypted_size, data.len());
let encrypted_data = aes_ctr_fini(&sock_enc, encrypted_size).unwrap();
assert_eq!(encrypted_data.len(), encrypted_size,);
drop(sock_enc);
let sock_dec = aes_ctr_init(&setup_fd, false).unwrap();
aes_ctr_dec(&sock_dec, &[], Some(&iv), true).unwrap();
let decrypted_size = aes_ctr_dec(&sock_dec, &encrypted_data.as_ref(), None, false).unwrap();
assert_eq!(decrypted_size, encrypted_size);
let decrypted_data = aes_ctr_fini(&sock_dec, encrypted_size).unwrap();
assert_eq!(decrypted_data.as_slice(), data);
}
#[test]
#[ignore]
fn test_aes_ctr_enc_2() {
if !check_kernel_crypto_support() {
return;
}
let key = Key::random().unwrap();
assert!(!key.is_zero(), "key is all zeros!");
let key_id = add_key(
"user",
"SYD-3-CRYPT-TEST",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
.unwrap();
let iv = IV::random().unwrap();
assert!(!iv.is_zero(), "iv is all zeros!");
let setup_fd = match aes_ctr_setup(key_id) {
Ok(fd) => fd,
Err(Errno::EAFNOSUPPORT) => {
eprintln!("KCAPI not supported, skipping!");
return;
}
Err(Errno::EACCES) => {
eprintln!("Session keyring isn't linked to user keyring, skipping!");
return;
}
Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
};
eprintln!("INITIALIZING ENCRYPTION");
let sock = aes_ctr_init(&setup_fd, false).unwrap();
eprintln!("SETTING IV");
aes_ctr_enc(&sock, &[], Some(&iv), true).unwrap();
let data_chunks = vec![
b"Heavy is ".to_vec(),
b"the root of light. ".to_vec(),
b"Still is ".to_vec(),
b"the master of moving.".to_vec(),
];
let mut total_encrypted_size = 0;
for (i, chunk) in data_chunks.iter().enumerate() {
let more = if i < data_chunks.len() - 1 {
true
} else {
false
};
eprintln!("ENCRYPTING CHUNK {i}");
let enc_result = aes_ctr_enc(&sock, chunk, None, more);
assert!(enc_result.is_ok(), "{enc_result:?}");
total_encrypted_size += enc_result.unwrap();
}
eprintln!("FINALIZING ENCRYPTION");
let encrypted_data = aes_ctr_fini(&sock, total_encrypted_size).unwrap();
drop(sock);
eprintln!("STARTING DECRYPTION");
let sock_dec = aes_ctr_init(&setup_fd, false).unwrap();
eprintln!("SETTING IV");
aes_ctr_dec(&sock_dec, &[], Some(&iv), true).unwrap();
eprintln!("WRITING ENCRYPTED DATA");
let dec_result = aes_ctr_dec(&sock_dec, &encrypted_data.as_ref(), None, false).unwrap();
assert_eq!(dec_result, total_encrypted_size);
eprintln!("FINALIZING DECRYPTION");
let decrypted_data = aes_ctr_fini(&sock_dec, total_encrypted_size).unwrap();
assert_eq!(
decrypted_data.len(),
total_encrypted_size,
"{:?}",
decrypted_data.as_slice()
);
let original_data: Vec<u8> = data_chunks.concat();
assert_eq!(decrypted_data.as_slice(), original_data.as_slice());
}
#[test]
fn test_aes_ctr_enc_3() {
if !check_kernel_crypto_support() {
return;
}
let key = Key::random().unwrap();
assert!(!key.is_zero(), "key is all zeros!");
let enc_key_id = add_key(
"user",
"SYD-3-CRYPT-TEST-MAIN",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
.unwrap();
let mac_key_id = add_key(
"user",
"SYD-3-CRYPT-TEST-AUTH",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
.unwrap();
let iv = IV::random().unwrap();
assert!(!iv.is_zero(), "iv is all zeros!");
let mut secret = Secret::new(enc_key_id, mac_key_id);
if let Err(errno) = secret.init() {
if errno == Errno::EAFNOSUPPORT {
eprintln!("KCAPI not supported, skipping!");
return;
} else if errno == Errno::EACCES {
eprintln!("Session keyring isn't linked to user keyring, skipping!");
return;
}
panic!("Secret::init failed with error: {errno}");
};
let (setup_enc, setup_mac) = if let Secret::Alg(setup_enc, setup_mac) = secret {
(setup_enc, setup_mac)
} else {
panic!("Secret::init failed to mutate key!");
};
let sock_enc = aes_ctr_init(&setup_enc, false).unwrap();
aes_ctr_enc(&sock_enc, &[], Some(&iv), true).unwrap();
let data =
b"Change return success. Going and coming without error. Action brings good fortune.";
let total_size = data.len();
let encrypted_size = aes_ctr_enc(&sock_enc, data, None, false).unwrap();
assert_eq!(encrypted_size, total_size);
let encrypted_data = aes_ctr_fini(&sock_enc, encrypted_size).unwrap();
drop(sock_enc);
let sock_mac = hmac_sha256_init(&setup_mac, false).unwrap();
hmac_sha256_feed(&sock_mac, &CRYPT_MAGIC, true).unwrap();
hmac_sha256_feed(&sock_mac, iv.as_ref(), true).unwrap();
hmac_sha256_feed(&sock_mac, data, false).unwrap();
let hmac_tag = hmac_sha256_fini(&sock_mac).unwrap();
let encrypted_memfd = safe_memfd_create(c"syd", MFdFlags::empty()).unwrap();
let nwrite = write(encrypted_memfd.as_fd(), CRYPT_MAGIC).unwrap();
assert_eq!(nwrite, CRYPT_MAGIC.len());
let nwrite = write(encrypted_memfd.as_fd(), hmac_tag.as_ref()).unwrap();
assert_eq!(nwrite, HMAC_TAG_SIZE);
let nwrite = write(encrypted_memfd.as_fd(), iv.as_ref()).unwrap();
assert_eq!(nwrite, IV_SIZE);
let nwrite = write(encrypted_memfd.as_fd(), &encrypted_data.as_ref()).unwrap();
assert_eq!(nwrite, encrypted_data.len());
let sock_dec = aes_ctr_init(&setup_enc, false).unwrap();
let tmp_dir = open("/tmp", OFlag::O_RDONLY, Mode::empty()).unwrap();
let (decrypted_memfd, _) = match aes_ctr_tmp(
(sock_dec.as_raw_fd(), sock_mac.as_raw_fd()),
&encrypted_memfd,
OFlag::empty(),
Some(tmp_dir.as_raw_fd()),
) {
Ok(fd) => fd.unwrap(),
Err(Errno::EOPNOTSUPP) => {
return;
}
Err(errno) => {
panic!("aes_ctr_tmp failed: {errno}");
}
};
drop(sock_dec);
let mut decrypted_data = vec![0u8; total_size];
lseek64(
&decrypted_memfd,
(CRYPT_MAGIC.len() + IV_SIZE) as i64,
Whence::SeekSet,
)
.unwrap();
read(decrypted_memfd, &mut decrypted_data).unwrap();
assert_eq!(
decrypted_data,
data,
"mismatch: {decrypted_data:?} != {data:?} ({} != {}, {} != {})",
String::from_utf8_lossy(&decrypted_data),
String::from_utf8_lossy(data),
decrypted_data.len(),
data.len()
);
}
}