1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use std::sync::{Arc, Mutex};
5
6pub use cryptoki;
7pub use r2d2;
8
9use cryptoki::{
10 context::{Function, Pkcs11},
11 error::RvError,
12 session::{Session, SessionState, UserType},
13 slot::{Limit, Slot},
14 types::AuthPin,
15};
16use r2d2::{CustomizeConnection, ManageConnection, NopConnectionCustomizer};
17
18pub type Pool = r2d2::Pool<SessionManager>;
20pub type PooledSession = r2d2::PooledConnection<SessionManager>;
22
23#[derive(Debug, Clone)]
25pub struct SessionManager {
26 pkcs11: Pkcs11,
27 slot: Slot,
28 session_state: SessionState,
29}
30
31#[derive(Debug, Clone)]
33pub enum SessionAuth {
34 RoPublic,
36 RoUser(AuthPin),
38 RwPublic,
40 RwUser(AuthPin),
42 RwSecurityOfficer(AuthPin),
44}
45
46#[derive(Debug, Clone)]
48struct LoginCustomizer {
49 auth_pin: AuthPin,
50 user_type: UserType,
51 active_sessions: Arc<Mutex<u32>>,
52}
53
54impl SessionAuth {
55 fn as_state(&self) -> SessionState {
56 match self {
57 Self::RoPublic => SessionState::RoPublic,
58 Self::RoUser(_) => SessionState::RoUser,
59 Self::RwPublic => SessionState::RwPublic,
60 Self::RwUser(_) => SessionState::RwUser,
61 Self::RwSecurityOfficer(_) => SessionState::RwSecurityOfficer,
62 }
63 }
64
65 pub fn into_customizer(self) -> Box<dyn CustomizeConnection<Session, cryptoki::error::Error>> {
67 match self {
68 Self::RoPublic | Self::RwPublic => Box::new(NopConnectionCustomizer),
69 Self::RoUser(auth_pin) | Self::RwUser(auth_pin) => Box::from(LoginCustomizer {
70 auth_pin,
71 user_type: UserType::User,
72 active_sessions: Default::default(),
73 }),
74 Self::RwSecurityOfficer(auth_pin) => Box::from(LoginCustomizer {
75 auth_pin,
76 user_type: UserType::So,
77 active_sessions: Default::default(),
78 }),
79 }
80 }
81}
82
83impl SessionManager {
84 pub fn new(pkcs11: Pkcs11, slot: Slot, session_auth: &SessionAuth) -> Self {
94 Self {
95 pkcs11,
96 slot,
97 session_state: session_auth.as_state(),
98 }
99 }
100
101 pub fn max_size(&self, maximum: u32) -> Result<Option<u32>, cryptoki::error::Error> {
124 let token_info = self.pkcs11.get_token_info(self.slot)?;
125 let limit = token_info.max_session_count();
126 let res = match limit {
127 Limit::Max(m) => Some(m.try_into().unwrap_or(u32::MAX)),
128 Limit::Unavailable => None,
129 Limit::Infinite => Some(u32::MAX),
130 };
131 Ok(if let Some(true) = res.map(|r| r > maximum) {
132 Some(maximum)
133 } else {
134 res
135 })
136 }
137}
138
139impl ManageConnection for SessionManager {
140 type Connection = Session;
141
142 type Error = cryptoki::error::Error;
143
144 fn connect(&self) -> Result<Self::Connection, Self::Error> {
145 let session = match self.session_state {
146 SessionState::RoPublic | SessionState::RoUser => {
147 self.pkcs11.open_ro_session(self.slot)?
148 }
149 SessionState::RwPublic | SessionState::RwUser | SessionState::RwSecurityOfficer => {
150 self.pkcs11.open_rw_session(self.slot)?
151 }
152 };
153 Ok(session)
154 }
155
156 fn is_valid(&self, session: &mut Self::Connection) -> Result<(), Self::Error> {
157 let actual_state = session.get_session_info()?.session_state();
158 if actual_state != self.session_state {
159 Err(Self::Error::Pkcs11(
160 RvError::UserNotLoggedIn,
161 Function::GetSessionInfo,
162 ))
163 } else {
164 Ok(())
165 }
166 }
167
168 fn has_broken(&self, _session: &mut Self::Connection) -> bool {
169 false
171 }
172}
173
174impl CustomizeConnection<Session, cryptoki::error::Error> for LoginCustomizer {
175 fn on_acquire(&self, session: &mut Session) -> Result<(), cryptoki::error::Error> {
176 let mutex = self.active_sessions.clone();
177 let mut active = mutex.lock().unwrap_or_else(|e| e.into_inner());
178
179 if *active == 0 {
181 match session.login(self.user_type, Some(&self.auth_pin)) {
182 Err(cryptoki::error::Error::Pkcs11(
184 RvError::UserAlreadyLoggedIn,
185 Function::Login,
186 )) => {}
187 res => res?,
188 };
189 };
190
191 *active += 1;
193
194 Ok(())
195 }
196
197 fn on_release(&self, _: Session) {
198 let mutex = self.active_sessions.clone();
199 let mut active = mutex.lock().unwrap_or_else(|e| e.into_inner());
200 if *active > 0 {
201 *active -= 1;
202 }
203 }
204}
205
206#[cfg(test)]
207mod test {
208 use std::{
209 env, fs,
210 path::Path,
211 time::{Duration, Instant},
212 };
213
214 use cached::proc_macro::{cached, once};
215 use cryptoki::{
216 context::{CInitializeArgs, CInitializeFlags},
217 mechanism::Mechanism,
218 object::{Attribute, KeyType, ObjectClass},
219 };
220 use r2d2::PooledConnection;
221
222 use super::*;
223
224 #[derive(Clone, Hash, PartialEq, Eq)]
225 struct Config {
226 max_sessions: Option<u32>,
227 label: Vec<u8>,
228 }
229
230 const DEFAULT_PIN: &str = "abcde";
231
232 #[once(sync_writes = true)]
234 fn default_pkcs11() -> Pkcs11 {
235 env::set_var("SOFTHSM2_CONF", "./test/softhsm2.conf");
236 let tokens_path = Path::new("./test/softhsm/tokens");
237 if tokens_path.exists() {
238 fs::remove_dir_all(tokens_path.to_str().unwrap()).unwrap();
239 }
240 fs::create_dir_all(tokens_path.to_str().unwrap()).unwrap();
241
242 let pkcs11 = Pkcs11::new("libsofthsm2.so").expect("Could not use pkcs11 library");
243 pkcs11
244 .initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))
245 .expect("Could not initialize pkcs11");
246 pkcs11
247 }
248
249 #[cached(sync_writes = "default")]
250 fn default_token(pin: String) -> (Pkcs11, Slot) {
251 let pkcs11 = default_pkcs11();
252 let slot = {
253 let slots = pkcs11
254 .get_slots_with_token()
255 .expect("Could not get slots with token");
256 *slots.first().expect("Could not find a slot")
257 };
258 pkcs11
259 .init_token(slot, &pin.clone().into(), "token")
260 .expect("Could not initialize token");
261 let session = pkcs11.open_rw_session(slot).unwrap();
262 session
263 .login(cryptoki::session::UserType::So, Some(&pin.clone().into()))
264 .unwrap();
265 session.init_pin(&pin.into()).unwrap();
266
267 (pkcs11, slot)
268 }
269
270 #[cached(sync_writes = "default")]
274 fn isolated_token(pin: String) -> (Pkcs11, Slot) {
275 static CLAIM_SLOT: Mutex<()> = Mutex::new(());
277
278 let (pkcs11, _) = default_token(DEFAULT_PIN.to_string());
280 let _guard = CLAIM_SLOT.lock().unwrap_or_else(|e| e.into_inner());
281 let initialized = pkcs11
282 .get_slots_with_initialized_token()
283 .expect("Could not get slots with initialized token");
284 let slot = pkcs11
285 .get_slots_with_token()
286 .expect("Could not get slots with token")
287 .into_iter()
288 .find(|slot| !initialized.contains(slot))
289 .expect("Could not find a spare slot to initialize an isolated token on");
290 pkcs11
291 .init_token(slot, &pin.clone().into(), "isolated")
292 .expect("Could not initialize token");
293 let session = pkcs11.open_rw_session(slot).unwrap();
294 session
295 .login(UserType::So, Some(&pin.clone().into()))
296 .unwrap();
297 session.init_pin(&pin.into()).unwrap();
298
299 (pkcs11, slot)
300 }
301
302 fn default_setup(config: Config) -> Pool {
303 let pin_string = DEFAULT_PIN.to_string();
304 let pin = AuthPin::new(pin_string.clone().into());
305 let (pkcs11, slot) = default_token(pin_string);
306
307 let login = SessionAuth::RwUser(pin);
308 let manager = SessionManager::new(pkcs11, slot, &login);
309 let pool_builder = Pool::builder().connection_customizer(login.into_customizer());
310 let pool_builder = if let Some(m) = config.max_sessions {
311 pool_builder.max_size(m)
312 } else {
313 pool_builder
314 };
315 let pool = pool_builder.build(manager).unwrap();
316
317 let mechanism = Mechanism::EccKeyPairGen;
318 let pub_key_template = vec![
319 Attribute::Token(true),
320 Attribute::Private(false),
321 Attribute::Derive(true),
322 Attribute::KeyType(KeyType::EC),
323 Attribute::Verify(true),
324 Attribute::EcParams(vec![
325 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
326 ]),
327 Attribute::Label(config.label.clone()),
328 ];
329 let priv_key_template = vec![
330 Attribute::Token(true),
331 Attribute::Private(false),
332 Attribute::Sensitive(true),
333 Attribute::Extractable(false),
334 Attribute::Derive(true),
335 Attribute::Sign(true),
336 Attribute::Label(config.label),
337 ];
338
339 backoff::retry(
341 backoff::backoff::Constant::new(Duration::from_millis(25)),
342 || {
343 Ok(pool.get().unwrap().generate_key_pair(
344 &mechanism,
345 &pub_key_template,
346 &priv_key_template,
347 )?)
348 },
349 )
350 .unwrap();
351 pool
352 }
353
354 fn sign(config: &Config, session: &PooledConnection<SessionManager>) -> Vec<u8> {
355 let template = vec![
356 Attribute::Class(ObjectClass::PRIVATE_KEY),
357 Attribute::Label(config.label.clone()),
358 ];
359 let objects = session.find_objects(&template).unwrap();
360 let private = objects.first().unwrap();
361 session
362 .sign(&Mechanism::Ecdsa, *private, "test_data".as_bytes())
363 .unwrap()
364 }
365 fn verify(config: &Config, session: &PooledConnection<SessionManager>, signature: &[u8]) {
366 let template = vec![
367 Attribute::Class(ObjectClass::PUBLIC_KEY),
368 Attribute::Label(config.label.clone()),
369 ];
370 let objects = session.find_objects(&template).unwrap();
371 let public = objects.first().unwrap();
372 session
373 .verify(
374 &Mechanism::Ecdsa,
375 *public,
376 "test_data".as_bytes(),
377 signature,
378 )
379 .unwrap();
380 }
381
382 #[test]
383 fn basic() {
384 let config = Config {
385 max_sessions: None,
386 label: "basic".into(),
387 };
388 let pool = default_setup(config.clone());
389 let sig = sign(&config, &pool.get().unwrap());
390 verify(&config, &pool.get().unwrap(), &sig);
391 }
392
393 fn basic_test(config: &Config, pool1: Pool) {
394 let pool2 = pool1.clone();
395 let config1 = config.clone();
396 let config2 = config.clone();
397 loom::thread::spawn(move || {
398 let sig = sign(&config1, &pool1.get().unwrap());
399 verify(&config1, &pool1.get().unwrap(), &sig);
400 });
401 let sig = sign(&config2, &pool2.get().unwrap());
402 verify(&config2, &pool2.get().unwrap(), &sig);
403 }
404
405 #[test]
406 fn basic_concurrency() {
407 loom::model(|| {
408 let config = Config {
409 max_sessions: None,
410 label: "basic_concurrency".into(),
411 };
412 let pool1 = default_setup(config.clone());
413 basic_test(&config, pool1);
414 });
415 }
416
417 #[test]
418 fn max_one_session() {
419 loom::model(|| {
420 let config = Config {
421 max_sessions: Some(1),
422 label: "max_one_session".into(),
423 };
424 let pool1 = default_setup(config.clone());
425 basic_test(&config, pool1);
426 });
427 }
428
429 fn session_state(session: &Session) -> SessionState {
430 session.get_session_info().unwrap().session_state()
431 }
432
433 #[test]
437 fn pool_stays_logged_in_while_shrinking() {
438 let pin_string = "baefc".to_string();
439 let pin = AuthPin::new(pin_string.clone().into());
440 let (pkcs11, slot) = isolated_token(pin_string);
441 let customizer = LoginCustomizer {
442 auth_pin: pin.clone(),
443 user_type: UserType::User,
444 active_sessions: Default::default(),
445 };
446 let active_sessions = customizer.active_sessions.clone();
447 let active = || *active_sessions.lock().unwrap();
448 let manager = SessionManager::new(pkcs11, slot, &SessionAuth::RwUser(pin));
449 let pool = Pool::builder()
450 .max_size(2)
451 .min_idle(Some(0))
454 .idle_timeout(Some(Duration::from_millis(1)))
455 .connection_customizer(Box::new(customizer))
456 .connection_timeout(Duration::from_secs(5))
457 .build(manager)
458 .unwrap();
459
460 let held = pool.get().unwrap();
462 drop(pool.get().unwrap());
463 assert_eq!(active(), 2);
464
465 let deadline = Instant::now() + Duration::from_secs(120);
467 while active() != 1 {
468 assert!(
469 Instant::now() < deadline,
470 "reaper did not discard the idle session"
471 );
472 std::thread::sleep(Duration::from_millis(250));
473 }
474
475 assert_eq!(session_state(&held), SessionState::RwUser);
476
477 let fresh = pool.get().unwrap();
480 assert_eq!(active(), 2);
481 assert_eq!(session_state(&fresh), SessionState::RwUser);
482 }
483
484 #[test]
488 fn pool_recovers_from_token_logout() {
489 let pin_string = "cbafe".to_string();
490 let pin = AuthPin::new(pin_string.clone().into());
491 let (pkcs11, slot) = isolated_token(pin_string);
492 let login = SessionAuth::RwUser(pin);
493 let manager = SessionManager::new(pkcs11.clone(), slot, &login);
494 let pool = Pool::builder()
495 .max_size(5)
496 .connection_customizer(login.into_customizer())
497 .connection_timeout(Duration::from_secs(5))
498 .build(manager)
499 .unwrap();
500
501 assert_eq!(session_state(&pool.get().unwrap()), SessionState::RwUser);
502
503 let outsider = pkcs11.open_rw_session(slot).unwrap();
504 outsider.logout().unwrap();
505 drop(outsider);
506
507 let session = pool.get().unwrap();
508 assert_eq!(session_state(&session), SessionState::RwUser);
509 }
510
511 #[test]
515 fn login_state_across_session_drops() {
516 let pin_string = "fedcb".to_string();
517 let pin = AuthPin::new(pin_string.clone().into());
518 let (pkcs11, slot) = isolated_token(pin_string);
519
520 let customizer = LoginCustomizer {
521 auth_pin: pin,
522 user_type: UserType::User,
523 active_sessions: Default::default(),
524 };
525 let active_sessions = customizer.active_sessions.clone();
526 let active = || *active_sessions.lock().unwrap();
527
528 let mut first = pkcs11.open_rw_session(slot).unwrap();
529 let mut second = pkcs11.open_rw_session(slot).unwrap();
530
531 customizer.on_acquire(&mut first).unwrap();
533 assert_eq!(active(), 1);
534 assert_eq!(session_state(&first), SessionState::RwUser);
535 customizer.on_acquire(&mut second).unwrap();
536 assert_eq!(active(), 2);
537 assert_eq!(session_state(&second), SessionState::RwUser);
538
539 customizer.on_release(first);
540 assert_eq!(active(), 1);
541 assert_eq!(session_state(&second), SessionState::RwUser);
542
543 customizer.on_release(second);
544 assert_eq!(active(), 0);
545
546 let mut third = pkcs11.open_rw_session(slot).unwrap();
548 assert_eq!(
549 session_state(&third),
550 SessionState::RwPublic,
551 "token should be logged out once its last session is closed"
552 );
553 customizer.on_acquire(&mut third).unwrap();
554 assert_eq!(active(), 1);
555 assert_eq!(session_state(&third), SessionState::RwUser);
556
557 *active_sessions.lock().unwrap() = 0;
560 let mut fourth = pkcs11.open_rw_session(slot).unwrap();
561 assert_eq!(session_state(&fourth), SessionState::RwUser);
562 customizer.on_acquire(&mut fourth).unwrap();
563 assert_eq!(active(), 1);
564 assert_eq!(session_state(&fourth), SessionState::RwUser);
565 }
566
567 #[test]
568 fn multiple_operations_per_session() {
569 loom::model(|| {
570 let config = Config {
571 max_sessions: Some(1),
572 label: "multiple_operations_per_session".into(),
573 };
574 let config2 = config.clone();
575 let pool1 = default_setup(config.clone());
576 let pool2 = pool1.clone();
577 loom::thread::spawn(move || {
578 let session = pool1.get().unwrap();
579 let sig = sign(&config, &session);
580 verify(&config, &session, &sig);
581 });
582 let session = pool2.get().unwrap();
583 let sig = sign(&config2, &session);
584 verify(&config2, &session, &sig);
585 });
586 }
587}