use crate::error::HsmResult;
use crate::pkcs11_abi::types::{CK_OBJECT_HANDLE, CK_SESSION_HANDLE, CK_ULONG};
use std::collections::HashSet;
use std::sync::atomic::{AtomicU64, Ordering};
const MAX_HANDLE: u64 = CK_ULONG::MAX as u64;
fn round_fn_16(half: u16, key: u64) -> u16 {
let x = (half as u64).wrapping_mul(key | 1); ((x >> 16) ^ x) as u16
}
fn round_fn_32(half: u32, key: u64) -> u32 {
let x = (half as u64).wrapping_mul(key | 1);
((x >> 32) ^ x) as u32
}
struct HandleScrambler {
round_keys: [u64; 4],
}
impl HandleScrambler {
fn new() -> Self {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
let round_keys = [
rng.next_u64(),
rng.next_u64(),
rng.next_u64(),
rng.next_u64(),
];
tracing::debug!(
"HandleScrambler keys sourced from OsRng \
(process-init Feistel-key generation, no DRBG yet available)"
);
Self { round_keys }
}
fn scramble(&self, counter: u64) -> u64 {
if MAX_HANDLE == u32::MAX as u64 {
self.feistel32_forward(counter as u32) as u64
} else {
self.feistel64_forward(counter)
}
}
fn unscramble(&self, handle: u64) -> u64 {
if MAX_HANDLE == u32::MAX as u64 {
self.feistel32_inverse(handle as u32) as u64
} else {
self.feistel64_inverse(handle)
}
}
fn feistel32_forward(&self, input: u32) -> u32 {
let mut left = (input >> 16) as u16;
let mut right = input as u16;
for key in &self.round_keys {
let f = round_fn_16(right, *key);
let new_right = left ^ f;
left = right;
right = new_right;
}
((left as u32) << 16) | (right as u32)
}
fn feistel32_inverse(&self, input: u32) -> u32 {
let mut left = (input >> 16) as u16;
let mut right = input as u16;
for key in self.round_keys.iter().rev() {
let f = round_fn_16(left, *key);
let new_left = right ^ f;
right = left;
left = new_left;
}
((left as u32) << 16) | (right as u32)
}
fn feistel64_forward(&self, input: u64) -> u64 {
let mut left = (input >> 32) as u32;
let mut right = input as u32;
for key in &self.round_keys {
let f = round_fn_32(right, *key);
let new_right = left ^ f;
left = right;
right = new_right;
}
((left as u64) << 32) | (right as u64)
}
fn feistel64_inverse(&self, input: u64) -> u64 {
let mut left = (input >> 32) as u32;
let mut right = input as u32;
for key in self.round_keys.iter().rev() {
let f = round_fn_32(left, *key);
let new_left = right ^ f;
right = left;
left = new_left;
}
((left as u64) << 32) | (right as u64)
}
}
pub struct SessionHandleAllocator {
next: AtomicU64,
scrambler: HandleScrambler,
}
impl SessionHandleAllocator {
pub fn new() -> Self {
Self {
next: AtomicU64::new(0),
scrambler: HandleScrambler::new(),
}
}
}
impl Default for SessionHandleAllocator {
fn default() -> Self {
Self::new()
}
}
impl SessionHandleAllocator {
pub fn next(&self) -> HsmResult<CK_SESSION_HANDLE> {
loop {
let current = self.next.load(Ordering::Acquire);
let next_val = if current == MAX_HANDLE {
0
} else {
current + 1
};
if self
.next
.compare_exchange_weak(current, next_val, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let handle = self.scrambler.scramble(current);
if handle != 0 {
return Ok(handle as CK_SESSION_HANDLE);
}
continue;
}
}
}
}
pub struct ObjectHandleAllocator {
next: AtomicU64,
scrambler: HandleScrambler,
reserved: parking_lot::Mutex<HashSet<u64>>,
}
impl ObjectHandleAllocator {
pub fn new() -> Self {
Self {
next: AtomicU64::new(0),
scrambler: HandleScrambler::new(),
reserved: parking_lot::Mutex::new(HashSet::new()),
}
}
}
impl Default for ObjectHandleAllocator {
fn default() -> Self {
Self::new()
}
}
impl ObjectHandleAllocator {
pub fn next(&self) -> HsmResult<CK_OBJECT_HANDLE> {
loop {
let current = self.next.load(Ordering::Acquire);
let next_val = if current == MAX_HANDLE {
0
} else {
current + 1
};
if self
.next
.compare_exchange_weak(current, next_val, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let handle = self.scrambler.scramble(current);
if handle == 0 || self.reserved.lock().contains(&handle) {
continue;
}
return Ok(handle as CK_OBJECT_HANDLE);
}
}
}
pub fn ensure_past(&self, handle: CK_OBJECT_HANDLE) {
self.reserved.lock().insert(handle as u64);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn feistel_is_bijective_32() {
let s = HandleScrambler::new();
for i in 0u32..1000 {
let scrambled = s.feistel32_forward(i);
let recovered = s.feistel32_inverse(scrambled);
assert_eq!(recovered, i, "Feistel32 round-trip failed for {i}");
}
}
#[test]
fn feistel_is_bijective_64() {
let s = HandleScrambler::new();
for i in 0u64..1000 {
let scrambled = s.feistel64_forward(i);
let recovered = s.feistel64_inverse(scrambled);
assert_eq!(recovered, i, "Feistel64 round-trip failed for {i}");
}
}
#[test]
fn scramble_produces_no_obvious_pattern() {
let s = HandleScrambler::new();
let h1 = s.scramble(0);
let h2 = s.scramble(1);
let h3 = s.scramble(2);
assert!(
!(h2 == h1 + 1 && h3 == h2 + 1),
"Scrambler produced sequential outputs"
);
}
#[test]
fn session_handle_allocator_skips_zero() {
let alloc = SessionHandleAllocator::new();
for _ in 0..10_000 {
let h = alloc.next().unwrap();
assert_ne!(h as u64, 0, "Allocator produced CK_INVALID_HANDLE (0)");
}
}
#[test]
fn object_handle_allocator_respects_reserved() {
let alloc = ObjectHandleAllocator::new();
let first = alloc.next().unwrap();
alloc.ensure_past(first);
for _ in 0..10_000 {
let h = alloc.next().unwrap();
assert_ne!(h, first, "Allocator reissued reserved handle {first}");
}
}
}