use std::{
ffi::CString,
hash::BuildHasher,
io::{IoSlice, Read},
os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd},
sync::LazyLock,
};
#[expect(clippy::disallowed_types)]
use ahash::{AHasher, RandomState};
use bitflags::bitflags;
use crc::{Crc, CRC_32_ISO_HDLC, CRC_64_ECMA_182};
use data_encoding::{HEXLOWER, HEXLOWER_PERMISSIVE, HEXUPPER};
use lexis::ToName;
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::{open, splice, tee, OFlag, SpliceFFlags},
sys::{
socket::{
bind, send, sendmsg, socket, AddressFamily, AlgAddr, ControlMessage, MsgFlags,
SockFlag, SockType,
},
stat::Mode,
},
unistd::{lseek64, read, write, Whence},
};
use procfs_core::{SelfTest, Type};
use sha1::Sha1;
use sha3::{Digest, Sha3_256, Sha3_384, Sha3_512};
use subtle::ConstantTimeEq;
use zeroize::Zeroizing;
use crate::{
config::*,
cookie::{safe_accept4, safe_pipe2},
err::SydResult,
fs::{create_memfd, retry_on_eintr, set_append, set_nonblock},
path::XPath,
proc::proc_crypto_read,
rng::{fillrandom, mkstempat},
};
#[derive(Debug, Clone, Copy)]
pub enum HashAlgorithm {
Crc32,
Crc64,
Md5,
Sha1,
Sha256,
Sha384,
Sha512,
}
impl TryFrom<usize> for HashAlgorithm {
type Error = Errno;
fn try_from(len: usize) -> Result<Self, Self::Error> {
match len {
4 => Ok(Self::Crc32),
8 => Ok(Self::Crc64),
16 => Ok(Self::Md5),
20 => Ok(Self::Sha1),
32 => Ok(Self::Sha256),
48 => Ok(Self::Sha384),
64 => Ok(Self::Sha512),
_ => Err(Errno::EINVAL),
}
}
}
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;
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(crate) const MSG_MORE: MsgFlags = MsgFlags::from_bits_retain(0x8000);
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() -> Self {
let atrnd = get_at_random();
let mut bytes = [0u8; IV_SIZE];
bytes.copy_from_slice(&atrnd[..IV_SIZE]);
let mut bytes = Zeroizing::new(bytes);
let _ = fillrandom(bytes.as_mut());
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,
std::mem::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 fn hash<R: Read>(mut reader: R, func: HashAlgorithm) -> SydResult<Vec<u8>> {
enum HashState<'a> {
Crc32(crc::Digest<'a, u32>),
Crc64(crc::Digest<'a, u64>),
Md5(md5::Context),
Sha1(Sha1),
Sha3_256(Sha3_256),
Sha3_384(Sha3_384),
Sha3_512(Sha3_512),
}
let crc32 = Crc::<u32>::new(&CRC_32_ISO_HDLC);
let crc64 = Crc::<u64>::new(&CRC_64_ECMA_182);
let mut hasher_state = match func {
HashAlgorithm::Crc32 => HashState::Crc32(crc32.digest()),
HashAlgorithm::Crc64 => HashState::Crc64(crc64.digest()),
HashAlgorithm::Md5 => HashState::Md5(md5::Context::new()),
HashAlgorithm::Sha1 => HashState::Sha1(Sha1::new()),
HashAlgorithm::Sha256 => HashState::Sha3_256(Sha3_256::new()),
HashAlgorithm::Sha384 => HashState::Sha3_384(Sha3_384::new()),
HashAlgorithm::Sha512 => HashState::Sha3_512(Sha3_512::new()),
};
let mut buffer = [0u8; 0x10000];
loop {
let read_count = match reader.read(&mut buffer) {
Ok(0) => break,
Ok(n) => n,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
};
match &mut hasher_state {
HashState::Crc32(d) => d.update(&buffer[..read_count]),
HashState::Crc64(d) => d.update(&buffer[..read_count]),
HashState::Md5(c) => c.consume(&buffer[..read_count]),
HashState::Sha1(s) => s.update(&buffer[..read_count]),
HashState::Sha3_256(s) => s.update(&buffer[..read_count]),
HashState::Sha3_384(s) => s.update(&buffer[..read_count]),
HashState::Sha3_512(s) => s.update(&buffer[..read_count]),
}
}
let digest = match hasher_state {
HashState::Crc32(d) => d.finalize().to_be_bytes().to_vec(),
HashState::Crc64(d) => d.finalize().to_be_bytes().to_vec(),
HashState::Md5(s) => s.finalize().to_vec(),
HashState::Sha1(s) => s.finalize().to_vec(),
HashState::Sha3_256(s) => s.finalize().to_vec(),
HashState::Sha3_384(s) => s.finalize().to_vec(),
HashState::Sha3_512(s) => s.finalize().to_vec(),
};
Ok(digest)
}
pub fn hmac_sha256_info() -> String {
#[expect(clippy::disallowed_methods)]
let fd = match open("/proc/crypto", OFlag::O_RDONLY, Mode::empty()) {
Ok(fd) => fd,
Err(e) => return format!("HMAC-SHA256: failed to open /proc/crypto: {e}!"),
};
match proc_crypto_read(fd) {
Err(e) => format!("HMAC-SHA256: failed to read /proc/crypto: {e}!"),
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<OwnedFd, Errno> {
let sock = socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::empty(),
None,
)?;
bind(sock.as_raw_fd(), &*HMAC_ADDR)?;
setsockopt_serial(&sock, key_id)?;
Ok(sock)
}
pub fn hmac_sha256_init<F: AsRawFd>(fd: &F, nonblock: bool) -> Result<OwnedFd, 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(|| unsafe {
safe_accept4(fd, std::ptr::null_mut(), std::ptr::null_mut(), flags)
})
}
pub fn hmac_sha256_feed<S: AsRawFd>(sock: &S, chunk: &[u8], more: bool) -> Result<usize, Errno> {
let iov = [IoSlice::new(chunk)];
let flags = if more { MSG_MORE } else { MsgFlags::empty() };
retry_on_eintr(|| sendmsg::<()>(sock.as_raw_fd(), &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,
Err(e) => return format!("AES-CTR: failed to open /proc/crypto: {e}!"),
};
match proc_crypto_read(fd) {
Err(e) => format!("AES-CTR: failed to read /proc/crypto: {e}!"),
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<OwnedFd, Errno> {
let sock = socket(
AddressFamily::Alg,
SockType::SeqPacket,
SockFlag::empty(),
None,
)?;
bind(sock.as_raw_fd(), &*AES_ADDR)?;
setsockopt_serial(&sock, key_id)?;
Ok(sock)
}
pub fn aes_ctr_init<F: AsRawFd>(fd: &F, nonblock: bool) -> Result<OwnedFd, 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(|| unsafe {
safe_accept4(fd, std::ptr::null_mut(), std::ptr::null_mut(), flags)
})
}
pub fn aes_ctr_enc<Fd: AsFd>(
sock: Fd,
chunk: &[u8],
iv: Option<&IV>,
more: bool,
) -> Result<usize, Errno> {
let flags = if more { MSG_MORE } else { MsgFlags::empty() };
let iov = if chunk.is_empty() {
&[][..]
} else {
&[IoSlice::new(chunk)][..]
};
if let Some(iv) = iv {
let cmsgs = &[
ControlMessage::AlgSetOp(&libc::ALG_OP_ENCRYPT),
ControlMessage::AlgSetIv(iv.as_ref()),
][..];
retry_on_eintr(|| sendmsg::<()>(sock.as_fd().as_raw_fd(), iov, cmsgs, flags, None))
} else {
retry_on_eintr(|| sendmsg::<()>(sock.as_fd().as_raw_fd(), iov, &[], flags, None))
}
}
pub fn aes_ctr_dec<S: AsRawFd>(
sock: &S,
chunk: &[u8],
iv: Option<&IV>,
more: bool,
) -> Result<usize, Errno> {
let flags = if more { MSG_MORE } else { MsgFlags::empty() };
let iov = if chunk.is_empty() {
&[][..]
} else {
&[IoSlice::new(chunk)][..]
};
if let Some(iv) = iv {
let cmsgs = &[
ControlMessage::AlgSetOp(&libc::ALG_OP_DECRYPT),
ControlMessage::AlgSetIv(iv.as_ref()),
][..];
retry_on_eintr(|| sendmsg::<()>(sock.as_raw_fd(), iov, cmsgs, flags, None))
} else {
retry_on_eintr(|| sendmsg::<()>(sock.as_raw_fd(), 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<(OwnedFd, 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_and_tag = if size == 0 {
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),
}
}
Some((iv, hmac_tag))
};
let dst_fd = if let Some(tmp) = tmp {
let tmp = unsafe { BorrowedFd::borrow_raw(tmp) };
mkstempat(tmp, XPath::from_bytes(b"syd-aes-"))
} else {
create_memfd(b"syd-aes\0", *SAFE_MFD_FLAGS)
}?;
let iv = if let Some((iv, hmac_tag)) = iv_and_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);
}
iv
} else {
IV::random()
};
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, 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().as_raw_fd(), &buf[nwrite..nread], 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::Cursor;
use nix::{fcntl::open, sys::stat::Mode};
use super::*;
use crate::fs::create_memfd;
struct HashTestCase(&'static [u8], &'static str, HashAlgorithm);
struct HmacTestCase(&'static [u8], &'static [u8], &'static str);
const HASH_TEST_CASES: &[HashTestCase] = &[
HashTestCase(
b"The quick brown fox jumps over the lazy dog",
"414FA339",
HashAlgorithm::Crc32,
),
HashTestCase(
b"",
"00000000",
HashAlgorithm::Crc32,
),
HashTestCase(
b"",
"0000000000000000",
HashAlgorithm::Crc64,
),
HashTestCase(
b"",
"D41D8CD98F00B204E9800998ECF8427E",
HashAlgorithm::Md5,
),
HashTestCase(
b"",
"DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
HashAlgorithm::Sha1,
),
HashTestCase(
b"",
"A7FFC6F8BF1ED76651C14756A061D662F580FF4DE43B49FA82D80A4B80F8434A",
HashAlgorithm::Sha256,
),
HashTestCase(
b"",
"0C63A75B845E4F7D01107D852E4C2485C51A50AAAA94FC61995E71BBEE983A2AC3713831264ADB47FB6BD1E058D5F004",
HashAlgorithm::Sha384,
),
HashTestCase(
b"",
"A69F73CCA23A9AC5C8B567DC185A756E97C982164FE25859E0D1DCC1475C80A615B2123AF1F5F94C11E3E9402C3AC558F500199D95B6D3E301758586281DCD26",
HashAlgorithm::Sha512,
),
HashTestCase(
b"abc",
"900150983CD24FB0D6963F7D28E17F72",
HashAlgorithm::Md5,
),
HashTestCase(
b"abc",
"A9993E364706816ABA3E25717850C26C9CD0D89D",
HashAlgorithm::Sha1,
),
HashTestCase(
b"abc",
"3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532",
HashAlgorithm::Sha256,
),
HashTestCase(
b"abc",
"EC01498288516FC926459F58E2C6AD8DF9B473CB0FC08C2596DA7CF0E49BE4B298D88CEA927AC7F539F1EDF228376D25",
HashAlgorithm::Sha384,
),
HashTestCase(
b"abc",
"B751850B1A57168A5693CD924B6B096E08F621827444F70D884F5D0240D2712E10E116E9192AF3C91A7EC57647E3934057340B4CF408D5A56592F8274EEC53F0",
HashAlgorithm::Sha512
),
HashTestCase(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"8215EF0796A20BCAAAE116D3876C664A",
HashAlgorithm::Md5,
),
HashTestCase(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"84983E441C3BD26EBAAE4AA1F95129E5E54670F1",
HashAlgorithm::Sha1,
),
HashTestCase(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"41C0DBA2A9D6240849100376A8235E2C82E1B9998A999E21DB32DD97496D3376",
HashAlgorithm::Sha256,
),
HashTestCase(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"991C665755EB3A4B6BBDFB75C78A492E8C56A22C5C4D7E429BFDBC32B9D4AD5AA04A1F076E62FEA19EEF51ACD0657C22",
HashAlgorithm::Sha384,
),
HashTestCase(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"04A371E84ECFB5B8B77CB48610FCA8182DD457CE6F326A0FD3D7EC2F1E91636DEE691FBE0C985302BA1B0D8DC78C086346B533B49C030D99A27DAF1139D6E75E",
HashAlgorithm::Sha512,
),
HashTestCase(
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
"03DD8807A93175FB062DFB55DC7D359C",
HashAlgorithm::Md5,
),
HashTestCase(
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
"A49B2446A02C645BF419F995B67091253A04A259",
HashAlgorithm::Sha1,
),
HashTestCase(
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
"916F6061FE879741CA6469B43971DFDB28B1A32DC36CB3254E812BE27AAD1D18",
HashAlgorithm::Sha256,
),
HashTestCase(
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
"79407D3B5916B59C3E30B09822974791C313FB9ECC849E406F23592D04F625DC8C709B98B43B3852B337216179AA7FC7",
HashAlgorithm::Sha384,
),
HashTestCase(
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
"AFEBB2EF542E6579C50CAD06D2E578F9F8DD6881D7DC824D26360FEEBF18A4FA73E3261122948EFCFD492E74E82E2189ED0FB440D187F382270CB455F21DD185",
HashAlgorithm::Sha512,
),
];
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
}
#[test]
fn test_hash_simple() {
let mut errors = Vec::new();
for case in HASH_TEST_CASES {
let input_cursor = Cursor::new(case.0);
let result = match hash(input_cursor, case.2) {
Ok(hash) => HEXUPPER.encode(&hash),
Err(e) => {
errors.push(format!(
"Hashing failed for {:?} with error: {:?}",
case.2, e
));
continue;
}
};
if result != case.1 {
errors.push(format!(
"Mismatch for {:?}: expected {}, got {}",
case.2, case.1, result
));
}
}
assert!(errors.is_empty(), "Errors encountered: {:?}", errors);
}
#[test]
fn test_hash_long() {
let mut errors = Vec::new();
let input = b"a".repeat(1_000_000);
let cases = &[
(HashAlgorithm::Md5, "7707D6AE4E027C70EEA2A935C2296F21"),
(HashAlgorithm::Sha1, "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"),
(HashAlgorithm::Sha256, "5C8875AE474A3634BA4FD55EC85BFFD661F32ACA75C6D699D0CDCB6C115891C1"),
(HashAlgorithm::Sha384, "EEE9E24D78C1855337983451DF97C8AD9EEDF256C6334F8E948D252D5E0E76847AA0774DDB90A842190D2C558B4B8340"),
(HashAlgorithm::Sha512, "3C3A876DA14034AB60627C077BB98F7E120A2A5370212DFFB3385A18D4F38859ED311D0A9D5141CE9CC5C66EE689B266A8AA18ACE8282A0E0DB596C90B0A7B87"),
];
for case in cases {
let input_cursor = Cursor::new(input.clone());
let result = match hash(input_cursor, case.0) {
Ok(hash) => HEXUPPER.encode(&hash),
Err(e) => {
errors.push(format!(
"Hashing failed for {:?} with error: {:?}",
case.0, e
));
continue;
}
};
if result != case.1 {
errors.push(format!(
"Mismatch for {:?}: expected {}, got {}",
case.0, case.1, result
));
}
}
assert!(errors.is_empty(), "Errors encountered: {:?}", errors);
}
#[test]
#[ignore] fn test_hash_extremely_long() {
let mut errors = Vec::new();
let input =
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno".repeat(16_777_216);
let cases = &[
(HashAlgorithm::Md5, "D338139169D50F55526194C790EC0448"),
(HashAlgorithm::Sha1, "7789F0C9EF7BFC40D93311143DFBE69E2017F592"),
(HashAlgorithm::Sha256, "ECBBC42CBF296603ACB2C6BC0410EF4378BAFB24B710357F12DF607758B33E2B"),
(HashAlgorithm::Sha384, "A04296F4FCAAE14871BB5AD33E28DCF69238B04204D9941B8782E816D014BCB7540E4AF54F30D578F1A1CA2930847A12"),
(HashAlgorithm::Sha512, "235FFD53504EF836A1342B488F483B396EABBFE642CF78EE0D31FEEC788B23D0D18D5C339550DD5958A500D4B95363DA1B5FA18AFFC1BAB2292DC63B7D85097C"),
];
for case in cases {
let input_cursor = Cursor::new(input.clone());
let result = match hash(input_cursor, case.0) {
Ok(hash) => HEXUPPER.encode(&hash),
Err(e) => {
errors.push(format!(
"Hashing failed for {:?} with error: {:?}",
case.0, e
));
continue;
}
};
if result != case.1 {
errors.push(format!(
"Mismatch for {:?}: expected {}, got {}",
case.0, case.1, result
));
}
}
assert!(errors.is_empty(), "Errors encountered: {:?}", errors);
}
#[test]
fn test_hmac_sha256_simple() {
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(e) => {
errors.push(format!(
"Test case {}: hmac_sha256_setup failed with error: {:?}",
i + 1,
e
));
continue;
}
};
let init_sock = match hmac_sha256_init(&setup_fd, false) {
Ok(sock) => sock,
Err(e) => {
errors.push(format!(
"Test case {}: hmac_sha256_init failed with error: {e:?}",
i + 1,
));
continue;
}
};
let feed_result = hmac_sha256_feed(&init_sock, data, false);
if let Err(e) = feed_result {
errors.push(format!(
"Test case {}: hmac_sha256_feed failed with error: {e:?}",
i + 1,
));
continue;
}
let hmac_result = match hmac_sha256_fini(&init_sock) {
Ok(hmac) => hmac,
Err(e) => {
errors.push(format!(
"Test case {}: hmac_sha256_fini failed with error: {e:?}",
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_and_dec() {
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();
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_with_more_flag() {
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();
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_and_dec_tmp() {
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();
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 = create_memfd(b"syd\0", 0).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()
);
}
}