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 
Token(SunPKCS11 provider)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 
isWriteProtected()163     boolean isWriteProtected() {
164         return writeProtected;
165     }
166 
167     // return whether the token supports raw secret key material import
supportsRawSecretKeyImport()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
isLoggedIn(Session session)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
isLoggedInNow(Session session)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
ensureLoggedIn(Session session)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
isValid()240     boolean isValid() {
241         if (removable == false) {
242             return true;
243         }
244         return valid;
245     }
246 
ensureValid()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
isPresent(long sessionID)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 
destroy()293     void destroy() {
294         valid = false;
295         provider.uninitToken(this);
296     }
297 
getObjSession()298     Session getObjSession() throws PKCS11Exception {
299         return sessionManager.getObjSession();
300     }
301 
getOpSession()302     Session getOpSession() throws PKCS11Exception {
303         return sessionManager.getOpSession();
304     }
305 
releaseSession(Session session)306     Session releaseSession(Session session) {
307         return sessionManager.releaseSession(session);
308     }
309 
killSession(Session session)310     Session killSession(Session session) {
311         return sessionManager.killSession(session);
312     }
313 
getAttributes(String op, long type, long alg, CK_ATTRIBUTE[] attrs)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 
getKeyFactory(String algorithm)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 
getRandom()366     P11SecureRandom getRandom() {
367         if (secureRandom == null) {
368             secureRandom = new P11SecureRandom(this);
369         }
370         return secureRandom;
371     }
372 
getKeyStore()373     P11KeyStore getKeyStore() {
374         if (keyStore == null) {
375             keyStore = new P11KeyStore(this);
376         }
377         return keyStore;
378     }
379 
getMechanismInfo(long mechanism)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 
getTokenId()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 
writeReplace()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 
TokenRep(Token token)433         TokenRep(Token token) {
434             tokenId = token.getTokenId();
435         }
436 
readResolve()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 }
451