use std::collections::HashMap;
use std::fmt;
use zeroize::Zeroize;
use crate::pkcs11_abi::constants::*;
use crate::pkcs11_abi::types::*;
use crate::store::key_material::RawKeyMaterial;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum KeyLifecycleState {
PreActivation,
#[default]
Active,
Deactivated,
Compromised,
Destroyed,
}
impl fmt::Display for KeyLifecycleState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KeyLifecycleState::PreActivation => write!(f, "PreActivation"),
KeyLifecycleState::Active => write!(f, "Active"),
KeyLifecycleState::Deactivated => write!(f, "Deactivated"),
KeyLifecycleState::Compromised => write!(f, "Compromised"),
KeyLifecycleState::Destroyed => write!(f, "Destroyed"),
}
}
}
#[derive(Clone)]
pub struct StoredObject {
pub handle: CK_OBJECT_HANDLE,
pub slot_id: CK_ULONG,
pub class: CK_OBJECT_CLASS,
pub key_type: Option<CK_KEY_TYPE>,
pub label: Vec<u8>,
pub id: Vec<u8>,
pub token_object: bool,
pub private: bool,
pub sensitive: bool,
pub extractable: bool,
pub modifiable: bool,
pub destroyable: bool,
pub copyable: bool,
pub can_encrypt: bool,
pub can_decrypt: bool,
pub can_sign: bool,
pub can_verify: bool,
pub can_wrap: bool,
pub can_unwrap: bool,
pub can_derive: bool,
pub key_material: Option<RawKeyMaterial>,
pub public_key_data: Option<Vec<u8>>,
pub modulus: Option<Vec<u8>>,
pub modulus_bits: Option<CK_ULONG>,
pub public_exponent: Option<Vec<u8>>,
pub ec_params: Option<Vec<u8>>,
pub ec_point: Option<Vec<u8>>,
pub value_len: Option<CK_ULONG>,
pub extra_attributes: HashMap<CK_ATTRIBUTE_TYPE, Vec<u8>>,
pub start_date: Option<[u8; 8]>,
pub end_date: Option<[u8; 8]>,
pub lifecycle_state: KeyLifecycleState,
pub creation_time: u64,
}
impl Drop for StoredObject {
fn drop(&mut self) {
self.label.zeroize();
self.id.zeroize();
if let Some(value) = &mut self.public_key_data {
value.zeroize();
}
if let Some(value) = &mut self.modulus {
value.zeroize();
}
if let Some(value) = &mut self.public_exponent {
value.zeroize();
}
if let Some(value) = &mut self.ec_params {
value.zeroize();
}
if let Some(value) = &mut self.ec_point {
value.zeroize();
}
for value in self.extra_attributes.values_mut() {
value.zeroize();
}
}
}
pub const SENSITIVE_COMPONENT_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[
CKA_PRIVATE_EXPONENT,
CKA_PRIME_1,
CKA_PRIME_2,
CKA_EXPONENT_1,
CKA_EXPONENT_2,
CKA_COEFFICIENT,
];
pub fn is_secret_class(class: CK_OBJECT_CLASS) -> bool {
class == CKO_SECRET_KEY || class == CKO_PRIVATE_KEY
}
impl StoredObject {
pub fn new(handle: CK_OBJECT_HANDLE, class: CK_OBJECT_CLASS) -> Self {
let secret = is_secret_class(class);
Self {
handle,
slot_id: 0,
class,
key_type: None,
label: Vec::new(),
id: Vec::new(),
token_object: false,
private: true,
sensitive: secret,
extractable: !secret,
modifiable: true,
destroyable: true,
copyable: true,
can_encrypt: false,
can_decrypt: false,
can_sign: false,
can_verify: false,
can_wrap: false,
can_unwrap: false,
can_derive: false,
key_material: None,
public_key_data: None,
modulus: None,
modulus_bits: None,
public_exponent: None,
ec_params: None,
ec_point: None,
value_len: None,
extra_attributes: HashMap::new(),
start_date: None,
end_date: None,
lifecycle_state: KeyLifecycleState::Active,
creation_time: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
}
}
#[cfg(test)]
pub fn default_for_test() -> Self {
Self::new(1, 0)
}
pub fn parameter_set(&self) -> Option<CK_ULONG> {
let public_key = match (&self.public_key_data, &self.key_material) {
(Some(pk), _) => pk.as_slice(),
(None, Some(km)) if self.class == CKO_PUBLIC_KEY => km.as_bytes(),
_ => return None,
};
match (self.key_type?, public_key.len()) {
(CKK_ML_DSA, 1312) => Some(CKP_ML_DSA_44),
(CKK_ML_DSA, 1952) => Some(CKP_ML_DSA_65),
(CKK_ML_DSA, 2592) => Some(CKP_ML_DSA_87),
(CKK_ML_KEM, 800) => Some(CKP_ML_KEM_512),
(CKK_ML_KEM, 1184) => Some(CKP_ML_KEM_768),
(CKK_ML_KEM, 1568) => Some(CKP_ML_KEM_1024),
(CKK_SLH_DSA, 32) => Some(CKP_SLH_DSA_SHA2_128S),
(CKK_SLH_DSA, 64) => Some(CKP_SLH_DSA_SHA2_256S),
_ => None,
}
}
pub fn is_pqc_key(&self) -> bool {
matches!(self.key_type, Some(CKK_ML_KEM | CKK_ML_DSA | CKK_SLH_DSA))
}
pub fn matches_template(&self, template: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
let mut matched: u8 = 1;
for (attr_type, value) in template {
let attr_match: u8 = match *attr_type {
CKA_CLASS => match read_ck_ulong(value) {
Some(class) => (class == self.class) as u8,
None => 0,
},
CKA_KEY_TYPE => match (self.key_type, read_ck_ulong(value)) {
(Some(kt), Some(v)) => (normalize_key_type(v) == kt) as u8,
(None, _) => 0,
(_, None) => 0,
},
CKA_PARAMETER_SET => match (self.parameter_set(), read_ck_ulong(value)) {
(Some(ps), Some(v)) => (v == ps) as u8,
_ => 0,
},
CKA_LABEL => ct_bytes_eq(value, &self.label),
CKA_EC_POINT => match &self.ec_point {
Some(point) => ct_bytes_eq(crate::store::ec_point::decode(value), point),
None => 0,
},
CKA_ID => ct_bytes_eq(value, &self.id),
CKA_TOKEN => {
if !value.is_empty() {
((value[0] != 0) == self.token_object) as u8
} else {
1
}
}
CKA_PRIVATE => {
if !value.is_empty() {
((value[0] != 0) == self.private) as u8
} else {
1
}
}
CKA_SENSITIVE => {
if !value.is_empty() {
((value[0] != 0) == self.sensitive) as u8
} else {
1
}
}
CKA_EXTRACTABLE => {
if !value.is_empty() {
((value[0] != 0) == self.extractable) as u8
} else {
1
}
}
CKA_MODIFIABLE => {
if !value.is_empty() {
((value[0] != 0) == self.modifiable) as u8
} else {
1
}
}
CKA_DESTROYABLE => {
if !value.is_empty() {
((value[0] != 0) == self.destroyable) as u8
} else {
1
}
}
CKA_COPYABLE => {
if !value.is_empty() {
((value[0] != 0) == self.copyable) as u8
} else {
1
}
}
CKA_SIGN => {
if !value.is_empty() {
((value[0] != 0) == self.can_sign) as u8
} else {
1
}
}
CKA_VERIFY => {
if !value.is_empty() {
((value[0] != 0) == self.can_verify) as u8
} else {
1
}
}
CKA_ENCRYPT => {
if !value.is_empty() {
((value[0] != 0) == self.can_encrypt) as u8
} else {
1
}
}
CKA_DECRYPT => {
if !value.is_empty() {
((value[0] != 0) == self.can_decrypt) as u8
} else {
1
}
}
CKA_WRAP => {
if !value.is_empty() {
((value[0] != 0) == self.can_wrap) as u8
} else {
1
}
}
CKA_UNWRAP => {
if !value.is_empty() {
((value[0] != 0) == self.can_unwrap) as u8
} else {
1
}
}
CKA_DERIVE => {
if !value.is_empty() {
((value[0] != 0) == self.can_derive) as u8
} else {
1
}
}
CKA_VALUE_LEN => match (self.value_len, read_ck_ulong(value)) {
(Some(vl), Some(v)) => (v == vl) as u8,
_ => 0,
},
CKA_MODULUS_BITS => match (self.modulus_bits, read_ck_ulong(value)) {
(Some(mb), Some(v)) => (v == mb) as u8,
_ => 0,
},
CKA_EC_PARAMS => match &self.ec_params {
Some(params) => ct_bytes_eq(value, params),
None => 0,
},
_ => {
if let Some(stored) = self.extra_attributes.get(attr_type) {
ct_bytes_eq(stored, value)
} else {
0
}
}
};
matched &= attr_match;
}
matched == 1
}
pub fn approximate_size(&self) -> CK_ULONG {
let mut size: usize = std::mem::size_of::<StoredObject>();
if let Some(ref km) = self.key_material {
size = size.saturating_add(km.len());
}
if let Some(ref pk) = self.public_key_data {
size = size.saturating_add(pk.len());
}
if let Some(ref m) = self.modulus {
size = size.saturating_add(m.len());
}
size = size.saturating_add(self.label.len());
size = size.saturating_add(self.id.len());
CK_ULONG::try_from(size).unwrap_or(CK_ULONG::MAX)
}
}
impl fmt::Debug for StoredObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("StoredObject")
.field("handle", &self.handle)
.field("class", &self.class)
.field("key_type", &self.key_type)
.field("label_len", &self.label.len())
.field("sensitive", &self.sensitive)
.field("extractable", &self.extractable)
.field("lifecycle_state", &self.lifecycle_state)
.field("key_material", &"[REDACTED]")
.finish()
}
}
impl StoredObject {
pub fn check_lifecycle(&self, operation: &str) -> Result<(), crate::error::HsmError> {
use crate::error::HsmError;
let effective_state = self.effective_lifecycle_state();
match effective_state {
KeyLifecycleState::Active => Ok(()),
KeyLifecycleState::PreActivation => Err(HsmError::KeyFunctionNotPermitted),
KeyLifecycleState::Deactivated => {
match operation {
"verify" | "decrypt" | "unwrap" => Ok(()),
_ => Err(HsmError::KeyFunctionNotPermitted),
}
}
KeyLifecycleState::Compromised => Err(HsmError::KeyFunctionNotPermitted),
KeyLifecycleState::Destroyed => Err(HsmError::ObjectHandleInvalid),
}
}
pub fn effective_lifecycle_state(&self) -> KeyLifecycleState {
if self.lifecycle_state != KeyLifecycleState::Active {
return self.lifecycle_state;
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if let Some(start) = &self.start_date {
if let Some(start_epoch) = ck_date_to_epoch(start) {
if now < start_epoch {
return KeyLifecycleState::PreActivation;
}
}
}
if let Some(end) = &self.end_date {
if let Some(end_epoch) = ck_date_to_epoch(end) {
if now > end_epoch {
return KeyLifecycleState::Deactivated;
}
}
}
KeyLifecycleState::Active
}
pub fn transition_lifecycle_if_needed(
&mut self,
) -> Option<(KeyLifecycleState, KeyLifecycleState)> {
if matches!(
self.lifecycle_state,
KeyLifecycleState::Compromised | KeyLifecycleState::Destroyed
) {
return None;
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let new_state = self.compute_date_based_state(now);
if new_state != self.lifecycle_state {
let old = self.lifecycle_state;
self.lifecycle_state = new_state;
Some((old, new_state))
} else {
None
}
}
fn compute_date_based_state(&self, now: u64) -> KeyLifecycleState {
if let Some(start) = &self.start_date {
if let Some(start_epoch) = ck_date_to_epoch(start) {
if now < start_epoch {
return KeyLifecycleState::PreActivation;
}
}
}
if let Some(end) = &self.end_date {
if let Some(end_epoch) = ck_date_to_epoch(end) {
if now > end_epoch {
return KeyLifecycleState::Deactivated;
}
}
}
KeyLifecycleState::Active
}
pub fn set_compromised(&mut self) -> Option<KeyLifecycleState> {
match self.lifecycle_state {
KeyLifecycleState::Compromised | KeyLifecycleState::Destroyed => None,
other => {
self.lifecycle_state = KeyLifecycleState::Compromised;
Some(other)
}
}
}
pub fn set_destroyed(&mut self) -> Option<KeyLifecycleState> {
if self.lifecycle_state == KeyLifecycleState::Destroyed {
None
} else {
let old = self.lifecycle_state;
self.lifecycle_state = KeyLifecycleState::Destroyed;
Some(old)
}
}
}
fn ck_date_to_epoch(date: &[u8; 8]) -> Option<u64> {
let s = std::str::from_utf8(date).ok()?;
let year: u64 = s[0..4].parse().ok()?;
let month: u64 = s[4..6].parse().ok()?;
let day: u64 = s[6..8].parse().ok()?;
if !(1970..=2200).contains(&year) || !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return None;
}
let leap_years_before = |y: u64| -> u64 {
if y == 0 {
return 0;
}
let y = y - 1;
y / 4 - y / 100 + y / 400
};
let leaps = leap_years_before(year) - leap_years_before(1970);
let years_elapsed = year - 1970;
let mut days = years_elapsed * 365 + leaps;
const CUMULATIVE_DAYS: [u64; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
days += CUMULATIVE_DAYS[month as usize];
if month > 2 && is_leap_year(year) {
days += 1;
}
days += day - 1;
Some(days * 86400)
}
fn is_leap_year(year: u64) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
#[derive(Debug)]
pub enum CryptoObject {
RsaPrivateKey {
der: RawKeyMaterial,
},
RsaPublicKey {
modulus: Vec<u8>,
public_exponent: Vec<u8>,
},
AesKey {
key: RawKeyMaterial,
},
}
fn ct_bytes_eq(a: &[u8], b: &[u8]) -> u8 {
use subtle::ConstantTimeEq;
if a.len() != b.len() {
return 0;
}
a.ct_eq(b).unwrap_u8()
}
fn read_ck_ulong(bytes: &[u8]) -> Option<CK_ULONG> {
let size = std::mem::size_of::<CK_ULONG>();
if bytes.len() != size {
return None;
}
let mut buf = [0u8; 8];
buf[..size].copy_from_slice(&bytes[..size]);
Some(CK_ULONG::from_ne_bytes(buf[..size].try_into().ok()?))
}
#[cfg(test)]
mod tests {
use super::*;
fn date_offset_days(days: i64) -> [u8; 8] {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let target = (now as i64 + days * 86400) as u64;
epoch_to_ck_date(target)
}
fn epoch_to_ck_date(epoch: u64) -> [u8; 8] {
let total_days = epoch / 86400;
let mut y = 1970u64;
let mut remaining = total_days;
loop {
let days_in_year = if is_leap_year(y) { 366 } else { 365 };
if remaining < days_in_year {
break;
}
remaining -= days_in_year;
y += 1;
}
let leap = is_leap_year(y);
let month_days: [u64; 12] = [
31,
if leap { 29 } else { 28 },
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
let mut m = 1u64;
for &md in &month_days {
if remaining < md {
break;
}
remaining -= md;
m += 1;
}
let d = remaining + 1;
let s = format!("{:04}{:02}{:02}", y, m, d);
let mut buf = [0u8; 8];
buf.copy_from_slice(s.as_bytes());
buf
}
#[test]
fn transition_preactivation_to_active() {
let mut obj = StoredObject::default_for_test();
obj.start_date = Some(date_offset_days(-10));
obj.lifecycle_state = KeyLifecycleState::PreActivation;
let result = obj.transition_lifecycle_if_needed();
assert!(result.is_some());
let (old, new) = result.unwrap();
assert_eq!(old, KeyLifecycleState::PreActivation);
assert_eq!(new, KeyLifecycleState::Active);
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Active);
}
#[test]
fn transition_active_to_deactivated_by_date() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Active;
obj.end_date = Some(date_offset_days(-5));
let result = obj.transition_lifecycle_if_needed();
assert!(result.is_some());
let (old, new) = result.unwrap();
assert_eq!(old, KeyLifecycleState::Active);
assert_eq!(new, KeyLifecycleState::Deactivated);
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Deactivated);
}
#[test]
fn transition_returns_none_when_no_change() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Active;
let result = obj.transition_lifecycle_if_needed();
assert!(result.is_none());
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Active);
}
#[test]
fn compromised_not_overridden_by_dates() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Compromised;
obj.start_date = Some(date_offset_days(-30));
obj.end_date = Some(date_offset_days(30));
let result = obj.transition_lifecycle_if_needed();
assert!(result.is_none());
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Compromised);
}
#[test]
fn destroyed_not_overridden_by_dates() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Destroyed;
obj.start_date = Some(date_offset_days(-30));
obj.end_date = Some(date_offset_days(30));
let result = obj.transition_lifecycle_if_needed();
assert!(result.is_none());
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Destroyed);
}
#[test]
fn set_compromised_from_active() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Active;
let prev = obj.set_compromised();
assert_eq!(prev, Some(KeyLifecycleState::Active));
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Compromised);
}
#[test]
fn set_compromised_idempotent() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Compromised;
let prev = obj.set_compromised();
assert!(prev.is_none());
}
#[test]
fn set_compromised_blocked_when_destroyed() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Destroyed;
let prev = obj.set_compromised();
assert!(prev.is_none());
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Destroyed);
}
#[test]
fn set_destroyed_from_active() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Active;
let prev = obj.set_destroyed();
assert_eq!(prev, Some(KeyLifecycleState::Active));
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Destroyed);
}
#[test]
fn set_destroyed_from_compromised() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Compromised;
let prev = obj.set_destroyed();
assert_eq!(prev, Some(KeyLifecycleState::Compromised));
assert_eq!(obj.lifecycle_state, KeyLifecycleState::Destroyed);
}
#[test]
fn set_destroyed_idempotent() {
let mut obj = StoredObject::default_for_test();
obj.lifecycle_state = KeyLifecycleState::Destroyed;
let prev = obj.set_destroyed();
assert!(prev.is_none());
}
#[test]
fn display_lifecycle_states() {
assert_eq!(
format!("{}", KeyLifecycleState::PreActivation),
"PreActivation"
);
assert_eq!(format!("{}", KeyLifecycleState::Active), "Active");
assert_eq!(format!("{}", KeyLifecycleState::Deactivated), "Deactivated");
assert_eq!(format!("{}", KeyLifecycleState::Compromised), "Compromised");
assert_eq!(format!("{}", KeyLifecycleState::Destroyed), "Destroyed");
}
}