/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.tomcat.util.net;
/** * Flushes the buffer to the network, non blocking * @param buf ByteBuffer * @return boolean true if the buffer has been emptied out, false otherwise * @throws IOException An IO error occurred writing data
*/ protectedboolean flush(ByteBuffer buf) throws IOException { int remaining = buf.remaining(); if (remaining > 0) { return (sc.write(buf) >= remaining);
} else { returntrue;
}
}
/** * Performs SSL handshake, non blocking, but performs NEED_TASK on the same * thread. Hence, you should never call this method using your Acceptor * thread, as you would slow down your system significantly. If the return * value from this method is positive, the selection key should be * registered interestOps given by the return value. * * @param read boolean - true if the underlying channel is readable * @param write boolean - true if the underlying channel is writable * * @return 0 if hand shake is complete, -1 if an error (other than an * IOException) occurred, otherwise it returns a SelectionKey * interestOps value * * @throws IOException If an I/O error occurs during the handshake or if the * handshake fails during wrapping or unwrapping
*/
@Override publicint handshake(boolean read, boolean write) throws IOException { if (handshakeComplete) { return 0; //we have done our initial handshake
}
if (!sniComplete) { int sniResult = processSNI(); if (sniResult == 0) {
sniComplete = true;
} else { return sniResult;
}
}
if (!flush(netOutBuffer)) { return SelectionKey.OP_WRITE; //we still have data to write
}
SSLEngineResult handshake = null;
while (!handshakeComplete) { switch (handshakeStatus) { case NOT_HANDSHAKING: //should never happen thrownew IOException(sm.getString("channel.nio.ssl.notHandshaking")); case FINISHED: if (endpoint.hasNegotiableProtocols()) { if (sslEngine instanceof SSLUtil.ProtocolInfo) {
socketWrapper.setNegotiatedProtocol(
((SSLUtil.ProtocolInfo) sslEngine).getNegotiatedProtocol());
} else {
socketWrapper.setNegotiatedProtocol(sslEngine.getApplicationProtocol());
}
} //we are complete if we have delivered the last package
handshakeComplete = !netOutBuffer.hasRemaining(); //return 0 if we are complete, otherwise we still have data to write return handshakeComplete ? 0 : SelectionKey.OP_WRITE; case NEED_WRAP: //perform the wrap function try {
handshake = handshakeWrap(write);
} catch (SSLException e) {
handshake = handshakeWrap(write); throw e;
} if (handshake.getStatus() == Status.OK) { if (handshakeStatus == HandshakeStatus.NEED_TASK) {
handshakeStatus = tasks();
}
} elseif (handshake.getStatus() == Status.CLOSED) {
flush(netOutBuffer); return -1;
} else { //wrap should always work with our buffers thrownew IOException(sm.getString("channel.nio.ssl.unexpectedStatusDuringWrap", handshake.getStatus()));
} if (handshakeStatus != HandshakeStatus.NEED_UNWRAP || (!flush(netOutBuffer))) { //should actually return OP_READ if we have NEED_UNWRAP return SelectionKey.OP_WRITE;
} //fall down to NEED_UNWRAP on the same call, will result in a //BUFFER_UNDERFLOW if it needs data //$FALL-THROUGH$ case NEED_UNWRAP: //perform the unwrap function
handshake = handshakeUnwrap(read); if (handshake.getStatus() == Status.OK) { if (handshakeStatus == HandshakeStatus.NEED_TASK) {
handshakeStatus = tasks();
}
} elseif ( handshake.getStatus() == Status.BUFFER_UNDERFLOW ){ //read more data, reregister for OP_READ return SelectionKey.OP_READ;
} else { thrownew IOException(sm.getString("channel.nio.ssl.unexpectedStatusDuringWrap", handshake.getStatus()));
} break; case NEED_TASK:
handshakeStatus = tasks(); break; default: thrownew IllegalStateException(sm.getString("channel.nio.ssl.invalidStatus", handshakeStatus));
}
} // Handshake is complete if this point is reached return 0;
}
/* * Peeks at the initial network bytes to determine if the SNI extension is * present and, if it is, what host name has been requested. Based on the * provided host name, configure the SSLEngine for this connection. * * @return 0 if SNI processing is complete, -1 if an error (other than an * IOException) occurred, otherwise it returns a SelectionKey * interestOps value * * @throws IOException If an I/O error occurs during the SNI processing
*/ privateint processSNI() throws IOException { // Read some data into the network input buffer so we can peek at it. int bytesRead = sc.read(netInBuffer); if (bytesRead == -1) { // Reached end of stream before SNI could be processed. return -1;
}
TLSClientHelloExtractor extractor = new TLSClientHelloExtractor(netInBuffer);
while (extractor.getResult() == ExtractorResult.UNDERFLOW &&
netInBuffer.capacity() < endpoint.getSniParseLimit()) { // extractor needed more data to process but netInBuffer was full so // expand the buffer and read some more data. int newLimit = Math.min(netInBuffer.capacity() * 2, endpoint.getSniParseLimit());
log.info(sm.getString("channel.nio.ssl.expandNetInBuffer",
Integer.toString(newLimit)));
netInBuffer = ByteBufferUtils.expand(netInBuffer, newLimit); if (sc.read(netInBuffer) < 0) { return -1;
}
extractor = new TLSClientHelloExtractor(netInBuffer);
}
String hostName = null;
List<Cipher> clientRequestedCiphers = null;
List<String> clientRequestedApplicationProtocols = null; switch (extractor.getResult()) { case COMPLETE:
hostName = extractor.getSNIValue();
clientRequestedApplicationProtocols =
extractor.getClientRequestedApplicationProtocols(); //$FALL-THROUGH$ to set the client requested ciphers case NOT_PRESENT:
clientRequestedCiphers = extractor.getClientRequestedCiphers(); break; case NEED_READ: return SelectionKey.OP_READ; case UNDERFLOW: // Unable to buffer enough data to read SNI extension data if (log.isDebugEnabled()) {
log.debug(sm.getString("channel.nio.ssl.sniDefault"));
}
hostName = endpoint.getDefaultSSLHostConfigName();
clientRequestedCiphers = Collections.emptyList(); break; case NON_SECURE:
netOutBuffer.clear();
netOutBuffer.put(TLSClientHelloExtractor.USE_TLS_RESPONSE);
netOutBuffer.flip();
flushOutbound(); thrownew IOException(sm.getString("channel.nio.ssl.foundHttp"));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("channel.nio.ssl.sniHostName", sc, hostName));
}
// Populate additional TLS attributes obtained from the handshake that // aren't available from the session
additionalTlsAttributes.put(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY,
extractor.getClientRequestedProtocols());
additionalTlsAttributes.put(SSLSupport.REQUESTED_CIPHERS_KEY,
extractor.getClientRequestedCipherNames());
// Ensure the application buffers (which have to be created earlier) are // big enough.
getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize()); if (netOutBuffer.capacity() < sslEngine.getSession().getApplicationBufferSize()) { // Info for now as we may need to increase DEFAULT_NET_BUFFER_SIZE
log.info(sm.getString("channel.nio.ssl.expandNetOutBuffer",
Integer.toString(sslEngine.getSession().getApplicationBufferSize())));
}
netInBuffer = ByteBufferUtils.expand(netInBuffer, sslEngine.getSession().getPacketBufferSize());
netOutBuffer = ByteBufferUtils.expand(netOutBuffer, sslEngine.getSession().getPacketBufferSize());
// Set limit and position to expected values
netOutBuffer.position(0);
netOutBuffer.limit(0);
/** * Force a blocking handshake to take place for this key. * This requires that both network and application buffers have been emptied out prior to this call taking place, or a * IOException will be thrown. * @param timeout - timeout in milliseconds for each socket operation * @throws IOException - if an IO exception occurs or if application or network buffers contain data * @throws SocketTimeoutException - if a socket operation timed out
*/
@SuppressWarnings("null") // key cannot be null publicvoid rehandshake(long timeout) throws IOException { //validate the network buffers are empty if (netInBuffer.position() > 0 && netInBuffer.position() < netInBuffer.limit()) { thrownew IOException(sm.getString("channel.nio.ssl.netInputNotEmpty"));
} if (netOutBuffer.position() > 0 && netOutBuffer.position() < netOutBuffer.limit()) { thrownew IOException(sm.getString("channel.nio.ssl.netOutputNotEmpty"));
} if (!getBufHandler().isReadBufferEmpty()) { thrownew IOException(sm.getString("channel.nio.ssl.appInputNotEmpty"));
} if (!getBufHandler().isWriteBufferEmpty()) { thrownew IOException(sm.getString("channel.nio.ssl.appOutputNotEmpty"));
}
handshakeComplete = false; boolean isReadable = false; boolean isWritable = false; boolean handshaking = true;
Selector selector = null;
SelectionKey key = null; try {
sslEngine.beginHandshake();
handshakeStatus = sslEngine.getHandshakeStatus(); while (handshaking) { int hsStatus = this.handshake(isReadable, isWritable); switch (hsStatus) { case -1 : thrownew EOFException(sm.getString("channel.nio.ssl.eofDuringHandshake")); case 0 :
handshaking = false; break; default : long now = System.currentTimeMillis(); if (selector == null) {
selector = Selector.open();
key = getIOChannel().register(selector, hsStatus);
} else {
key.interestOps(hsStatus); // null warning suppressed
} int keyCount = selector.select(timeout); if (keyCount == 0 && ((System.currentTimeMillis()-now) >= timeout)) { thrownew SocketTimeoutException(sm.getString("channel.nio.ssl.timeoutDuringHandshake"));
}
isReadable = key.isReadable();
isWritable = key.isWritable();
}
}
} catch (IOException x) {
closeSilently(); throw x;
} catch (Exception cx) {
closeSilently();
IOException x = new IOException(cx); throw x;
} finally { if (key != null) { try {
key.cancel();
} catch (Exception ignore) {
}
} if (selector != null) { try {
selector.close();
} catch (Exception ignore) {
}
}
}
}
/** * Executes all the tasks needed on the same thread. * @return the status
*/ protected SSLEngineResult.HandshakeStatus tasks() {
Runnable r = null; while ((r = sslEngine.getDelegatedTask()) != null) {
r.run();
} return sslEngine.getHandshakeStatus();
}
/** * Performs the WRAP function * @param doWrite boolean * @return the result * @throws IOException An IO error occurred
*/ protected SSLEngineResult handshakeWrap(boolean doWrite) throws IOException { //this should never be called with a network buffer that contains data //so we can clear it here.
netOutBuffer.clear(); //perform the wrap
getBufHandler().configureWriteBufferForRead();
SSLEngineResult result = sslEngine.wrap(getBufHandler().getWriteBuffer(), netOutBuffer); //prepare the results to be written
netOutBuffer.flip(); //set the status
handshakeStatus = result.getHandshakeStatus(); //optimization, if we do have a writable channel, write it now if (doWrite) {
flush(netOutBuffer);
} return result;
}
/** * Perform handshake unwrap * @param doread boolean * @return the result * @throws IOException An IO error occurred
*/ protected SSLEngineResult handshakeUnwrap(boolean doread) throws IOException {
if (doread) { //if we have data to read, read it int read = sc.read(netInBuffer); if (read == -1) { thrownew IOException(sm.getString("channel.nio.ssl.eofDuringHandshake"));
}
}
SSLEngineResult result; boolean cont = false; //loop while we can perform pure SSLEngine data do { //prepare the buffer with the incoming data
netInBuffer.flip(); //call unwrap
getBufHandler().configureReadBufferForWrite();
result = sslEngine.unwrap(netInBuffer, getBufHandler().getReadBuffer()); /* * ByteBuffer.compact() is an optional method but netInBuffer is created from either ByteBuffer.allocate() * or ByteBuffer.allocateDirect() and the ByteBuffers returned by those methods do implement compact(). * The ByteBuffer must be in 'read from' mode when compact() is called and will be in 'write to' mode * afterwards.
*/
netInBuffer.compact(); //read in the status
handshakeStatus = result.getHandshakeStatus(); if (result.getStatus() == SSLEngineResult.Status.OK &&
result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { //execute tasks if we need to
handshakeStatus = tasks();
} //perform another unwrap?
cont = result.getStatus() == SSLEngineResult.Status.OK &&
handshakeStatus == HandshakeStatus.NEED_UNWRAP;
} while (cont); return result;
}
public SSLSupport getSSLSupport() { if (sslEngine != null) {
SSLSession session = sslEngine.getSession(); return endpoint.getSslImplementation().getSSLSupport(session, additionalTlsAttributes);
} returnnull;
}
/** * Sends an SSL close message, will not physically close the connection here. * <br>To close the connection, you could do something like * <pre><code> * close(); * while (isOpen() && !myTimeoutFunction()) Thread.sleep(25); * if ( isOpen() ) close(true); //forces a close if you timed out * </code></pre> * @throws IOException if an I/O error occurs * @throws IOException if there is data on the outgoing network buffer and * we are unable to flush it
*/
@Override publicvoid close() throws IOException { if (closing) { return;
}
closing = true; if (sslEngine == null) {
netOutBuffer.clear();
closed = true; return;
}
sslEngine.closeOutbound();
if (!flush(netOutBuffer)) { thrownew IOException(sm.getString("channel.nio.ssl.remainingDataDuringClose"));
} //prep the buffer for the close message
netOutBuffer.clear(); //perform the close, since we called sslEngine.closeOutbound
SSLEngineResult handshake = sslEngine.wrap(getEmptyBuf(), netOutBuffer); //we should be in a close state if (handshake.getStatus() != SSLEngineResult.Status.CLOSED) { thrownew IOException(sm.getString("channel.nio.ssl.invalidCloseState"));
} //prepare the buffer for writing
netOutBuffer.flip(); //if there is data to be written
flush(netOutBuffer);
privatevoid closeSilently() { try {
close(true);
} catch (IOException ioe) { // This is expected - swallowing the exception is the reason this // method exists. Log at debug in case someone is interested.
log.debug(sm.getString("channel.nio.ssl.closeSilentError"), ioe);
}
}
/** * Reads a sequence of bytes from this channel into the given buffer. * * @param dst The buffer into which bytes are to be transferred * @return The number of bytes read, possibly zero, or <code>-1</code> if * the channel has reached end-of-stream * @throws IOException If some other I/O error occurs * @throws IllegalArgumentException if the destination buffer is different * than getBufHandler().getReadBuffer()
*/
@Override publicint read(ByteBuffer dst) throws IOException { //are we in the middle of closing or closed? if (closing || closed) { return -1;
} //did we finish our handshake? if (!handshakeComplete) { thrownew IllegalStateException(sm.getString("channel.nio.ssl.incompleteHandshake"));
}
//read from the network int netread = sc.read(netInBuffer); //did we reach EOF? if so send EOF up one layer. if (netread == -1) { return -1;
}
//the data read int read = 0; //the SSL engine result
SSLEngineResult unwrap; do { //prepare the buffer
netInBuffer.flip(); //unwrap the data
unwrap = sslEngine.unwrap(netInBuffer, dst); //compact the buffer
netInBuffer.compact();
if (unwrap.getStatus() == Status.OK || unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { //we did receive some data, add it to our total
read += unwrap.bytesProduced(); //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
tasks();
} //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { break;
}
} elseif (unwrap.getStatus() == Status.BUFFER_OVERFLOW) { if (read > 0) { // Buffer overflow can happen if we have read data. Return // so the destination buffer can be emptied before another // read is attempted break;
} else { // The SSL session has increased the required buffer size // since the buffer was created. if (dst == getBufHandler().getReadBuffer()) { // This is the normal case for this code
getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
dst = getBufHandler().getReadBuffer();
} elseif (getAppReadBufHandler() != null && dst == getAppReadBufHandler().getByteBuffer()) {
getAppReadBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
dst = getAppReadBufHandler().getByteBuffer();
} else { // Can't expand the buffer as there is no way to signal // to the caller that the buffer has been replaced. thrownew IOException(
sm.getString("channel.nio.ssl.unwrapFailResize", unwrap.getStatus()));
}
}
} elseif (unwrap.getStatus() == Status.CLOSED && netInBuffer.position() == 0 && read > 0) { // Clean TLS close on input side but there is application data // to process. Can't tell if the client closed the connection // mid-request or if the client is performing a half-close after // a complete request. Assume it is a half-close and allow // processing to continue. If the connection has been closed // mid-request then the next attempt to read will trigger an // EOF.
} else { // Something else went wrong thrownew IOException(sm.getString("channel.nio.ssl.unwrapFail", unwrap.getStatus()));
}
} while (netInBuffer.position() != 0); //continue to unwrapping as long as the input buffer has stuff return read;
}
@Override publiclong read(ByteBuffer[] dsts, int offset, int length) throws IOException { //are we in the middle of closing or closed? if (closing || closed) { return -1;
} //did we finish our handshake? if (!handshakeComplete) { thrownew IllegalStateException(sm.getString("channel.nio.ssl.incompleteHandshake"));
}
//read from the network int netread = sc.read(netInBuffer); //did we reach EOF? if so send EOF up one layer. if (netread == -1) { return -1;
}
//the data read int read = 0; //the SSL engine result
SSLEngineResult unwrap;
OverflowState overflowState = OverflowState.NONE; do { if (overflowState == OverflowState.PROCESSING) {
overflowState = OverflowState.DONE;
} //prepare the buffer
netInBuffer.flip(); //unwrap the data
unwrap = sslEngine.unwrap(netInBuffer, dsts, offset, length); //compact the buffer
netInBuffer.compact();
if (unwrap.getStatus() == Status.OK || unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { //we did receive some data, add it to our total
read += unwrap.bytesProduced(); if (overflowState == OverflowState.DONE) { // Remove the data read into the overflow buffer
read -= getBufHandler().getReadBuffer().position();
} //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
tasks();
} //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { break;
}
} elseif (unwrap.getStatus() == Status.BUFFER_OVERFLOW) { if (read > 0) { // Buffer overflow can happen if we have read data. Return // so the destination buffer can be emptied before another // read is attempted break;
} else {
ByteBuffer readBuffer = getBufHandler().getReadBuffer(); boolean found = false; boolean resized = true; for (int i = 0; i < length; i++) { // The SSL session has increased the required buffer size // since the buffer was created. if (dsts[offset + i] == getBufHandler().getReadBuffer()) {
getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize()); if (dsts[offset + i] == getBufHandler().getReadBuffer()) {
resized = false;
}
dsts[offset + i] = getBufHandler().getReadBuffer();
found = true;
} elseif (getAppReadBufHandler() != null && dsts[offset + i] == getAppReadBufHandler().getByteBuffer()) {
getAppReadBufHandler().expand(sslEngine.getSession().getApplicationBufferSize()); if (dsts[offset + i] == getAppReadBufHandler().getByteBuffer()) {
resized = false;
}
dsts[offset + i] = getAppReadBufHandler().getByteBuffer();
found = true;
}
} if (found) { if (!resized) { thrownew IOException(sm.getString("channel.nio.ssl.unwrapFail", unwrap.getStatus()));
}
} else { // Add the main read buffer in the destinations and try again
ByteBuffer[] dsts2 = new ByteBuffer[dsts.length + 1]; int dstOffset = 0; for (int i = 0; i < dsts.length + 1; i++) { if (i == offset + length) {
dsts2[i] = readBuffer;
dstOffset = -1;
} else {
dsts2[i] = dsts[i + dstOffset];
}
}
dsts = dsts2;
length++;
getBufHandler().configureReadBufferForWrite();
overflowState = OverflowState.PROCESSING;
}
}
} else { // Something else went wrong thrownew IOException(sm.getString("channel.nio.ssl.unwrapFail", unwrap.getStatus()));
}
} while ((netInBuffer.position() != 0 || overflowState == OverflowState.PROCESSING) &&
overflowState != OverflowState.DONE); return read;
}
/** * Writes a sequence of bytes to this channel from the given buffer. * * @param src The buffer from which bytes are to be retrieved * @return The number of bytes written, possibly zero * @throws IOException If some other I/O error occurs
*/
@Override publicint write(ByteBuffer src) throws IOException {
checkInterruptStatus(); if (src == this.netOutBuffer) { int written = sc.write(src); return written;
} else { // Are we closing or closed? if (closing || closed) { thrownew IOException(sm.getString("channel.nio.ssl.closing"));
}
if (!flush(netOutBuffer)) { // We haven't emptied out the buffer yet return 0;
}
if (!src.hasRemaining()) { // Nothing left to write return 0;
}
// The data buffer is empty, we can reuse the entire buffer.
netOutBuffer.clear();
SSLEngineResult result = sslEngine.wrap(src, netOutBuffer); // The number of bytes written int written = result.bytesConsumed();
netOutBuffer.flip();
if (result.getStatus() == Status.OK) { if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
tasks();
}
} else { thrownew IOException(sm.getString("channel.nio.ssl.wrapFail", result.getStatus()));
}
// Force a flush
flush(netOutBuffer);
return written;
}
}
@Override publiclong write(ByteBuffer[] srcs, int offset, int length) throws IOException {
checkInterruptStatus(); // Are we closing or closed? if (closing || closed) { thrownew IOException(sm.getString("channel.nio.ssl.closing"));
}
if (!flush(netOutBuffer)) { // We haven't emptied out the buffer yet return 0;
}
// The data buffer is empty, we can reuse the entire buffer.
netOutBuffer.clear();
SSLEngineResult result = sslEngine.wrap(srcs, offset, length, netOutBuffer); // The number of bytes written int written = result.bytesConsumed();
netOutBuffer.flip();
if (result.getStatus() == Status.OK) { if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
tasks();
}
} else { thrownew IOException(sm.getString("channel.nio.ssl.wrapFail", result.getStatus()));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.