--- old/src/jdk.crypto.token/share/classes/sun/security/pkcs11/Token.java 2017-01-20 09:52:41.290288845 -0800 +++ /dev/null 2017-01-18 09:30:05.425422781 -0800 @@ -1,450 +0,0 @@ -/* - * 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"); - } - } - -} --- /dev/null 2017-01-18 09:30:05.425422781 -0800 +++ new/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java 2017-01-20 09:52:41.070288853 -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"); + } + } + +}