| /* |
| * Copyright (c) 1996, 2008, 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.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.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 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 certificate for X509-based cipher suites, and |
| * Principal 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 { |
| // Eliminate dependency on KerberosPrincipal |
| return peerPrincipal; |
| } |
| } |
| if (peerCerts == null) { |
| throw new SSLPeerUnverifiedException("peer not authenticated"); |
| } |
| return 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 |
| * Principal 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)) { |
| // Eliminate dependency on KerberosPrincipal |
| return (localPrincipal == null ? null : localPrincipal); |
| } |
| return (localCerts == null ? null : |
| 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); |
| } |
| } |