1 /* 2 * Copyright (c) 2003, 2013, 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 import java.util.concurrent.ConcurrentHashMap; 30 import java.io.*; 31 import java.lang.ref.*; 32 33 import java.security.*; 34 import javax.security.auth.login.LoginException; 35 36 import sun.security.jca.JCAUtil; 37 38 import sun.security.pkcs11.wrapper.*; 39 import static sun.security.pkcs11.TemplateManager.*; 40 import static sun.security.pkcs11.wrapper.PKCS11Constants.*; 41 42 /** 43 * PKCS#11 token. 44 * 45 * @author Andreas Sterbenz 46 * @since 1.5 47 */ 48 class Token implements Serializable { 49 50 // need to be serializable to allow SecureRandom to be serialized 51 private static final long serialVersionUID = 2541527649100571747L; 52 53 // how often to check if the token is still present (in ms) 54 // this is different from checking if a token has been inserted, 55 // that is done in SunPKCS11. Currently 50 ms. 56 private final static long CHECK_INTERVAL = 50; 57 58 final SunPKCS11 provider; 59 60 final PKCS11 p11; 61 62 final Config config; 63 64 final CK_TOKEN_INFO tokenInfo; 65 66 // session manager to pool sessions 67 final SessionManager sessionManager; 68 69 // template manager to customize the attributes used when creating objects 70 private final TemplateManager templateManager; 71 72 // flag indicating whether we need to explicitly cancel operations 73 // we started on the token. If false, we assume operations are 74 // automatically cancelled once we start another one 75 final boolean explicitCancel; 76 77 // translation cache for secret keys 78 final KeyCache secretCache; 79 80 // translation cache for asymmetric keys (public and private) 81 final KeyCache privateCache; 82 83 // cached instances of the various key factories, initialized on demand 84 private volatile P11KeyFactory rsaFactory, dsaFactory, dhFactory, ecFactory; 85 86 // table which maps mechanisms to the corresponding cached 87 // MechanismInfo objects 88 private final Map<Long, CK_MECHANISM_INFO> mechInfoMap; 89 90 // single SecureRandomSpi instance we use per token 91 // initialized on demand (if supported) 92 private volatile P11SecureRandom secureRandom; 93 94 // single KeyStoreSpi instance we use per provider 95 // initialized on demand 96 private volatile P11KeyStore keyStore; 97 98 // whether this token is a removable token 99 private final boolean removable; 100 101 // for removable tokens: whether this token is valid or has been removed 102 private volatile boolean valid; 103 104 // for removable tokens: time last checked for token presence 105 private long lastPresentCheck; 106 107 // unique token id, used for serialization only 108 private byte[] tokenId; 109 110 // flag indicating whether the token is write protected 111 private boolean writeProtected; 112 113 // flag indicating whether we are logged in 114 private volatile boolean loggedIn; 115 116 // time we last checked login status 117 private long lastLoginCheck; 118 119 // mutex for token-present-check 120 private final static Object CHECK_LOCK = new Object(); 121 122 // object for indicating unsupported mechanism in 'mechInfoMap' 123 private final static CK_MECHANISM_INFO INVALID_MECH = 124 new CK_MECHANISM_INFO(0, 0, 0); 125 126 // flag indicating whether the token supports raw secret key material import 127 private Boolean supportsRawSecretKeyImport; 128 129 Token(SunPKCS11 provider) throws PKCS11Exception { 130 this.provider = provider; 131 this.removable = provider.removable; 132 this.valid = true; 133 p11 = provider.p11; 134 config = provider.config; 135 tokenInfo = p11.C_GetTokenInfo(provider.slotID); 136 writeProtected = (tokenInfo.flags & CKF_WRITE_PROTECTED) != 0; 137 // create session manager and open a test session 138 SessionManager sessionManager; 139 try { 140 sessionManager = new SessionManager(this); 141 Session s = sessionManager.getOpSession(); 142 sessionManager.releaseSession(s); 143 } catch (PKCS11Exception e) { 144 if (writeProtected) { 145 throw e; 146 } 147 // token might not permit RW sessions even though 148 // CKF_WRITE_PROTECTED is not set 149 writeProtected = true; 150 sessionManager = new SessionManager(this); 151 Session s = sessionManager.getOpSession(); 152 sessionManager.releaseSession(s); 153 } 154 this.sessionManager = sessionManager; 155 secretCache = new KeyCache(); 156 privateCache = new KeyCache(); 157 templateManager = config.getTemplateManager(); 158 explicitCancel = config.getExplicitCancel(); 159 mechInfoMap = 160 new ConcurrentHashMap<Long, CK_MECHANISM_INFO>(10); 161 } 162 163 boolean isWriteProtected() { 164 return writeProtected; 165 } 166 167 // return whether the token supports raw secret key material import 168 boolean supportsRawSecretKeyImport() { 169 if (supportsRawSecretKeyImport == null) { 170 SecureRandom random = JCAUtil.getSecureRandom(); 171 byte[] encoded = new byte[48]; 172 random.nextBytes(encoded); 173 174 CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[3]; 175 attributes[0] = new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY); 176 attributes[1] = new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET); 177 attributes[2] = new CK_ATTRIBUTE(CKA_VALUE, encoded); 178 179 Session session = null; 180 try { 181 attributes = getAttributes(O_IMPORT, 182 CKO_SECRET_KEY, CKK_GENERIC_SECRET, attributes); 183 session = getObjSession(); 184 long keyID = p11.C_CreateObject(session.id(), attributes); 185 186 supportsRawSecretKeyImport = Boolean.TRUE; 187 } catch (PKCS11Exception e) { 188 supportsRawSecretKeyImport = Boolean.FALSE; 189 } finally { 190 releaseSession(session); 191 } 192 } 193 194 return supportsRawSecretKeyImport; 195 } 196 197 // return whether we are logged in 198 // uses cached result if current. session is optional and may be null 199 boolean isLoggedIn(Session session) throws PKCS11Exception { 200 // volatile load first 201 boolean loggedIn = this.loggedIn; 202 long time = System.currentTimeMillis(); 203 if (time - lastLoginCheck > CHECK_INTERVAL) { 204 loggedIn = isLoggedInNow(session); 205 lastLoginCheck = time; 206 } 207 return loggedIn; 208 } 209 210 // return whether we are logged in now 211 // does not use cache 212 boolean isLoggedInNow(Session session) throws PKCS11Exception { 213 boolean allocSession = (session == null); 214 try { 215 if (allocSession) { 216 session = getOpSession(); 217 } 218 CK_SESSION_INFO info = p11.C_GetSessionInfo(session.id()); 219 boolean loggedIn = (info.state == CKS_RO_USER_FUNCTIONS) || 220 (info.state == CKS_RW_USER_FUNCTIONS); 221 this.loggedIn = loggedIn; 222 return loggedIn; 223 } finally { 224 if (allocSession) { 225 releaseSession(session); 226 } 227 } 228 } 229 230 // ensure that we are logged in 231 // call provider.login() if not 232 void ensureLoggedIn(Session session) throws PKCS11Exception, LoginException { 233 if (isLoggedIn(session) == false) { 234 provider.login(null, null); 235 } 236 } 237 238 // return whether this token object is valid (i.e. token not removed) 239 // returns value from last check, does not perform new check 240 boolean isValid() { 241 if (removable == false) { 242 return true; 243 } 244 return valid; 245 } 246 247 void ensureValid() { 248 if (isValid() == false) { 249 throw new ProviderException("Token has been removed"); 250 } 251 } 252 253 // return whether a token is present (i.e. token not removed) 254 // returns cached value if current, otherwise performs new check 255 boolean isPresent(long sessionID) { 256 if (removable == false) { 257 return true; 258 } 259 if (valid == false) { 260 return false; 261 } 262 long time = System.currentTimeMillis(); 263 if ((time - lastPresentCheck) >= CHECK_INTERVAL) { 264 synchronized (CHECK_LOCK) { 265 if ((time - lastPresentCheck) >= CHECK_INTERVAL) { 266 boolean ok = false; 267 try { 268 // check if token still present 269 CK_SLOT_INFO slotInfo = 270 provider.p11.C_GetSlotInfo(provider.slotID); 271 if ((slotInfo.flags & CKF_TOKEN_PRESENT) != 0) { 272 // if the token has been removed and re-inserted, 273 // the token should return an error 274 CK_SESSION_INFO sessInfo = 275 provider.p11.C_GetSessionInfo 276 (sessionID); 277 ok = true; 278 } 279 } catch (PKCS11Exception e) { 280 // empty 281 } 282 valid = ok; 283 lastPresentCheck = System.currentTimeMillis(); 284 if (ok == false) { 285 destroy(); 286 } 287 } 288 } 289 } 290 return valid; 291 } 292 293 void destroy() { 294 valid = false; 295 provider.uninitToken(this); 296 } 297 298 Session getObjSession() throws PKCS11Exception { 299 return sessionManager.getObjSession(); 300 } 301 302 Session getOpSession() throws PKCS11Exception { 303 return sessionManager.getOpSession(); 304 } 305 306 Session releaseSession(Session session) { 307 return sessionManager.releaseSession(session); 308 } 309 310 Session killSession(Session session) { 311 return sessionManager.killSession(session); 312 } 313 314 CK_ATTRIBUTE[] getAttributes(String op, long type, long alg, 315 CK_ATTRIBUTE[] attrs) throws PKCS11Exception { 316 CK_ATTRIBUTE[] newAttrs = 317 templateManager.getAttributes(op, type, alg, attrs); 318 for (CK_ATTRIBUTE attr : newAttrs) { 319 if (attr.type == CKA_TOKEN) { 320 if (attr.getBoolean()) { 321 try { 322 ensureLoggedIn(null); 323 } catch (LoginException e) { 324 throw new ProviderException("Login failed", e); 325 } 326 } 327 // break once we have found a CKA_TOKEN attribute 328 break; 329 } 330 } 331 return newAttrs; 332 } 333 334 P11KeyFactory getKeyFactory(String algorithm) { 335 P11KeyFactory f; 336 if (algorithm.equals("RSA")) { 337 f = rsaFactory; 338 if (f == null) { 339 f = new P11RSAKeyFactory(this, algorithm); 340 rsaFactory = f; 341 } 342 } else if (algorithm.equals("DSA")) { 343 f = dsaFactory; 344 if (f == null) { 345 f = new P11DSAKeyFactory(this, algorithm); 346 dsaFactory = f; 347 } 348 } else if (algorithm.equals("DH")) { 349 f = dhFactory; 350 if (f == null) { 351 f = new P11DHKeyFactory(this, algorithm); 352 dhFactory = f; 353 } 354 } else if (algorithm.equals("EC")) { 355 f = ecFactory; 356 if (f == null) { 357 f = new P11ECKeyFactory(this, algorithm); 358 ecFactory = f; 359 } 360 } else { 361 throw new ProviderException("Unknown algorithm " + algorithm); 362 } 363 return f; 364 } 365 366 P11SecureRandom getRandom() { 367 if (secureRandom == null) { 368 secureRandom = new P11SecureRandom(this); 369 } 370 return secureRandom; 371 } 372 373 P11KeyStore getKeyStore() { 374 if (keyStore == null) { 375 keyStore = new P11KeyStore(this); 376 } 377 return keyStore; 378 } 379 380 CK_MECHANISM_INFO getMechanismInfo(long mechanism) throws PKCS11Exception { 381 CK_MECHANISM_INFO result = mechInfoMap.get(mechanism); 382 if (result == null) { 383 try { 384 result = p11.C_GetMechanismInfo(provider.slotID, 385 mechanism); 386 mechInfoMap.put(mechanism, result); 387 } catch (PKCS11Exception e) { 388 if (e.getErrorCode() != PKCS11Constants.CKR_MECHANISM_INVALID) { 389 throw e; 390 } else { 391 mechInfoMap.put(mechanism, INVALID_MECH); 392 } 393 } 394 } else if (result == INVALID_MECH) { 395 result = null; 396 } 397 return result; 398 } 399 400 private synchronized byte[] getTokenId() { 401 if (tokenId == null) { 402 SecureRandom random = JCAUtil.getSecureRandom(); 403 tokenId = new byte[20]; 404 random.nextBytes(tokenId); 405 serializedTokens.add(new WeakReference<Token>(this)); 406 } 407 return tokenId; 408 } 409 410 // list of all tokens that have been serialized within this VM 411 // NOTE that elements are never removed from this list 412 // the assumption is that the number of tokens that are serialized 413 // is relatively small 414 private static final List<Reference<Token>> serializedTokens = 415 new ArrayList<Reference<Token>>(); 416 417 private Object writeReplace() throws ObjectStreamException { 418 if (isValid() == false) { 419 throw new NotSerializableException("Token has been removed"); 420 } 421 return new TokenRep(this); 422 } 423 424 // serialized representation of a token 425 // tokens can only be de-serialized within the same VM invocation 426 // and if the token has not been removed in the meantime 427 private static class TokenRep implements Serializable { 428 429 private static final long serialVersionUID = 3503721168218219807L; 430 431 private final byte[] tokenId; 432 433 TokenRep(Token token) { 434 tokenId = token.getTokenId(); 435 } 436 437 private Object readResolve() throws ObjectStreamException { 438 for (Reference<Token> tokenRef : serializedTokens) { 439 Token token = tokenRef.get(); 440 if ((token != null) && token.isValid()) { 441 if (Arrays.equals(token.getTokenId(), tokenId)) { 442 return token; 443 } 444 } 445 } 446 throw new NotSerializableException("Could not find token"); 447 } 448 } 449 450 }