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