11#![ warn( missing_docs) ]
22#![ doc = include_str ! ( "../README.md" ) ]
33
4+ use std:: sync:: { Arc , Mutex } ;
5+
46pub use cryptoki;
57pub 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
1719pub type Pool = r2d2:: Pool < SessionManager > ;
@@ -23,12 +25,12 @@ pub type PooledSession = r2d2::PooledConnection<SessionManager>;
2325pub 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
5683impl 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) ]
166207mod 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