--- /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);
+ }
+}