8005535: SSLSessionImpl should have protected finalize()
Reviewed-by: weijun, wetmore
/*
* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.net.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
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.SSLSessionContext;
import javax.net.ssl.SSLSessionBindingListener;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLPermission;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.SNIServerName;
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 extends ExtendedSSLSession {
/*
* 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 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;
private String[] localSupportedSignAlgs;
private String[] peerSupportedSignAlgs;
private List<SNIServerName> requestedServerNames;
// 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, 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,
Collection<SignatureAndHashAlgorithm> algorithms,
SecureRandom generator, String host, int port) {
this(protocolVersion, cipherSuite, algorithms,
new SessionId(defaultRejoinable, generator), host, port);
}
/*
* Record a new session, using a given cipher spec and session ID.
*/
SSLSessionImpl(ProtocolVersion protocolVersion, CipherSuite cipherSuite,
Collection<SignatureAndHashAlgorithm> algorithms,
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;
localSupportedSignAlgs =
SignatureAndHashAlgorithm.getAlgorithmNames(algorithms);
if (debug != null && Debug.isOn("session")) {
System.out.println("%% Initialized: " + 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;
}
void setPeerSupportedSignatureAlgorithms(
Collection<SignatureAndHashAlgorithm> algorithms) {
peerSupportedSignAlgs =
SignatureAndHashAlgorithm.getAlgorithmNames(algorithms);
}
void setRequestedServerNames(List<SNIServerName> requestedServerNames) {
this.requestedServerNames = new ArrayList<>(requestedServerNames);
}
/**
* 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();
}
@Override
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.
*/
@Override
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.
*/
@Override
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;
}
/**
* Resets the cipher spec in use on this session
*/
void setSuite(CipherSuite suite) {
cipherSuite = suite;
if (debug != null && Debug.isOn("session")) {
System.out.println("%% Negotiating: " + this);
}
}
/**
* Returns the name of the cipher suite in use on this session
*/
@Override
public String getCipherSuite() {
return getSuite().name;
}
ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
/**
* Returns the standard name of the protocol in use on this session
*/
@Override
public String getProtocol() {
return getProtocolVersion().name;
}
/**
* Returns the compression technique used in this session
*/
byte getCompression() {
return compressionMethod;
}
/**
* Returns the hashcode for this session
*/
@Override
public int hashCode() {
return sessionId.hashCode();
}
/**
* Returns true if sessions have same ids, false otherwise.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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
*/
@Override
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.
*/
@Override
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.
*/
@Override
public long getCreationTime() {
return creationTime;
}
/**
* Returns the last time this session was used to initialize
* a connection.
*/
@Override
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;
}
}
@Override
public String getPeerHost() {
return host;
}
/**
* Need to provide the port info for caching sessions based on
* host and port. Accessed by SSLSessionContextImpl
*/
@Override
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.
*/
@Override
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<>();
/**
* Assigns a session value. Session change events are given if
* appropriate, to any original value as well as the new value.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
public String[] getValueNames() {
Enumeration<SecureKey> e;
Vector<Object> v = new Vector<>();
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.
*/
@Override
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.
*/
@Override
public synchronized int getApplicationBufferSize() {
return getPacketBufferSize() - Record.headerSize;
}
/**
* Gets an array of supported signature algorithms that the local side is
* willing to verify.
*/
@Override
public String[] getLocalSupportedSignatureAlgorithms() {
if (localSupportedSignAlgs != null) {
return localSupportedSignAlgs.clone();
}
return new String[0];
}
/**
* Gets an array of supported signature algorithms that the peer is
* able to verify.
*/
@Override
public String[] getPeerSupportedSignatureAlgorithms() {
if (peerSupportedSignAlgs != null) {
return peerSupportedSignAlgs.clone();
}
return new String[0];
}
/**
* Obtains a <code>List</code> containing all {@link SNIServerName}s
* of the requested Server Name Indication (SNI) extension.
*/
@Override
public List<SNIServerName> getRequestedServerNames() {
if (requestedServerNames != null && !requestedServerNames.isEmpty()) {
return Collections.<SNIServerName>unmodifiableList(
requestedServerNames);
}
return Collections.<SNIServerName>emptyList();
}
/** Returns a string representation of this SSL session */
@Override
public String toString() {
return "[Session-" + sessionCount
+ ", " + getCipherSuite()
+ "]";
}
/**
* When SSL sessions are finalized, all values bound to
* them are removed.
*/
@Override
protected void finalize() throws Throwable {
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;
}
@Override
public int hashCode() {
return appKey.hashCode() ^ securityCtx.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof SecureKey && ((SecureKey)o).appKey.equals(appKey)
&& ((SecureKey)o).securityCtx.equals(securityCtx);
}
}