jdk/src/share/classes/sun/security/ssl/SSLSessionImpl.java
changeset 2 90ce3da70b43
child 51 6fe31bc95bbc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/ssl/SSLSessionImpl.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,782 @@
+/*
+ * Copyright 1996-2007 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.net.*;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateEncodingException;
+
+import javax.crypto.SecretKey;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLPermission;
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.x500.X500Principal;
+
+import static sun.security.ssl.CipherSuite.*;
+import static sun.security.ssl.CipherSuite.KeyExchange.*;
+
+/**
+ * Implements the SSL session interface, and exposes the session context
+ * which is maintained by SSL servers.
+ *
+ * <P> Servers have the ability to manage the sessions associated with
+ * their authentication context(s).  They can do this by enumerating the
+ * IDs of the sessions which are cached, examining those sessions, and then
+ * perhaps invalidating a given session so that it can't be used again.
+ * If servers do not explicitly manage the cache, sessions will linger
+ * until memory is low enough that the runtime environment purges cache
+ * entries automatically to reclaim space.
+ *
+ * <P><em> The only reason this class is not package-private is that
+ * there's no other public way to get at the server session context which
+ * is associated with any given authentication context. </em>
+ *
+ * @author David Brownell
+ */
+final class SSLSessionImpl implements SSLSession {
+
+    /*
+     * we only really need a single null session
+     */
+    static final SSLSessionImpl         nullSession = new SSLSessionImpl();
+
+    // compression methods
+    private static final byte           compression_null = 0;
+
+    /*
+     * The state of a single session, as described in section 7.1
+     * of the SSLv3 spec.
+     */
+    private final ProtocolVersion       protocolVersion;
+    private final SessionId             sessionId;
+    private X509Certificate[]   peerCerts;
+    private byte                compressionMethod;
+    private final CipherSuite   cipherSuite;
+    private SecretKey           masterSecret;
+
+    /*
+     * Information not part of the SSLv3 protocol spec, but used
+     * to support session management policies.
+     */
+    private final long          creationTime = System.currentTimeMillis();
+    private long                lastUsedTime = 0;
+    private final String        host;
+    private final int           port;
+    private SSLSessionContextImpl       context;
+    private int                 sessionCount;
+    private boolean             invalidated;
+    private X509Certificate[]   localCerts;
+    private PrivateKey          localPrivateKey;
+
+    // Principals for non-certificate based cipher suites
+    private Principal peerPrincipal;
+    private Principal localPrincipal;
+
+    /*
+     * We count session creations, eventually for statistical data but
+     * also since counters make shorter debugging IDs than the big ones
+     * we use in the protocol for uniqueness-over-time.
+     */
+    private static volatile int counter = 0;
+
+    /*
+     * Use of session caches is globally enabled/disabled.
+     */
+    private static boolean      defaultRejoinable = true;
+
+    /* Class and subclass dynamic debugging support */
+    private static final Debug debug = Debug.getInstance("ssl");
+
+    /*
+     * Create a new non-rejoinable session, using the default (null)
+     * cipher spec.  This constructor returns a session which could
+     * be used either by a client or by a server, as a connection is
+     * first opened and before handshaking begins.
+     */
+    private SSLSessionImpl() {
+        this(ProtocolVersion.NONE, CipherSuite.C_NULL,
+             new SessionId(false, null), null, -1);
+    }
+
+    /*
+     * Create a new session, using a given cipher spec.  This will
+     * be rejoinable if session caching is enabled; the constructor
+     * is intended mostly for use by serves.
+     */
+    SSLSessionImpl(ProtocolVersion protocolVersion, CipherSuite cipherSuite,
+            SecureRandom generator, String host, int port) {
+        this(protocolVersion, cipherSuite,
+             new SessionId(defaultRejoinable, generator), host, port);
+    }
+
+    /*
+     * Record a new session, using a given cipher spec and session ID.
+     */
+    SSLSessionImpl(ProtocolVersion protocolVersion, CipherSuite cipherSuite,
+            SessionId id, String host, int port) {
+        this.protocolVersion = protocolVersion;
+        sessionId = id;
+        peerCerts = null;
+        compressionMethod = compression_null;
+        this.cipherSuite = cipherSuite;
+        masterSecret = null;
+        this.host = host;
+        this.port = port;
+        sessionCount = ++counter;
+
+        if (debug != null && Debug.isOn("session")) {
+            System.out.println("%% Created:  " + this);
+        }
+    }
+
+    void setMasterSecret(SecretKey secret) {
+        if (masterSecret == null) {
+            masterSecret = secret;
+        } else {
+            throw new RuntimeException("setMasterSecret() error");
+        }
+    }
+
+    /**
+     * Returns the master secret ... treat with extreme caution!
+     */
+    SecretKey getMasterSecret() {
+        return masterSecret;
+    }
+
+    void setPeerCertificates(X509Certificate[] peer) {
+        if (peerCerts == null) {
+            peerCerts = peer;
+        }
+    }
+
+    void setLocalCertificates(X509Certificate[] local) {
+        localCerts = local;
+    }
+
+    void setLocalPrivateKey(PrivateKey privateKey) {
+        localPrivateKey = privateKey;
+    }
+
+    /**
+     * Set the peer principal.
+     */
+    void setPeerPrincipal(Principal principal) {
+        if (peerPrincipal == null) {
+            peerPrincipal = principal;
+        }
+    }
+
+    /**
+     * Set the local principal.
+     */
+    void setLocalPrincipal(Principal principal) {
+        localPrincipal = principal;
+    }
+
+    /**
+     * Returns true iff this session may be resumed ... sessions are
+     * usually resumable.  Security policies may suggest otherwise,
+     * for example sessions that haven't been used for a while (say,
+     * a working day) won't be resumable, and sessions might have a
+     * maximum lifetime in any case.
+     */
+    boolean isRejoinable() {
+        return sessionId != null && sessionId.length() != 0 &&
+            !invalidated && isLocalAuthenticationValid();
+    }
+
+    public synchronized boolean isValid() {
+        return isRejoinable();
+    }
+
+    /**
+     * Check if the authentication used when establishing this session
+     * is still valid. Returns true if no authentication was used
+     */
+    boolean isLocalAuthenticationValid() {
+        if (localPrivateKey != null) {
+            try {
+                // if the private key is no longer valid, getAlgorithm()
+                // should throw an exception
+                // (e.g. Smartcard has been removed from the reader)
+                localPrivateKey.getAlgorithm();
+            } catch (Exception e) {
+                invalidate();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the ID for this session.  The ID is fixed for the
+     * duration of the session; neither it, nor its value, changes.
+     */
+    public byte[] getId() {
+        return sessionId.getId();
+    }
+
+    /**
+     * For server sessions, this returns the set of sessions which
+     * are currently valid in this process.  For client sessions,
+     * this returns null.
+     */
+    public SSLSessionContext getSessionContext() {
+        /*
+         * An interim security policy until we can do something
+         * more specific in 1.2. Only allow trusted code (code which
+         * can set system properties) to get an
+         * SSLSessionContext. This is to limit the ability of code to
+         * look up specific sessions or enumerate over them. Otherwise,
+         * code can only get session objects from successful SSL
+         * connections which implies that they must have had permission
+         * to make the network connection in the first place.
+         */
+        SecurityManager sm;
+        if ((sm = System.getSecurityManager()) != null) {
+            sm.checkPermission(new SSLPermission("getSSLSessionContext"));
+        }
+
+        return context;
+    }
+
+
+    SessionId getSessionId() {
+        return sessionId;
+    }
+
+
+    /**
+     * Returns the cipher spec in use on this session
+     */
+    CipherSuite getSuite() {
+        return cipherSuite;
+    }
+
+    /**
+     * Returns the name of the cipher suite in use on this session
+     */
+    public String getCipherSuite() {
+        return getSuite().name;
+    }
+
+    ProtocolVersion getProtocolVersion() {
+        return protocolVersion;
+    }
+
+    /**
+     * Returns the standard name of the protocol in use on this session
+     */
+    public String getProtocol() {
+        return getProtocolVersion().name;
+    }
+
+    /**
+     * Returns the compression technique used in this session
+     */
+    byte getCompression() {
+        return compressionMethod;
+    }
+
+    /**
+     * Returns the hashcode for this session
+     */
+    public int hashCode() {
+        return sessionId.hashCode();
+    }
+
+
+    /**
+     * Returns true if sessions have same ids, false otherwise.
+     */
+    public boolean equals(Object obj) {
+
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof SSLSessionImpl) {
+            SSLSessionImpl sess = (SSLSessionImpl) obj;
+            return (sessionId != null) && (sessionId.equals(
+                        sess.getSessionId()));
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Return the cert chain presented by the peer in the
+     * java.security.cert format.
+     * Note: This method can be used only when using certificate-based
+     * cipher suites; using it with non-certificate-based cipher suites,
+     * such as Kerberos, will throw an SSLPeerUnverifiedException.
+     *
+     * @return array of peer X.509 certs, with the peer's own cert
+     *  first in the chain, and with the "root" CA last.
+     */
+    public java.security.cert.Certificate[] getPeerCertificates()
+            throws SSLPeerUnverifiedException {
+        //
+        // clone to preserve integrity of session ... caller can't
+        // change record of peer identity even by accident, much
+        // less do it intentionally.
+        //
+        if ((cipherSuite.keyExchange == K_KRB5) ||
+            (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+            throw new SSLPeerUnverifiedException("no certificates expected"
+                        + " for Kerberos cipher suites");
+        }
+        if (peerCerts == null) {
+            throw new SSLPeerUnverifiedException("peer not authenticated");
+        }
+        // Certs are immutable objects, therefore we don't clone them.
+        // But do need to clone the array, so that nothing is inserted
+        // into peerCerts.
+        return (java.security.cert.Certificate[])peerCerts.clone();
+    }
+
+    /**
+     * Return the cert chain presented to the peer in the
+     * java.security.cert format.
+     * Note: This method is useful only when using certificate-based
+     * cipher suites.
+     *
+     * @return array of peer X.509 certs, with the peer's own cert
+     *  first in the chain, and with the "root" CA last.
+     */
+    public java.security.cert.Certificate[] getLocalCertificates() {
+        //
+        // clone to preserve integrity of session ... caller can't
+        // change record of peer identity even by accident, much
+        // less do it intentionally.
+        return (localCerts == null ? null :
+            (java.security.cert.Certificate[])localCerts.clone());
+    }
+
+    /**
+     * Return the cert chain presented by the peer in the
+     * javax.security.cert format.
+     * Note: This method can be used only when using certificate-based
+     * cipher suites; using it with non-certificate-based cipher suites,
+     * such as Kerberos, will throw an SSLPeerUnverifiedException.
+     *
+     * @return array of peer X.509 certs, with the peer's own cert
+     *  first in the chain, and with the "root" CA last.
+     */
+    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        //
+        // clone to preserve integrity of session ... caller can't
+        // change record of peer identity even by accident, much
+        // less do it intentionally.
+        //
+        if ((cipherSuite.keyExchange == K_KRB5) ||
+            (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+            throw new SSLPeerUnverifiedException("no certificates expected"
+                        + " for Kerberos cipher suites");
+        }
+        if (peerCerts == null) {
+            throw new SSLPeerUnverifiedException("peer not authenticated");
+        }
+        javax.security.cert.X509Certificate[] certs;
+        certs = new javax.security.cert.X509Certificate[peerCerts.length];
+        for (int i = 0; i < peerCerts.length; i++) {
+            byte[] der = null;
+            try {
+                der = peerCerts[i].getEncoded();
+                certs[i] = javax.security.cert.X509Certificate.getInstance(der);
+            } catch (CertificateEncodingException e) {
+                throw new SSLPeerUnverifiedException(e.getMessage());
+            } catch (javax.security.cert.CertificateException e) {
+                throw new SSLPeerUnverifiedException(e.getMessage());
+            }
+        }
+
+        return certs;
+    }
+
+    /**
+     * Return the cert chain presented by the peer.
+     * Note: This method can be used only when using certificate-based
+     * cipher suites; using it with non-certificate-based cipher suites,
+     * such as Kerberos, will throw an SSLPeerUnverifiedException.
+     *
+     * @return array of peer X.509 certs, with the peer's own cert
+     *  first in the chain, and with the "root" CA last.
+     */
+    public X509Certificate[] getCertificateChain()
+            throws SSLPeerUnverifiedException {
+        /*
+         * clone to preserve integrity of session ... caller can't
+         * change record of peer identity even by accident, much
+         * less do it intentionally.
+         */
+        if ((cipherSuite.keyExchange == K_KRB5) ||
+            (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+            throw new SSLPeerUnverifiedException("no certificates expected"
+                        + " for Kerberos cipher suites");
+        }
+        if (peerCerts != null) {
+            return (X509Certificate [])peerCerts.clone();
+        } else {
+            throw new SSLPeerUnverifiedException("peer not authenticated");
+        }
+    }
+
+    /**
+     * Returns the identity of the peer which was established as part of
+     * defining the session.
+     *
+     * @return the peer's principal. Returns an X500Principal of the
+     * end-entity certiticate for X509-based cipher suites, and
+     * KerberosPrincipal for Kerberos cipher suites.
+     *
+     * @throws SSLPeerUnverifiedException if the peer's identity has not
+     *          been verified
+     */
+    public Principal getPeerPrincipal()
+                throws SSLPeerUnverifiedException
+    {
+        if ((cipherSuite.keyExchange == K_KRB5) ||
+            (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+            if (peerPrincipal == null) {
+                throw new SSLPeerUnverifiedException("peer not authenticated");
+            } else {
+                return (KerberosPrincipal)peerPrincipal;
+            }
+        }
+        if (peerCerts == null) {
+            throw new SSLPeerUnverifiedException("peer not authenticated");
+        }
+        return ((X500Principal)peerCerts[0].getSubjectX500Principal());
+    }
+
+    /**
+     * Returns the principal that was sent to the peer during handshaking.
+     *
+     * @return the principal sent to the peer. Returns an X500Principal
+     * of the end-entity certificate for X509-based cipher suites, and
+     * KerberosPrincipal for Kerberos cipher suites. If no principal was
+     * sent, then null is returned.
+     */
+    public Principal getLocalPrincipal() {
+
+        if ((cipherSuite.keyExchange == K_KRB5) ||
+            (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+                return (localPrincipal == null ? null :
+                        (KerberosPrincipal)localPrincipal);
+        }
+        return (localCerts == null ? null :
+            (X500Principal)localCerts[0].getSubjectX500Principal());
+    }
+
+    /**
+     * Returns the time this session was created.
+     */
+    public long getCreationTime() {
+        return creationTime;
+    }
+
+    /**
+     * Returns the last time this session was used to initialize
+     * a connection.
+     */
+    public long getLastAccessedTime() {
+        return (lastUsedTime != 0) ? lastUsedTime : creationTime;
+    }
+
+    void setLastAccessedTime(long time) {
+        lastUsedTime = time;
+    }
+
+
+    /**
+     * Returns the network address of the session's peer.  This
+     * implementation does not insist that connections between
+     * different ports on the same host must necessarily belong
+     * to different sessions, though that is of course allowed.
+     */
+    public InetAddress getPeerAddress() {
+        try {
+            return InetAddress.getByName(host);
+        } catch (java.net.UnknownHostException e) {
+            return null;
+        }
+    }
+
+    public String getPeerHost() {
+        return host;
+    }
+
+    /**
+     * Need to provide the port info for caching sessions based on
+     * host and port. Accessed by SSLSessionContextImpl
+     */
+    public int getPeerPort() {
+        return port;
+    }
+
+    void setContext(SSLSessionContextImpl ctx) {
+        if (context == null) {
+            context = ctx;
+        }
+    }
+
+    /**
+     * Invalidate a session.  Active connections may still exist, but
+     * no connections will be able to rejoin this session.
+     */
+    synchronized public void invalidate() {
+        //
+        // Can't invalidate the NULL session -- this would be
+        // attempted when we get a handshaking error on a brand
+        // new connection, with no "real" session yet.
+        //
+        if (this == nullSession) {
+            return;
+        }
+        invalidated = true;
+        if (debug != null && Debug.isOn("session")) {
+            System.out.println("%% Invalidated:  " + this);
+        }
+        if (context != null) {
+            context.remove(sessionId);
+            context = null;
+        }
+    }
+
+    /*
+     * Table of application-specific session data indexed by an application
+     * key and the calling security context. This is important since
+     * sessions can be shared across different protection domains.
+     */
+    private Hashtable<SecureKey, Object> table =
+                                new Hashtable<SecureKey, Object>();
+
+    /**
+     * Assigns a session value.  Session change events are given if
+     * appropriate, to any original value as well as the new value.
+     */
+    public void putValue(String key, Object value) {
+        if ((key == null) || (value == null)) {
+            throw new IllegalArgumentException("arguments can not be null");
+        }
+
+        SecureKey secureKey = new SecureKey(key);
+        Object oldValue = table.put(secureKey, value);
+
+        if (oldValue instanceof SSLSessionBindingListener) {
+            SSLSessionBindingEvent e;
+
+            e = new SSLSessionBindingEvent(this, key);
+            ((SSLSessionBindingListener)oldValue).valueUnbound(e);
+        }
+        if (value instanceof SSLSessionBindingListener) {
+            SSLSessionBindingEvent e;
+
+            e = new SSLSessionBindingEvent(this, key);
+            ((SSLSessionBindingListener)value).valueBound(e);
+        }
+    }
+
+
+    /**
+     * Returns the specified session value.
+     */
+    public Object getValue(String key) {
+        if (key == null) {
+            throw new IllegalArgumentException("argument can not be null");
+        }
+
+        SecureKey secureKey = new SecureKey(key);
+        return table.get(secureKey);
+    }
+
+
+    /**
+     * Removes the specified session value, delivering a session changed
+     * event as appropriate.
+     */
+    public void removeValue(String key) {
+        if (key == null) {
+            throw new IllegalArgumentException("argument can not be null");
+        }
+
+        SecureKey secureKey = new SecureKey(key);
+        Object value = table.remove(secureKey);
+
+        if (value instanceof SSLSessionBindingListener) {
+            SSLSessionBindingEvent e;
+
+            e = new SSLSessionBindingEvent(this, key);
+            ((SSLSessionBindingListener)value).valueUnbound(e);
+        }
+    }
+
+
+    /**
+     * Lists the names of the session values.
+     */
+    public String[] getValueNames() {
+        Enumeration<SecureKey> e;
+        Vector<Object> v = new Vector<Object>();
+        SecureKey key;
+        Object securityCtx = SecureKey.getCurrentSecurityContext();
+
+        for (e = table.keys(); e.hasMoreElements(); ) {
+            key = e.nextElement();
+
+            if (securityCtx.equals(key.getSecurityContext())) {
+                v.addElement(key.getAppKey());
+            }
+        }
+        String[] names = new String[v.size()];
+        v.copyInto(names);
+
+        return names;
+    }
+
+    /**
+     * Use large packet sizes now or follow RFC 2246 packet sizes (2^14)
+     * until changed.
+     *
+     * In the TLS specification (section 6.2.1, RFC2246), it is not
+     * recommended that the plaintext has more than 2^14 bytes.
+     * However, some TLS implementations violate the specification.
+     * This is a workaround for interoperability with these stacks.
+     *
+     * Application could accept large fragments up to 2^15 bytes by
+     * setting the system property jsse.SSLEngine.acceptLargeFragments
+     * to "true".
+     */
+    private boolean acceptLargeFragments =
+        Debug.getBooleanProperty("jsse.SSLEngine.acceptLargeFragments", false);
+
+    /**
+     * Expand the buffer size of both SSL/TLS network packet and
+     * application data.
+     */
+    protected synchronized void expandBufferSizes() {
+        acceptLargeFragments = true;
+    }
+
+    /**
+     * Gets the current size of the largest SSL/TLS packet that is expected
+     * when using this session.
+     */
+    public synchronized int getPacketBufferSize() {
+        return acceptLargeFragments ?
+                Record.maxLargeRecordSize : Record.maxRecordSize;
+    }
+
+    /**
+     * Gets the current size of the largest application data that is
+     * expected when using this session.
+     */
+    public synchronized int getApplicationBufferSize() {
+        return getPacketBufferSize() - Record.headerSize;
+    }
+
+    /** Returns a string representation of this SSL session */
+    public String toString() {
+        return "[Session-" + sessionCount
+            + ", " + getCipherSuite()
+            + "]";
+    }
+
+    /**
+     * When SSL sessions are finalized, all values bound to
+     * them are removed.
+     */
+    public void finalize() {
+        String[] names = getValueNames();
+        for (int i = 0; i < names.length; i++) {
+            removeValue(names[i]);
+        }
+    }
+}
+
+
+/**
+ * This "struct" class serves as a Hash Key that combines an
+ * application-specific key and a security context.
+ */
+class SecureKey {
+    private static Object       nullObject = new Object();
+    private Object        appKey;
+    private Object      securityCtx;
+
+    static Object getCurrentSecurityContext() {
+        SecurityManager sm = System.getSecurityManager();
+        Object context = null;
+
+        if (sm != null)
+            context = sm.getSecurityContext();
+        if (context == null)
+            context = nullObject;
+        return context;
+    }
+
+    SecureKey(Object key) {
+        this.appKey = key;
+        this.securityCtx = getCurrentSecurityContext();
+    }
+
+    Object getAppKey() {
+        return appKey;
+    }
+
+    Object getSecurityContext() {
+        return securityCtx;
+    }
+
+    public int hashCode() {
+        return appKey.hashCode() ^ securityCtx.hashCode();
+    }
+
+    public boolean equals(Object o) {
+        return o instanceof SecureKey && ((SecureKey)o).appKey.equals(appKey)
+                        && ((SecureKey)o).securityCtx.equals(securityCtx);
+    }
+}