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 }