use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::error::{HsmError, HsmResult};
use crate::pkcs11_abi::constants::*;
use crate::pkcs11_abi::types::*;
use crate::store::object::StoredObject;
use zeroize::Zeroizing;
#[derive(Debug, Clone, PartialEq)]
pub enum SessionState {
RoPublic,
RoUser,
RwPublic,
RwUser,
RwSO,
}
impl SessionState {
pub fn to_ck_state(&self) -> CK_STATE {
match self {
SessionState::RoPublic => CKS_RO_PUBLIC_SESSION,
SessionState::RoUser => CKS_RO_USER_FUNCTIONS,
SessionState::RwPublic => CKS_RW_PUBLIC_SESSION,
SessionState::RwUser => CKS_RW_USER_FUNCTIONS,
SessionState::RwSO => CKS_RW_SO_FUNCTIONS,
}
}
pub fn is_rw(&self) -> bool {
matches!(
self,
SessionState::RwPublic | SessionState::RwUser | SessionState::RwSO
)
}
pub fn is_logged_in(&self) -> bool {
matches!(
self,
SessionState::RoUser | SessionState::RwUser | SessionState::RwSO
)
}
pub fn is_so(&self) -> bool {
matches!(self, SessionState::RwSO)
}
}
pub enum ActiveOperation {
Encrypt {
mechanism: CK_MECHANISM_TYPE,
key_handle: CK_OBJECT_HANDLE,
mechanism_param: Zeroizing<Vec<u8>>,
data: Zeroizing<Vec<u8>>,
cached_object: Option<Arc<parking_lot::RwLock<StoredObject>>>,
},
Decrypt {
mechanism: CK_MECHANISM_TYPE,
key_handle: CK_OBJECT_HANDLE,
mechanism_param: Zeroizing<Vec<u8>>,
data: Zeroizing<Vec<u8>>,
cached_object: Option<Arc<parking_lot::RwLock<StoredObject>>>,
},
Sign {
mechanism: CK_MECHANISM_TYPE,
key_handle: CK_OBJECT_HANDLE,
data: Zeroizing<Vec<u8>>,
hasher: Option<Box<dyn crate::crypto::digest::DigestAccumulator>>,
cached_object: Option<Arc<parking_lot::RwLock<StoredObject>>>,
},
Verify {
mechanism: CK_MECHANISM_TYPE,
key_handle: CK_OBJECT_HANDLE,
data: Zeroizing<Vec<u8>>,
hasher: Option<Box<dyn crate::crypto::digest::DigestAccumulator>>,
cached_object: Option<Arc<parking_lot::RwLock<StoredObject>>>,
},
Digest {
mechanism: CK_MECHANISM_TYPE,
hasher: Option<Box<dyn crate::crypto::digest::DigestAccumulator>>,
accumulated_input: Zeroizing<Vec<u8>>,
},
}
const OP_TYPE_ENCRYPT: u8 = 0;
const OP_TYPE_DECRYPT: u8 = 1;
const OP_TYPE_SIGN: u8 = 2;
const OP_TYPE_VERIFY: u8 = 3;
const OP_TYPE_DIGEST: u8 = 4;
const OP_STATE_HMAC_LEN: usize = 32;
const OP_STATE_MAX_PARAM_LEN: usize = 64 * 1024;
const OP_STATE_MAX_DATA_LEN: usize = 4 * 1024 * 1024;
const OP_STATE_MAGIC: [u8; 4] = *b"OPS1";
impl ActiveOperation {
pub fn serialize_state(
&self,
state_hmac_key: &[u8; 32],
session_handle: CK_SESSION_HANDLE,
slot_id: CK_SLOT_ID,
) -> HsmResult<Zeroizing<Vec<u8>>> {
let mut buf = Zeroizing::new(Vec::new());
buf.extend_from_slice(&OP_STATE_MAGIC);
buf.extend_from_slice(&(session_handle as u64).to_le_bytes());
buf.extend_from_slice(&(slot_id as u64).to_le_bytes());
fn write_field(buf: &mut Vec<u8>, field: &[u8], max: usize) -> HsmResult<()> {
if field.len() > max {
return Err(HsmError::DataLenRange);
}
buf.extend_from_slice(&(field.len() as u32).to_le_bytes());
buf.extend_from_slice(field);
Ok(())
}
match self {
ActiveOperation::Encrypt {
mechanism,
key_handle,
mechanism_param,
data,
..
} => {
buf.push(OP_TYPE_ENCRYPT);
buf.extend_from_slice(&(*mechanism as u64).to_le_bytes());
buf.extend_from_slice(&(*key_handle as u64).to_le_bytes());
write_field(&mut buf, mechanism_param, OP_STATE_MAX_PARAM_LEN)?;
write_field(&mut buf, data, OP_STATE_MAX_DATA_LEN)?;
}
ActiveOperation::Decrypt {
mechanism,
key_handle,
mechanism_param,
data,
..
} => {
buf.push(OP_TYPE_DECRYPT);
buf.extend_from_slice(&(*mechanism as u64).to_le_bytes());
buf.extend_from_slice(&(*key_handle as u64).to_le_bytes());
write_field(&mut buf, mechanism_param, OP_STATE_MAX_PARAM_LEN)?;
write_field(&mut buf, data, OP_STATE_MAX_DATA_LEN)?;
}
ActiveOperation::Sign {
mechanism,
key_handle,
data,
..
} => {
buf.push(OP_TYPE_SIGN);
buf.extend_from_slice(&(*mechanism as u64).to_le_bytes());
buf.extend_from_slice(&(*key_handle as u64).to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes()); write_field(&mut buf, data, OP_STATE_MAX_DATA_LEN)?;
}
ActiveOperation::Verify {
mechanism,
key_handle,
data,
..
} => {
buf.push(OP_TYPE_VERIFY);
buf.extend_from_slice(&(*mechanism as u64).to_le_bytes());
buf.extend_from_slice(&(*key_handle as u64).to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes()); write_field(&mut buf, data, OP_STATE_MAX_DATA_LEN)?;
}
ActiveOperation::Digest {
mechanism,
accumulated_input,
..
} => {
buf.push(OP_TYPE_DIGEST);
buf.extend_from_slice(&(*mechanism as u64).to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); write_field(&mut buf, accumulated_input, OP_STATE_MAX_DATA_LEN)?;
}
}
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
let mut mac =
HmacSha256::new_from_slice(state_hmac_key).expect("HMAC key length is always valid");
mac.update(&buf[..]);
let tag = mac.finalize().into_bytes();
buf.extend_from_slice(&tag);
Ok(buf)
}
pub fn deserialize_state(
blob: &[u8],
state_hmac_key: &[u8; 32],
expected_session: CK_SESSION_HANDLE,
expected_slot: CK_SLOT_ID,
) -> HsmResult<(
u8,
CK_MECHANISM_TYPE,
CK_OBJECT_HANDLE,
Zeroizing<Vec<u8>>,
Zeroizing<Vec<u8>>,
)> {
const BIND_LEN: usize = 4 + 8 + 8; const INNER_HEADER_LEN: usize = 1 + 8 + 8 + 4; const HEADER_LEN: usize = BIND_LEN + INNER_HEADER_LEN;
if blob.len() < HEADER_LEN + OP_STATE_HMAC_LEN {
return Err(HsmError::DataInvalid);
}
let payload = &blob[..blob.len() - OP_STATE_HMAC_LEN];
let provided_tag = &blob[blob.len() - OP_STATE_HMAC_LEN..];
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
let mut mac =
HmacSha256::new_from_slice(state_hmac_key).expect("HMAC key length is always valid");
mac.update(payload);
mac.verify_slice(provided_tag)
.map_err(|_| HsmError::DataInvalid)?;
if payload[..4] != OP_STATE_MAGIC {
return Err(HsmError::DataInvalid);
}
let blob_session = u64::from_le_bytes(
payload[4..12]
.try_into()
.map_err(|_| HsmError::DataInvalid)?,
);
let blob_slot = u64::from_le_bytes(
payload[12..20]
.try_into()
.map_err(|_| HsmError::DataInvalid)?,
);
if blob_session != expected_session as u64 || blob_slot != expected_slot as u64 {
return Err(HsmError::DataInvalid);
}
let op_type = payload[BIND_LEN];
match op_type {
OP_TYPE_ENCRYPT | OP_TYPE_DECRYPT | OP_TYPE_SIGN | OP_TYPE_VERIFY | OP_TYPE_DIGEST => {}
_ => return Err(HsmError::DataInvalid),
}
let mechanism = u64::from_le_bytes(
payload[BIND_LEN + 1..BIND_LEN + 9]
.try_into()
.map_err(|_| HsmError::DataInvalid)?,
) as CK_MECHANISM_TYPE;
let key_handle = u64::from_le_bytes(
payload[BIND_LEN + 9..BIND_LEN + 17]
.try_into()
.map_err(|_| HsmError::DataInvalid)?,
) as CK_OBJECT_HANDLE;
let param_len = u32::from_le_bytes(
payload[BIND_LEN + 17..BIND_LEN + 21]
.try_into()
.map_err(|_| HsmError::DataInvalid)?,
) as usize;
if param_len > OP_STATE_MAX_PARAM_LEN {
return Err(HsmError::DataInvalid);
}
if payload.len() < HEADER_LEN + param_len + 4 {
return Err(HsmError::DataInvalid);
}
let mechanism_param = Zeroizing::new(payload[HEADER_LEN..HEADER_LEN + param_len].to_vec());
let data_offset = HEADER_LEN + param_len;
let data_len = u32::from_le_bytes(
payload[data_offset..data_offset + 4]
.try_into()
.map_err(|_| HsmError::DataInvalid)?,
) as usize;
if data_len > OP_STATE_MAX_DATA_LEN {
return Err(HsmError::DataInvalid);
}
if payload.len() < data_offset + 4 + data_len {
return Err(HsmError::DataInvalid);
}
let data = Zeroizing::new(payload[data_offset + 4..data_offset + 4 + data_len].to_vec());
Ok((op_type, mechanism, key_handle, mechanism_param, data))
}
}
impl std::fmt::Debug for ActiveOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ActiveOperation::Encrypt {
mechanism,
key_handle,
..
} => f
.debug_struct("Encrypt")
.field("mechanism", mechanism)
.field("key_handle", key_handle)
.finish(),
ActiveOperation::Decrypt {
mechanism,
key_handle,
..
} => f
.debug_struct("Decrypt")
.field("mechanism", mechanism)
.field("key_handle", key_handle)
.finish(),
ActiveOperation::Sign {
mechanism,
key_handle,
..
} => f
.debug_struct("Sign")
.field("mechanism", mechanism)
.field("key_handle", key_handle)
.finish(),
ActiveOperation::Verify {
mechanism,
key_handle,
..
} => f
.debug_struct("Verify")
.field("mechanism", mechanism)
.field("key_handle", key_handle)
.finish(),
ActiveOperation::Digest { mechanism, .. } => f
.debug_struct("Digest")
.field("mechanism", mechanism)
.finish(),
}
}
}
#[derive(Debug)]
pub struct FindContext {
pub results: Vec<CK_OBJECT_HANDLE>,
pub position: usize,
}
pub struct Session {
pub handle: CK_SESSION_HANDLE,
pub slot_id: CK_SLOT_ID,
pub flags: CK_FLAGS,
pub state: SessionState,
pub active_operation: Option<ActiveOperation>,
pub find_context: Option<FindContext>,
pub last_operation_fips_approved: Option<bool>,
pub last_activity: Instant,
pub closed: bool,
}
impl Drop for Session {
fn drop(&mut self) {
self.active_operation = None;
self.find_context = None;
}
}
impl Session {
pub fn new(handle: CK_SESSION_HANDLE, slot_id: CK_SLOT_ID, flags: CK_FLAGS) -> Self {
let is_rw = (flags & CKF_RW_SESSION) != 0;
let state = if is_rw {
SessionState::RwPublic
} else {
SessionState::RoPublic
};
Self {
handle,
slot_id,
flags,
state,
active_operation: None,
find_context: None,
last_operation_fips_approved: None,
last_activity: Instant::now(),
closed: false,
}
}
pub fn touch(&mut self) {
self.last_activity = Instant::now();
}
pub fn idle_duration(&self) -> Duration {
self.last_activity.elapsed()
}
pub fn is_rw(&self) -> bool {
self.state.is_rw()
}
pub fn on_user_login(&mut self) -> HsmResult<()> {
self.state = match self.state {
SessionState::RoPublic => SessionState::RoUser,
SessionState::RwPublic => SessionState::RwUser,
ref s if s.is_logged_in() => return Err(HsmError::UserAlreadyLoggedIn),
_ => return Err(HsmError::GeneralError),
};
Ok(())
}
pub fn on_so_login(&mut self) -> HsmResult<()> {
self.state = match self.state {
SessionState::RwPublic => SessionState::RwSO,
SessionState::RoPublic => return Err(HsmError::SessionReadOnly),
ref s if s.is_logged_in() => return Err(HsmError::UserAlreadyLoggedIn),
_ => return Err(HsmError::GeneralError),
};
Ok(())
}
pub fn on_logout(&mut self) -> HsmResult<()> {
self.state = match self.state {
SessionState::RoUser => SessionState::RoPublic,
SessionState::RwUser => SessionState::RwPublic,
SessionState::RwSO => SessionState::RwPublic,
_ => return Err(HsmError::UserNotLoggedIn),
};
self.active_operation = None;
self.find_context = None;
Ok(())
}
pub fn get_info(&self) -> CK_SESSION_INFO {
CK_SESSION_INFO {
slot_id: self.slot_id,
state: self.state.to_ck_state(),
flags: self.flags,
device_error: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixed_key() -> [u8; 32] {
let mut k = [0u8; 32];
for (i, b) in k.iter_mut().enumerate() {
*b = i as u8;
}
k
}
fn sample_digest_op() -> ActiveOperation {
ActiveOperation::Digest {
mechanism: CKM_SHA256,
hasher: None,
accumulated_input: Zeroizing::new(b"hello world".to_vec()),
}
}
#[test]
fn roundtrip_matching_session_and_slot_ok() {
let key = fixed_key();
let op = sample_digest_op();
let blob = op
.serialize_state(&key, 1, 2)
.expect("serialize_state should succeed");
let (op_type, mechanism, key_handle, param, data) =
ActiveOperation::deserialize_state(&blob, &key, 1, 2)
.expect("matching session/slot must deserialize cleanly");
assert_eq!(op_type, OP_TYPE_DIGEST);
assert_eq!(mechanism, CKM_SHA256);
assert_eq!(key_handle, 0);
assert!(param.is_empty());
assert_eq!(&data[..], b"hello world");
}
#[test]
fn mismatched_session_handle_is_rejected() {
let key = fixed_key();
let op = sample_digest_op();
let blob = op.serialize_state(&key, 1, 2).unwrap();
let err = ActiveOperation::deserialize_state(&blob, &key, 3, 2)
.expect_err("mismatched session_handle must be rejected");
assert!(matches!(err, HsmError::DataInvalid));
}
#[test]
fn mismatched_slot_id_is_rejected() {
let key = fixed_key();
let op = sample_digest_op();
let blob = op.serialize_state(&key, 1, 2).unwrap();
let err = ActiveOperation::deserialize_state(&blob, &key, 1, 5)
.expect_err("mismatched slot_id must be rejected");
assert!(matches!(err, HsmError::DataInvalid));
}
#[test]
fn legacy_pre_binding_blob_is_rejected() {
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
let key = fixed_key();
let mut legacy = Vec::new();
legacy.push(OP_TYPE_DIGEST);
legacy.extend_from_slice(&(CKM_SHA256 as u64).to_le_bytes());
legacy.extend_from_slice(&0u64.to_le_bytes()); legacy.extend_from_slice(&0u32.to_le_bytes()); legacy.extend_from_slice(&0u32.to_le_bytes());
let mut mac = HmacSha256::new_from_slice(&key).unwrap();
mac.update(&legacy);
let tag = mac.finalize().into_bytes();
legacy.extend_from_slice(&tag);
let err = ActiveOperation::deserialize_state(&legacy, &key, 1, 2)
.expect_err("legacy blob without magic must be rejected");
assert!(matches!(err, HsmError::DataInvalid));
}
#[test]
fn tampered_binding_fails_hmac() {
let key = fixed_key();
let op = sample_digest_op();
let mut blob = op.serialize_state(&key, 1, 2).unwrap().to_vec();
blob[4] ^= 0xFF;
let err = ActiveOperation::deserialize_state(&blob, &key, 1, 2)
.expect_err("HMAC must catch in-blob tampering of the binding");
assert!(matches!(err, HsmError::DataInvalid));
}
}