jdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java
changeset 43248 5e15de85a1a0
parent 42693 6645de32a866
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java	Mon Jan 23 11:49:01 2017 -0800
@@ -0,0 +1,1188 @@
+/*
+ * Copyright (c) 2003, 2016, 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.io.*;
+import java.lang.ref.*;
+import java.math.BigInteger;
+import java.util.*;
+
+import java.security.*;
+import java.security.interfaces.*;
+import java.security.spec.*;
+
+import javax.crypto.*;
+import javax.crypto.interfaces.*;
+import javax.crypto.spec.*;
+
+import sun.security.rsa.RSAPublicKeyImpl;
+
+import sun.security.internal.interfaces.TlsMasterSecret;
+
+import sun.security.pkcs11.wrapper.*;
+import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
+
+import sun.security.util.Debug;
+import sun.security.util.DerValue;
+import sun.security.util.Length;
+import sun.security.util.ECUtil;
+
+/**
+ * Key implementation classes.
+ *
+ * In PKCS#11, the components of private and secret keys may or may not
+ * be accessible. If they are, we use the algorithm specific key classes
+ * (e.g. DSAPrivateKey) for compatibility with existing applications.
+ * If the components are not accessible, we use a generic class that
+ * only implements PrivateKey (or SecretKey). Whether the components of a
+ * key are extractable is automatically determined when the key object is
+ * created.
+ *
+ * @author  Andreas Sterbenz
+ * @since   1.5
+ */
+abstract class P11Key implements Key, Length {
+
+    private static final long serialVersionUID = -2575874101938349339L;
+
+    private final static String PUBLIC = "public";
+    private final static String PRIVATE = "private";
+    private final static String SECRET = "secret";
+
+    // type of key, one of (PUBLIC, PRIVATE, SECRET)
+    final String type;
+
+    // token instance
+    final Token token;
+
+    // algorithm name, returned by getAlgorithm(), etc.
+    final String algorithm;
+
+    // key id
+    final long keyID;
+
+    // effective key length of the key, e.g. 56 for a DES key
+    final int keyLength;
+
+    // flags indicating whether the key is a token object, sensitive, extractable
+    final boolean tokenObject, sensitive, extractable;
+
+    // phantom reference notification clean up for session keys
+    private final SessionKeyRef sessionKeyRef;
+
+    P11Key(String type, Session session, long keyID, String algorithm,
+            int keyLength, CK_ATTRIBUTE[] attributes) {
+        this.type = type;
+        this.token = session.token;
+        this.keyID = keyID;
+        this.algorithm = algorithm;
+        this.keyLength = keyLength;
+        boolean tokenObject = false;
+        boolean sensitive = false;
+        boolean extractable = true;
+        int n = (attributes == null) ? 0 : attributes.length;
+        for (int i = 0; i < n; i++) {
+            CK_ATTRIBUTE attr = attributes[i];
+            if (attr.type == CKA_TOKEN) {
+                tokenObject = attr.getBoolean();
+            } else if (attr.type == CKA_SENSITIVE) {
+                sensitive = attr.getBoolean();
+            } else if (attr.type == CKA_EXTRACTABLE) {
+                extractable = attr.getBoolean();
+            }
+        }
+        this.tokenObject = tokenObject;
+        this.sensitive = sensitive;
+        this.extractable = extractable;
+        if (tokenObject == false) {
+            sessionKeyRef = new SessionKeyRef(this, keyID, session);
+        } else {
+            sessionKeyRef = null;
+        }
+    }
+
+    // see JCA spec
+    public final String getAlgorithm() {
+        token.ensureValid();
+        return algorithm;
+    }
+
+    // see JCA spec
+    public final byte[] getEncoded() {
+        byte[] b = getEncodedInternal();
+        return (b == null) ? null : b.clone();
+    }
+
+    abstract byte[] getEncodedInternal();
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        // equals() should never throw exceptions
+        if (token.isValid() == false) {
+            return false;
+        }
+        if (obj instanceof Key == false) {
+            return false;
+        }
+        String thisFormat = getFormat();
+        if (thisFormat == null) {
+            // no encoding, key only equal to itself
+            // XXX getEncoded() for unextractable keys will change that
+            return false;
+        }
+        Key other = (Key)obj;
+        if (thisFormat.equals(other.getFormat()) == false) {
+            return false;
+        }
+        byte[] thisEnc = this.getEncodedInternal();
+        byte[] otherEnc;
+        if (obj instanceof P11Key) {
+            otherEnc = ((P11Key)other).getEncodedInternal();
+        } else {
+            otherEnc = other.getEncoded();
+        }
+        return MessageDigest.isEqual(thisEnc, otherEnc);
+    }
+
+    public int hashCode() {
+        // hashCode() should never throw exceptions
+        if (token.isValid() == false) {
+            return 0;
+        }
+        byte[] b1 = getEncodedInternal();
+        if (b1 == null) {
+            return 0;
+        }
+        int r = b1.length;
+        for (int i = 0; i < b1.length; i++) {
+            r += (b1[i] & 0xff) * 37;
+        }
+        return r;
+    }
+
+    protected Object writeReplace() throws ObjectStreamException {
+        KeyRep.Type type;
+        String format = getFormat();
+        if (isPrivate() && "PKCS#8".equals(format)) {
+            type = KeyRep.Type.PRIVATE;
+        } else if (isPublic() && "X.509".equals(format)) {
+            type = KeyRep.Type.PUBLIC;
+        } else if (isSecret() && "RAW".equals(format)) {
+            type = KeyRep.Type.SECRET;
+        } else {
+            // XXX short term serialization for unextractable keys
+            throw new NotSerializableException
+                ("Cannot serialize sensitive and unextractable keys");
+        }
+        return new KeyRep(type, getAlgorithm(), format, getEncoded());
+    }
+
+    public String toString() {
+        token.ensureValid();
+        String s1 = token.provider.getName() + " " + algorithm + " " + type
+                + " key, " + keyLength + " bits";
+        s1 += " (id " + keyID + ", "
+                + (tokenObject ? "token" : "session") + " object";
+        if (isPublic()) {
+            s1 += ")";
+        } else {
+            s1 += ", " + (sensitive ? "" : "not ") + "sensitive";
+            s1 += ", " + (extractable ? "" : "un") + "extractable)";
+        }
+        return s1;
+    }
+
+    /**
+     * Return bit length of the key.
+     */
+    @Override
+    public int length() {
+        return keyLength;
+    }
+
+    boolean isPublic() {
+        return type == PUBLIC;
+    }
+
+    boolean isPrivate() {
+        return type == PRIVATE;
+    }
+
+    boolean isSecret() {
+        return type == SECRET;
+    }
+
+    void fetchAttributes(CK_ATTRIBUTE[] attributes) {
+        Session tempSession = null;
+        try {
+            tempSession = token.getOpSession();
+            token.p11.C_GetAttributeValue(tempSession.id(), keyID, attributes);
+        } catch (PKCS11Exception e) {
+            throw new ProviderException(e);
+        } finally {
+            token.releaseSession(tempSession);
+        }
+    }
+
+    private final static CK_ATTRIBUTE[] A0 = new CK_ATTRIBUTE[0];
+
+    private static CK_ATTRIBUTE[] getAttributes(Session session, long keyID,
+            CK_ATTRIBUTE[] knownAttributes, CK_ATTRIBUTE[] desiredAttributes) {
+        if (knownAttributes == null) {
+            knownAttributes = A0;
+        }
+        for (int i = 0; i < desiredAttributes.length; i++) {
+            // For each desired attribute, check to see if we have the value
+            // available already. If everything is here, we save a native call.
+            CK_ATTRIBUTE attr = desiredAttributes[i];
+            for (CK_ATTRIBUTE known : knownAttributes) {
+                if ((attr.type == known.type) && (known.pValue != null)) {
+                    attr.pValue = known.pValue;
+                    break; // break inner for loop
+                }
+            }
+            if (attr.pValue == null) {
+                // nothing found, need to call C_GetAttributeValue()
+                for (int j = 0; j < i; j++) {
+                    // clear values copied from knownAttributes
+                    desiredAttributes[j].pValue = null;
+                }
+                try {
+                    session.token.p11.C_GetAttributeValue
+                            (session.id(), keyID, desiredAttributes);
+                } catch (PKCS11Exception e) {
+                    throw new ProviderException(e);
+                }
+                break; // break loop, goto return
+            }
+        }
+        return desiredAttributes;
+    }
+
+    static SecretKey secretKey(Session session, long keyID, String algorithm,
+            int keyLength, CK_ATTRIBUTE[] attributes) {
+        attributes = getAttributes(session, keyID, attributes, new CK_ATTRIBUTE[] {
+            new CK_ATTRIBUTE(CKA_TOKEN),
+            new CK_ATTRIBUTE(CKA_SENSITIVE),
+            new CK_ATTRIBUTE(CKA_EXTRACTABLE),
+        });
+        return new P11SecretKey(session, keyID, algorithm, keyLength, attributes);
+    }
+
+    static SecretKey masterSecretKey(Session session, long keyID, String algorithm,
+            int keyLength, CK_ATTRIBUTE[] attributes, int major, int minor) {
+        attributes = getAttributes(session, keyID, attributes, new CK_ATTRIBUTE[] {
+            new CK_ATTRIBUTE(CKA_TOKEN),
+            new CK_ATTRIBUTE(CKA_SENSITIVE),
+            new CK_ATTRIBUTE(CKA_EXTRACTABLE),
+        });
+        return new P11TlsMasterSecretKey
+                (session, keyID, algorithm, keyLength, attributes, major, minor);
+    }
+
+    // we assume that all components of public keys are always accessible
+    static PublicKey publicKey(Session session, long keyID, String algorithm,
+            int keyLength, CK_ATTRIBUTE[] attributes) {
+        switch (algorithm) {
+            case "RSA":
+                return new P11RSAPublicKey
+                    (session, keyID, algorithm, keyLength, attributes);
+            case "DSA":
+                return new P11DSAPublicKey
+                    (session, keyID, algorithm, keyLength, attributes);
+            case "DH":
+                return new P11DHPublicKey
+                    (session, keyID, algorithm, keyLength, attributes);
+            case "EC":
+                return new P11ECPublicKey
+                    (session, keyID, algorithm, keyLength, attributes);
+            default:
+                throw new ProviderException
+                    ("Unknown public key algorithm " + algorithm);
+        }
+    }
+
+    static PrivateKey privateKey(Session session, long keyID, String algorithm,
+            int keyLength, CK_ATTRIBUTE[] attributes) {
+        attributes = getAttributes(session, keyID, attributes, new CK_ATTRIBUTE[] {
+            new CK_ATTRIBUTE(CKA_TOKEN),
+            new CK_ATTRIBUTE(CKA_SENSITIVE),
+            new CK_ATTRIBUTE(CKA_EXTRACTABLE),
+        });
+        if (attributes[1].getBoolean() || (attributes[2].getBoolean() == false)) {
+            return new P11PrivateKey
+                (session, keyID, algorithm, keyLength, attributes);
+        } else {
+            switch (algorithm) {
+                case "RSA":
+                    // In order to decide if this is RSA CRT key, we first query
+                    // and see if all extra CRT attributes are available.
+                    CK_ATTRIBUTE[] attrs2 = new CK_ATTRIBUTE[] {
+                        new CK_ATTRIBUTE(CKA_PUBLIC_EXPONENT),
+                        new CK_ATTRIBUTE(CKA_PRIME_1),
+                        new CK_ATTRIBUTE(CKA_PRIME_2),
+                        new CK_ATTRIBUTE(CKA_EXPONENT_1),
+                        new CK_ATTRIBUTE(CKA_EXPONENT_2),
+                        new CK_ATTRIBUTE(CKA_COEFFICIENT),
+                    };
+                    boolean crtKey;
+                    try {
+                        session.token.p11.C_GetAttributeValue
+                            (session.id(), keyID, attrs2);
+                        crtKey = ((attrs2[0].pValue instanceof byte[]) &&
+                                  (attrs2[1].pValue instanceof byte[]) &&
+                                  (attrs2[2].pValue instanceof byte[]) &&
+                                  (attrs2[3].pValue instanceof byte[]) &&
+                                  (attrs2[4].pValue instanceof byte[]) &&
+                                  (attrs2[5].pValue instanceof byte[])) ;
+                    } catch (PKCS11Exception e) {
+                        // ignore, assume not available
+                        crtKey = false;
+                    }
+                    if (crtKey) {
+                        return new P11RSAPrivateKey
+                                (session, keyID, algorithm, keyLength, attributes, attrs2);
+                    } else {
+                        return new P11RSAPrivateNonCRTKey
+                                (session, keyID, algorithm, keyLength, attributes);
+                    }
+                case "DSA":
+                    return new P11DSAPrivateKey
+                            (session, keyID, algorithm, keyLength, attributes);
+                case "DH":
+                    return new P11DHPrivateKey
+                            (session, keyID, algorithm, keyLength, attributes);
+                case "EC":
+                    return new P11ECPrivateKey
+                            (session, keyID, algorithm, keyLength, attributes);
+                default:
+                    throw new ProviderException
+                            ("Unknown private key algorithm " + algorithm);
+            }
+        }
+    }
+
+    // class for sensitive and unextractable private keys
+    private static final class P11PrivateKey extends P11Key
+                                                implements PrivateKey {
+        private static final long serialVersionUID = -2138581185214187615L;
+
+        P11PrivateKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PRIVATE, session, keyID, algorithm, keyLength, attributes);
+        }
+        // XXX temporary encoding for serialization purposes
+        public String getFormat() {
+            token.ensureValid();
+            return null;
+        }
+        byte[] getEncodedInternal() {
+            token.ensureValid();
+            return null;
+        }
+    }
+
+    private static class P11SecretKey extends P11Key implements SecretKey {
+        private static final long serialVersionUID = -7828241727014329084L;
+        private volatile byte[] encoded;
+        P11SecretKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(SECRET, session, keyID, algorithm, keyLength, attributes);
+        }
+        public String getFormat() {
+            token.ensureValid();
+            if (sensitive || (extractable == false)) {
+                return null;
+            } else {
+                return "RAW";
+            }
+        }
+        byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (getFormat() == null) {
+                return null;
+            }
+            byte[] b = encoded;
+            if (b == null) {
+                synchronized (this) {
+                    b = encoded;
+                    if (b == null) {
+                        Session tempSession = null;
+                        try {
+                            tempSession = token.getOpSession();
+                            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                                new CK_ATTRIBUTE(CKA_VALUE),
+                            };
+                            token.p11.C_GetAttributeValue
+                                (tempSession.id(), keyID, attributes);
+                            b = attributes[0].getByteArray();
+                        } catch (PKCS11Exception e) {
+                            throw new ProviderException(e);
+                        } finally {
+                            token.releaseSession(tempSession);
+                        }
+                        encoded = b;
+                    }
+                }
+            }
+            return b;
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private static class P11TlsMasterSecretKey extends P11SecretKey
+            implements TlsMasterSecret {
+        private static final long serialVersionUID = -1318560923770573441L;
+
+        private final int majorVersion, minorVersion;
+        P11TlsMasterSecretKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes, int major, int minor) {
+            super(session, keyID, algorithm, keyLength, attributes);
+            this.majorVersion = major;
+            this.minorVersion = minor;
+        }
+        public int getMajorVersion() {
+            return majorVersion;
+        }
+
+        public int getMinorVersion() {
+            return minorVersion;
+        }
+    }
+
+    // RSA CRT private key
+    private static final class P11RSAPrivateKey extends P11Key
+                implements RSAPrivateCrtKey {
+        private static final long serialVersionUID = 9215872438913515220L;
+
+        private BigInteger n, e, d, p, q, pe, qe, coeff;
+        private byte[] encoded;
+        P11RSAPrivateKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attrs, CK_ATTRIBUTE[] crtAttrs) {
+            super(PRIVATE, session, keyID, algorithm, keyLength, attrs);
+
+            for (CK_ATTRIBUTE a : crtAttrs) {
+                if (a.type == CKA_PUBLIC_EXPONENT) {
+                    e = a.getBigInteger();
+                } else if (a.type == CKA_PRIME_1) {
+                    p = a.getBigInteger();
+                } else if (a.type == CKA_PRIME_2) {
+                    q = a.getBigInteger();
+                } else if (a.type == CKA_EXPONENT_1) {
+                    pe = a.getBigInteger();
+                } else if (a.type == CKA_EXPONENT_2) {
+                    qe = a.getBigInteger();
+                } else if (a.type == CKA_COEFFICIENT) {
+                    coeff = a.getBigInteger();
+                }
+            }
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (n != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_MODULUS),
+                new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT),
+            };
+            fetchAttributes(attributes);
+            n = attributes[0].getBigInteger();
+            d = attributes[1].getBigInteger();
+        }
+
+        public String getFormat() {
+            token.ensureValid();
+            return "PKCS#8";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    // XXX make constructor in SunRsaSign provider public
+                    // and call it directly
+                    KeyFactory factory = KeyFactory.getInstance
+                        ("RSA", P11Util.getSunRsaSignProvider());
+                    Key newKey = factory.translateKey(this);
+                    encoded = newKey.getEncoded();
+                } catch (GeneralSecurityException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getModulus() {
+            fetchValues();
+            return n;
+        }
+        public BigInteger getPublicExponent() {
+            return e;
+        }
+        public BigInteger getPrivateExponent() {
+            fetchValues();
+            return d;
+        }
+        public BigInteger getPrimeP() {
+            return p;
+        }
+        public BigInteger getPrimeQ() {
+            return q;
+        }
+        public BigInteger getPrimeExponentP() {
+            return pe;
+        }
+        public BigInteger getPrimeExponentQ() {
+            return qe;
+        }
+        public BigInteger getCrtCoefficient() {
+            return coeff;
+        }
+    }
+
+    // RSA non-CRT private key
+    private static final class P11RSAPrivateNonCRTKey extends P11Key
+                implements RSAPrivateKey {
+        private static final long serialVersionUID = 1137764983777411481L;
+
+        private BigInteger n, d;
+        private byte[] encoded;
+        P11RSAPrivateNonCRTKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PRIVATE, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (n != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_MODULUS),
+                new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT),
+            };
+            fetchAttributes(attributes);
+            n = attributes[0].getBigInteger();
+            d = attributes[1].getBigInteger();
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "PKCS#8";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    // XXX make constructor in SunRsaSign provider public
+                    // and call it directly
+                    KeyFactory factory = KeyFactory.getInstance
+                        ("RSA", P11Util.getSunRsaSignProvider());
+                    Key newKey = factory.translateKey(this);
+                    encoded = newKey.getEncoded();
+                } catch (GeneralSecurityException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getModulus() {
+            fetchValues();
+            return n;
+        }
+        public BigInteger getPrivateExponent() {
+            fetchValues();
+            return d;
+        }
+    }
+
+    private static final class P11RSAPublicKey extends P11Key
+                                                implements RSAPublicKey {
+        private static final long serialVersionUID = -826726289023854455L;
+
+        private BigInteger n, e;
+        private byte[] encoded;
+        P11RSAPublicKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PUBLIC, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (n != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_MODULUS),
+                new CK_ATTRIBUTE(CKA_PUBLIC_EXPONENT),
+            };
+            fetchAttributes(attributes);
+            n = attributes[0].getBigInteger();
+            e = attributes[1].getBigInteger();
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "X.509";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    encoded = new RSAPublicKeyImpl(n, e).getEncoded();
+                } catch (InvalidKeyException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getModulus() {
+            fetchValues();
+            return n;
+        }
+        public BigInteger getPublicExponent() {
+            fetchValues();
+            return e;
+        }
+        public String toString() {
+            fetchValues();
+            return super.toString() +  "\n  modulus: " + n
+                + "\n  public exponent: " + e;
+        }
+    }
+
+    private static final class P11DSAPublicKey extends P11Key
+                                                implements DSAPublicKey {
+        private static final long serialVersionUID = 5989753793316396637L;
+
+        private BigInteger y;
+        private DSAParams params;
+        private byte[] encoded;
+        P11DSAPublicKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PUBLIC, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (y != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_VALUE),
+                new CK_ATTRIBUTE(CKA_PRIME),
+                new CK_ATTRIBUTE(CKA_SUBPRIME),
+                new CK_ATTRIBUTE(CKA_BASE),
+            };
+            fetchAttributes(attributes);
+            y = attributes[0].getBigInteger();
+            params = new DSAParameterSpec(
+                attributes[1].getBigInteger(),
+                attributes[2].getBigInteger(),
+                attributes[3].getBigInteger()
+            );
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "X.509";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    Key key = new sun.security.provider.DSAPublicKey
+                            (y, params.getP(), params.getQ(), params.getG());
+                    encoded = key.getEncoded();
+                } catch (InvalidKeyException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getY() {
+            fetchValues();
+            return y;
+        }
+        public DSAParams getParams() {
+            fetchValues();
+            return params;
+        }
+        public String toString() {
+            fetchValues();
+            return super.toString() +  "\n  y: " + y + "\n  p: " + params.getP()
+                + "\n  q: " + params.getQ() + "\n  g: " + params.getG();
+        }
+    }
+
+    private static final class P11DSAPrivateKey extends P11Key
+                                                implements DSAPrivateKey {
+        private static final long serialVersionUID = 3119629997181999389L;
+
+        private BigInteger x;
+        private DSAParams params;
+        private byte[] encoded;
+        P11DSAPrivateKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PRIVATE, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (x != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_VALUE),
+                new CK_ATTRIBUTE(CKA_PRIME),
+                new CK_ATTRIBUTE(CKA_SUBPRIME),
+                new CK_ATTRIBUTE(CKA_BASE),
+            };
+            fetchAttributes(attributes);
+            x = attributes[0].getBigInteger();
+            params = new DSAParameterSpec(
+                attributes[1].getBigInteger(),
+                attributes[2].getBigInteger(),
+                attributes[3].getBigInteger()
+            );
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "PKCS#8";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    Key key = new sun.security.provider.DSAPrivateKey
+                            (x, params.getP(), params.getQ(), params.getG());
+                    encoded = key.getEncoded();
+                } catch (InvalidKeyException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getX() {
+            fetchValues();
+            return x;
+        }
+        public DSAParams getParams() {
+            fetchValues();
+            return params;
+        }
+    }
+
+    private static final class P11DHPrivateKey extends P11Key
+                                                implements DHPrivateKey {
+        private static final long serialVersionUID = -1698576167364928838L;
+
+        private BigInteger x;
+        private DHParameterSpec params;
+        private byte[] encoded;
+        P11DHPrivateKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PRIVATE, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (x != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_VALUE),
+                new CK_ATTRIBUTE(CKA_PRIME),
+                new CK_ATTRIBUTE(CKA_BASE),
+            };
+            fetchAttributes(attributes);
+            x = attributes[0].getBigInteger();
+            params = new DHParameterSpec(
+                attributes[1].getBigInteger(),
+                attributes[2].getBigInteger()
+            );
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "PKCS#8";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    DHPrivateKeySpec spec = new DHPrivateKeySpec
+                        (x, params.getP(), params.getG());
+                    KeyFactory kf = KeyFactory.getInstance
+                        ("DH", P11Util.getSunJceProvider());
+                    Key key = kf.generatePrivate(spec);
+                    encoded = key.getEncoded();
+                } catch (GeneralSecurityException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getX() {
+            fetchValues();
+            return x;
+        }
+        public DHParameterSpec getParams() {
+            fetchValues();
+            return params;
+        }
+        public int hashCode() {
+            if (token.isValid() == false) {
+                return 0;
+            }
+            fetchValues();
+            return Objects.hash(x, params.getP(), params.getG());
+        }
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            // equals() should never throw exceptions
+            if (token.isValid() == false) {
+                return false;
+            }
+            if (!(obj instanceof DHPrivateKey)) {
+                return false;
+            }
+            fetchValues();
+            DHPrivateKey other = (DHPrivateKey) obj;
+            DHParameterSpec otherParams = other.getParams();
+            return ((this.x.compareTo(other.getX()) == 0) &&
+                    (this.params.getP().compareTo(otherParams.getP()) == 0) &&
+                    (this.params.getG().compareTo(otherParams.getG()) == 0));
+        }
+    }
+
+    private static final class P11DHPublicKey extends P11Key
+                                                implements DHPublicKey {
+        static final long serialVersionUID = -598383872153843657L;
+
+        private BigInteger y;
+        private DHParameterSpec params;
+        private byte[] encoded;
+        P11DHPublicKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PUBLIC, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (y != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_VALUE),
+                new CK_ATTRIBUTE(CKA_PRIME),
+                new CK_ATTRIBUTE(CKA_BASE),
+            };
+            fetchAttributes(attributes);
+            y = attributes[0].getBigInteger();
+            params = new DHParameterSpec(
+                attributes[1].getBigInteger(),
+                attributes[2].getBigInteger()
+            );
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "X.509";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    DHPublicKeySpec spec = new DHPublicKeySpec
+                        (y, params.getP(), params.getG());
+                    KeyFactory kf = KeyFactory.getInstance
+                        ("DH", P11Util.getSunJceProvider());
+                    Key key = kf.generatePublic(spec);
+                    encoded = key.getEncoded();
+                } catch (GeneralSecurityException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getY() {
+            fetchValues();
+            return y;
+        }
+        public DHParameterSpec getParams() {
+            fetchValues();
+            return params;
+        }
+        public String toString() {
+            fetchValues();
+            return super.toString() +  "\n  y: " + y + "\n  p: " + params.getP()
+                + "\n  g: " + params.getG();
+        }
+        public int hashCode() {
+            if (token.isValid() == false) {
+                return 0;
+            }
+            fetchValues();
+            return Objects.hash(y, params.getP(), params.getG());
+        }
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            // equals() should never throw exceptions
+            if (token.isValid() == false) {
+                return false;
+            }
+            if (!(obj instanceof DHPublicKey)) {
+                return false;
+            }
+            fetchValues();
+            DHPublicKey other = (DHPublicKey) obj;
+            DHParameterSpec otherParams = other.getParams();
+            return ((this.y.compareTo(other.getY()) == 0) &&
+                    (this.params.getP().compareTo(otherParams.getP()) == 0) &&
+                    (this.params.getG().compareTo(otherParams.getG()) == 0));
+        }
+    }
+
+    private static final class P11ECPrivateKey extends P11Key
+                                                implements ECPrivateKey {
+        private static final long serialVersionUID = -7786054399510515515L;
+
+        private BigInteger s;
+        private ECParameterSpec params;
+        private byte[] encoded;
+        P11ECPrivateKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PRIVATE, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (s != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_VALUE),
+                new CK_ATTRIBUTE(CKA_EC_PARAMS, params),
+            };
+            fetchAttributes(attributes);
+            s = attributes[0].getBigInteger();
+            try {
+                params = P11ECKeyFactory.decodeParameters
+                            (attributes[1].getByteArray());
+            } catch (Exception e) {
+                throw new RuntimeException("Could not parse key values", e);
+            }
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "PKCS#8";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    Key key = ECUtil.generateECPrivateKey(s, params);
+                    encoded = key.getEncoded();
+                } catch (InvalidKeySpecException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public BigInteger getS() {
+            fetchValues();
+            return s;
+        }
+        public ECParameterSpec getParams() {
+            fetchValues();
+            return params;
+        }
+    }
+
+    private static final class P11ECPublicKey extends P11Key
+                                                implements ECPublicKey {
+        private static final long serialVersionUID = -6371481375154806089L;
+
+        private ECPoint w;
+        private ECParameterSpec params;
+        private byte[] encoded;
+        P11ECPublicKey(Session session, long keyID, String algorithm,
+                int keyLength, CK_ATTRIBUTE[] attributes) {
+            super(PUBLIC, session, keyID, algorithm, keyLength, attributes);
+        }
+        private synchronized void fetchValues() {
+            token.ensureValid();
+            if (w != null) {
+                return;
+            }
+            CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
+                new CK_ATTRIBUTE(CKA_EC_POINT),
+                new CK_ATTRIBUTE(CKA_EC_PARAMS),
+            };
+            fetchAttributes(attributes);
+
+            try {
+                params = P11ECKeyFactory.decodeParameters
+                            (attributes[1].getByteArray());
+                byte[] ecKey = attributes[0].getByteArray();
+
+                // Check whether the X9.63 encoding of an EC point is wrapped
+                // in an ASN.1 OCTET STRING
+                if (!token.config.getUseEcX963Encoding()) {
+                    DerValue wECPoint = new DerValue(ecKey);
+
+                    if (wECPoint.getTag() != DerValue.tag_OctetString) {
+                        throw new IOException("Could not DER decode EC point." +
+                            " Unexpected tag: " + wECPoint.getTag());
+                    }
+                    w = P11ECKeyFactory.decodePoint
+                        (wECPoint.getDataBytes(), params.getCurve());
+
+                } else {
+                    w = P11ECKeyFactory.decodePoint(ecKey, params.getCurve());
+                }
+
+            } catch (Exception e) {
+                throw new RuntimeException("Could not parse key values", e);
+            }
+        }
+        public String getFormat() {
+            token.ensureValid();
+            return "X.509";
+        }
+        synchronized byte[] getEncodedInternal() {
+            token.ensureValid();
+            if (encoded == null) {
+                fetchValues();
+                try {
+                    return ECUtil.x509EncodeECPublicKey(w, params);
+                } catch (InvalidKeySpecException e) {
+                    throw new ProviderException(e);
+                }
+            }
+            return encoded;
+        }
+        public ECPoint getW() {
+            fetchValues();
+            return w;
+        }
+        public ECParameterSpec getParams() {
+            fetchValues();
+            return params;
+        }
+        public String toString() {
+            fetchValues();
+            return super.toString()
+                + "\n  public x coord: " + w.getAffineX()
+                + "\n  public y coord: " + w.getAffineY()
+                + "\n  parameters: " + params;
+        }
+    }
+}
+
+/*
+ * NOTE: Must use PhantomReference here and not WeakReference
+ * otherwise the key maybe cleared before other objects which
+ * still use these keys during finalization such as SSLSocket.
+ */
+final class SessionKeyRef extends PhantomReference<P11Key>
+    implements Comparable<SessionKeyRef> {
+    private static ReferenceQueue<P11Key> refQueue =
+        new ReferenceQueue<P11Key>();
+    private static Set<SessionKeyRef> refList =
+        Collections.synchronizedSortedSet(new TreeSet<SessionKeyRef>());
+
+    static ReferenceQueue<P11Key> referenceQueue() {
+        return refQueue;
+    }
+
+    private static void drainRefQueueBounded() {
+        Session sess = null;
+        Token tkn = null;
+        while (true) {
+            SessionKeyRef next = (SessionKeyRef) refQueue.poll();
+            if (next == null) {
+                break;
+            }
+
+            // If the token is still valid, try to remove the object
+            if (next.session.token.isValid()) {
+                // If this key's token is the same as the previous key, the
+                // same session can be used for C_DestroyObject.
+                try {
+                    if (next.session.token != tkn || sess == null) {
+                        // Release session if not using previous token
+                        if (tkn != null && sess != null) {
+                            tkn.releaseSession(sess);
+                            sess = null;
+                        }
+
+                        tkn = next.session.token;
+                        sess = tkn.getOpSession();
+                    }
+                    next.disposeNative(sess);
+                } catch (PKCS11Exception e) {
+                    // ignore
+                }
+            }
+            // Regardless of native results, dispose of java references
+            next.dispose();
+        }
+
+        if (tkn != null && sess != null) {
+            tkn.releaseSession(sess);
+        }
+    }
+
+    // handle to the native key
+    private long keyID;
+    private Session session;
+
+    SessionKeyRef(P11Key key , long keyID, Session session) {
+        super(key, refQueue);
+        this.keyID = keyID;
+        this.session = session;
+        this.session.addObject();
+        refList.add(this);
+        drainRefQueueBounded();
+    }
+
+    private void disposeNative(Session s) throws PKCS11Exception {
+        session.token.p11.C_DestroyObject(s.id(), keyID);
+    }
+
+    private void dispose() {
+        refList.remove(this);
+        this.clear();
+        session.removeObject();
+    }
+
+    public int compareTo(SessionKeyRef other) {
+        if (this.keyID == other.keyID) {
+            return 0;
+        } else {
+            return (this.keyID < other.keyID) ? -1 : 1;
+        }
+    }
+}