Path: blob/master/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java
41154 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.pkcs11;2627import java.util.*;28import java.security.ProviderException;2930import sun.security.util.Debug;31import sun.security.pkcs11.wrapper.*;32import static sun.security.pkcs11.wrapper.PKCS11Constants.*;3334import java.util.concurrent.LinkedBlockingQueue;35import java.util.concurrent.atomic.AtomicInteger;3637/**38* Session manager. There is one session manager object per PKCS#1139* provider. It allows code to checkout a session, release it40* back to the pool, or force it to be closed.41*42* The session manager pools sessions to minimize the number of43* C_OpenSession() and C_CloseSession() that have to be made. It44* maintains two pools: one for "object" sessions and one for45* "operation" sessions.46*47* The reason for this separation is how PKCS#11 deals with session objects.48* It defines that when a session is closed, all objects created within49* that session are destroyed. In other words, we may never close a session50* while a Key created it in is still in use. We would like to keep the51* number of such sessions low. Note that we occasionally want to explicitly52* close a session, see P11Signature.53*54* NOTE that sessions obtained from this class SHOULD be returned using55* either releaseSession() or closeSession() using a finally block when56* not needed anymore. Otherwise, they will be left for cleanup via the57* PhantomReference mechanism when GC kicks in, but it's best not to rely58* on that since GC may not run timely enough since the native PKCS11 library59* is also consuming memory.60*61* Note that sessions are automatically closed when they are not used for a62* period of time, see Session.63*64* @author Andreas Sterbenz65* @since 1.566*/67final class SessionManager {6869private static final int DEFAULT_MAX_SESSIONS = 32;7071private static final Debug debug = Debug.getInstance("pkcs11");7273// token instance74private final Token token;7576// maximum number of sessions to open with this token77private final int maxSessions;7879// total number of active sessions80private AtomicInteger activeSessions = new AtomicInteger();8182// pool of available object sessions83private final Pool objSessions;8485// pool of available operation sessions86private final Pool opSessions;8788// maximum number of active sessions during this invocation, for debugging89private int maxActiveSessions;90private Object maxActiveSessionsLock;9192// flags to use in the C_OpenSession() call93private final long openSessionFlags;9495SessionManager(Token token) {96long n;97if (token.isWriteProtected()) {98openSessionFlags = CKF_SERIAL_SESSION;99n = token.tokenInfo.ulMaxSessionCount;100} else {101openSessionFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION;102n = token.tokenInfo.ulMaxRwSessionCount;103}104if (n == CK_EFFECTIVELY_INFINITE) {105n = Integer.MAX_VALUE;106} else if ((n == CK_UNAVAILABLE_INFORMATION) || (n < 0)) {107// choose an arbitrary concrete value108n = DEFAULT_MAX_SESSIONS;109}110maxSessions = (int)Math.min(n, Integer.MAX_VALUE);111this.token = token;112this.objSessions = new Pool(this, true);113this.opSessions = new Pool(this, false);114if (debug != null) {115maxActiveSessionsLock = new Object();116}117}118119// returns whether only a fairly low number of sessions are120// supported by this token.121boolean lowMaxSessions() {122return (maxSessions <= DEFAULT_MAX_SESSIONS);123}124125Session getObjSession() throws PKCS11Exception {126Session session = objSessions.poll();127if (session != null) {128return ensureValid(session);129}130session = opSessions.poll();131if (session != null) {132return ensureValid(session);133}134session = openSession();135return ensureValid(session);136}137138Session getOpSession() throws PKCS11Exception {139Session session = opSessions.poll();140if (session != null) {141return ensureValid(session);142}143// create a new session rather than re-using an obj session144// that avoids potential expensive cancels() for Signatures & RSACipher145if (maxSessions == Integer.MAX_VALUE ||146activeSessions.get() < maxSessions) {147session = openSession();148return ensureValid(session);149}150session = objSessions.poll();151if (session != null) {152return ensureValid(session);153}154throw new ProviderException("Could not obtain session");155}156157private Session ensureValid(Session session) {158session.id();159return session;160}161162Session killSession(Session session) {163if ((session == null) || (token.isValid() == false)) {164return null;165}166if (debug != null) {167String location = new Exception().getStackTrace()[2].toString();168System.out.println("Killing session (" + location + ") active: "169+ activeSessions.get());170}171172session.kill();173activeSessions.decrementAndGet();174return null;175}176177Session releaseSession(Session session) {178if ((session == null) || (token.isValid() == false)) {179return null;180}181if (session.hasObjects()) {182objSessions.release(session);183} else {184opSessions.release(session);185}186return null;187}188189void clearPools() {190objSessions.closeAll();191opSessions.closeAll();192}193194void demoteObjSession(Session session) {195if (token.isValid() == false) {196return;197}198if (debug != null) {199System.out.println("Demoting session, active: " +200activeSessions.get());201}202203boolean present = objSessions.remove(session);204if (present == false) {205// session is currently in use206// will be added to correct pool on release, nothing to do now207return;208}209opSessions.release(session);210}211212private Session openSession() throws PKCS11Exception {213if ((maxSessions != Integer.MAX_VALUE) &&214(activeSessions.get() >= maxSessions)) {215throw new ProviderException("No more sessions available");216}217218long id = token.p11.C_OpenSession219(token.provider.slotID, openSessionFlags, null, null);220Session session = new Session(token, id);221activeSessions.incrementAndGet();222if (debug != null) {223synchronized(maxActiveSessionsLock) {224if (activeSessions.get() > maxActiveSessions) {225maxActiveSessions = activeSessions.get();226if (maxActiveSessions % 10 == 0) {227System.out.println("Open sessions: " + maxActiveSessions);228}229}230}231}232return session;233}234235private void closeSession(Session session) {236session.close();237activeSessions.decrementAndGet();238}239240public static final class Pool {241242private final SessionManager mgr;243private final AbstractQueue<Session> pool;244private final int SESSION_MAX = 5;245private volatile boolean closed = false;246247// Object session pools can contain unlimited sessions.248// Operation session pools are limited and enforced by the queue.249Pool(SessionManager mgr, boolean obj) {250this.mgr = mgr;251if (obj) {252pool = new LinkedBlockingQueue<Session>();253} else {254pool = new LinkedBlockingQueue<Session>(SESSION_MAX);255}256}257258boolean remove(Session session) {259return pool.remove(session);260}261262Session poll() {263return pool.poll();264}265266void release(Session session) {267// Object session pools never return false, only Operation ones268if (closed || !pool.offer(session)) {269mgr.closeSession(session);270free();271}272}273274// Free any old operation session if this queue is full275void free() {276// quick return path277if (pool.size() == 0) return;278279int n = SESSION_MAX;280int i = 0;281Session oldestSession;282long time = System.currentTimeMillis();283// Check if the session head is too old and continue through pool284// until only one is left.285do {286oldestSession = pool.peek();287if (oldestSession == null || oldestSession.isLive(time) ||288!pool.remove(oldestSession)) {289break;290}291292i++;293mgr.closeSession(oldestSession);294} while ((n - i) > 1);295296if (debug != null) {297System.out.println("Closing " + i + " idle sessions, active: "298+ mgr.activeSessions);299}300}301302// empty out all sessions inside 'pool' and close them.303// however the Pool can still accept sessions304void closeAll() {305closed = true;306Session s;307while ((s = pool.poll()) != null) {308mgr.killSession(s);309}310}311}312}313314315