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 }