|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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.*; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
class Token implements Serializable { |
|
|
|
|
|
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, |
|
|
|
private final static long CHECK_INTERVAL = 50; |
|
|
|
final SunPKCS11 provider; |
|
|
|
final PKCS11 p11; |
|
|
|
final Config config; |
|
|
|
final CK_TOKEN_INFO tokenInfo; |
|
|
|
|
|
final SessionManager sessionManager; |
|
|
|
|
|
private final TemplateManager templateManager; |
|
|
|
// flag indicating whether we need to explicitly cancel operations |
|
// we started on the token. If false, we assume operations are |
|
|
|
final boolean explicitCancel; |
|
|
|
|
|
final KeyCache secretCache; |
|
|
|
|
|
final KeyCache privateCache; |
|
|
|
|
|
private volatile P11KeyFactory rsaFactory, dsaFactory, dhFactory, ecFactory; |
|
|
|
// table which maps mechanisms to the corresponding cached |
|
|
|
private final Map<Long, CK_MECHANISM_INFO> mechInfoMap; |
|
|
|
// single SecureRandomSpi instance we use per token |
|
|
|
private volatile P11SecureRandom secureRandom; |
|
|
|
// single KeyStoreSpi instance we use per provider |
|
|
|
private volatile P11KeyStore keyStore; |
|
|
|
|
|
private final boolean removable; |
|
|
|
|
|
private volatile boolean valid; |
|
|
|
|
|
private long lastPresentCheck; |
|
|
|
|
|
private byte[] tokenId; |
|
|
|
|
|
private boolean writeProtected; |
|
|
|
|
|
private volatile boolean loggedIn; |
|
|
|
|
|
private long lastLoginCheck; |
|
|
|
|
|
private final static Object CHECK_LOCK = new Object(); |
|
|
|
|
|
private final static CK_MECHANISM_INFO INVALID_MECH = |
|
new CK_MECHANISM_INFO(0, 0, 0); |
|
|
|
|
|
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; |
|
|
|
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 |
|
|
|
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<Long, CK_MECHANISM_INFO>(10); |
|
} |
|
|
|
boolean isWriteProtected() { |
|
return writeProtected; |
|
} |
|
|
|
|
|
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 |
|
|
|
boolean isLoggedIn(Session session) throws PKCS11Exception { |
|
|
|
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 |
|
|
|
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 |
|
|
|
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) |
|
|
|
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) |
|
|
|
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 { |
|
|
|
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, |
|
|
|
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; |
|
} |
|
} |
|
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<Token>(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 |
|
|
|
private static final List<Reference<Token>> serializedTokens = |
|
new ArrayList<Reference<Token>>(); |
|
|
|
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 |
|
|
|
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<Token> 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"); |
|
} |
|
} |
|
|
|
} |