< prev index next >
src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 2003, 2016, 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
--- 1,7 ----
/*
! * Copyright (c) 2003, 2018, 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
*** 23,1334 ****
* questions.
*/
package sun.security.ssl;
! import java.io.*;
! import java.nio.*;
! import java.security.*;
! import java.util.*;
import java.util.function.BiFunction;
! import javax.crypto.BadPaddingException;
!
! import javax.net.ssl.*;
! import javax.net.ssl.SSLEngineResult.*;
!
! /**
! * Implementation of an non-blocking SSLEngine.
! *
! * *Currently*, the SSLEngine code exists in parallel with the current
! * SSLSocket. As such, the current implementation is using legacy code
! * with many of the same abstractions. However, it varies in many
! * areas, most dramatically in the IO handling.
! *
! * There are three main I/O threads that can be existing in parallel:
! * wrap(), unwrap(), and beginHandshake(). We are encouraging users to
! * not call multiple instances of wrap or unwrap, because the data could
! * appear to flow out of the SSLEngine in a non-sequential order. We
! * take all steps we can to at least make sure the ordering remains
! * consistent, but once the calls returns, anything can happen. For
! * example, thread1 and thread2 both call wrap, thread1 gets the first
! * packet, thread2 gets the second packet, but thread2 gets control back
! * before thread1, and sends the data. The receiving side would see an
! * out-of-order error.
! *
! * @author Brad Wetmore
! */
! public final class SSLEngineImpl extends SSLEngine {
!
! //
! // Fields and global comments
! //
!
! /*
! * There's a state machine associated with each connection, which
! * among other roles serves to negotiate session changes.
! *
! * - START with constructor, until the TCP connection's around.
! * - HANDSHAKE picks session parameters before allowing traffic.
! * There are many substates due to sequencing requirements
! * for handshake messages.
! * - DATA may be transmitted.
! * - RENEGOTIATE state allows concurrent data and handshaking
! * traffic ("same" substates as HANDSHAKE), and terminates
! * in selection of new session (and connection) parameters
! * - ERROR state immediately precedes abortive disconnect.
! * - CLOSED when one side closes down, used to start the shutdown
! * process. SSL connection objects are not reused.
! *
! * State affects what SSL record types may legally be sent:
! *
! * - Handshake ... only in HANDSHAKE and RENEGOTIATE states
! * - App Data ... only in DATA and RENEGOTIATE states
! * - Alert ... in HANDSHAKE, DATA, RENEGOTIATE
! *
! * Re what may be received: same as what may be sent, except that
! * HandshakeRequest handshaking messages can come from servers even
! * in the application data state, to request entry to RENEGOTIATE.
! *
! * The state machine within HANDSHAKE and RENEGOTIATE states controls
! * the pending session, not the connection state, until the change
! * cipher spec and "Finished" handshake messages are processed and
! * make the "new" session become the current one.
! *
! * NOTE: details of the SMs always need to be nailed down better.
! * The text above illustrates the core ideas.
! *
! * +---->-------+------>--------->-------+
! * | | |
! * <-----< ^ ^ <-----< |
! *START>----->HANDSHAKE>----->DATA>----->RENEGOTIATE |
! * v v v |
! * | | | |
! * +------------+---------------+ |
! * | |
! * v |
! * ERROR>------>----->CLOSED<--------<----+
! *
! * ALSO, note that the purpose of handshaking (renegotiation is
! * included) is to assign a different, and perhaps new, session to
! * the connection. The SSLv3 spec is a bit confusing on that new
! * protocol feature.
! */
! private int connectionState;
!
! private static final int cs_START = 0;
! private static final int cs_HANDSHAKE = 1;
! private static final int cs_DATA = 2;
! private static final int cs_RENEGOTIATE = 3;
! private static final int cs_ERROR = 4;
! private static final int cs_CLOSED = 6;
!
! /*
! * Once we're in state cs_CLOSED, we can continue to
! * wrap/unwrap until we finish sending/receiving the messages
! * for close_notify.
! */
! private boolean inboundDone = false;
! private boolean outboundDone = false;
!
! /*
! * The authentication context holds all information used to establish
! * who this end of the connection is (certificate chains, private keys,
! * etc) and who is trusted (e.g. as CAs or websites).
! */
! private SSLContextImpl sslContext;
!
! /*
! * This connection is one of (potentially) many associated with
! * any given session. The output of the handshake protocol is a
! * new session ... although all the protocol description talks
! * about changing the cipher spec (and it does change), in fact
! * that's incidental since it's done by changing everything that
! * is associated with a session at the same time. (TLS/IETF may
! * change that to add client authentication w/o new key exchg.)
! */
! private Handshaker handshaker;
! private SSLSessionImpl sess;
! private volatile SSLSessionImpl handshakeSession;
!
! /*
! * Flag indicating if the next record we receive MUST be a Finished
! * message. Temporarily set during the handshake to ensure that
! * a change cipher spec message is followed by a finished message.
! */
! private boolean expectingFinished;
!
!
! /*
! * If someone tries to closeInbound() (say at End-Of-Stream)
! * our engine having received a close_notify, we need to
! * notify the app that we may have a truncation attack underway.
! */
! private boolean recvCN;
!
! /*
! * For improved diagnostics, we detail connection closure
! * If the engine is closed (connectionState >= cs_ERROR),
! * closeReason != null indicates if the engine was closed
! * because of an error or because or normal shutdown.
! */
! private SSLException closeReason;
!
! /*
! * Per-connection private state that doesn't change when the
! * session is changed.
! */
! private ClientAuthType doClientAuth =
! ClientAuthType.CLIENT_AUTH_NONE;
! private boolean enableSessionCreation = true;
! InputRecord inputRecord;
! OutputRecord outputRecord;
! private AccessControlContext acc;
!
! // The cipher suites enabled for use on this connection.
! private CipherSuiteList enabledCipherSuites;
!
! // the endpoint identification protocol
! private String identificationProtocol = null;
!
! // The cryptographic algorithm constraints
! private AlgorithmConstraints algorithmConstraints = null;
!
! // The server name indication and matchers
! List<SNIServerName> serverNames =
! Collections.<SNIServerName>emptyList();
! Collection<SNIMatcher> sniMatchers =
! Collections.<SNIMatcher>emptyList();
!
! // Configured application protocol values
! String[] applicationProtocols = new String[0];
!
! // Negotiated application protocol value.
! //
! // The value under negotiation will be obtained from handshaker.
! String applicationProtocol = null;
!
! // Callback function that selects the application protocol value during
! // the SSL/TLS handshake.
! BiFunction<SSLEngine, List<String>, String> applicationProtocolSelector;
!
! // Have we been told whether we're client or server?
! private boolean serverModeSet = false;
! private boolean roleIsServer;
!
! /*
! * The protocol versions enabled for use on this connection.
! *
! * Note: we support a pseudo protocol called SSLv2Hello which when
! * set will result in an SSL v2 Hello being sent with SSL (version 3.0)
! * or TLS (version 3.1, 3.2, etc.) version info.
! */
! private ProtocolList enabledProtocols;
!
! /*
! * The SSL version associated with this connection.
! */
! private ProtocolVersion protocolVersion;
!
! /*
! * security parameters for secure renegotiation.
! */
! private boolean secureRenegotiation;
! private byte[] clientVerifyData;
! private byte[] serverVerifyData;
!
! /*
! * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME *
! * IMPORTANT STUFF TO UNDERSTANDING THE SYNCHRONIZATION ISSUES.
! * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME *
! *
! * There are several locks here.
! *
! * The primary lock is the per-instance lock used by
! * synchronized(this) and the synchronized methods. It controls all
! * access to things such as the connection state and variables which
! * affect handshaking. If we are inside a synchronized method, we
! * can access the state directly, otherwise, we must use the
! * synchronized equivalents.
! *
! * Note that we must never acquire the <code>this</code> lock after
! * <code>writeLock</code> or run the risk of deadlock.
! *
! * Grab some coffee, and be careful with any code changes.
! */
! private Object wrapLock;
! private Object unwrapLock;
! Object writeLock;
!
! /*
! * Whether local cipher suites preference in server side should be
! * honored during handshaking?
! */
! private boolean preferLocalCipherSuites = false;
!
! /*
! * whether DTLS handshake retransmissions should be enabled?
! */
! private boolean enableRetransmissions = false;
!
! /*
! * The maximum expected network packet size for SSL/TLS/DTLS records.
! */
! private int maximumPacketSize = 0;
!
! /*
! * Is this an instance for Datagram Transport Layer Security (DTLS)?
! */
! private final boolean isDTLS;
!
! /*
! * Class and subclass dynamic debugging support
! */
! private static final Debug debug = Debug.getInstance("ssl");
!
! //
! // Initialization/Constructors
! //
!
! /**
! * Constructor for an SSLEngine from SSLContext, without
! * host/port hints. This Engine will not be able to cache
! * sessions, but must renegotiate everything by hand.
! */
! SSLEngineImpl(SSLContextImpl ctx, boolean isDTLS) {
! super();
! this.isDTLS = isDTLS;
! init(ctx, isDTLS);
! }
!
! /**
! * Constructor for an SSLEngine from SSLContext.
! */
! SSLEngineImpl(SSLContextImpl ctx, String host, int port, boolean isDTLS) {
! super(host, port);
! this.isDTLS = isDTLS;
! init(ctx, isDTLS);
! }
!
! /**
! * Initializes the Engine
! */
! private void init(SSLContextImpl ctx, boolean isDTLS) {
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println("Using SSLEngineImpl.");
! }
!
! sslContext = ctx;
! sess = SSLSessionImpl.nullSession;
! handshakeSession = null;
! protocolVersion = isDTLS ?
! ProtocolVersion.DEFAULT_DTLS : ProtocolVersion.DEFAULT_TLS;
!
! /*
! * State is cs_START until we initialize the handshaker.
! *
! * Apps using SSLEngine are probably going to be server.
! * Somewhat arbitrary choice.
! */
! roleIsServer = true;
! connectionState = cs_START;
!
! // default server name indication
! serverNames =
! Utilities.addToSNIServerNameList(serverNames, getPeerHost());
!
! // default security parameters for secure renegotiation
! secureRenegotiation = false;
! clientVerifyData = new byte[0];
! serverVerifyData = new byte[0];
!
! enabledCipherSuites =
! sslContext.getDefaultCipherSuiteList(roleIsServer);
! enabledProtocols =
! sslContext.getDefaultProtocolList(roleIsServer);
!
! wrapLock = new Object();
! unwrapLock = new Object();
! writeLock = new Object();
!
! /*
! * Save the Access Control Context. This will be used later
! * for a couple of things, including providing a context to
! * run tasks in, and for determining which credentials
! * to use for Subject based (JAAS) decisions
! */
! acc = AccessController.getContext();
!
! /*
! * All outbound application data goes through this OutputRecord,
! * other data goes through their respective records created
! * elsewhere. All inbound data goes through this one
! * input record.
! */
! if (isDTLS) {
! enableRetransmissions = true;
!
! // SSLEngine needs no record local buffer
! outputRecord = new DTLSOutputRecord();
! inputRecord = new DTLSInputRecord();
!
! } else {
! outputRecord = new SSLEngineOutputRecord();
! inputRecord = new SSLEngineInputRecord();
! }
!
! maximumPacketSize = outputRecord.getMaxPacketSize();
! }
!
! /**
! * Initialize the handshaker object. This means:
! *
! * . if a handshake is already in progress (state is cs_HANDSHAKE
! * or cs_RENEGOTIATE), do nothing and return
! *
! * . if the engine is already closed, throw an Exception (internal error)
! *
! * . otherwise (cs_START or cs_DATA), create the appropriate handshaker
! * object and advance the connection state (to cs_HANDSHAKE or
! * cs_RENEGOTIATE, respectively).
! *
! * This method is called right after a new engine is created, when
! * starting renegotiation, or when changing client/server mode of the
! * engine.
! */
! private void initHandshaker() {
! switch (connectionState) {
!
! //
! // Starting a new handshake.
! //
! case cs_START:
! case cs_DATA:
! break;
!
! //
! // We're already in the middle of a handshake.
! //
! case cs_HANDSHAKE:
! case cs_RENEGOTIATE:
! return;
!
! //
! // Anyone allowed to call this routine is required to
! // do so ONLY if the connection state is reasonable...
! //
! default:
! throw new IllegalStateException("Internal error");
! }
!
! // state is either cs_START or cs_DATA
! if (connectionState == cs_START) {
! connectionState = cs_HANDSHAKE;
! } else { // cs_DATA
! connectionState = cs_RENEGOTIATE;
! }
!
! if (roleIsServer) {
! handshaker = new ServerHandshaker(this, sslContext,
! enabledProtocols, doClientAuth,
! protocolVersion, connectionState == cs_HANDSHAKE,
! secureRenegotiation, clientVerifyData, serverVerifyData,
! isDTLS);
! handshaker.setSNIMatchers(sniMatchers);
! handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites);
! } else {
! handshaker = new ClientHandshaker(this, sslContext,
! enabledProtocols,
! protocolVersion, connectionState == cs_HANDSHAKE,
! secureRenegotiation, clientVerifyData, serverVerifyData,
! isDTLS);
! handshaker.setSNIServerNames(serverNames);
! }
! handshaker.setMaximumPacketSize(maximumPacketSize);
! handshaker.setEnabledCipherSuites(enabledCipherSuites);
! handshaker.setEnableSessionCreation(enableSessionCreation);
! handshaker.setApplicationProtocols(applicationProtocols);
! handshaker.setApplicationProtocolSelectorSSLEngine(
! applicationProtocolSelector);
!
! outputRecord.initHandshaker();
! }
!
! /*
! * Report the current status of the Handshaker
! */
! private HandshakeStatus getHSStatus(HandshakeStatus hss) {
!
! if (hss != null) {
! return hss;
! }
!
! synchronized (this) {
! if (!outputRecord.isEmpty()) {
! // If no handshaking, special case to wrap alters.
! return HandshakeStatus.NEED_WRAP;
! } else if (handshaker != null) {
! if (handshaker.taskOutstanding()) {
! return HandshakeStatus.NEED_TASK;
! } else if (isDTLS && !inputRecord.isEmpty()) {
! return HandshakeStatus.NEED_UNWRAP_AGAIN;
! } else {
! return HandshakeStatus.NEED_UNWRAP;
! }
! } else if (connectionState == cs_CLOSED) {
! /*
! * Special case where we're closing, but
! * still need the close_notify before we
! * can officially be closed.
! *
! * Note isOutboundDone is taken care of by
! * hasOutboundData() above.
! */
! if (!isInboundDone()) {
! return HandshakeStatus.NEED_UNWRAP;
! } // else not handshaking
! }
!
! return HandshakeStatus.NOT_HANDSHAKING;
! }
! }
!
! private synchronized void checkTaskThrown() throws SSLException {
! if (handshaker != null) {
! handshaker.checkThrown();
! }
! }
!
! //
! // Handshaking and connection state code
! //
!
! /*
! * Provides "this" synchronization for connection state.
! * Otherwise, you can access it directly.
! */
! private synchronized int getConnectionState() {
! return connectionState;
! }
!
! private synchronized void setConnectionState(int state) {
! connectionState = state;
! }
!
! /*
! * Get the Access Control Context.
! *
! * Used for a known context to
! * run tasks in, and for determining which credentials
! * to use for Subject-based (JAAS) decisions.
! */
! AccessControlContext getAcc() {
! return acc;
! }
!
! /*
! * Is a handshake currently underway?
! */
! @Override
! public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
! return getHSStatus(null);
! }
!
! /*
! * used by Handshaker to change the active write cipher, follows
! * the output of the CCS message.
! *
! * Also synchronized on "this" from readRecord/delegatedTask.
! */
! void changeWriteCiphers() throws IOException {
!
! Authenticator writeAuthenticator;
! CipherBox writeCipher;
! try {
! writeCipher = handshaker.newWriteCipher();
! writeAuthenticator = handshaker.newWriteAuthenticator();
! } catch (GeneralSecurityException e) {
! // "can't happen"
! throw new SSLException("Algorithm missing: ", e);
! }
!
! outputRecord.changeWriteCiphers(writeAuthenticator, writeCipher);
! }
!
! /*
! * Updates the SSL version associated with this connection.
! * Called from Handshaker once it has determined the negotiated version.
! */
! synchronized void setVersion(ProtocolVersion protocolVersion) {
! this.protocolVersion = protocolVersion;
! outputRecord.setVersion(protocolVersion);
! }
!
!
! /**
! * Kickstart the handshake if it is not already in progress.
! * This means:
! *
! * . if handshaking is already underway, do nothing and return
! *
! * . if the engine is not connected or already closed, throw an
! * Exception.
! *
! * . otherwise, call initHandshake() to initialize the handshaker
! * object and progress the state. Then, send the initial
! * handshaking message if appropriate (always on clients and
! * on servers when renegotiating).
! */
! private synchronized void kickstartHandshake() throws IOException {
! switch (connectionState) {
!
! case cs_START:
! if (!serverModeSet) {
! throw new IllegalStateException(
! "Client/Server mode not yet set.");
! }
! initHandshaker();
! break;
!
! case cs_HANDSHAKE:
! // handshaker already setup, proceed
! break;
!
! case cs_DATA:
! if (!secureRenegotiation && !Handshaker.allowUnsafeRenegotiation) {
! throw new SSLHandshakeException(
! "Insecure renegotiation is not allowed");
! }
!
! if (!secureRenegotiation) {
! if (debug != null && Debug.isOn("handshake")) {
! System.out.println(
! "Warning: Using insecure renegotiation");
! }
! }
!
! // initialize the handshaker, move to cs_RENEGOTIATE
! initHandshaker();
! break;
!
! case cs_RENEGOTIATE:
! // handshaking already in progress, return
! return;
!
! default:
! // cs_ERROR/cs_CLOSED
! throw new SSLException("SSLEngine is closing/closed");
! }
!
! //
! // Kickstart handshake state machine if we need to ...
! //
! if (!handshaker.activated()) {
! // prior to handshaking, activate the handshake
! if (connectionState == cs_RENEGOTIATE) {
! // don't use SSLv2Hello when renegotiating
! handshaker.activate(protocolVersion);
! } else {
! handshaker.activate(null);
! }
!
! if (handshaker instanceof ClientHandshaker) {
! // send client hello
! handshaker.kickstart();
! } else { // instanceof ServerHandshaker
! if (connectionState == cs_HANDSHAKE) {
! // initial handshake, no kickstart message to send
! } else {
! // we want to renegotiate, send hello request
! handshaker.kickstart();
! }
! }
! }
! }
!
! /*
! * Start a SSLEngine handshake
! */
! @Override
! public void beginHandshake() throws SSLException {
! try {
! kickstartHandshake();
! } catch (Exception e) {
! fatal(Alerts.alert_handshake_failure,
! "Couldn't kickstart handshaking", e);
! }
! }
!
!
! //
! // Read/unwrap side
! //
!
!
! /**
! * Unwraps a buffer. Does a variety of checks before grabbing
! * the unwrapLock, which blocks multiple unwraps from occurring.
! */
! @Override
! public SSLEngineResult unwrap(ByteBuffer netData, ByteBuffer[] appData,
! int offset, int length) throws SSLException {
!
! // check engine parameters
! checkEngineParas(netData, appData, offset, length, false);
!
! try {
! synchronized (unwrapLock) {
! return readNetRecord(netData, appData, offset, length);
! }
! } catch (SSLProtocolException spe) {
! // may be an unexpected handshake message
! fatal(Alerts.alert_unexpected_message, spe.getMessage(), spe);
! return null; // make compiler happy
! } catch (Exception e) {
! /*
! * Don't reset position so it looks like we didn't
! * consume anything. We did consume something, and it
! * got us into this situation, so report that much back.
! * Our days of consuming are now over anyway.
! */
! fatal(Alerts.alert_internal_error,
! "problem unwrapping net record", e);
! return null; // make compiler happy
! }
! }
!
! private static void checkEngineParas(ByteBuffer netData,
! ByteBuffer[] appData, int offset, int len, boolean isForWrap) {
!
! if ((netData == null) || (appData == null)) {
! throw new IllegalArgumentException("src/dst is null");
! }
!
! if ((offset < 0) || (len < 0) || (offset > appData.length - len)) {
! throw new IndexOutOfBoundsException();
! }
!
! /*
! * If wrapping, make sure the destination bufffer is writable.
! */
! if (isForWrap && netData.isReadOnly()) {
! throw new ReadOnlyBufferException();
! }
!
! for (int i = offset; i < offset + len; i++) {
! if (appData[i] == null) {
! throw new IllegalArgumentException(
! "appData[" + i + "] == null");
! }
!
! /*
! * If unwrapping, make sure the destination bufffers are writable.
! */
! if (!isForWrap && appData[i].isReadOnly()) {
! throw new ReadOnlyBufferException();
! }
! }
! }
!
! /*
! * Makes additional checks for unwrap, but this time more
! * specific to this packet and the current state of the machine.
! */
! private SSLEngineResult readNetRecord(ByteBuffer netData,
! ByteBuffer[] appData, int offset, int length) throws IOException {
!
! Status status = null;
! HandshakeStatus hsStatus = null;
!
! /*
! * See if the handshaker needs to report back some SSLException.
! */
! checkTaskThrown();
!
! /*
! * Check if we are closing/closed.
! */
! if (isInboundDone()) {
! return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0);
! }
!
! /*
! * If we're still in cs_HANDSHAKE, make sure it's been
! * started.
! */
! synchronized (this) {
! if ((connectionState == cs_HANDSHAKE) ||
! (connectionState == cs_START)) {
! kickstartHandshake();
!
! /*
! * If there's still outbound data to flush, we
! * can return without trying to unwrap anything.
! */
! hsStatus = getHSStatus(null);
!
! if (hsStatus == HandshakeStatus.NEED_WRAP) {
! return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
! }
! }
! }
!
! /*
! * Grab a copy of this if it doesn't already exist,
! * and we can use it several places before anything major
! * happens on this side. Races aren't critical
! * here.
! */
! if (hsStatus == null) {
! hsStatus = getHSStatus(null);
! }
!
! /*
! * If we have a task outstanding, this *MUST* be done before
! * doing any more unwrapping, because we could be in the middle
! * of receiving a handshake message, for example, a finished
! * message which would change the ciphers.
! */
! if (hsStatus == HandshakeStatus.NEED_TASK) {
! return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
! }
!
! if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
! Plaintext plainText = null;
! try {
! plainText = readRecord(null, null, 0, 0);
! } catch (SSLException e) {
! throw e;
! } catch (IOException e) {
! throw new SSLException("readRecord", e);
! }
!
! status = (isInboundDone() ? Status.CLOSED : Status.OK);
! hsStatus = getHSStatus(plainText.handshakeStatus);
!
! return new SSLEngineResult(
! status, hsStatus, 0, 0, plainText.recordSN);
! }
!
! /*
! * Check the packet to make sure enough is here.
! * This will also indirectly check for 0 len packets.
! */
! int packetLen = 0;
! try {
! packetLen = inputRecord.bytesInCompletePacket(netData);
! } catch (SSLException ssle) {
! // Need to discard invalid records for DTLS protocols.
! if (isDTLS) {
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(
! Thread.currentThread().getName() +
! " discard invalid record: " + ssle);
! }
!
! // invalid, discard the entire data [section 4.1.2.7, RFC 6347]
! int deltaNet = netData.remaining();
! netData.position(netData.limit());
!
! status = (isInboundDone() ? Status.CLOSED : Status.OK);
! hsStatus = getHSStatus(hsStatus);
!
! return new SSLEngineResult(status, hsStatus, deltaNet, 0, -1L);
! } else {
! throw ssle;
! }
! }
!
! // Is this packet bigger than SSL/TLS normally allows?
! if (packetLen > sess.getPacketBufferSize()) {
! int largestRecordSize = isDTLS ?
! DTLSRecord.maxRecordSize : SSLRecord.maxLargeRecordSize;
! if ((packetLen <= largestRecordSize) && !isDTLS) {
! // Expand the expected maximum packet/application buffer
! // sizes.
! //
! // Only apply to SSL/TLS protocols.
!
! // Old behavior: shall we honor the System Property
! // "jsse.SSLEngine.acceptLargeFragments" if it is "false"?
! sess.expandBufferSizes();
! }
!
! // check the packet again
! largestRecordSize = sess.getPacketBufferSize();
! if (packetLen > largestRecordSize) {
! throw new SSLProtocolException(
! "Input record too big: max = " +
! largestRecordSize + " len = " + packetLen);
! }
! }
!
! int netPos = netData.position();
! int appRemains = 0;
! for (int i = offset; i < offset + length; i++) {
! if (appData[i] == null) {
! throw new IllegalArgumentException(
! "appData[" + i + "] == null");
! }
! appRemains += appData[i].remaining();
! }
!
! /*
! * Check for OVERFLOW.
! *
! * Delay enforcing the application buffer free space requirement
! * until after the initial handshaking.
! */
! // synchronize connectionState?
! if ((connectionState == cs_DATA) ||
! (connectionState == cs_RENEGOTIATE)) {
!
! int FragLen = inputRecord.estimateFragmentSize(packetLen);
! if (FragLen > appRemains) {
! return new SSLEngineResult(
! Status.BUFFER_OVERFLOW, hsStatus, 0, 0);
! }
! }
!
! // check for UNDERFLOW.
! if ((packetLen == -1) || (netData.remaining() < packetLen)) {
! return new SSLEngineResult(Status.BUFFER_UNDERFLOW, hsStatus, 0, 0);
! }
!
! /*
! * We're now ready to actually do the read.
! */
! Plaintext plainText = null;
! try {
! plainText = readRecord(netData, appData, offset, length);
! } catch (SSLException e) {
! throw e;
! } catch (IOException e) {
! throw new SSLException("readRecord", e);
! }
!
! /*
! * Check the various condition that we could be reporting.
! *
! * It's *possible* something might have happened between the
! * above and now, but it was better to minimally lock "this"
! * during the read process. We'll return the current
! * status, which is more representative of the current state.
! *
! * status above should cover: FINISHED, NEED_TASK
! */
! status = (isInboundDone() ? Status.CLOSED : Status.OK);
! hsStatus = getHSStatus(plainText.handshakeStatus);
!
! int deltaNet = netData.position() - netPos;
! int deltaApp = appRemains;
! for (int i = offset; i < offset + length; i++) {
! deltaApp -= appData[i].remaining();
! }
!
! return new SSLEngineResult(
! status, hsStatus, deltaNet, deltaApp, plainText.recordSN);
! }
!
! // the caller have synchronized readLock
! void expectingFinishFlight() {
! inputRecord.expectingFinishFlight();
! }
!
! /*
! * Actually do the read record processing.
! *
! * Returns a Status if it can make specific determinations
! * of the engine state. In particular, we need to signal
! * that a handshake just completed.
! *
! * It would be nice to be symmetrical with the write side and move
! * the majority of this to SSLInputRecord, but there's too much
! * SSLEngine state to do that cleanly. It must still live here.
! */
! private Plaintext readRecord(ByteBuffer netData,
! ByteBuffer[] appData, int offset, int length) throws IOException {
!
! /*
! * The various operations will return new sliced BB's,
! * this will avoid having to worry about positions and
! * limits in the netBB.
! */
! Plaintext plainText = null;
!
! if (getConnectionState() == cs_ERROR) {
! return Plaintext.PLAINTEXT_NULL;
! }
!
! /*
! * Read a record ... maybe emitting an alert if we get a
! * comprehensible but unsupported "hello" message during
! * format checking (e.g. V2).
! */
! try {
! if (isDTLS) {
! // Don't process the incoming record until all of the
! // buffered records get handled.
! plainText = inputRecord.acquirePlaintext();
! }
!
! if ((!isDTLS || plainText == null) && netData != null) {
! plainText = inputRecord.decode(netData);
! }
! } catch (UnsupportedOperationException unsoe) { // SSLv2Hello
! // Hack code to deliver SSLv2 error message for SSL/TLS connections.
! if (!isDTLS) {
! outputRecord.encodeV2NoCipher();
! }
!
! fatal(Alerts.alert_unexpected_message, unsoe);
! } catch (BadPaddingException e) {
! /*
! * The basic SSLv3 record protection involves (optional)
! * encryption for privacy, and an integrity check ensuring
! * data origin authentication. We do them both here, and
! * throw a fatal alert if the integrity check fails.
! */
! byte alertType = (connectionState != cs_DATA) ?
! Alerts.alert_handshake_failure :
! Alerts.alert_bad_record_mac;
! fatal(alertType, e.getMessage(), e);
! } catch (SSLHandshakeException she) {
! // may be record sequence number overflow
! fatal(Alerts.alert_handshake_failure, she);
! } catch (IOException ioe) {
! fatal(Alerts.alert_unexpected_message, ioe);
! }
!
! // plainText should never be null for TLS protocols
! HandshakeStatus hsStatus = null;
! if (plainText == Plaintext.PLAINTEXT_NULL) {
! // Only happens for DTLS protocols.
! //
! // Received a retransmitted flight, and need to retransmit the
! // previous delivered handshake flight messages.
! if (enableRetransmissions) {
! if (debug != null && Debug.isOn("verbose")) {
! Debug.log(
! "Retransmit the previous handshake flight messages.");
! }
!
! synchronized (this) {
! outputRecord.launchRetransmission();
! }
! } // Otherwise, discard the retransmitted flight.
! } else if (!isDTLS || plainText != null) {
! hsStatus = processInputRecord(plainText, appData, offset, length);
! }
!
! if (hsStatus == null) {
! hsStatus = getHSStatus(null);
! }
!
! if (plainText == null) {
! plainText = Plaintext.PLAINTEXT_NULL;
! }
! plainText.handshakeStatus = hsStatus;
!
! return plainText;
! }
!
! /*
! * Process the record.
*/
! private synchronized HandshakeStatus processInputRecord(
! Plaintext plainText,
! ByteBuffer[] appData, int offset, int length) throws IOException {
! HandshakeStatus hsStatus = null;
! switch (plainText.contentType) {
! case Record.ct_handshake:
! /*
! * Handshake messages always go to a pending session
! * handshaker ... if there isn't one, create one. This
! * must work asynchronously, for renegotiation.
*
! * NOTE that handshaking will either resume a session
! * which was in the cache (and which might have other
! * connections in it already), or else will start a new
! * session (new keys exchanged) with just this connection
! * in it.
*/
! initHandshaker();
! if (!handshaker.activated()) {
! // prior to handshaking, activate the handshake
! if (connectionState == cs_RENEGOTIATE) {
! // don't use SSLv2Hello when renegotiating
! handshaker.activate(protocolVersion);
! } else {
! handshaker.activate(null);
! }
}
! /*
! * process the handshake record ... may contain just
! * a partial handshake message or multiple messages.
! *
! * The handshaker state machine will ensure that it's
! * a finished message.
*/
! handshaker.processRecord(plainText.fragment, expectingFinished);
! expectingFinished = false;
!
! if (handshaker.invalidated) {
! finishHandshake();
!
! // if state is cs_RENEGOTIATE, revert it to cs_DATA
! if (connectionState == cs_RENEGOTIATE) {
! connectionState = cs_DATA;
! }
! } else if (handshaker.isDone()) {
! // reset the parameters for secure renegotiation.
! secureRenegotiation =
! handshaker.isSecureRenegotiation();
! clientVerifyData = handshaker.getClientVerifyData();
! serverVerifyData = handshaker.getServerVerifyData();
! // set connection ALPN value
! applicationProtocol =
! handshaker.getHandshakeApplicationProtocol();
!
! sess = handshaker.getSession();
! handshakeSession = null;
! if (outputRecord.isEmpty()) {
! hsStatus = finishHandshake();
! connectionState = cs_DATA;
! }
!
! // No handshakeListeners here. That's a
! // SSLSocket thing.
! } else if (handshaker.taskOutstanding()) {
! hsStatus = HandshakeStatus.NEED_TASK;
! }
! break;
!
! case Record.ct_application_data:
! // Pass this right back up to the application.
! if ((connectionState != cs_DATA)
! && (connectionState != cs_RENEGOTIATE)
! && (connectionState != cs_CLOSED)) {
! throw new SSLProtocolException(
! "Data received in non-data state: " +
! connectionState);
}
! if (expectingFinished) {
! throw new SSLProtocolException
! ("Expecting finished message, received data");
! }
!
! if (!inboundDone) {
! ByteBuffer fragment = plainText.fragment;
! int remains = fragment.remaining();
!
! // Should have enough room in appData.
! for (int i = offset;
! ((i < (offset + length)) && (remains > 0)); i++) {
! int amount = Math.min(appData[i].remaining(), remains);
! fragment.limit(fragment.position() + amount);
! appData[i].put(fragment);
! remains -= amount;
}
}
! break;
!
! case Record.ct_alert:
! recvAlert(plainText.fragment);
! break;
!
! case Record.ct_change_cipher_spec:
! if ((connectionState != cs_HANDSHAKE
! && connectionState != cs_RENEGOTIATE)) {
! // For the CCS message arriving in the wrong state
! fatal(Alerts.alert_unexpected_message,
! "illegal change cipher spec msg, conn state = "
! + connectionState);
! } else if (plainText.fragment.remaining() != 1
! || plainText.fragment.get() != 1) {
! // For structural/content issues with the CCS
! fatal(Alerts.alert_unexpected_message,
! "Malformed change cipher spec msg");
}
- //
- // The first message after a change_cipher_spec
- // record MUST be a "Finished" handshake record,
- // else it's a protocol violation. We force this
- // to be checked by a minor tweak to the state
- // machine.
- //
- handshaker.receiveChangeCipherSpec();
-
- CipherBox readCipher;
- Authenticator readAuthenticator;
try {
! readCipher = handshaker.newReadCipher();
! readAuthenticator = handshaker.newReadAuthenticator();
! } catch (GeneralSecurityException e) {
! // can't happen
! throw new SSLException("Algorithm missing: ", e);
! }
! inputRecord.changeReadCiphers(readAuthenticator, readCipher);
!
! // next message MUST be a finished message
! expectingFinished = true;
! break;
!
! default:
! //
! // TLS requires that unrecognized records be ignored.
! //
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", Received record type: " + plainText.contentType);
! }
! break;
! } // switch
!
! /*
! * We only need to check the sequence number state for
! * non-handshaking record.
! *
! * Note that in order to maintain the handshake status
! * properly, we check the sequence number after the last
! * record reading process. As we request renegotiation
! * or close the connection for wrapped sequence number
! * when there is enough sequence number space left to
! * handle a few more records, so the sequence number
! * of the last record cannot be wrapped.
! */
! hsStatus = getHSStatus(hsStatus);
! if (connectionState < cs_ERROR && !isInboundDone() &&
! (hsStatus == HandshakeStatus.NOT_HANDSHAKING) &&
! (inputRecord.seqNumIsHuge())) {
! /*
! * Ask for renegotiation when need to renew sequence number.
! *
! * Don't bother to kickstart the renegotiation when the local is
! * asking for it.
! */
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", request renegotiation " +
! "to avoid sequence number overflow");
! }
!
! beginHandshake();
!
! hsStatus = getHSStatus(null);
}
-
- return hsStatus;
}
-
- //
- // write/wrap side
- //
-
-
- /**
- * Wraps a buffer. Does a variety of checks before grabbing
- * the wrapLock, which blocks multiple wraps from occurring.
- */
@Override
! public SSLEngineResult wrap(ByteBuffer[] appData,
int offset, int length, ByteBuffer netData) throws SSLException {
! // check engine parameters
! checkEngineParas(netData, appData, offset, length, true);
! /*
! * We can be smarter about using smaller buffer sizes later.
! * For now, force it to be large enough to handle any valid record.
! */
! if (netData.remaining() < sess.getPacketBufferSize()) {
! return new SSLEngineResult(
! Status.BUFFER_OVERFLOW, getHSStatus(null), 0, 0);
}
try {
! synchronized (wrapLock) {
! return writeAppRecord(appData, offset, length, netData);
! }
} catch (SSLProtocolException spe) {
// may be an unexpected handshake message
! fatal(Alerts.alert_unexpected_message, spe.getMessage(), spe);
! return null; // make compiler happy
! } catch (Exception e) {
! fatal(Alerts.alert_internal_error,
! "problem wrapping app data", e);
! return null; // make compiler happy
! }
}
! /*
! * Makes additional checks for unwrap, but this time more
! * specific to this packet and the current state of the machine.
! */
! private SSLEngineResult writeAppRecord(ByteBuffer[] appData,
! int offset, int length, ByteBuffer netData) throws IOException {
!
! Status status = null;
! HandshakeStatus hsStatus = null;
! /*
! * See if the handshaker needs to report back some SSLException.
! */
! checkTaskThrown();
- /*
- * short circuit if we're closed/closing.
- */
if (isOutboundDone()) {
! return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0);
}
! /*
! * If we're still in cs_HANDSHAKE, make sure it's been
! * started.
! */
! synchronized (this) {
! if ((connectionState == cs_HANDSHAKE) ||
! (connectionState == cs_START)) {
!
! kickstartHandshake();
! /*
! * If there's no HS data available to write, we can return
! * without trying to wrap anything.
! */
! hsStatus = getHSStatus(null);
if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
/*
* For DTLS, if the handshake state is
* HandshakeStatus.NEED_UNWRAP, a call to SSLEngine.wrap()
* means that the previous handshake packets (if delivered)
* get lost, and need retransmit the handshake messages.
*/
! if (!isDTLS || !enableRetransmissions ||
! (handshaker == null) || outputRecord.firstMessage) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
} // otherwise, need retransmission
}
}
- }
- /*
- * Grab a copy of this if it doesn't already exist,
- * and we can use it several places before anything major
- * happens on this side. Races aren't critical
- * here.
- */
if (hsStatus == null) {
! hsStatus = getHSStatus(null);
}
/*
* If we have a task outstanding, this *MUST* be done before
* doing any more wrapping, because we could be in the middle
--- 23,192 ----
* questions.
*/
package sun.security.ssl;
! import java.io.IOException;
! import java.nio.ByteBuffer;
! import java.nio.ReadOnlyBufferException;
! import java.security.AccessController;
! import java.security.PrivilegedActionException;
! import java.security.PrivilegedExceptionAction;
! import java.util.List;
! import java.util.Map;
import java.util.function.BiFunction;
+ import javax.net.ssl.SSLEngine;
+ import javax.net.ssl.SSLEngineResult;
+ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+ import javax.net.ssl.SSLEngineResult.Status;
+ import javax.net.ssl.SSLException;
+ import javax.net.ssl.SSLHandshakeException;
+ import javax.net.ssl.SSLKeyException;
+ import javax.net.ssl.SSLParameters;
+ import javax.net.ssl.SSLPeerUnverifiedException;
+ import javax.net.ssl.SSLProtocolException;
+ import javax.net.ssl.SSLSession;
! /**
! * Implementation of an non-blocking SSLEngine.
! *
! * @author Brad Wetmore
*/
! final class SSLEngineImpl extends SSLEngine implements SSLTransport {
! private final SSLContextImpl sslContext;
! final TransportContext conContext;
! /**
! * Constructor for an SSLEngine from SSLContext, without
! * host/port hints.
*
! * This Engine will not be able to cache sessions, but must renegotiate
! * everything by hand.
*/
! SSLEngineImpl(SSLContextImpl sslContext) {
! this(sslContext, null, -1);
}
! /**
! * Constructor for an SSLEngine from SSLContext.
*/
! SSLEngineImpl(SSLContextImpl sslContext,
! String host, int port) {
! super(host, port);
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! if (sslContext.isDTLS()) {
! this.conContext = new TransportContext(sslContext, this,
! new DTLSInputRecord(handshakeHash),
! new DTLSOutputRecord(handshakeHash));
! } else {
! this.conContext = new TransportContext(sslContext, this,
! new SSLEngineInputRecord(handshakeHash),
! new SSLEngineOutputRecord(handshakeHash));
}
! // Server name indication is a connection scope extension.
! if (host != null) {
! this.conContext.sslConfig.serverNames =
! Utilities.addToSNIServerNameList(
! conContext.sslConfig.serverNames, host);
}
}
! @Override
! public synchronized void beginHandshake() throws SSLException {
! if (conContext.isUnsureMode) {
! throw new IllegalStateException(
! "Client/Server mode has not yet been set.");
}
try {
! conContext.kickstart();
! } catch (IOException ioe) {
! conContext.fatal(Alert.HANDSHAKE_FAILURE,
! "Couldn't kickstart handshaking", ioe);
! } catch (Exception ex) { // including RuntimeException
! conContext.fatal(Alert.INTERNAL_ERROR,
! "Fail to begin handshake", ex);
}
}
@Override
! public synchronized SSLEngineResult wrap(ByteBuffer[] appData,
int offset, int length, ByteBuffer netData) throws SSLException {
+ return wrap(
+ appData, offset, length, new ByteBuffer[]{ netData }, 0, 1);
+ }
! // @Override
! public synchronized SSLEngineResult wrap(
! ByteBuffer[] srcs, int srcsOffset, int srcsLength,
! ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException {
! if (conContext.isUnsureMode) {
! throw new IllegalStateException(
! "Client/Server mode has not yet been set.");
}
+ // See if the handshaker needs to report back some SSLException.
+ if (conContext.outputRecord.isEmpty()) {
+ checkTaskThrown();
+ } // Otherwise, deliver cached records before throwing task exception.
+
+ // check parameters
+ checkParams(srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
+
try {
! return writeRecord(
! srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
} catch (SSLProtocolException spe) {
// may be an unexpected handshake message
! conContext.fatal(Alert.UNEXPECTED_MESSAGE, spe);
! } catch (IOException ioe) {
! conContext.fatal(Alert.INTERNAL_ERROR,
! "problem wrapping app data", ioe);
! } catch (Exception ex) { // including RuntimeException
! conContext.fatal(Alert.INTERNAL_ERROR,
! "Fail to wrap application data", ex);
}
! return null; // make compiler happy
! }
! private SSLEngineResult writeRecord(
! ByteBuffer[] srcs, int srcsOffset, int srcsLength,
! ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
if (isOutboundDone()) {
! return new SSLEngineResult(
! Status.CLOSED, getHandshakeStatus(), 0, 0);
}
! HandshakeContext hc = conContext.handshakeContext;
! HandshakeStatus hsStatus = null;
! if (!conContext.isNegotiated) {
! conContext.kickstart();
! hsStatus = getHandshakeStatus();
if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
/*
* For DTLS, if the handshake state is
* HandshakeStatus.NEED_UNWRAP, a call to SSLEngine.wrap()
* means that the previous handshake packets (if delivered)
* get lost, and need retransmit the handshake messages.
*/
! if (!sslContext.isDTLS() || hc == null ||
! !hc.sslConfig.enableRetransmissions ||
! conContext.outputRecord.firstMessage) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
} // otherwise, need retransmission
}
}
if (hsStatus == null) {
! hsStatus = getHandshakeStatus();
}
/*
* If we have a task outstanding, this *MUST* be done before
* doing any more wrapping, because we could be in the middle
*** 1337,2310 ****
*/
if (hsStatus == HandshakeStatus.NEED_TASK) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
! /*
! * This will obtain any waiting outbound data, or will
! * process the outbound appData.
! */
! int netPos = netData.position();
! int appRemains = 0;
! for (int i = offset; i < offset + length; i++) {
! if (appData[i] == null) {
! throw new IllegalArgumentException(
! "appData[" + i + "] == null");
}
! appRemains += appData[i].remaining();
}
Ciphertext ciphertext = null;
try {
! if (appRemains != 0) {
! synchronized (writeLock) {
! ciphertext = writeRecord(appData, offset, length, netData);
}
! } else {
! synchronized (writeLock) {
! ciphertext = writeRecord(null, 0, 0, netData);
}
}
- } catch (SSLException e) {
- throw e;
- } catch (IOException e) {
- throw new SSLException("Write problems", e);
}
/*
! * writeRecord might have reported some status.
! * Now check for the remaining cases.
! *
! * status above should cover: NEED_WRAP/FINISHED
*/
! status = (isOutboundDone() ? Status.CLOSED : Status.OK);
! hsStatus = getHSStatus(ciphertext.handshakeStatus);
! int deltaNet = netData.position() - netPos;
! int deltaApp = appRemains;
! for (int i = offset; i < offset + length; i++) {
! deltaApp -= appData[i].remaining();
}
! return new SSLEngineResult(
! status, hsStatus, deltaApp, deltaNet, ciphertext.recordSN);
}
! /*
! * Central point to write/get all of the outgoing data.
! */
! private Ciphertext writeRecord(ByteBuffer[] appData,
! int offset, int length, ByteBuffer netData) throws IOException {
Ciphertext ciphertext = null;
try {
! // Acquire the buffered to-be-delivered records or retransmissions.
! //
! // May have buffered records, or need retransmission if handshaking.
! if (!outputRecord.isEmpty() ||
! (enableRetransmissions && handshaker != null)) {
! ciphertext = outputRecord.acquireCiphertext(netData);
! }
!
! if ((ciphertext == null) && (appData != null)) {
! ciphertext = outputRecord.encode(
! appData, offset, length, netData);
! }
} catch (SSLHandshakeException she) {
// may be record sequence number overflow
! fatal(Alerts.alert_handshake_failure, she);
!
! return Ciphertext.CIPHERTEXT_NULL; // make the complier happy
} catch (IOException e) {
! fatal(Alerts.alert_unexpected_message, e);
!
! return Ciphertext.CIPHERTEXT_NULL; // make the complier happy
}
if (ciphertext == null) {
return Ciphertext.CIPHERTEXT_NULL;
}
! HandshakeStatus hsStatus = null;
! Ciphertext.RecordType recordType = ciphertext.recordType;
! if ((recordType.contentType == Record.ct_handshake) &&
! (recordType.handshakeType == HandshakeMessage.ht_finished) &&
! outputRecord.isEmpty()) {
!
! if (handshaker == null) {
! hsStatus = HandshakeStatus.FINISHED;
! } else if (handshaker.isDone()) {
! hsStatus = finishHandshake();
! connectionState = cs_DATA;
!
! // Retransmit the last flight twice.
//
// The application data transactions may begin immediately
// after the last flight. If the last flight get lost, the
// application data may be discarded accordingly. As could
// be an issue for some applications. This impact can be
// mitigated by sending the last fligth twice.
! if (isDTLS && enableRetransmissions) {
! if (debug != null && Debug.isOn("verbose")) {
! Debug.log(
! "Retransmit the last flight messages.");
! }
!
! synchronized (this) {
! outputRecord.launchRetransmission();
}
hsStatus = HandshakeStatus.NEED_WRAP;
}
- }
- } // Otherwise, the followed call to getHSStatus() will help.
! /*
! * We only need to check the sequence number state for
! * non-handshaking record.
! *
! * Note that in order to maintain the handshake status
! * properly, we check the sequence number after the last
! * record writing process. As we request renegotiation
! * or close the connection for wrapped sequence number
! * when there is enough sequence number space left to
! * handle a few more records, so the sequence number
! * of the last record cannot be wrapped.
! */
! hsStatus = getHSStatus(hsStatus);
! if (connectionState < cs_ERROR && !isOutboundDone() &&
! (hsStatus == HandshakeStatus.NOT_HANDSHAKING) &&
! (outputRecord.seqNumIsHuge())) {
! /*
! * Ask for renegotiation when need to renew sequence number.
! *
! * Don't bother to kickstart the renegotiation when the local is
! * asking for it.
! */
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", request renegotiation " +
! "to avoid sequence number overflow");
}
! beginHandshake();
!
! hsStatus = getHSStatus(null);
}
ciphertext.handshakeStatus = hsStatus;
return ciphertext;
}
! private HandshakeStatus finishHandshake() {
! handshaker = null;
! inputRecord.setHandshakeHash(null);
! outputRecord.setHandshakeHash(null);
! connectionState = cs_DATA;
!
! return HandshakeStatus.FINISHED;
}
! //
! // Close code
! //
/**
! * Signals that no more outbound application data will be sent
! * on this <code>SSLEngine</code>.
*/
! private void closeOutboundInternal() {
!
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", closeOutboundInternal()");
}
! /*
! * Already closed, ignore
! */
! if (outboundDone) {
! return;
}
! switch (connectionState) {
! /*
! * If we haven't even started yet, don't bother reading inbound.
! */
! case cs_START:
! try {
! outputRecord.close();
! } catch (IOException ioe) {
! // ignore
}
- outboundDone = true;
! try {
! inputRecord.close();
! } catch (IOException ioe) {
! // ignore
}
- inboundDone = true;
- break;
! case cs_ERROR:
! case cs_CLOSED:
! break;
! /*
! * Otherwise we indicate clean termination.
! */
! // case cs_HANDSHAKE:
! // case cs_DATA:
! // case cs_RENEGOTIATE:
! default:
! warning(Alerts.alert_close_notify);
! try {
! outputRecord.close();
! } catch (IOException ioe) {
! // ignore
}
- outboundDone = true;
- break;
}
! connectionState = cs_CLOSED;
}
- @Override
- public synchronized void closeOutbound() {
/*
! * Dump out a close_notify to the remote side
*/
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", called closeOutbound()");
}
-
- closeOutboundInternal();
}
- /**
- * Returns the outbound application data closure state
- */
@Override
! public boolean isOutboundDone() {
! return outboundDone && outputRecord.isEmpty();
}
! /**
! * Signals that no more inbound network data will be sent
! * to this <code>SSLEngine</code>.
! */
! private void closeInboundInternal() {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", closeInboundInternal()");
}
! /*
! * Already closed, ignore
! */
! if (inboundDone) {
! return;
! }
! closeOutboundInternal();
try {
! inputRecord.close();
} catch (IOException ioe) {
! // ignore
}
- inboundDone = true;
! connectionState = cs_CLOSED;
}
/*
! * Close the inbound side of the connection. We grab the
! * lock here, and do the real work in the internal verison.
! * We do check for truncation attacks.
! */
! @Override
! public synchronized void closeInbound() throws SSLException {
! /*
! * Currently closes the outbound side as well. The IETF TLS
! * working group has expressed the opinion that 1/2 open
! * connections are not allowed by the spec. May change
! * someday in the future.
*/
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", called closeInbound()");
}
/*
! * No need to throw an Exception if we haven't even started yet.
! */
! if ((connectionState != cs_START) && !recvCN) {
! recvCN = true; // Only receive the Exception once
! fatal(Alerts.alert_internal_error,
! "Inbound closed before receiving peer's close_notify: " +
! "possible truncation attack?");
! } else {
! /*
! * Currently, this is a no-op, but in case we change
! * the close inbound code later.
*/
! closeInboundInternal();
}
}
! /**
! * Returns the network inbound data closure state
! */
! @Override
! public synchronized boolean isInboundDone() {
! return inboundDone;
}
!
! //
! // Misc stuff
! //
!
!
! /**
! * Returns the current <code>SSLSession</code> for this
! * <code>SSLEngine</code>
! * <P>
! * These can be long lived, and frequently correspond to an
! * entire login session for some user.
*/
! @Override
! public synchronized SSLSession getSession() {
! return sess;
}
! @Override
! public synchronized SSLSession getHandshakeSession() {
! return handshakeSession;
}
! synchronized void setHandshakeSession(SSLSessionImpl session) {
! // update the fragment size, which may be negotiated during handshaking
! inputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
! outputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
!
! handshakeSession = session;
}
! /**
! * Returns a delegated <code>Runnable</code> task for
! * this <code>SSLEngine</code>.
! */
! @Override
! public synchronized Runnable getDelegatedTask() {
! if (handshaker != null) {
! return handshaker.getTask();
! }
! return null;
}
!
! //
! // EXCEPTION AND ALERT HANDLING
! //
/*
! * Send a warning alert.
*/
! void warning(byte description) {
! sendAlert(Alerts.alert_warning, description);
}
! synchronized void fatal(byte description, String diagnostic)
! throws SSLException {
! fatal(description, diagnostic, null, false);
! }
! synchronized void fatal(byte description, Throwable cause)
! throws SSLException {
! fatal(description, null, cause, false);
}
! synchronized void fatal(byte description, String diagnostic,
! Throwable cause) throws SSLException {
! fatal(description, diagnostic, cause, false);
}
! /*
! * We've got a fatal error here, so start the shutdown process.
! *
! * Because of the way the code was written, we have some code
! * calling fatal directly when the "description" is known
! * and some throwing Exceptions which are then caught by higher
! * levels which then call here. This code needs to determine
! * if one of the lower levels has already started the process.
! *
! * We won't worry about Errors, if we have one of those,
! * we're in worse trouble. Note: the networking code doesn't
! * deal with Errors either.
! */
! synchronized void fatal(byte description, String diagnostic,
! Throwable cause, boolean recvFatalAlert) throws SSLException {
! /*
! * If we have no further information, make a general-purpose
! * message for folks to see. We generally have one or the other.
! */
! if (diagnostic == null) {
! diagnostic = "General SSLEngine problem";
}
- if (cause == null) {
- cause = Alerts.getSSLException(description, cause, diagnostic);
}
/*
! * If we've already shutdown because of an error,
! * there is nothing we can do except rethrow the exception.
*
! * Most exceptions seen here will be SSLExceptions.
! * We may find the occasional Exception which hasn't been
! * converted to a SSLException, so we'll do it here.
*/
! if (closeReason != null) {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", fatal: engine already closed. Rethrowing " +
! cause.toString());
! }
! if (cause instanceof RuntimeException) {
! throw (RuntimeException)cause;
! } else if (cause instanceof SSLException) {
! throw (SSLException)cause;
! } else if (cause instanceof Exception) {
! throw new SSLException("fatal SSLEngine condition", cause);
}
}
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName()
! + ", fatal error: " + description +
! ": " + diagnostic + "\n" + cause.toString());
}
/*
! * Ok, this engine's going down.
*/
! int oldState = connectionState;
! connectionState = cs_ERROR;
!
try {
! inputRecord.close();
} catch (IOException ioe) {
! // ignore
}
- inboundDone = true;
-
- sess.invalidate();
- if (handshakeSession != null) {
- handshakeSession.invalidate();
}
/*
! * If we haven't even started handshaking yet, or we are the
! * recipient of a fatal alert, no need to generate a fatal close
! * alert.
*/
! if (oldState != cs_START && !recvFatalAlert) {
! sendAlert(Alerts.alert_fatal, description);
! }
!
! if (cause instanceof SSLException) { // only true if != null
! closeReason = (SSLException)cause;
} else {
! /*
! * Including RuntimeExceptions, but we'll throw those
! * down below. The closeReason isn't used again,
! * except for null checks.
! */
! closeReason =
! Alerts.getSSLException(description, cause, diagnostic);
}
! try {
! outputRecord.close();
! } catch (IOException ioe) {
! // ignore
}
- outboundDone = true;
! connectionState = cs_CLOSED;
!
! if (cause instanceof RuntimeException) {
! throw (RuntimeException)cause;
! } else {
! throw closeReason;
! }
}
! /*
! * Process an incoming alert ... caller must already have synchronized
! * access to "this".
! */
! private void recvAlert(ByteBuffer fragment) throws IOException {
! byte level = fragment.get();
! byte description = fragment.get();
!
! if (debug != null && (Debug.isOn("record") ||
! Debug.isOn("handshake"))) {
! synchronized (System.out) {
! System.out.print(Thread.currentThread().getName());
! System.out.print(", RECV " + protocolVersion + " ALERT: ");
! if (level == Alerts.alert_fatal) {
! System.out.print("fatal, ");
! } else if (level == Alerts.alert_warning) {
! System.out.print("warning, ");
! } else {
! System.out.print("<level " + (0x0ff & level) + ">, ");
! }
! System.out.println(Alerts.alertDescription(description));
! }
}
! if (level == Alerts.alert_warning) {
! if (description == -1) { // check for short message
! fatal(Alerts.alert_illegal_parameter, "Short alert message");
! } else if (description == Alerts.alert_close_notify) {
! if (connectionState == cs_HANDSHAKE) {
! fatal(Alerts.alert_unexpected_message,
! "Received close_notify during handshake");
} else {
! recvCN = true;
! closeInboundInternal(); // reply to close
}
- } else {
! //
! // The other legal warnings relate to certificates,
! // e.g. no_certificate, bad_certificate, etc; these
! // are important to the handshaking code, which can
! // also handle illegal protocol alerts if needed.
! //
! if (handshaker != null) {
! handshaker.handshakeAlert(description);
! }
! }
! } else { // fatal or unknown level
! String reason = "Received fatal alert: "
! + Alerts.alertDescription(description);
!
! // The inbound and outbound queues will be closed as part of
! // the call to fatal. The handhaker to needs to be set to null
! // so subsequent calls to getHandshakeStatus will return
! // NOT_HANDSHAKING.
! handshaker = null;
! Throwable cause = Alerts.getSSLException(description, reason);
! fatal(description, null, cause, true);
}
}
!
! /*
! * Emit alerts. Caller must have synchronized with "this".
! */
! private void sendAlert(byte level, byte description) {
! // the connectionState cannot be cs_START
! if (connectionState >= cs_CLOSED) {
! return;
}
! // For initial handshaking, don't send alert message to peer if
! // handshaker has not started.
! //
! // Shall we send an fatal alter to terminate the connection gracefully?
! if (connectionState <= cs_HANDSHAKE &&
! (handshaker == null || !handshaker.started() ||
! !handshaker.activated())) {
! return;
}
! try {
! outputRecord.encodeAlert(level, description);
! } catch (IOException ioe) {
! // ignore
! }
}
-
- //
- // VARIOUS OTHER METHODS (COMMON TO SSLSocket)
- //
-
-
- /**
- * Controls whether new connections may cause creation of new SSL
- * sessions.
- *
- * As long as handshaking has not started, we can change
- * whether we enable session creations. Otherwise,
- * we will need to wait for the next handshake.
- */
@Override
! public synchronized void setEnableSessionCreation(boolean flag) {
! enableSessionCreation = flag;
!
! if ((handshaker != null) && !handshaker.activated()) {
! handshaker.setEnableSessionCreation(enableSessionCreation);
! }
}
- /**
- * Returns true if new connections may cause creation of new SSL
- * sessions.
- */
@Override
! public synchronized boolean getEnableSessionCreation() {
! return enableSessionCreation;
}
-
- /**
- * Sets the flag controlling whether a server mode engine
- * *REQUIRES* SSL client authentication.
- *
- * As long as handshaking has not started, we can change
- * whether client authentication is needed. Otherwise,
- * we will need to wait for the next handshake.
- */
@Override
! public synchronized void setNeedClientAuth(boolean flag) {
! doClientAuth = (flag ?
! ClientAuthType.CLIENT_AUTH_REQUIRED :
! ClientAuthType.CLIENT_AUTH_NONE);
!
! if ((handshaker != null) &&
! (handshaker instanceof ServerHandshaker) &&
! !handshaker.activated()) {
! ((ServerHandshaker) handshaker).setClientAuth(doClientAuth);
! }
}
@Override
! public synchronized boolean getNeedClientAuth() {
! return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED);
}
- /**
- * Sets the flag controlling whether a server mode engine
- * *REQUESTS* SSL client authentication.
- *
- * As long as handshaking has not started, we can change
- * whether client authentication is requested. Otherwise,
- * we will need to wait for the next handshake.
- */
@Override
! public synchronized void setWantClientAuth(boolean flag) {
! doClientAuth = (flag ?
! ClientAuthType.CLIENT_AUTH_REQUESTED :
! ClientAuthType.CLIENT_AUTH_NONE);
!
! if ((handshaker != null) &&
! (handshaker instanceof ServerHandshaker) &&
! !handshaker.activated()) {
! ((ServerHandshaker) handshaker).setClientAuth(doClientAuth);
! }
}
@Override
! public synchronized boolean getWantClientAuth() {
! return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED);
}
-
- /**
- * Sets the flag controlling whether the engine is in SSL
- * client or server mode. Must be called before any SSL
- * traffic has started.
- */
@Override
! @SuppressWarnings("fallthrough")
! public synchronized void setUseClientMode(boolean flag) {
! switch (connectionState) {
!
! case cs_START:
! /*
! * If we need to change the socket mode and the enabled
! * protocols and cipher suites haven't specifically been
! * set by the user, change them to the corresponding
! * default ones.
! */
! if (roleIsServer != (!flag)) {
! if (sslContext.isDefaultProtocolList(enabledProtocols)) {
! enabledProtocols =
! sslContext.getDefaultProtocolList(!flag);
}
! if (sslContext.isDefaultCipherSuiteList(enabledCipherSuites)) {
! enabledCipherSuites =
! sslContext.getDefaultCipherSuiteList(!flag);
! }
}
! roleIsServer = !flag;
! serverModeSet = true;
! break;
!
! case cs_HANDSHAKE:
! /*
! * If we have a handshaker, but haven't started
! * SSL traffic, we can throw away our current
! * handshaker, and start from scratch. Don't
! * need to call doneConnect() again, we already
! * have the streams.
! */
! assert(handshaker != null);
! if (!handshaker.activated()) {
! /*
! * If we need to change the socket mode and the enabled
! * protocols and cipher suites haven't specifically been
! * set by the user, change them to the corresponding
! * default ones.
! */
! if (roleIsServer != (!flag)) {
! if (sslContext.isDefaultProtocolList(enabledProtocols)) {
! enabledProtocols =
! sslContext.getDefaultProtocolList(!flag);
}
! if (sslContext.isDefaultCipherSuiteList(
! enabledCipherSuites)) {
! enabledCipherSuites =
! sslContext.getDefaultCipherSuiteList(!flag);
}
}
! roleIsServer = !flag;
! connectionState = cs_START;
! initHandshaker();
! break;
}
! // If handshake has started, that's an error. Fall through...
! default:
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", setUseClientMode() invoked in state = " +
! connectionState);
}
! /*
! * We can let them continue if they catch this correctly,
! * we don't need to shut this down.
! */
! throw new IllegalArgumentException(
! "Cannot change mode after SSL traffic has started");
}
}
@Override
public synchronized boolean getUseClientMode() {
! return !roleIsServer;
}
- /**
- * Returns the names of the cipher suites which could be enabled for use
- * on an SSL connection. Normally, only a subset of these will actually
- * be enabled by default, since this list may include cipher suites which
- * do not support the mutual authentication of servers and clients, or
- * which do not protect data confidentiality. Servers may also need
- * certain kinds of certificates to use certain cipher suites.
- *
- * @return an array of cipher suite names
- */
@Override
! public String[] getSupportedCipherSuites() {
! return sslContext.getSupportedCipherSuiteList().toStringArray();
}
- /**
- * Controls which particular cipher suites are enabled for use on
- * this connection. The cipher suites must have been listed by
- * getCipherSuites() as being supported. Even if a suite has been
- * enabled, it might never be used if no peer supports it or the
- * requisite certificates (and private keys) are not available.
- *
- * @param suites Names of all the cipher suites to enable.
- */
@Override
! public synchronized void setEnabledCipherSuites(String[] suites) {
! enabledCipherSuites = new CipherSuiteList(suites);
! if ((handshaker != null) && !handshaker.activated()) {
! handshaker.setEnabledCipherSuites(enabledCipherSuites);
}
}
- /**
- * Returns the names of the SSL cipher suites which are currently enabled
- * for use on this connection. When an SSL engine is first created,
- * all enabled cipher suites <em>(a)</em> protect data confidentiality,
- * by traffic encryption, and <em>(b)</em> can mutually authenticate
- * both clients and servers. Thus, in some environments, this value
- * might be empty.
- *
- * @return an array of cipher suite names
- */
@Override
! public synchronized String[] getEnabledCipherSuites() {
! return enabledCipherSuites.toStringArray();
}
- /**
- * Returns the protocols that are supported by this implementation.
- * A subset of the supported protocols may be enabled for this connection
- * @return an array of protocol names.
- */
@Override
! public String[] getSupportedProtocols() {
! return sslContext.getSuportedProtocolList().toStringArray();
}
- /**
- * Controls which protocols are enabled for use on
- * this connection. The protocols must have been listed by
- * getSupportedProtocols() as being supported.
- *
- * @param protocols protocols to enable.
- * @exception IllegalArgumentException when one of the protocols
- * named by the parameter is not supported.
- */
@Override
! public synchronized void setEnabledProtocols(String[] protocols) {
! enabledProtocols = new ProtocolList(protocols);
! if ((handshaker != null) && !handshaker.activated()) {
! handshaker.setEnabledProtocols(enabledProtocols);
}
}
@Override
! public synchronized String[] getEnabledProtocols() {
! return enabledProtocols.toStringArray();
}
- /**
- * Returns the SSLParameters in effect for this SSLEngine.
- */
@Override
! public synchronized SSLParameters getSSLParameters() {
! SSLParameters params = super.getSSLParameters();
!
! // the super implementation does not handle the following parameters
! params.setEndpointIdentificationAlgorithm(identificationProtocol);
! params.setAlgorithmConstraints(algorithmConstraints);
! params.setSNIMatchers(sniMatchers);
! params.setServerNames(serverNames);
! params.setUseCipherSuitesOrder(preferLocalCipherSuites);
! params.setEnableRetransmissions(enableRetransmissions);
! params.setMaximumPacketSize(maximumPacketSize);
! params.setApplicationProtocols(applicationProtocols);
! return params;
}
- /**
- * Applies SSLParameters to this engine.
- */
@Override
! public synchronized void setSSLParameters(SSLParameters params) {
! super.setSSLParameters(params);
! // the super implementation does not handle the following parameters
! identificationProtocol = params.getEndpointIdentificationAlgorithm();
! algorithmConstraints = params.getAlgorithmConstraints();
! preferLocalCipherSuites = params.getUseCipherSuitesOrder();
! enableRetransmissions = params.getEnableRetransmissions();
! maximumPacketSize = params.getMaximumPacketSize();
! if (maximumPacketSize != 0) {
! outputRecord.changePacketSize(maximumPacketSize);
! } else {
! // use the implicit maximum packet size.
! maximumPacketSize = outputRecord.getMaxPacketSize();
}
! List<SNIServerName> sniNames = params.getServerNames();
! if (sniNames != null) {
! serverNames = sniNames;
}
! Collection<SNIMatcher> matchers = params.getSNIMatchers();
! if (matchers != null) {
! sniMatchers = matchers;
}
- applicationProtocols = params.getApplicationProtocols();
! if ((handshaker != null) && !handshaker.activated()) {
! handshaker.setIdentificationProtocol(identificationProtocol);
! handshaker.setAlgorithmConstraints(algorithmConstraints);
! handshaker.setMaximumPacketSize(maximumPacketSize);
! handshaker.setApplicationProtocols(applicationProtocols);
! if (roleIsServer) {
! handshaker.setSNIMatchers(sniMatchers);
! handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites);
} else {
! handshaker.setSNIServerNames(serverNames);
}
}
}
@Override
! public synchronized String getApplicationProtocol() {
! return applicationProtocol;
}
! @Override
! public synchronized String getHandshakeApplicationProtocol() {
! if ((handshaker != null) && handshaker.started()) {
! return handshaker.getHandshakeApplicationProtocol();
}
- return null;
}
! @Override
! public synchronized void setHandshakeApplicationProtocolSelector(
! BiFunction<SSLEngine, List<String>, String> selector) {
! applicationProtocolSelector = selector;
! if ((handshaker != null) && !handshaker.activated()) {
! handshaker.setApplicationProtocolSelectorSSLEngine(selector);
}
}
! @Override
! public synchronized BiFunction<SSLEngine, List<String>, String>
! getHandshakeApplicationProtocolSelector() {
! return this.applicationProtocolSelector;
}
- /**
- * Returns a printable representation of this end of the connection.
- */
@Override
! public String toString() {
! StringBuilder retval = new StringBuilder(80);
!
! retval.append(Integer.toHexString(hashCode()));
! retval.append("[");
! retval.append("SSLEngine[hostname=");
! String host = getPeerHost();
! retval.append((host == null) ? "null" : host);
! retval.append(" port=");
! retval.append(Integer.toString(getPeerPort()));
! retval.append(" role=" + (roleIsServer ? "Server" : "Client"));
! retval.append("] ");
! retval.append(getSession().getCipherSuite());
! retval.append("]");
!
! return retval.toString();
}
}
--- 195,985 ----
*/
if (hsStatus == HandshakeStatus.NEED_TASK) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
! int dstsRemains = 0;
! for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
! dstsRemains += dsts[i].remaining();
! }
!
! // Check destination buffer size.
! //
! // We can be smarter about using smaller buffer sizes later. For
! // now, force it to be large enough to handle any valid record.
! if (dstsRemains < conContext.conSession.getPacketBufferSize()) {
! return new SSLEngineResult(
! Status.BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0);
}
!
! int srcsRemains = 0;
! for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
! srcsRemains += srcs[i].remaining();
}
Ciphertext ciphertext = null;
try {
! // Acquire the buffered to-be-delivered records or retransmissions.
! //
! // May have buffered records, or need retransmission if handshaking.
! if (!conContext.outputRecord.isEmpty() || (hc != null &&
! hc.sslConfig.enableRetransmissions &&
! hc.sslContext.isDTLS() &&
! hsStatus == HandshakeStatus.NEED_UNWRAP)) {
! ciphertext = encode(null, 0, 0,
! dsts, dstsOffset, dstsLength);
}
!
! if (ciphertext == null && srcsRemains != 0) {
! ciphertext = encode(srcs, srcsOffset, srcsLength,
! dsts, dstsOffset, dstsLength);
}
+ } catch (IOException ioe) {
+ if (ioe instanceof SSLException) {
+ throw ioe;
+ } else {
+ throw new SSLException("Write problems", ioe);
}
}
/*
! * Check for status.
*/
! Status status = (isOutboundDone() ? Status.CLOSED : Status.OK);
! if (ciphertext != null && ciphertext.handshakeStatus != null) {
! hsStatus = ciphertext.handshakeStatus;
! } else {
! hsStatus = getHandshakeStatus();
! }
! int deltaSrcs = srcsRemains;
! for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
! deltaSrcs -= srcs[i].remaining();
}
! int deltaDsts = dstsRemains;
! for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
! deltaDsts -= dsts[i].remaining();
}
! return new SSLEngineResult(status, hsStatus, deltaSrcs, deltaDsts,
! ciphertext != null ? ciphertext.recordSN : -1L);
! }
!
! private Ciphertext encode(
! ByteBuffer[] srcs, int srcsOffset, int srcsLength,
! ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
Ciphertext ciphertext = null;
try {
! ciphertext = conContext.outputRecord.encode(
! srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
} catch (SSLHandshakeException she) {
// may be record sequence number overflow
! conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
} catch (IOException e) {
! conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
}
if (ciphertext == null) {
return Ciphertext.CIPHERTEXT_NULL;
}
! // Is the handshake completed?
! boolean needRetransmission =
! conContext.sslContext.isDTLS() &&
! conContext.handshakeContext != null &&
! conContext.handshakeContext.sslConfig.enableRetransmissions;
! HandshakeStatus hsStatus =
! tryToFinishHandshake(ciphertext.contentType);
! if (needRetransmission &&
! hsStatus == HandshakeStatus.FINISHED &&
! conContext.sslContext.isDTLS() &&
! ciphertext.handshakeType == SSLHandshake.FINISHED.id) {
! // Retransmit the last flight for DTLS.
//
// The application data transactions may begin immediately
// after the last flight. If the last flight get lost, the
// application data may be discarded accordingly. As could
// be an issue for some applications. This impact can be
// mitigated by sending the last fligth twice.
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) {
! SSLLogger.finest("retransmit the last flight messages");
}
+ conContext.outputRecord.launchRetransmission();
hsStatus = HandshakeStatus.NEED_WRAP;
}
! if (hsStatus == null) {
! hsStatus = conContext.getHandshakeStatus();
}
! // Is the sequence number is nearly overflow?
! if (conContext.outputRecord.seqNumIsHuge()) {
! hsStatus = tryKeyUpdate(hsStatus);
}
+
+ // update context status
ciphertext.handshakeStatus = hsStatus;
return ciphertext;
}
! private HandshakeStatus tryToFinishHandshake(byte contentType) {
! HandshakeStatus hsStatus = null;
! if ((contentType == ContentType.HANDSHAKE.id) &&
! conContext.outputRecord.isEmpty()) {
! if (conContext.handshakeContext == null) {
! hsStatus = HandshakeStatus.FINISHED;
! } else if (conContext.handshakeContext.handshakeFinished) {
! hsStatus = conContext.finishHandshake();
}
+ } // Otherwise, the followed call to getHSStatus() will help.
! return hsStatus;
! }
/**
! * Try renegotiation or key update for sequence number wrap.
! *
! * Note that in order to maintain the handshake status properly, we check
! * the sequence number after the last record reading/writing process. As
! * we request renegotiation or close the connection for wrapped sequence
! * number when there is enough sequence number space left to handle a few
! * more records, so the sequence number of the last record cannot be
! * wrapped.
*/
! private HandshakeStatus tryKeyUpdate(
! HandshakeStatus currentHandshakeStatus) throws IOException {
! // Don't bother to kickstart the renegotiation or key update when the
! // local is asking for it.
! if ((conContext.handshakeContext == null) &&
! !conContext.isClosed() && !conContext.isBroken) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.finest("key update to wrap sequence number");
! }
! conContext.keyUpdate();
! return conContext.getHandshakeStatus();
}
! return currentHandshakeStatus;
}
! private static void checkParams(
! ByteBuffer[] srcs, int srcsOffset, int srcsLength,
! ByteBuffer[] dsts, int dstsOffset, int dstsLength) {
! if ((srcs == null) || (dsts == null)) {
! throw new IllegalArgumentException(
! "source or destination buffer is null");
}
! if ((srcsOffset < 0) || (srcsLength < 0) ||
! (srcsOffset > srcs.length - srcsLength)) {
! throw new IndexOutOfBoundsException(
! "index out of bound of the source buffers");
}
! if ((dstsOffset < 0) || (dstsLength < 0) ||
! (dstsOffset > dsts.length - dstsLength)) {
! throw new IndexOutOfBoundsException(
! "index out of bound of the destination buffers");
! }
! for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
! if (srcs[i] == null) {
! throw new IllegalArgumentException(
! "source buffer[" + i + "] == null");
}
}
! for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
! if (dsts[i] == null) {
! throw new IllegalArgumentException(
! "destination buffer[" + i + "] == null");
}
/*
! * Make sure the destination bufffers are writable.
*/
! if (dsts[i].isReadOnly()) {
! throw new ReadOnlyBufferException();
! }
}
}
@Override
! public synchronized SSLEngineResult unwrap(ByteBuffer src,
! ByteBuffer[] dsts, int offset, int length) throws SSLException {
! return unwrap(
! new ByteBuffer[]{src}, 0, 1, dsts, offset, length);
}
! // @Override
! public synchronized SSLEngineResult unwrap(
! ByteBuffer[] srcs, int srcsOffset, int srcsLength,
! ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException {
! if (conContext.isUnsureMode) {
! throw new IllegalStateException(
! "Client/Server mode has not yet been set.");
}
! // See if the handshaker needs to report back some SSLException.
! checkTaskThrown();
! // check parameters
! checkParams(srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
try {
! return readRecord(
! srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
! } catch (SSLProtocolException spe) {
! // may be an unexpected handshake message
! conContext.fatal(Alert.UNEXPECTED_MESSAGE,
! spe.getMessage(), spe);
} catch (IOException ioe) {
! /*
! * Don't reset position so it looks like we didn't
! * consume anything. We did consume something, and it
! * got us into this situation, so report that much back.
! * Our days of consuming are now over anyway.
! */
! conContext.fatal(Alert.INTERNAL_ERROR,
! "problem unwrapping net record", ioe);
! } catch (Exception ex) { // including RuntimeException
! conContext.fatal(Alert.INTERNAL_ERROR,
! "Fail to unwrap network record", ex);
}
! return null; // make compiler happy
}
+ private SSLEngineResult readRecord(
+ ByteBuffer[] srcs, int srcsOffset, int srcsLength,
+ ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
+
/*
! * Check if we are closing/closed.
*/
! if (isInboundDone()) {
! return new SSLEngineResult(
! Status.CLOSED, getHandshakeStatus(), 0, 0);
}
+ HandshakeStatus hsStatus = null;
+ if (!conContext.isNegotiated) {
+ conContext.kickstart();
+
/*
! * If there's still outbound data to flush, we
! * can return without trying to unwrap anything.
*/
! hsStatus = getHandshakeStatus();
! if (hsStatus == HandshakeStatus.NEED_WRAP) {
! return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
}
! if (hsStatus == null) {
! hsStatus = getHandshakeStatus();
}
! /*
! * If we have a task outstanding, this *MUST* be done before
! * doing any more unwrapping, because we could be in the middle
! * of receiving a handshake message, for example, a finished
! * message which would change the ciphers.
*/
! if (hsStatus == HandshakeStatus.NEED_TASK) {
! return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
! }
!
! if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
! Plaintext plainText = null;
! try {
! plainText = decode(null, 0, 0,
! dsts, dstsOffset, dstsLength);
! } catch (IOException ioe) {
! if (ioe instanceof SSLException) {
! throw ioe;
! } else {
! throw new SSLException("readRecord", ioe);
! }
}
! Status status = (isInboundDone() ? Status.CLOSED : Status.OK);
! if (plainText.handshakeStatus != null) {
! hsStatus = plainText.handshakeStatus;
! } else {
! hsStatus = getHandshakeStatus();
}
! return new SSLEngineResult(
! status, hsStatus, 0, 0, plainText.recordSN);
}
! int srcsRemains = 0;
! for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
! srcsRemains += srcs[i].remaining();
}
! if (srcsRemains == 0) {
! return new SSLEngineResult(Status.OK, getHandshakeStatus(), 0, 0);
! }
/*
! * Check the packet to make sure enough is here.
! * This will also indirectly check for 0 len packets.
*/
! int packetLen = 0;
! try {
! packetLen = conContext.inputRecord.bytesInCompletePacket(
! srcs, srcsOffset, srcsLength);
! } catch (SSLException ssle) {
! // Need to discard invalid records for DTLS protocols.
! if (sslContext.isDTLS()) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) {
! SSLLogger.finest("Discard invalid DTLS records", ssle);
}
! // invalid, discard the entire data [section 4.1.2.7, RFC 6347]
! // TODO
! int deltaNet = 0;
! // int deltaNet = netData.remaining();
! // netData.position(netData.limit());
! Status status = (isInboundDone() ? Status.CLOSED : Status.OK);
! if (hsStatus == null) {
! hsStatus = getHandshakeStatus();
}
! return new SSLEngineResult(status, hsStatus, deltaNet, 0, -1L);
! } else {
! throw ssle;
! }
}
! // Is this packet bigger than SSL/TLS normally allows?
! if (packetLen > conContext.conSession.getPacketBufferSize()) {
! int largestRecordSize = sslContext.isDTLS() ?
! DTLSRecord.maxRecordSize : SSLRecord.maxLargeRecordSize;
! if ((packetLen <= largestRecordSize) && !sslContext.isDTLS()) {
! // Expand the expected maximum packet/application buffer
! // sizes.
! //
! // Only apply to SSL/TLS protocols.
! // Old behavior: shall we honor the System Property
! // "jsse.SSLEngine.acceptLargeFragments" if it is "false"?
! conContext.conSession.expandBufferSizes();
! }
!
! // check the packet again
! largestRecordSize = conContext.conSession.getPacketBufferSize();
! if (packetLen > largestRecordSize) {
! throw new SSLProtocolException(
! "Input record too big: max = " +
! largestRecordSize + " len = " + packetLen);
}
}
/*
! * Check for OVERFLOW.
*
! * Delay enforcing the application buffer free space requirement
! * until after the initial handshaking.
*/
! int dstsRemains = 0;
! for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
! dstsRemains += dsts[i].remaining();
! }
!
! if (conContext.isNegotiated) {
! int FragLen =
! conContext.inputRecord.estimateFragmentSize(packetLen);
! if (FragLen > dstsRemains) {
! return new SSLEngineResult(
! Status.BUFFER_OVERFLOW, hsStatus, 0, 0);
}
}
! // check for UNDERFLOW.
! if ((packetLen == -1) || (srcsRemains < packetLen)) {
! return new SSLEngineResult(Status.BUFFER_UNDERFLOW, hsStatus, 0, 0);
}
/*
! * We're now ready to actually do the read.
*/
! Plaintext plainText = null;
try {
! plainText = decode(srcs, srcsOffset, srcsLength,
! dsts, dstsOffset, dstsLength);
} catch (IOException ioe) {
! if (ioe instanceof SSLException) {
! throw ioe;
! } else {
! throw new SSLException("readRecord", ioe);
}
}
/*
! * Check the various condition that we could be reporting.
! *
! * It's *possible* something might have happened between the
! * above and now, but it was better to minimally lock "this"
! * during the read process. We'll return the current
! * status, which is more representative of the current state.
! *
! * status above should cover: FINISHED, NEED_TASK
*/
! Status status = (isInboundDone() ? Status.CLOSED : Status.OK);
! if (plainText.handshakeStatus != null) {
! hsStatus = plainText.handshakeStatus;
} else {
! hsStatus = getHandshakeStatus();
}
! int deltaNet = srcsRemains;
! for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
! deltaNet -= srcs[i].remaining();
}
! int deltaApp = dstsRemains;
! for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
! deltaApp -= dsts[i].remaining();
}
! return new SSLEngineResult(
! status, hsStatus, deltaNet, deltaApp, plainText.recordSN);
}
! private Plaintext decode(
! ByteBuffer[] srcs, int srcsOffset, int srcsLength,
! ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
!
! Plaintext pt = SSLTransport.decode(conContext,
! srcs, srcsOffset, srcsLength,
! dsts, dstsOffset, dstsLength);
!
! // Is the handshake completed?
! if (pt != Plaintext.PLAINTEXT_NULL) {
! HandshakeStatus hsStatus = tryToFinishHandshake(pt.contentType);
! if (hsStatus == null) {
! pt.handshakeStatus = conContext.getHandshakeStatus();
} else {
! pt.handshakeStatus = hsStatus;
}
! // Is the sequence number is nearly overflow?
! if (conContext.inputRecord.seqNumIsHuge()) {
! pt.handshakeStatus =
! tryKeyUpdate(pt.handshakeStatus);
}
}
! return pt;
}
! @Override
! public synchronized Runnable getDelegatedTask() {
! if (conContext.handshakeContext != null &&
! !conContext.handshakeContext.taskDelegated &&
! !conContext.handshakeContext.delegatedActions.isEmpty()) {
! conContext.handshakeContext.taskDelegated = true;
! return new DelegatedTask(this);
}
! return null;
}
@Override
! public synchronized void closeInbound() throws SSLException {
! conContext.closeInbound();
}
@Override
! public synchronized boolean isInboundDone() {
! return conContext.isInboundDone();
}
@Override
! public synchronized void closeOutbound() {
! conContext.closeOutbound();
}
@Override
! public synchronized boolean isOutboundDone() {
! return conContext.isOutboundDone();
}
@Override
! public String[] getSupportedCipherSuites() {
! return CipherSuite.namesOf(sslContext.getSupportedCipherSuites());
}
@Override
! public synchronized String[] getEnabledCipherSuites() {
! return CipherSuite.namesOf(conContext.sslConfig.enabledCipherSuites);
}
@Override
! public synchronized void setEnabledCipherSuites(String[] suites) {
! if (suites == null) {
! throw new IllegalArgumentException("CipherSuites cannot be null");
}
! conContext.sslConfig.enabledCipherSuites =
! CipherSuite.validValuesOf(suites);
}
! @Override
! public String[] getSupportedProtocols() {
! return ProtocolVersion.toStringArray(
! sslContext.getSuportedProtocolVersions());
}
! @Override
! public synchronized String[] getEnabledProtocols() {
! return ProtocolVersion.toStringArray(
! conContext.sslConfig.enabledProtocols);
}
+
+ @Override
+ public synchronized void setEnabledProtocols(String[] protocols) {
+ if (protocols == null) {
+ throw new IllegalArgumentException("Protocols cannot be null");
}
! conContext.sslConfig.enabledProtocols =
! ProtocolVersion.namesOf(protocols);
}
! @Override
! public synchronized SSLSession getSession() {
! return conContext.conSession;
! }
! @Override
! public synchronized SSLSession getHandshakeSession() {
! return conContext.handshakeContext == null ?
! null : conContext.handshakeContext.handshakeSession;
}
! @Override
! public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
! return conContext.getHandshakeStatus();
}
+
+ @Override
+ public synchronized void setUseClientMode(boolean mode) {
+ conContext.setUseClientMode(mode);
}
@Override
public synchronized boolean getUseClientMode() {
! return conContext.sslConfig.isClientMode;
}
+ @Override
+ public synchronized void setNeedClientAuth(boolean need) {
+ conContext.sslConfig.clientAuthType =
+ (need ? ClientAuthType.CLIENT_AUTH_REQUIRED :
+ ClientAuthType.CLIENT_AUTH_NONE);
+ }
@Override
! public synchronized boolean getNeedClientAuth() {
! return (conContext.sslConfig.clientAuthType ==
! ClientAuthType.CLIENT_AUTH_REQUIRED);
}
@Override
! public synchronized void setWantClientAuth(boolean want) {
! conContext.sslConfig.clientAuthType =
! (want ? ClientAuthType.CLIENT_AUTH_REQUESTED :
! ClientAuthType.CLIENT_AUTH_NONE);
}
+
+ @Override
+ public synchronized boolean getWantClientAuth() {
+ return (conContext.sslConfig.clientAuthType ==
+ ClientAuthType.CLIENT_AUTH_REQUESTED);
}
@Override
! public synchronized void setEnableSessionCreation(boolean flag) {
! conContext.sslConfig.enableSessionCreation = flag;
}
+ @Override
+ public synchronized boolean getEnableSessionCreation() {
+ return conContext.sslConfig.enableSessionCreation;
+ }
@Override
! public synchronized SSLParameters getSSLParameters() {
! return conContext.sslConfig.getSSLParameters();
}
@Override
! public synchronized void setSSLParameters(SSLParameters params) {
! conContext.sslConfig.setSSLParameters(params);
!
! if (conContext.sslConfig.maximumPacketSize != 0) {
! conContext.outputRecord.changePacketSize(
! conContext.sslConfig.maximumPacketSize);
}
}
@Override
! public synchronized String getApplicationProtocol() {
! return conContext.applicationProtocol;
}
@Override
! public synchronized String getHandshakeApplicationProtocol() {
! return conContext.handshakeContext == null ?
! null : conContext.handshakeContext.applicationProtocol;
! }
! @Override
! public synchronized void setHandshakeApplicationProtocolSelector(
! BiFunction<SSLEngine, List<String>, String> selector) {
! conContext.sslConfig.engineAPSelector = selector;
}
@Override
! public synchronized BiFunction<SSLEngine, List<String>, String>
! getHandshakeApplicationProtocolSelector() {
! return conContext.sslConfig.engineAPSelector;
! }
! @Override
! public boolean useDelegatedTask() {
! return true;
! }
! private synchronized void checkTaskThrown() throws SSLException {
! HandshakeContext hc = conContext.handshakeContext;
! if (hc != null && hc.delegatedThrown != null) {
! try {
! throw getTaskThrown(hc.delegatedThrown);
! } finally {
! hc.delegatedThrown = null;
! }
}
! if (conContext.isBroken && conContext.closeReason != null) {
! throw getTaskThrown(conContext.closeReason);
}
+ }
+
+ private static SSLException getTaskThrown(Exception taskThrown) {
+ String msg = taskThrown.getMessage();
! if (msg == null) {
! msg = "Delegated task threw Exception or Error";
}
! if (taskThrown instanceof RuntimeException) {
! throw new RuntimeException(msg, taskThrown);
! } else if (taskThrown instanceof SSLHandshakeException) {
! return (SSLHandshakeException)
! new SSLHandshakeException(msg).initCause(taskThrown);
! } else if (taskThrown instanceof SSLKeyException) {
! return (SSLKeyException)
! new SSLKeyException(msg).initCause(taskThrown);
! } else if (taskThrown instanceof SSLPeerUnverifiedException) {
! return (SSLPeerUnverifiedException)
! new SSLPeerUnverifiedException(msg).initCause(taskThrown);
! } else if (taskThrown instanceof SSLProtocolException) {
! return (SSLProtocolException)
! new SSLProtocolException(msg).initCause(taskThrown);
! } else if (taskThrown instanceof SSLException) {
! return (SSLException)taskThrown;
} else {
! return new SSLException(msg, taskThrown);
}
}
+
+ /**
+ * Implement a simple task delegator.
+ */
+ private static class DelegatedTask implements Runnable {
+ private final SSLEngineImpl engine;
+
+ DelegatedTask(SSLEngineImpl engineInstance) {
+ this.engine = engineInstance;
}
@Override
! public void run() {
! synchronized (engine) {
! HandshakeContext hc = engine.conContext.handshakeContext;
! if (hc == null || hc.delegatedActions.isEmpty()) {
! return;
}
! try {
! AccessController.doPrivileged(
! new DelegatedAction(hc), engine.conContext.acc);
! } catch (PrivilegedActionException pae) {
! // Get the handshake context again in case the
! // handshaking has completed.
! hc = engine.conContext.handshakeContext;
! if (hc != null) {
! hc.delegatedThrown = pae.getException();
! } else if (engine.conContext.closeReason != null) {
! engine.conContext.closeReason =
! getTaskThrown(pae.getException());
! }
! } catch (RuntimeException rte) {
! // Get the handshake context again in case the
! // handshaking has completed.
! hc = engine.conContext.handshakeContext;
! if (hc != null) {
! hc.delegatedThrown = rte;
! } else if (engine.conContext.closeReason != null) {
! engine.conContext.closeReason = rte;
}
}
! // Get the handshake context again in case the
! // handshaking has completed.
! hc = engine.conContext.handshakeContext;
! if (hc != null) {
! hc.taskDelegated = false;
! }
}
}
! private static class DelegatedAction
! implements PrivilegedExceptionAction<Void> {
! final HandshakeContext context;
! DelegatedAction(HandshakeContext context) {
! this.context = context;
}
@Override
! public Void run() throws Exception {
! while (!context.delegatedActions.isEmpty()) {
! // Report back the task SSLException
! if (context.delegatedThrown != null) {
! Exception delegatedThrown = context.delegatedThrown;
! context.delegatedThrown = null;
! throw getTaskThrown(delegatedThrown);
! }
!
! Map.Entry<Byte, ByteBuffer> me =
! context.delegatedActions.poll();
! if (me != null) {
! context.dispatch(me.getKey(), me.getValue());
! }
! }
! return null;
! }
! }
}
}
< prev index next >