|
1 /* |
|
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 |
|
26 package sun.security.pkcs11; |
|
27 |
|
28 import java.util.*; |
|
29 import java.io.*; |
|
30 import java.lang.ref.*; |
|
31 |
|
32 import java.security.*; |
|
33 import javax.security.auth.login.LoginException; |
|
34 |
|
35 import sun.security.jca.JCAUtil; |
|
36 |
|
37 import sun.security.pkcs11.wrapper.*; |
|
38 import static sun.security.pkcs11.wrapper.PKCS11Constants.*; |
|
39 |
|
40 /** |
|
41 * PKCS#11 token. |
|
42 * |
|
43 * @author Andreas Sterbenz |
|
44 * @since 1.5 |
|
45 */ |
|
46 class 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 } |