📄 sslchannel.java
字号:
/*
* Copyright 2004 WIT-Software, Lda.
* - web: http://www.wit-software.com
* - email: info@wit-software.com
*
* All rights reserved. Relased under terms of the
* Creative Commons' Attribution-NonCommercial-ShareAlike license.
*/
package ssl;
import handlers.Channel;
import handlers.ChannelListener;
import io.SelectorThread;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
/**
* @author Nuno Santos
*/
public class SSLChannel extends Channel {
private final static Logger log = Logger.getLogger("handlers");
// private final static Logger log = new Logger();
private final SSLSession session;
private final SSLEngine engine;
/** Application data decrypted from the data received from the peer.
* This buffer must have enough space for a full unwrap operation,
* so we can't use the buffer provided by the application, since we
* have no control over its size.
*/
private final ByteBuffer peerAppData;
/** Network data received from the peer. Encrypted. */
private final ByteBuffer peerNetData;
/** Network data to be sent to the peer. Encrypted. */
private final ByteBuffer netData;
/**
* Whether the listener is interested in read events
*/
private boolean appReadInterestSet = false;
/**
* Whether the listener is interested in write events
*/
private boolean appWriteInterestSet = false;
private boolean channelReadInterestSet = false;
private boolean channelWriteInterestSet = false;
/**
* Set to true during the initial handshake. The initial handshake
* is special since no application data can flow during it. Subsequent
* handshake are dealt with in a somewhat different way.
*/
private boolean initialHandshake = false;
private SSLEngineResult.HandshakeStatus hsStatus;
/** Used during handshake, for the operations that don't consume any data */
private ByteBuffer dummy;
private boolean shutdown = false;
private boolean closed = false;
/**
* Stores the result from the last operation performed by the SSLEngine
*/
private SSLEngineResult.Status status = null;
/**
* If an error occurs while processing a callback from the selector
* thread, the exception is saved in this field to be thrown to the
* application the next time it calls a public method of this class.
*/
private IOException asynchException = null;
/**
* @param st
* @param sc
* @param listener
* @throws Exception
*/
public SSLChannel(
SelectorThread st,
SocketChannel sc,
ChannelListener listener,
SSLEngine engine) throws Exception {
super(st, sc, listener);
this.engine = engine;
session = engine.getSession();
peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
netData = ByteBuffer.allocate(session.getPacketBufferSize());
log.fine("peerNetData: " + peerNetData.capacity() +
", peerAppData: " + peerAppData.capacity() +
", netData: " + netData.capacity());
// The peerNetData buffer is assumed to be ready to be written,
// while the other buffers are assumed to be ready to be read from.
// Change the position of the buffers so that a
// call to hasRemaining() returns false. A buffer is considered
// empty when the position is set to its limit, that is when
// hasRemaining() returns false.
peerAppData.position(peerAppData.limit());
netData.position(netData.limit());
st.registerChannelNow(sc, 0, this);
log.fine("Starting handshake");
engine.beginHandshake();
hsStatus = engine.getHandshakeStatus();
initialHandshake = true;
dummy = ByteBuffer.allocate(0);
doHandshake();
}
private void checkChannelStillValid() throws IOException {
if (closed) {
throw new ClosedChannelException();
}
if (asynchException != null) {
IOException ioe = new IOException(
"Asynchronous failure: " + asynchException.getMessage());
ioe.initCause(asynchException);
throw ioe;
}
}
public int read(ByteBuffer dst) throws IOException {
// log.fine("");
checkChannelStillValid();
if (initialHandshake) {
return 0;
}
// Perhaps we should always try to read some data from
// the socket. In some situations, it might not be possible
// to unwrap some of the data stored on the buffers before
// reading more.
// Check if the stream is closed.
if (engine.isInboundDone()) {
// We reached EOF.
return -1;
}
// First check if there is decrypted data waiting in the buffers
if (!peerAppData.hasRemaining()) {
int appBytesProduced = readAndUnwrap();
if (appBytesProduced == -1 || appBytesProduced == 0) {
return appBytesProduced;
}
}
// It's not certain that we will have some data decrypted ready to
// be sent to the application. Anyway, copy as much data as possible
int limit = Math.min(peerAppData.remaining(), dst.remaining());
for (int i = 0; i < limit; i++) {
dst.put(peerAppData.get());
}
return limit;
}
private int readAndUnwrap() throws IOException {
assert !peerAppData.hasRemaining() : "Application buffer not empty";
// No decrypted data left on the buffers.
// Try to read from the socket. There may be some data
// on the peerNetData buffer, but it might not be sufficient.
int bytesRead = sc.read(peerNetData);
log.fine("Read from socket: " + bytesRead);
if (bytesRead == -1) {
// We will not receive any more data. Closing the engine
// is a signal that the end of stream was reached.
engine.closeInbound();
// EOF. But do we still have some useful data available?
if (peerNetData.position() == 0 ||
status == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
// Yup. Either the buffer is empty or it's in underflow,
// meaning that there is not enough data to reassemble a
// TLS packet. So we can return EOF.
return -1;
}
// Although we reach EOF, we still have some data left to
// be decrypted. We must process it
}
// Prepare the application buffer to receive decrypted data
peerAppData.clear();
// Prepare the net data for reading.
peerNetData.flip();
SSLEngineResult res;
do {
res = engine.unwrap(peerNetData, peerAppData);
log.info("Unwrapping:\n" + res);
// During an handshake renegotiation we might need to perform
// several unwraps to consume the handshake data.
} while (res.getStatus() == SSLEngineResult.Status.OK &&
res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
res.bytesProduced() == 0);
// If the initial handshake finish after an unwrap, we must activate
// the application interestes, if any were set during the handshake
if (res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
finishInitialHandshake();
}
// If no data was produced, and the status is still ok, try to read once more
if (peerAppData.position() == 0 &&
res.getStatus() == SSLEngineResult.Status.OK &&
peerNetData.hasRemaining()) {
res = engine.unwrap(peerNetData, peerAppData);
log.info("Unwrapping:\n" + res);
}
/*
* The status may be:
* OK - Normal operation
* OVERFLOW - Should never happen since the application buffer is
* sized to hold the maximum packet size.
* UNDERFLOW - Need to read more data from the socket. It's normal.
* CLOSED - The other peer closed the socket. Also normal.
*/
status = res.getStatus();
hsStatus = res.getHandshakeStatus();
// Should never happen, the peerAppData must always have enough space
// for an unwrap operation
assert status != SSLEngineResult.Status.BUFFER_OVERFLOW :
"Buffer should not overflow: " + res.toString();
// The handshake status here can be different than NOT_HANDSHAKING
// if the other peer closed the connection. So only check for it
// after testing for closure.
if (status == SSLEngineResult.Status.CLOSED) {
log.fine("Connection is being closed by peer.");
shutdown = true;
doShutdown();
return -1;
}
// Prepare the buffer to be written again.
peerNetData.compact();
// And the app buffer to be read.
peerAppData.flip();
if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
hsStatus == SSLEngineResult.HandshakeStatus.FINISHED)
{
log.fine("Rehandshaking...");
doHandshake();
}
return peerAppData.remaining();
}
/**
*
*/
public int write(ByteBuffer src) throws IOException {
checkChannelStillValid();
if (initialHandshake) {
log.fine("Writing not possible during handshake");
// Not ready to write
return 0;
}
log.fine("Trying to write");
// First, check if we still have some data waiting to be sent.
if (netData.hasRemaining()) {
// There is. Don't try to send it. We should be registered
// waiting for a write event from the selector thread
assert channelWriteInterestSet : "Write interest should be active" + netData;
return 0;
}
assert !channelWriteInterestSet : "Write interest should not be active";
// There is no data left to be sent. Clear the buffer and get
// ready to encrypt more data.
netData.clear();
SSLEngineResult res = engine.wrap(src, netData);
log.info("Wrapping:\n" + res);
// Prepare the buffer for reading
netData.flip();
flushData();
// Return the number of bytes read
// from the source buffer
return res.bytesConsumed();
}
/**
* This method may result in a read attempt from the socket.
*/
public void registerForRead() throws IOException {
checkChannelStillValid();
if (!appReadInterestSet) {
appReadInterestSet = true;
if (initialHandshake) {
// Wait for handshake to finish
return;
} else {
if (peerAppData.hasRemaining()) {
// There is decrypted data available, so prepare
// to fire the read event to the application
st.getSscManager().registerForRead(this);
} else {
// There is no decrypted data. But there may be some
// encrypted data.
if (peerNetData.position() == 0 ||
status == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
// Must read more data since either there is no encrypted
// data available or there is data available but not
// enough to reassemble a packet.
selectorRegisterForRead();
} else {
// There is encrypted data available. It may or may not
// be enough to reassemble a full packet. We have to check it.
if (readAndUnwrap() == 0) {
// Not possible to reassemble a full packet.
selectorRegisterForRead();
} else {
// either EOF or there is application data ready. In both
// cases we must inform the application
st.getSscManager().registerForRead(this);
}
}
}
}
}
}
public void unregisterForRead() throws IOException {
checkChannelStillValid();
appReadInterestSet = false;
st.getSscManager().unregisterForRead(this);
}
/**
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -