Skip to content

Commit d33d256

Browse files
authored
Use ConnectionCustomizer to ensure login is only done when needed (#4)
1 parent a7e3eeb commit d33d256

File tree

2 files changed

+86
-37
lines changed

2 files changed

+86
-37
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
Session pool manager for [cryptoki](https://github.com/parallaxsecond/rust-cryptoki/).
77

8+
Cryptoki has a single login state for all sessions.
9+
Only when all sessions are closed, a login is needed again.
10+
This library requires a `ConnectionCustomizer` on the pool to ensure login is only done when needed.
11+
The `SessionAuth` can be converted into the appropriate `ConnectionCustomizer`.
12+
813
## Example
914

1015
```rust no_run
@@ -14,9 +19,11 @@ let pkcs11 = Pkcs11::new("libsofthsm2.so").unwrap();
1419
pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();
1520
let slots = pkcs11.get_slots_with_token().unwrap();
1621
let slot = slots.first().unwrap();
17-
let manager = SessionManager::new(pkcs11, *slot, SessionType::RwUser(AuthPin::new("fedcba".to_string())));
22+
let session_auth = SessionAuth::RwUser(AuthPin::new("fedcba".to_string()));
23+
let manager = SessionManager::new(pkcs11, *slot, &session_auth);
1824

19-
let pool = r2d2::Pool::builder().build(manager).unwrap();
25+
let pool_builder = Pool::builder().connection_customizer(session_auth.into_customizer());
26+
let pool = pool_builder.build(manager).unwrap();
2027

2128
let session = pool.get().unwrap();
2229
println!("{:?}", session.get_session_info().unwrap());

src/lib.rs

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![warn(missing_docs)]
22
#![doc = include_str!("../README.md")]
33

4+
use std::sync::{Arc, Mutex};
5+
46
pub use cryptoki;
57
pub use r2d2;
68

@@ -11,7 +13,7 @@ use cryptoki::{
1113
slot::{Limit, Slot},
1214
types::AuthPin,
1315
};
14-
use r2d2::ManageConnection;
16+
use r2d2::{CustomizeConnection, ManageConnection, NopConnectionCustomizer};
1517

1618
/// Alias for this crate's instance of r2d2's Pool
1719
pub type Pool = r2d2::Pool<SessionManager>;
@@ -23,12 +25,12 @@ pub type PooledSession = r2d2::PooledConnection<SessionManager>;
2325
pub struct SessionManager {
2426
pkcs11: Pkcs11,
2527
slot: Slot,
26-
session_type: SessionType,
28+
session_state: SessionState,
2729
}
2830

2931
/// Session types, holding the pin for the authenticated sessions
3032
#[derive(Debug, Clone)]
31-
pub enum SessionType {
33+
pub enum SessionAuth {
3234
/// [SessionState::RoPublic]
3335
RoPublic,
3436
/// [SessionState::RoUser]
@@ -41,7 +43,15 @@ pub enum SessionType {
4143
RwSecurityOfficer(AuthPin),
4244
}
4345

44-
impl SessionType {
46+
/// Mandatory connection customizer for logins
47+
#[derive(Debug, Clone)]
48+
struct LoginCustomizer {
49+
auth_pin: AuthPin,
50+
user_type: UserType,
51+
active_sessions: Arc<Mutex<u32>>,
52+
}
53+
54+
impl SessionAuth {
4555
fn as_state(&self) -> SessionState {
4656
match self {
4757
Self::RoPublic => SessionState::RoPublic,
@@ -51,6 +61,23 @@ impl SessionType {
5161
Self::RwSecurityOfficer(_) => SessionState::RwSecurityOfficer,
5262
}
5363
}
64+
65+
/// Returns the correct customizer to use for the specified session auth
66+
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+
}
5481
}
5582

5683
impl SessionManager {
@@ -61,13 +88,13 @@ impl SessionManager {
6188
/// pkcs11 .initialize(CInitializeArgs::OsThreads).unwrap();
6289
/// let slots = pkcs11.get_slots_with_token().unwrap();
6390
/// let slot = slots.first().unwrap();
64-
/// let manager = SessionManager::new(pkcs11, *slot, SessionType::RwUser(AuthPin::new("abcd".to_string())));
91+
/// let manager = SessionManager::new(pkcs11, *slot, &SessionAuth::RwUser(AuthPin::new("abcd".to_string())));
6592
/// ```
66-
pub fn new(pkcs11: Pkcs11, slot: Slot, session_type: SessionType) -> Self {
93+
pub fn new(pkcs11: Pkcs11, slot: Slot, session_auth: &SessionAuth) -> Self {
6794
Self {
6895
pkcs11,
6996
slot,
70-
session_type,
97+
session_state: session_auth.as_state(),
7198
}
7299
}
73100

@@ -83,8 +110,9 @@ impl SessionManager {
83110
/// # pkcs11.initialize(CInitializeArgs::OsThreads);
84111
/// # let slots = pkcs11.get_slots_with_token().unwrap();
85112
/// # let slot = slots.first().unwrap();
86-
/// # let manager = SessionManager::new(pkcs11, *slot, SessionType::RwUser(AuthPin::new("fedcba".to_string())));
87-
/// let pool_builder = r2d2::Pool::builder();
113+
/// # let session_auth = SessionAuth::RwUser(AuthPin::new("fedcba".to_string()));
114+
/// # let manager = SessionManager::new(pkcs11, *slot, &session_auth);
115+
/// let pool_builder = Pool::builder().connection_customizer(session_auth.into_customizer());
88116
/// let pool_builder = if let Some(max_size) = manager.max_size(100).unwrap() {
89117
/// pool_builder.max_size(max_size)
90118
/// } else {
@@ -94,12 +122,7 @@ impl SessionManager {
94122
/// ```
95123
pub fn max_size(&self, maximum: u32) -> Result<Option<u32>, cryptoki::error::Error> {
96124
let token_info = self.pkcs11.get_token_info(self.slot)?;
97-
let limit = match self.session_type {
98-
SessionType::RoPublic | SessionType::RoUser(_) => token_info.max_session_count(),
99-
SessionType::RwPublic | SessionType::RwUser(_) | SessionType::RwSecurityOfficer(_) => {
100-
token_info.max_session_count()
101-
}
102-
};
125+
let limit = token_info.max_session_count();
103126
let res = match limit {
104127
Limit::Max(m) => Some(m.try_into().unwrap_or(u32::MAX)),
105128
Limit::Unavailable => None,
@@ -118,35 +141,21 @@ impl ManageConnection for SessionManager {
118141

119142
type Error = cryptoki::error::Error;
120143

121-
// Login is global, once a session logs in, all sessions are logged in https://stackoverflow.com/a/40225885.
122-
// TODO cryptoki automatically logs out on Drop, so this is ineficient and we will need to find a better way to check the login state when we start having a pool of sessions
123144
fn connect(&self) -> Result<Self::Connection, Self::Error> {
124-
let session = match self.session_type {
125-
SessionType::RoPublic | SessionType::RoUser(_) => {
145+
let session = match self.session_state {
146+
SessionState::RoPublic | SessionState::RoUser => {
126147
self.pkcs11.open_ro_session(self.slot)?
127148
}
128-
SessionType::RwPublic | SessionType::RwUser(_) | SessionType::RwSecurityOfficer(_) => {
149+
SessionState::RwPublic | SessionState::RwUser | SessionState::RwSecurityOfficer => {
129150
self.pkcs11.open_rw_session(self.slot)?
130151
}
131152
};
132-
let maybe_user_info = match &self.session_type {
133-
SessionType::RoPublic | SessionType::RwPublic => None,
134-
SessionType::RoUser(pin) | SessionType::RwUser(pin) => Some((UserType::User, pin)),
135-
SessionType::RwSecurityOfficer(pin) => Some((UserType::So, pin)),
136-
};
137-
if let Some(user_type) = maybe_user_info {
138-
match session.login(user_type.0, Some(user_type.1)) {
139-
Err(Self::Error::Pkcs11(RvError::UserAlreadyLoggedIn, Function::Login)) => {}
140-
res => res?,
141-
};
142-
}
143153
Ok(session)
144154
}
145155

146156
fn is_valid(&self, session: &mut Self::Connection) -> Result<(), Self::Error> {
147157
let actual_state = session.get_session_info()?.session_state();
148-
let expected_state = &self.session_type;
149-
if actual_state != expected_state.as_state() {
158+
if actual_state != self.session_state {
150159
Err(Self::Error::Pkcs11(
151160
RvError::UserNotLoggedIn,
152161
Function::GetSessionInfo,
@@ -162,6 +171,38 @@ impl ManageConnection for SessionManager {
162171
}
163172
}
164173

174+
impl 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+
// Login is global, once a session logs in, all sessions are logged in https://stackoverflow.com/a/40225885.
180+
if *active == 0 {
181+
match session.login(self.user_type, Some(&self.auth_pin)) {
182+
// Can happen with poisoned mutex
183+
Err(cryptoki::error::Error::Pkcs11(
184+
RvError::UserAlreadyLoggedIn,
185+
Function::Login,
186+
)) => {}
187+
res => res?,
188+
};
189+
};
190+
191+
// Increase after login to prefer login too many over too few
192+
*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+
165206
#[cfg(test)]
166207
mod test {
167208
use std::{env, fs, path::Path, time::Duration};
@@ -225,8 +266,9 @@ mod test {
225266
let pin = AuthPin::new(pin_string.clone());
226267
let (pkcs11, slot) = default_token(pin_string);
227268

228-
let manager = SessionManager::new(pkcs11, slot, SessionType::RwUser(pin));
229-
let pool_builder = r2d2::Pool::builder();
269+
let login = SessionAuth::RwUser(pin);
270+
let manager = SessionManager::new(pkcs11, slot, &login);
271+
let pool_builder = Pool::builder().connection_customizer(login.into_customizer());
230272
let pool_builder = if let Some(m) = config.max_sessions {
231273
pool_builder.max_size(m)
232274
} else {

0 commit comments

Comments
 (0)