--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (c) 1996, 2015, 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;
+ private List<byte[]> statusResponses;
+
+ private int negotiatedMaxFragLen;
+ private int maximumPacketSize;
+
+ // Principals for non-certificate based cipher suites
+ private Principal peerPrincipal;
+ private Principal localPrincipal;
+
+ /*
+ * Is the session currently re-established with a session-resumption
+ * abbreviated initial handshake?
+ *
+ * Note that currently we only set this variable in client side.
+ */
+ private boolean isSessionResumption = false;
+
+ /*
+ * 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;
+
+ /*
+ * 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);
+ negotiatedMaxFragLen = -1;
+ statusResponses = null;
+
+ 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);
+ }
+
+ /**
+ * Provide status response data obtained during the SSL handshake.
+ *
+ * @param responses a {@link List} of responses in binary form.
+ */
+ void setStatusResponses(List<byte[]> responses) {
+ if (responses != null && !responses.isEmpty()) {
+ statusResponses = responses;
+ } else {
+ statusResponses = Collections.emptyList();
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Return true if the session is currently re-established with a
+ * session-resumption abbreviated initial handshake.
+ */
+ boolean isSessionResumption() {
+ return isSessionResumption;
+ }
+
+ /**
+ * Resets whether the session is re-established with a session-resumption
+ * abbreviated initial handshake.
+ */
+ void setAsSessionResumption(boolean flag) {
+ isSessionResumption = flag;
+ }
+
+ /**
+ * 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 (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
+ throw new SSLPeerUnverifiedException("no certificates expected"
+ + " for " + cipherSuite.keyExchange + " 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.
+ *
+ * @deprecated This method returns the deprecated
+ * {@code javax.security.cert.X509Certificate} type.
+ * Use {@code getPeerCertificates()} instead.
+ */
+ @Override
+ @Deprecated
+ 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 (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
+ throw new SSLPeerUnverifiedException("no certificates expected"
+ + " for " + cipherSuite.keyExchange + " 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 (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
+ throw new SSLPeerUnverifiedException("no certificates expected"
+ + " for " + cipherSuite.keyExchange + " cipher suites");
+ }
+ if (peerCerts != null) {
+ return peerCerts.clone();
+ } else {
+ throw new SSLPeerUnverifiedException("peer not authenticated");
+ }
+ }
+
+ /**
+ * Return a List of status responses presented by the peer.
+ * Note: This method can be used only when using certificate-based
+ * server authentication; otherwise an empty {@code List} will be returned.
+ *
+ * @return an unmodifiable {@code List} of byte arrays, each consisting
+ * of a DER-encoded OCSP response (see RFC 6960). If no responses have
+ * been presented by the server or non-certificate based server
+ * authentication is used then an empty {@code List} is returned.
+ */
+ @Override
+ public List<byte[]> getStatusResponses() {
+ if (statusResponses == null || statusResponses.isEmpty()) {
+ return Collections.emptyList();
+ } else {
+ // Clone both the list and the contents
+ List<byte[]> responses = new ArrayList<>(statusResponses.size());
+ for (byte[] respBytes : statusResponses) {
+ responses.add(respBytes.clone());
+ }
+ return Collections.unmodifiableList(responses);
+ }
+ }
+
+ /**
+ * 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, etc.
+ *
+ * @throws SSLPeerUnverifiedException if the peer's identity has not
+ * been verified
+ */
+ @Override
+ public Principal getPeerPrincipal()
+ throws SSLPeerUnverifiedException
+ {
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
+ if (peerPrincipal == null) {
+ throw new SSLPeerUnverifiedException("peer not authenticated");
+ } else {
+ 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, etc. If no principal was
+ * sent, then null is returned.
+ */
+ @Override
+ public Principal getLocalPrincipal() {
+
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
+ 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
+ public synchronized 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() {
+ // Use the bigger packet size calculated from maximumPacketSize
+ // and negotiatedMaxFragLen.
+ int packetSize = 0;
+ if (negotiatedMaxFragLen > 0) {
+ packetSize = cipherSuite.calculatePacketSize(
+ negotiatedMaxFragLen, protocolVersion,
+ protocolVersion.isDTLSProtocol());
+ }
+
+ if (maximumPacketSize > 0) {
+ return (maximumPacketSize > packetSize) ?
+ maximumPacketSize : packetSize;
+ }
+
+ if (packetSize != 0) {
+ return packetSize;
+ }
+
+ if (protocolVersion.isDTLSProtocol()) {
+ return DTLSRecord.maxRecordSize;
+ } else {
+ return acceptLargeFragments ?
+ SSLRecord.maxLargeRecordSize : SSLRecord.maxRecordSize;
+ }
+ }
+
+ /**
+ * Gets the current size of the largest application data that is
+ * expected when using this session.
+ */
+ @Override
+ public synchronized int getApplicationBufferSize() {
+ // Use the bigger fragment size calculated from maximumPacketSize
+ // and negotiatedMaxFragLen.
+ int fragmentSize = 0;
+ if (maximumPacketSize > 0) {
+ fragmentSize = cipherSuite.calculateFragSize(
+ maximumPacketSize, protocolVersion,
+ protocolVersion.isDTLSProtocol());
+ }
+
+ if (negotiatedMaxFragLen > 0) {
+ return (negotiatedMaxFragLen > fragmentSize) ?
+ negotiatedMaxFragLen : fragmentSize;
+ }
+
+ if (fragmentSize != 0) {
+ return fragmentSize;
+ }
+
+ if (protocolVersion.isDTLSProtocol()) {
+ return Record.maxDataSize;
+ } else {
+ int maxPacketSize = acceptLargeFragments ?
+ SSLRecord.maxLargeRecordSize : SSLRecord.maxRecordSize;
+ return (maxPacketSize - SSLRecord.headerSize);
+ }
+ }
+
+ /**
+ * Sets the negotiated maximum fragment length, as specified by the
+ * max_fragment_length ClientHello extension in RFC 6066.
+ *
+ * @param negotiatedMaxFragLen
+ * the negotiated maximum fragment length, or {@code -1} if
+ * no such length has been negotiated.
+ */
+ synchronized void setNegotiatedMaxFragSize(
+ int negotiatedMaxFragLen) {
+
+ this.negotiatedMaxFragLen = negotiatedMaxFragLen;
+ }
+
+ /**
+ * Get the negotiated maximum fragment length, as specified by the
+ * max_fragment_length ClientHello extension in RFC 6066.
+ *
+ * @return the negotiated maximum fragment length, or {@code -1} if
+ * no such length has been negotiated.
+ */
+ synchronized int getNegotiatedMaxFragSize() {
+ return negotiatedMaxFragLen;
+ }
+
+ synchronized void setMaximumPacketSize(int maximumPacketSize) {
+ this.maximumPacketSize = maximumPacketSize;
+ }
+
+ synchronized int getMaximumPacketSize() {
+ return maximumPacketSize;
+ }
+
+ /**
+ * 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()
+ + "]";
+ }
+
+}
+
+
+/**
+ * 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);
+ }
+}