|
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 } |