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