--- /dev/null 2017-01-18 09:30:05.425422781 -0800 +++ new/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java 2017-01-18 23:07:20.643885414 -0800 @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.io.*; +import java.lang.ref.*; + +import java.security.*; +import javax.security.auth.login.LoginException; + +import sun.security.jca.JCAUtil; + +import sun.security.pkcs11.wrapper.*; +import static sun.security.pkcs11.TemplateManager.*; +import static sun.security.pkcs11.wrapper.PKCS11Constants.*; + +/** + * PKCS#11 token. + * + * @author Andreas Sterbenz + * @since 1.5 + */ +class Token implements Serializable { + + // need to be serializable to allow SecureRandom to be serialized + private static final long serialVersionUID = 2541527649100571747L; + + // how often to check if the token is still present (in ms) + // this is different from checking if a token has been inserted, + // that is done in SunPKCS11. Currently 50 ms. + private final static long CHECK_INTERVAL = 50; + + final SunPKCS11 provider; + + final PKCS11 p11; + + final Config config; + + final CK_TOKEN_INFO tokenInfo; + + // session manager to pool sessions + final SessionManager sessionManager; + + // template manager to customize the attributes used when creating objects + private final TemplateManager templateManager; + + // flag indicating whether we need to explicitly cancel operations + // we started on the token. If false, we assume operations are + // automatically cancelled once we start another one + final boolean explicitCancel; + + // translation cache for secret keys + final KeyCache secretCache; + + // translation cache for asymmetric keys (public and private) + final KeyCache privateCache; + + // cached instances of the various key factories, initialized on demand + private volatile P11KeyFactory rsaFactory, dsaFactory, dhFactory, ecFactory; + + // table which maps mechanisms to the corresponding cached + // MechanismInfo objects + private final Map mechInfoMap; + + // single SecureRandomSpi instance we use per token + // initialized on demand (if supported) + private volatile P11SecureRandom secureRandom; + + // single KeyStoreSpi instance we use per provider + // initialized on demand + private volatile P11KeyStore keyStore; + + // whether this token is a removable token + private final boolean removable; + + // for removable tokens: whether this token is valid or has been removed + private volatile boolean valid; + + // for removable tokens: time last checked for token presence + private long lastPresentCheck; + + // unique token id, used for serialization only + private byte[] tokenId; + + // flag indicating whether the token is write protected + private boolean writeProtected; + + // flag indicating whether we are logged in + private volatile boolean loggedIn; + + // time we last checked login status + private long lastLoginCheck; + + // mutex for token-present-check + private final static Object CHECK_LOCK = new Object(); + + // object for indicating unsupported mechanism in 'mechInfoMap' + private final static CK_MECHANISM_INFO INVALID_MECH = + new CK_MECHANISM_INFO(0, 0, 0); + + // flag indicating whether the token supports raw secret key material import + private Boolean supportsRawSecretKeyImport; + + Token(SunPKCS11 provider) throws PKCS11Exception { + this.provider = provider; + this.removable = provider.removable; + this.valid = true; + p11 = provider.p11; + config = provider.config; + tokenInfo = p11.C_GetTokenInfo(provider.slotID); + writeProtected = (tokenInfo.flags & CKF_WRITE_PROTECTED) != 0; + // create session manager and open a test session + SessionManager sessionManager; + try { + sessionManager = new SessionManager(this); + Session s = sessionManager.getOpSession(); + sessionManager.releaseSession(s); + } catch (PKCS11Exception e) { + if (writeProtected) { + throw e; + } + // token might not permit RW sessions even though + // CKF_WRITE_PROTECTED is not set + writeProtected = true; + sessionManager = new SessionManager(this); + Session s = sessionManager.getOpSession(); + sessionManager.releaseSession(s); + } + this.sessionManager = sessionManager; + secretCache = new KeyCache(); + privateCache = new KeyCache(); + templateManager = config.getTemplateManager(); + explicitCancel = config.getExplicitCancel(); + mechInfoMap = + new ConcurrentHashMap(10); + } + + boolean isWriteProtected() { + return writeProtected; + } + + // return whether the token supports raw secret key material import + boolean supportsRawSecretKeyImport() { + if (supportsRawSecretKeyImport == null) { + SecureRandom random = JCAUtil.getSecureRandom(); + byte[] encoded = new byte[48]; + random.nextBytes(encoded); + + CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[3]; + attributes[0] = new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY); + attributes[1] = new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET); + attributes[2] = new CK_ATTRIBUTE(CKA_VALUE, encoded); + + Session session = null; + try { + attributes = getAttributes(O_IMPORT, + CKO_SECRET_KEY, CKK_GENERIC_SECRET, attributes); + session = getObjSession(); + long keyID = p11.C_CreateObject(session.id(), attributes); + + supportsRawSecretKeyImport = Boolean.TRUE; + } catch (PKCS11Exception e) { + supportsRawSecretKeyImport = Boolean.FALSE; + } finally { + releaseSession(session); + } + } + + return supportsRawSecretKeyImport; + } + + // return whether we are logged in + // uses cached result if current. session is optional and may be null + boolean isLoggedIn(Session session) throws PKCS11Exception { + // volatile load first + boolean loggedIn = this.loggedIn; + long time = System.currentTimeMillis(); + if (time - lastLoginCheck > CHECK_INTERVAL) { + loggedIn = isLoggedInNow(session); + lastLoginCheck = time; + } + return loggedIn; + } + + // return whether we are logged in now + // does not use cache + boolean isLoggedInNow(Session session) throws PKCS11Exception { + boolean allocSession = (session == null); + try { + if (allocSession) { + session = getOpSession(); + } + CK_SESSION_INFO info = p11.C_GetSessionInfo(session.id()); + boolean loggedIn = (info.state == CKS_RO_USER_FUNCTIONS) || + (info.state == CKS_RW_USER_FUNCTIONS); + this.loggedIn = loggedIn; + return loggedIn; + } finally { + if (allocSession) { + releaseSession(session); + } + } + } + + // ensure that we are logged in + // call provider.login() if not + void ensureLoggedIn(Session session) throws PKCS11Exception, LoginException { + if (isLoggedIn(session) == false) { + provider.login(null, null); + } + } + + // return whether this token object is valid (i.e. token not removed) + // returns value from last check, does not perform new check + boolean isValid() { + if (removable == false) { + return true; + } + return valid; + } + + void ensureValid() { + if (isValid() == false) { + throw new ProviderException("Token has been removed"); + } + } + + // return whether a token is present (i.e. token not removed) + // returns cached value if current, otherwise performs new check + boolean isPresent(long sessionID) { + if (removable == false) { + return true; + } + if (valid == false) { + return false; + } + long time = System.currentTimeMillis(); + if ((time - lastPresentCheck) >= CHECK_INTERVAL) { + synchronized (CHECK_LOCK) { + if ((time - lastPresentCheck) >= CHECK_INTERVAL) { + boolean ok = false; + try { + // check if token still present + CK_SLOT_INFO slotInfo = + provider.p11.C_GetSlotInfo(provider.slotID); + if ((slotInfo.flags & CKF_TOKEN_PRESENT) != 0) { + // if the token has been removed and re-inserted, + // the token should return an error + CK_SESSION_INFO sessInfo = + provider.p11.C_GetSessionInfo + (sessionID); + ok = true; + } + } catch (PKCS11Exception e) { + // empty + } + valid = ok; + lastPresentCheck = System.currentTimeMillis(); + if (ok == false) { + destroy(); + } + } + } + } + return valid; + } + + void destroy() { + valid = false; + provider.uninitToken(this); + } + + Session getObjSession() throws PKCS11Exception { + return sessionManager.getObjSession(); + } + + Session getOpSession() throws PKCS11Exception { + return sessionManager.getOpSession(); + } + + Session releaseSession(Session session) { + return sessionManager.releaseSession(session); + } + + Session killSession(Session session) { + return sessionManager.killSession(session); + } + + CK_ATTRIBUTE[] getAttributes(String op, long type, long alg, + CK_ATTRIBUTE[] attrs) throws PKCS11Exception { + CK_ATTRIBUTE[] newAttrs = + templateManager.getAttributes(op, type, alg, attrs); + for (CK_ATTRIBUTE attr : newAttrs) { + if (attr.type == CKA_TOKEN) { + if (attr.getBoolean()) { + try { + ensureLoggedIn(null); + } catch (LoginException e) { + throw new ProviderException("Login failed", e); + } + } + // break once we have found a CKA_TOKEN attribute + break; + } + } + return newAttrs; + } + + P11KeyFactory getKeyFactory(String algorithm) { + P11KeyFactory f; + if (algorithm.equals("RSA")) { + f = rsaFactory; + if (f == null) { + f = new P11RSAKeyFactory(this, algorithm); + rsaFactory = f; + } + } else if (algorithm.equals("DSA")) { + f = dsaFactory; + if (f == null) { + f = new P11DSAKeyFactory(this, algorithm); + dsaFactory = f; + } + } else if (algorithm.equals("DH")) { + f = dhFactory; + if (f == null) { + f = new P11DHKeyFactory(this, algorithm); + dhFactory = f; + } + } else if (algorithm.equals("EC")) { + f = ecFactory; + if (f == null) { + f = new P11ECKeyFactory(this, algorithm); + ecFactory = f; + } + } else { + throw new ProviderException("Unknown algorithm " + algorithm); + } + return f; + } + + P11SecureRandom getRandom() { + if (secureRandom == null) { + secureRandom = new P11SecureRandom(this); + } + return secureRandom; + } + + P11KeyStore getKeyStore() { + if (keyStore == null) { + keyStore = new P11KeyStore(this); + } + return keyStore; + } + + CK_MECHANISM_INFO getMechanismInfo(long mechanism) throws PKCS11Exception { + CK_MECHANISM_INFO result = mechInfoMap.get(mechanism); + if (result == null) { + try { + result = p11.C_GetMechanismInfo(provider.slotID, + mechanism); + mechInfoMap.put(mechanism, result); + } catch (PKCS11Exception e) { + if (e.getErrorCode() != PKCS11Constants.CKR_MECHANISM_INVALID) { + throw e; + } else { + mechInfoMap.put(mechanism, INVALID_MECH); + } + } + } else if (result == INVALID_MECH) { + result = null; + } + return result; + } + + private synchronized byte[] getTokenId() { + if (tokenId == null) { + SecureRandom random = JCAUtil.getSecureRandom(); + tokenId = new byte[20]; + random.nextBytes(tokenId); + serializedTokens.add(new WeakReference(this)); + } + return tokenId; + } + + // list of all tokens that have been serialized within this VM + // NOTE that elements are never removed from this list + // the assumption is that the number of tokens that are serialized + // is relatively small + private static final List> serializedTokens = + new ArrayList>(); + + private Object writeReplace() throws ObjectStreamException { + if (isValid() == false) { + throw new NotSerializableException("Token has been removed"); + } + return new TokenRep(this); + } + + // serialized representation of a token + // tokens can only be de-serialized within the same VM invocation + // and if the token has not been removed in the meantime + private static class TokenRep implements Serializable { + + private static final long serialVersionUID = 3503721168218219807L; + + private final byte[] tokenId; + + TokenRep(Token token) { + tokenId = token.getTokenId(); + } + + private Object readResolve() throws ObjectStreamException { + for (Reference tokenRef : serializedTokens) { + Token token = tokenRef.get(); + if ((token != null) && token.isValid()) { + if (Arrays.equals(token.getTokenId(), tokenId)) { + return token; + } + } + } + throw new NotSerializableException("Could not find token"); + } + } + +}