1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Craton Software Company
//! Core HSM state — the central struct shared by C ABI, daemon, and admin CLI.
use std::sync::Arc;
use crate::audit::log::AuditLog;
use crate::config::{AlgorithmConfig, HsmConfig};
use crate::crypto::backend::CryptoBackend;
use crate::crypto::conditional_test::ConditionalSelfTest;
use crate::crypto::drbg::HmacDrbg;
use crate::crypto::metrics::MetricsCollector;
use crate::crypto::rate_limit::RateLimiter;
#[cfg(feature = "rustcrypto-backend")]
use crate::crypto::rustcrypto_backend::RustCryptoBackend;
use crate::session::manager::SessionManager;
use crate::store::attributes::ObjectStore;
use crate::store::encrypted_store::EncryptedStore;
use crate::token::slot::SlotManager;
use zeroize::Zeroizing;
/// The global HSM state. Holds all managers needed for PKCS#11 operations.
pub struct HsmCore {
pub(crate) slot_manager: SlotManager,
pub(crate) session_manager: SessionManager,
pub(crate) object_store: ObjectStore,
pub(crate) audit_log: Arc<AuditLog>,
pub(crate) crypto_backend: Arc<dyn CryptoBackend>,
/// SP 800-90A HMAC_DRBG instance for FIPS-compliant random number generation.
pub(crate) drbg: parking_lot::Mutex<HmacDrbg>,
/// Algorithm policy configuration (FIPS approved mode, PQC enable, weak RSA, etc.).
/// Stored at runtime so crypto operations can enforce policy checks.
pub(crate) algorithm_config: AlgorithmConfig,
/// Token serial number (configurable, 16-char space-padded).
pub(crate) serial_number: [u8; 16],
/// Per-instance HMAC key for authenticating serialized operation state blobs
/// (C_GetOperationState / C_SetOperationState). Generated randomly at init
/// time to prevent callers from tampering with saved state (e.g., swapping
/// key handles or mechanisms).
pub(crate) state_hmac_key: Zeroizing<[u8; 32]>,
/// FIPS 140-3 conditional self-test tracker. Ensures each algorithm family
/// passes a KAT before first use.
pub(crate) conditional_self_test: ConditionalSelfTest,
/// Lock-free metrics collector for operation observability.
pub(crate) metrics: MetricsCollector,
/// Per-session and global rate limiter for crypto operations.
pub(crate) rate_limiter: RateLimiter,
/// Whether token-object persistence is enabled (`[token] persist_objects`).
/// Read by the ABI login path to decide whether to derive and install the
/// EncryptedStore key from the user PIN.
pub(crate) persist_objects: bool,
}
impl HsmCore {
/// Build a 16-byte space-padded serial number from config string.
fn make_serial(config: &HsmConfig) -> [u8; 16] {
let mut serial = [b' '; 16];
let s = config.token.serial_number.as_bytes();
let len = s.len().min(16);
serial[..len].copy_from_slice(&s[..len]);
serial
}
/// Build the object store, enabling encrypted persistence when
/// `[token] persist_objects = true`.
///
/// When enabled, the encrypted redb database lives at
/// `<storage_path>/objects.redb` and is guarded by an exclusive
/// `<...>.lock` file (multi-process safety). The directory is created if
/// absent. The per-object encryption key is *not* set here — it is derived
/// from the user PIN and installed at `C_Login` time (see `C_Login` in the
/// ABI layer). When disabled, returns a purely in-memory store, preserving
/// the legacy default.
fn build_object_store(config: &HsmConfig) -> Result<ObjectStore, crate::error::HsmError> {
if !config.token.persist_objects {
return Ok(ObjectStore::new());
}
let dir = &config.token.storage_path;
std::fs::create_dir_all(dir).map_err(|e| {
tracing::error!(
"failed to create storage directory '{}' for object persistence: {}",
dir.display(),
e
);
crate::error::HsmError::GeneralError
})?;
let db_path = dir.join("objects.redb");
let db_path_str = db_path.to_str().ok_or_else(|| {
tracing::error!(
"object store path '{}' is not valid UTF-8",
db_path.display()
);
crate::error::HsmError::GeneralError
})?;
let store = EncryptedStore::new(Some(db_path_str))?;
tracing::info!(
"object persistence enabled — encrypted store at '{}'",
db_path.display()
);
Ok(ObjectStore::with_persistence(store))
}
/// Select the crypto backend based on config + compiled features.
///
/// The built-in RustCrypto backend is the default. External backends
/// (e.g., the FIPS-validated aws-lc-rs backend in `craton_hsm-awslc`)
/// can be injected via [`HsmCore::new_with_backend`].
/// Resolve the crypto backend a given configuration selects, without
/// building a core.
///
/// Public because any embedder that runs the power-on self-tests must run
/// them against the backend it is about to use — see
/// [`crate::crypto::self_test::run_post_algorithms`]. Both `C_Initialize`
/// and the daemon select once here and pass the same instance to
/// [`HsmCore::new_with_backend`], so the tested and served implementations
/// cannot diverge. Selection itself performs no cryptography.
pub fn select_crypto_backend(config: &HsmConfig) -> Arc<dyn CryptoBackend> {
let requested = config.algorithms.crypto_backend.as_str();
match requested {
#[cfg(feature = "awslc-backend")]
"awslc" => return Arc::new(crate::crypto::awslc_backend::AwsLcBackend),
#[cfg(not(feature = "awslc-backend"))]
"awslc" => {
tracing::warn!(
"Config requests 'awslc' backend but awslc-backend feature not compiled. \
Use HsmCore::new_with_backend() to inject it. \
Falling back to rustcrypto."
);
}
#[cfg(feature = "rustcrypto-backend")]
"rustcrypto" => return Arc::new(RustCryptoBackend),
#[cfg(not(feature = "rustcrypto-backend"))]
"rustcrypto" => {
tracing::warn!(
"Config requests 'rustcrypto' backend but rustcrypto-backend feature not compiled; falling back"
);
}
other => {
tracing::warn!(
"Unknown crypto_backend '{}' in config; use HsmCore::new_with_backend() \
to inject enterprise backends (openssl, cng, pkcs11-passthrough)",
other
);
}
}
// Fallback: prefer awslc-backend if compiled, else rustcrypto
#[cfg(feature = "awslc-backend")]
{
return Arc::new(crate::crypto::awslc_backend::AwsLcBackend);
}
#[cfg(all(feature = "rustcrypto-backend", not(feature = "awslc-backend")))]
{
Arc::new(RustCryptoBackend)
}
#[cfg(not(any(feature = "rustcrypto-backend", feature = "awslc-backend")))]
{
compile_error!(
"No crypto backend available. Enable the `rustcrypto-backend` or \
`awslc-backend` feature, or inject a custom backend via \
HsmCore::new_with_backend()."
);
}
}
/// Create a new HsmCore from a loaded configuration.
///
/// # Panics
///
/// Panics if [`HsmCore::try_new`] fails: the HMAC_DRBG cannot be
/// instantiated, or the object store or audit log cannot be opened (for
/// example, another process holds the store's lock).
pub fn new(config: &HsmConfig) -> Self {
Self::try_new(config).unwrap_or_else(|e| panic!("HSM core initialization failed: {e:?}"))
}
/// Generate a random 32-byte key for HMAC-binding operation state blobs.
fn generate_state_hmac_key() -> Zeroizing<[u8; 32]> {
use rand::rngs::OsRng;
use rand::RngCore;
let mut key = Zeroizing::new([0u8; 32]);
OsRng.fill_bytes(&mut *key);
key
}
/// Fallible constructor — returns an error if the HMAC_DRBG cannot be
/// seeded (e.g., OS entropy source unavailable).
pub fn try_new(config: &HsmConfig) -> Result<Self, crate::error::HsmError> {
let drbg = HmacDrbg::new().map_err(|_| crate::error::HsmError::GeneralError)?;
Ok(Self {
slot_manager: SlotManager::new_with_config(config),
session_manager: SessionManager::new(),
object_store: Self::build_object_store(config)?,
audit_log: {
let log = if config.audit.enabled {
AuditLog::new_with_path(config.audit.log_path.clone())?
} else {
AuditLog::new()
};
let log = Arc::new(log);
log.register_global_logger();
log
},
crypto_backend: Self::select_crypto_backend(config),
drbg: parking_lot::Mutex::new(drbg),
algorithm_config: config.algorithms.clone(),
serial_number: Self::make_serial(config),
state_hmac_key: Self::generate_state_hmac_key(),
conditional_self_test: ConditionalSelfTest::new(),
metrics: MetricsCollector::new(true),
rate_limiter: RateLimiter::new(&crate::crypto::rate_limit::RateLimitConfig::default()),
persist_objects: config.token.persist_objects,
})
}
/// Create a new HsmCore with an externally-provided crypto backend.
///
/// Use this to inject the FIPS-validated `AwsLcBackend` from
/// the `craton_hsm-awslc` crate (enterprise), or any custom backend.
///
/// # Panics
///
/// Panics if [`HsmCore::try_new_with_backend`] fails: the HMAC_DRBG
/// cannot be instantiated, or the object store or audit log cannot be
/// opened (for example, another process holds the store's lock).
pub fn new_with_backend(config: &HsmConfig, backend: Arc<dyn CryptoBackend>) -> Self {
Self::try_new_with_backend(config, backend)
.unwrap_or_else(|e| panic!("HSM core initialization failed: {e:?}"))
}
/// Fallible constructor with an externally-provided crypto backend.
pub fn try_new_with_backend(
config: &HsmConfig,
backend: Arc<dyn CryptoBackend>,
) -> Result<Self, crate::error::HsmError> {
let drbg = HmacDrbg::new().map_err(|_| crate::error::HsmError::GeneralError)?;
Ok(Self {
slot_manager: SlotManager::new_with_config(config),
session_manager: SessionManager::new(),
object_store: Self::build_object_store(config)?,
audit_log: {
let log = if config.audit.enabled {
AuditLog::new_with_path(config.audit.log_path.clone())?
} else {
AuditLog::new()
};
let log = Arc::new(log);
log.register_global_logger();
log
},
crypto_backend: backend,
drbg: parking_lot::Mutex::new(drbg),
algorithm_config: config.algorithms.clone(),
serial_number: Self::make_serial(config),
state_hmac_key: Self::generate_state_hmac_key(),
conditional_self_test: ConditionalSelfTest::new(),
metrics: MetricsCollector::new(true),
rate_limiter: RateLimiter::new(&crate::crypto::rate_limit::RateLimitConfig::default()),
persist_objects: config.token.persist_objects,
})
}
/// Create with default configuration.
///
/// Returns an error if the config file is present but malformed, or if
/// any security parameter fails validation.
pub fn new_default() -> Result<Self, crate::error::HsmError> {
let config = HsmConfig::load()?;
Self::try_new(&config)
}
// ========================================================================
// Public accessors for external workspace crates (daemon, admin CLI).
// Internal code (pkcs11_abi) uses pub(crate) field access directly.
// ========================================================================
/// Returns a reference to the slot manager.
pub fn slot_manager(&self) -> &SlotManager {
&self.slot_manager
}
/// Returns a reference to the session manager.
pub fn session_manager(&self) -> &SessionManager {
&self.session_manager
}
/// Returns a reference to the object store.
pub fn object_store(&self) -> &ObjectStore {
&self.object_store
}
/// Returns a reference to the audit log.
pub fn audit_log(&self) -> &Arc<AuditLog> {
&self.audit_log
}
/// Returns a reference to the active crypto backend.
pub fn crypto_backend(&self) -> &Arc<dyn CryptoBackend> {
&self.crypto_backend
}
/// Returns a reference to the algorithm configuration.
pub fn algorithm_config(&self) -> &AlgorithmConfig {
&self.algorithm_config
}
/// Returns a reference to the HMAC_DRBG instance (SP 800-90A).
pub fn drbg(&self) -> &parking_lot::Mutex<HmacDrbg> {
&self.drbg
}
/// Returns a reference to the metrics collector.
pub fn metrics(&self) -> &MetricsCollector {
&self.metrics
}
/// Whether the SP 800-90B entropy source health monitor has tripped.
///
/// Returns `true` if either continuous health test (repetition count or
/// adaptive proportion) has failed, or if the startup test failed. Once
/// set, this stays set for the process lifetime — callers that observe a
/// `true` here MUST refuse to perform crypto operations and return
/// `CKR_DEVICE_ERROR` / `CKR_GENERAL_ERROR` to the PKCS#11 client.
pub fn entropy_health_error_state(&self) -> bool {
crate::crypto::entropy_health::global_is_error_state()
}
}