jdk/src/jdk.crypto.token/share/classes/sun/security/pkcs11/Token.java
changeset 42693 6645de32a866
parent 25859 3317bb8137f4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.crypto.token/share/classes/sun/security/pkcs11/Token.java	Wed Dec 14 10:51:13 2016 -0800
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.pkcs11;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.io.*;
+import java.lang.ref.*;
+
+import java.security.*;
+import javax.security.auth.login.LoginException;
+
+import sun.security.jca.JCAUtil;
+
+import sun.security.pkcs11.wrapper.*;
+import static sun.security.pkcs11.TemplateManager.*;
+import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
+
+/**
+ * PKCS#11 token.
+ *
+ * @author  Andreas Sterbenz
+ * @since   1.5
+ */
+class Token implements Serializable {
+
+    // need to be serializable to allow SecureRandom to be serialized
+    private static final long serialVersionUID = 2541527649100571747L;
+
+    // how often to check if the token is still present (in ms)
+    // this is different from checking if a token has been inserted,
+    // that is done in SunPKCS11. Currently 50 ms.
+    private final static long CHECK_INTERVAL = 50;
+
+    final SunPKCS11 provider;
+
+    final PKCS11 p11;
+
+    final Config config;
+
+    final CK_TOKEN_INFO tokenInfo;
+
+    // session manager to pool sessions
+    final SessionManager sessionManager;
+
+    // template manager to customize the attributes used when creating objects
+    private final TemplateManager templateManager;
+
+    // flag indicating whether we need to explicitly cancel operations
+    // we started on the token. If false, we assume operations are
+    // automatically cancelled once we start another one
+    final boolean explicitCancel;
+
+    // translation cache for secret keys
+    final KeyCache secretCache;
+
+    // translation cache for asymmetric keys (public and private)
+    final KeyCache privateCache;
+
+    // cached instances of the various key factories, initialized on demand
+    private volatile P11KeyFactory rsaFactory, dsaFactory, dhFactory, ecFactory;
+
+    // table which maps mechanisms to the corresponding cached
+    // MechanismInfo objects
+    private final Map<Long, CK_MECHANISM_INFO> mechInfoMap;
+
+    // single SecureRandomSpi instance we use per token
+    // initialized on demand (if supported)
+    private volatile P11SecureRandom secureRandom;
+
+    // single KeyStoreSpi instance we use per provider
+    // initialized on demand
+    private volatile P11KeyStore keyStore;
+
+    // whether this token is a removable token
+    private final boolean removable;
+
+    // for removable tokens: whether this token is valid or has been removed
+    private volatile boolean valid;
+
+    // for removable tokens: time last checked for token presence
+    private long lastPresentCheck;
+
+    // unique token id, used for serialization only
+    private byte[] tokenId;
+
+    // flag indicating whether the token is write protected
+    private boolean writeProtected;
+
+    // flag indicating whether we are logged in
+    private volatile boolean loggedIn;
+
+    // time we last checked login status
+    private long lastLoginCheck;
+
+    // mutex for token-present-check
+    private final static Object CHECK_LOCK = new Object();
+
+    // object for indicating unsupported mechanism in 'mechInfoMap'
+    private final static CK_MECHANISM_INFO INVALID_MECH =
+        new CK_MECHANISM_INFO(0, 0, 0);
+
+    // flag indicating whether the token supports raw secret key material import
+    private Boolean supportsRawSecretKeyImport;
+
+    Token(SunPKCS11 provider) throws PKCS11Exception {
+        this.provider = provider;
+        this.removable = provider.removable;
+        this.valid = true;
+        p11 = provider.p11;
+        config = provider.config;
+        tokenInfo = p11.C_GetTokenInfo(provider.slotID);
+        writeProtected = (tokenInfo.flags & CKF_WRITE_PROTECTED) != 0;
+        // create session manager and open a test session
+        SessionManager sessionManager;
+        try {
+            sessionManager = new SessionManager(this);
+            Session s = sessionManager.getOpSession();
+            sessionManager.releaseSession(s);
+        } catch (PKCS11Exception e) {
+            if (writeProtected) {
+                throw e;
+            }
+            // token might not permit RW sessions even though
+            // CKF_WRITE_PROTECTED is not set
+            writeProtected = true;
+            sessionManager = new SessionManager(this);
+            Session s = sessionManager.getOpSession();
+            sessionManager.releaseSession(s);
+        }
+        this.sessionManager = sessionManager;
+        secretCache = new KeyCache();
+        privateCache = new KeyCache();
+        templateManager = config.getTemplateManager();
+        explicitCancel = config.getExplicitCancel();
+        mechInfoMap =
+            new ConcurrentHashMap<Long, CK_MECHANISM_INFO>(10);
+    }
+
+    boolean isWriteProtected() {
+        return writeProtected;
+    }
+
+    // return whether the token supports raw secret key material import
+    boolean supportsRawSecretKeyImport() {
+        if (supportsRawSecretKeyImport == null) {
+            SecureRandom random = JCAUtil.getSecureRandom();
+            byte[] encoded = new byte[48];
+            random.nextBytes(encoded);
+
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[3];
+            attributes[0] = new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY);
+            attributes[1] = new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET);
+            attributes[2] = new CK_ATTRIBUTE(CKA_VALUE, encoded);
+
+            Session session = null;
+            try {
+                attributes = getAttributes(O_IMPORT,
+                        CKO_SECRET_KEY, CKK_GENERIC_SECRET, attributes);
+                session = getObjSession();
+                long keyID = p11.C_CreateObject(session.id(), attributes);
+
+                supportsRawSecretKeyImport = Boolean.TRUE;
+            } catch (PKCS11Exception e) {
+                supportsRawSecretKeyImport = Boolean.FALSE;
+            } finally {
+                releaseSession(session);
+            }
+        }
+
+        return supportsRawSecretKeyImport;
+    }
+
+    // return whether we are logged in
+    // uses cached result if current. session is optional and may be null
+    boolean isLoggedIn(Session session) throws PKCS11Exception {
+        // volatile load first
+        boolean loggedIn = this.loggedIn;
+        long time = System.currentTimeMillis();
+        if (time - lastLoginCheck > CHECK_INTERVAL) {
+            loggedIn = isLoggedInNow(session);
+            lastLoginCheck = time;
+        }
+        return loggedIn;
+    }
+
+    // return whether we are logged in now
+    // does not use cache
+    boolean isLoggedInNow(Session session) throws PKCS11Exception {
+        boolean allocSession = (session == null);
+        try {
+            if (allocSession) {
+                session = getOpSession();
+            }
+            CK_SESSION_INFO info = p11.C_GetSessionInfo(session.id());
+            boolean loggedIn = (info.state == CKS_RO_USER_FUNCTIONS) ||
+                                (info.state == CKS_RW_USER_FUNCTIONS);
+            this.loggedIn = loggedIn;
+            return loggedIn;
+        } finally {
+            if (allocSession) {
+                releaseSession(session);
+            }
+        }
+    }
+
+    // ensure that we are logged in
+    // call provider.login() if not
+    void ensureLoggedIn(Session session) throws PKCS11Exception, LoginException {
+        if (isLoggedIn(session) == false) {
+            provider.login(null, null);
+        }
+    }
+
+    // return whether this token object is valid (i.e. token not removed)
+    // returns value from last check, does not perform new check
+    boolean isValid() {
+        if (removable == false) {
+            return true;
+        }
+        return valid;
+    }
+
+    void ensureValid() {
+        if (isValid() == false) {
+            throw new ProviderException("Token has been removed");
+        }
+    }
+
+    // return whether a token is present (i.e. token not removed)
+    // returns cached value if current, otherwise performs new check
+    boolean isPresent(long sessionID) {
+        if (removable == false) {
+            return true;
+        }
+        if (valid == false) {
+            return false;
+        }
+        long time = System.currentTimeMillis();
+        if ((time - lastPresentCheck) >= CHECK_INTERVAL) {
+            synchronized (CHECK_LOCK) {
+                if ((time - lastPresentCheck) >= CHECK_INTERVAL) {
+                    boolean ok = false;
+                    try {
+                        // check if token still present
+                        CK_SLOT_INFO slotInfo =
+                                provider.p11.C_GetSlotInfo(provider.slotID);
+                        if ((slotInfo.flags & CKF_TOKEN_PRESENT) != 0) {
+                            // if the token has been removed and re-inserted,
+                            // the token should return an error
+                            CK_SESSION_INFO sessInfo =
+                                    provider.p11.C_GetSessionInfo
+                                    (sessionID);
+                            ok = true;
+                        }
+                    } catch (PKCS11Exception e) {
+                        // empty
+                    }
+                    valid = ok;
+                    lastPresentCheck = System.currentTimeMillis();
+                    if (ok == false) {
+                        destroy();
+                    }
+                }
+            }
+        }
+        return valid;
+    }
+
+    void destroy() {
+        valid = false;
+        provider.uninitToken(this);
+    }
+
+    Session getObjSession() throws PKCS11Exception {
+        return sessionManager.getObjSession();
+    }
+
+    Session getOpSession() throws PKCS11Exception {
+        return sessionManager.getOpSession();
+    }
+
+    Session releaseSession(Session session) {
+        return sessionManager.releaseSession(session);
+    }
+
+    Session killSession(Session session) {
+        return sessionManager.killSession(session);
+    }
+
+    CK_ATTRIBUTE[] getAttributes(String op, long type, long alg,
+            CK_ATTRIBUTE[] attrs) throws PKCS11Exception {
+        CK_ATTRIBUTE[] newAttrs =
+                    templateManager.getAttributes(op, type, alg, attrs);
+        for (CK_ATTRIBUTE attr : newAttrs) {
+            if (attr.type == CKA_TOKEN) {
+                if (attr.getBoolean()) {
+                    try {
+                        ensureLoggedIn(null);
+                    } catch (LoginException e) {
+                        throw new ProviderException("Login failed", e);
+                    }
+                }
+                // break once we have found a CKA_TOKEN attribute
+                break;
+            }
+        }
+        return newAttrs;
+    }
+
+    P11KeyFactory getKeyFactory(String algorithm) {
+        P11KeyFactory f;
+        if (algorithm.equals("RSA")) {
+            f = rsaFactory;
+            if (f == null) {
+                f = new P11RSAKeyFactory(this, algorithm);
+                rsaFactory = f;
+            }
+        } else if (algorithm.equals("DSA")) {
+            f = dsaFactory;
+            if (f == null) {
+                f = new P11DSAKeyFactory(this, algorithm);
+                dsaFactory = f;
+            }
+        } else if (algorithm.equals("DH")) {
+            f = dhFactory;
+            if (f == null) {
+                f = new P11DHKeyFactory(this, algorithm);
+                dhFactory = f;
+            }
+        } else if (algorithm.equals("EC")) {
+            f = ecFactory;
+            if (f == null) {
+                f = new P11ECKeyFactory(this, algorithm);
+                ecFactory = f;
+            }
+        } else {
+            throw new ProviderException("Unknown algorithm " + algorithm);
+        }
+        return f;
+    }
+
+    P11SecureRandom getRandom() {
+        if (secureRandom == null) {
+            secureRandom = new P11SecureRandom(this);
+        }
+        return secureRandom;
+    }
+
+    P11KeyStore getKeyStore() {
+        if (keyStore == null) {
+            keyStore = new P11KeyStore(this);
+        }
+        return keyStore;
+    }
+
+    CK_MECHANISM_INFO getMechanismInfo(long mechanism) throws PKCS11Exception {
+        CK_MECHANISM_INFO result = mechInfoMap.get(mechanism);
+        if (result == null) {
+            try {
+                result = p11.C_GetMechanismInfo(provider.slotID,
+                                                mechanism);
+                mechInfoMap.put(mechanism, result);
+            } catch (PKCS11Exception e) {
+                if (e.getErrorCode() != PKCS11Constants.CKR_MECHANISM_INVALID) {
+                    throw e;
+                } else {
+                    mechInfoMap.put(mechanism, INVALID_MECH);
+                }
+            }
+        } else if (result == INVALID_MECH) {
+            result = null;
+        }
+        return result;
+    }
+
+    private synchronized byte[] getTokenId() {
+        if (tokenId == null) {
+            SecureRandom random = JCAUtil.getSecureRandom();
+            tokenId = new byte[20];
+            random.nextBytes(tokenId);
+            serializedTokens.add(new WeakReference<Token>(this));
+        }
+        return tokenId;
+    }
+
+    // list of all tokens that have been serialized within this VM
+    // NOTE that elements are never removed from this list
+    // the assumption is that the number of tokens that are serialized
+    // is relatively small
+    private static final List<Reference<Token>> serializedTokens =
+        new ArrayList<Reference<Token>>();
+
+    private Object writeReplace() throws ObjectStreamException {
+        if (isValid() == false) {
+            throw new NotSerializableException("Token has been removed");
+        }
+        return new TokenRep(this);
+    }
+
+    // serialized representation of a token
+    // tokens can only be de-serialized within the same VM invocation
+    // and if the token has not been removed in the meantime
+    private static class TokenRep implements Serializable {
+
+        private static final long serialVersionUID = 3503721168218219807L;
+
+        private final byte[] tokenId;
+
+        TokenRep(Token token) {
+            tokenId = token.getTokenId();
+        }
+
+        private Object readResolve() throws ObjectStreamException {
+            for (Reference<Token> tokenRef : serializedTokens) {
+                Token token = tokenRef.get();
+                if ((token != null) && token.isValid()) {
+                    if (Arrays.equals(token.getTokenId(), tokenId)) {
+                        return token;
+                    }
+                }
+            }
+            throw new NotSerializableException("Could not find token");
+        }
+    }
+
+}