1 /* 2 * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.pkcs11; 27 28 import java.util.*; 29 30 import java.security.ProviderException; 31 32 import sun.security.util.Debug; 33 34 import sun.security.pkcs11.wrapper.*; 35 import static sun.security.pkcs11.wrapper.PKCS11Constants.*; 36 37 import java.util.concurrent.LinkedBlockingQueue; 38 import java.util.concurrent.atomic.AtomicInteger; 39 40 /** 41 * Session manager. There is one session manager object per PKCS#11 42 * provider. It allows code to checkout a session, release it 43 * back to the pool, or force it to be closed. 44 * 45 * The session manager pools sessions to minimize the number of 46 * C_OpenSession() and C_CloseSession() that have to be made. It 47 * maintains two pools: one for "object" sessions and one for 48 * "operation" sessions. 49 * 50 * The reason for this separation is how PKCS#11 deals with session objects. 51 * It defines that when a session is closed, all objects created within 52 * that session are destroyed. In other words, we may never close a session 53 * while a Key created it in is still in use. We would like to keep the 54 * number of such sessions low. Note that we occasionally want to explicitly 55 * close a session, see P11Signature. 56 * 57 * NOTE that sessions obtained from this class SHOULD be returned using 58 * either releaseSession() or closeSession() using a finally block when 59 * not needed anymore. Otherwise, they will be left for cleanup via the 60 * PhantomReference mechanism when GC kicks in, but it's best not to rely 61 * on that since GC may not run timely enough since the native PKCS11 library 62 * is also consuming memory. 63 * 64 * Note that sessions are automatically closed when they are not used for a 65 * period of time, see Session. 66 * 67 * @author Andreas Sterbenz 68 * @since 1.5 69 */ 70 final class SessionManager { 71 72 private final static int DEFAULT_MAX_SESSIONS = 32; 73 74 private final static Debug debug = Debug.getInstance("pkcs11"); 75 76 // token instance 77 private final Token token; 78 79 // maximum number of sessions to open with this token 80 private final int maxSessions; 81 82 // total number of active sessions 83 private AtomicInteger activeSessions = new AtomicInteger(); 84 85 // pool of available object sessions 86 private final Pool objSessions; 87 88 // pool of available operation sessions 89 private final Pool opSessions; 90 91 // maximum number of active sessions during this invocation, for debugging 92 private int maxActiveSessions; 93 private Object maxActiveSessionsLock; 94 95 // flags to use in the C_OpenSession() call 96 private final long openSessionFlags; 97 98 SessionManager(Token token) { 99 long n; 100 if (token.isWriteProtected()) { 101 openSessionFlags = CKF_SERIAL_SESSION; 102 n = token.tokenInfo.ulMaxSessionCount; 103 } else { 104 openSessionFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; 105 n = token.tokenInfo.ulMaxRwSessionCount; 106 } 107 if (n == CK_EFFECTIVELY_INFINITE) { 108 n = Integer.MAX_VALUE; 109 } else if ((n == CK_UNAVAILABLE_INFORMATION) || (n < 0)) { 110 // choose an arbitrary concrete value 111 n = DEFAULT_MAX_SESSIONS; 112 } 113 maxSessions = (int)Math.min(n, Integer.MAX_VALUE); 114 this.token = token; 115 this.objSessions = new Pool(this, true); 116 this.opSessions = new Pool(this, false); 117 if (debug != null) { 118 maxActiveSessionsLock = new Object(); 119 } 120 } 121 122 // returns whether only a fairly low number of sessions are 123 // supported by this token. 124 boolean lowMaxSessions() { 125 return (maxSessions <= DEFAULT_MAX_SESSIONS); 126 } 127 128 Session getObjSession() throws PKCS11Exception { 129 Session session = objSessions.poll(); 130 if (session != null) { 131 return ensureValid(session); 132 } 133 session = opSessions.poll(); 134 if (session != null) { 135 return ensureValid(session); 136 } 137 session = openSession(); 138 return ensureValid(session); 139 } 140 141 Session getOpSession() throws PKCS11Exception { 142 Session session = opSessions.poll(); 143 if (session != null) { 144 return ensureValid(session); 145 } 146 // create a new session rather than re-using an obj session 147 // that avoids potential expensive cancels() for Signatures & RSACipher 148 if (maxSessions == Integer.MAX_VALUE || 149 activeSessions.get() < maxSessions) { 150 session = openSession(); 151 return ensureValid(session); 152 } 153 session = objSessions.poll(); 154 if (session != null) { 155 return ensureValid(session); 156 } 157 throw new ProviderException("Could not obtain session"); 158 } 159 160 private Session ensureValid(Session session) { 161 session.id(); 162 return session; 163 } 164 165 Session killSession(Session session) { 166 if ((session == null) || (token.isValid() == false)) { 167 return null; 168 } 169 if (debug != null) { 170 String location = new Exception().getStackTrace()[2].toString(); 171 System.out.println("Killing session (" + location + ") active: " 172 + activeSessions.get()); 173 } 174 closeSession(session); 175 return null; 176 } 177 178 Session releaseSession(Session session) { 179 if ((session == null) || (token.isValid() == false)) { 180 return null; 181 } 182 183 if (session.hasObjects()) { 184 objSessions.release(session); 185 } else { 186 opSessions.release(session); 187 } 188 return null; 189 } 190 191 void demoteObjSession(Session session) { 192 if (token.isValid() == false) { 193 return; 194 } 195 if (debug != null) { 196 System.out.println("Demoting session, active: " + 197 activeSessions.get()); 198 } 199 boolean present = objSessions.remove(session); 200 if (present == false) { 201 // session is currently in use 202 // will be added to correct pool on release, nothing to do now 203 return; 204 } 205 opSessions.release(session); 206 } 207 208 private Session openSession() throws PKCS11Exception { 209 if ((maxSessions != Integer.MAX_VALUE) && 210 (activeSessions.get() >= maxSessions)) { 211 throw new ProviderException("No more sessions available"); 212 } 213 214 long id = token.p11.C_OpenSession 215 (token.provider.slotID, openSessionFlags, null, null); 216 Session session = new Session(token, id); 217 activeSessions.incrementAndGet(); 218 if (debug != null) { 219 synchronized(maxActiveSessionsLock) { 220 if (activeSessions.get() > maxActiveSessions) { 221 maxActiveSessions = activeSessions.get(); 222 if (maxActiveSessions % 10 == 0) { 223 System.out.println("Open sessions: " + maxActiveSessions); 224 } 225 } 226 } 227 } 228 return session; 229 } 230 231 private void closeSession(Session session) { 232 session.close(); 233 activeSessions.decrementAndGet(); 234 } 235 236 public static final class Pool { 237 238 private final SessionManager mgr; 239 private final AbstractQueue<Session> pool; 240 private final int SESSION_MAX = 5; 241 242 // Object session pools can contain unlimited sessions. 243 // Operation session pools are limited and enforced by the queue. 244 Pool(SessionManager mgr, boolean obj) { 245 this.mgr = mgr; 246 if (obj) { 247 pool = new LinkedBlockingQueue<Session>(); 248 } else { 249 pool = new LinkedBlockingQueue<Session>(SESSION_MAX); 250 } 251 } 252 253 boolean remove(Session session) { 254 return pool.remove(session); 255 } 256 257 Session poll() { 258 return pool.poll(); 259 } 260 261 void release(Session session) { 262 // Object session pools never return false, only Operation ones 263 if (!pool.offer(session)) { 264 mgr.closeSession(session); 265 free(); 266 } 267 } 268 269 // Free any old operation session if this queue is full 270 void free() { 271 int n = SESSION_MAX; 272 int i = 0; 273 Session oldestSession; 274 long time = System.currentTimeMillis(); 275 // Check if the session head is too old and continue through pool 276 // until only one is left. 277 do { 278 oldestSession = pool.peek(); 279 if (oldestSession == null || oldestSession.isLive(time) || 280 !pool.remove(oldestSession)) { 281 break; 282 } 283 284 i++; 285 mgr.closeSession(oldestSession); 286 } while ((n - i) > 1); 287 288 if (debug != null) { 289 System.out.println("Closing " + i + " idle sessions, active: " 290 + mgr.activeSessions); 291 } 292 } 293 294 } 295 296 }