当我在Java应用程序上运行客户端握手过程以建立SSL连接时,在第二次调用wrap方法时得到SSLException
。我了解到这一点,客户端将CLientKeyExchange
和ChangeCipherSpec
发送到服务器。我从异常中得到的错误消息是“常规SSLEngine问题”。也许我需要发送的证书有问题?
这是异常堆栈的详细信息:
Details: General SSLEngine problem
Trace detail #0: com.sun.net.ssl.internal.ssl.Handshaker.checkThrown(Handshaker.java:994)
Trace detail #1: com.sun.net.ssl.internal.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:459)
Trace detail #2: com.sun.net.ssl.internal.ssl.SSLEngineImpl.writeAppRecord(SSLEngineImpl.java:1058)
Trace detail #3: com.sun.net.ssl.internal.ssl.SSLEngineImpl.wrap(SSLEngineImpl.java:1030)
Trace detail #4: javax.net.ssl.SSLEngine.wrap(SSLEngine.java:411)
Trace detail #5: epic.clarity.extract.CRSslSocketHandler.doHandshake(CRSslSocketHandler.java:333)
Trace detail #6: epic.clarity.extract.CRSslSocketHandler.readAndUnwrap(CRSslSocketHandler.java:282)
Trace detail #7: epic.clarity.extract.CRSslSocketHandler.doHandshake(CRSslSocketHandler.java:322)
Trace detail #8: epic.clarity.extract.CRSslSocketHandler.readAndUnwrap(CRSslSocketHandler.java:282)
Trace detail #9: epic.clarity.extract.CRSslSocketHandler.doHandshake(CRSslSocketHandler.java:322)
Trace detail #10: epic.clarity.extract.CRSslSocketHandler.ReadExtFile(CRSslSocketHandler.java:159)
这是我的代码,握手从
Read
方法开始:public class CRSslSocketHandler extends CRSocketHandler{
private SSLEngine _engine;
private SSLEngineResult.HandshakeStatus hsStatus;
/**
* Stores the result from the last operation performed by the SSLEngine
*/
private SSLEngineResult.Status status = null;
/** 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;
/** Used during handshake, for the operations that don't consume any data */
private ByteBuffer dummy;
private boolean initialHandshake = false;
private final String[] AvailProtocol = {"TLSv1"};
private boolean FirstTime = true;
//Debug
private BufferedWriter debugFile;
private BufferedWriter test;
/** Creates a new instance of CRSslSocketHandler */
public CRSslSocketHandler(SocketChannel channel, DualKeyHashtable queryCollection, DualKeyHashtable routineCollection, long sn, String hostName, int portNum) throws Exception {
super(channel, queryCollection, routineCollection, sn);
// Debug purposes
debugFile = new BufferedWriter(new FileWriter("SslDebug.txt",true));
test = new BufferedWriter(new FileWriter("ssltest.txt",true));
try{
System.out.println("Create Ssl Context");
test.write("Create Ssl Context");
SSLContext sslContext = SSLContext.getInstance("TLSv1");
System.out.println("Init Ssl Context");
test.write("Init Ssl Context");
/String Pass = "password";
char[] passphrase = Pass.toCharArray();
System.out.println("Get keys");
test.write("Get keys");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("keystore.ks"), passphrase);
System.out.println("Get trust");
test.write("Get trust");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
System.out.println("Init trust");
test.write("Init trust");
tmf.init(ks);
System.out.println("Init context");
test.write("Init context");
sslContext.init(null, tmf.getTrustManagers(), null);
System.out.println("Create engine host name: " + hostName + " port number: " + portNum);
test.write("Create engine host name: " + hostName + " port number: " + portNum);
_engine = sslContext.createSSLEngine(hostName, portNum);
// _engine = sslContext.createSSLEngine();
System.out.println("Set client mode");
test.write("Set client mode");
_engine.setUseClientMode(true);
_engine.setEnabledProtocols(AvailProtocol);
test.write("Begin Hanshaking");
System.out.println("Begin Hanshaking");
_engine.beginHandshake();
hsStatus = _engine.getHandshakeStatus();
}
catch (IOException e){
}
catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
test.write("Exception: " + e.getMessage());
test.close();
}
System.out.println("Alocate buffers");
test.write("Alocate buffers");
SSLSession session = _engine.getSession();
peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
netData = ByteBuffer.allocate(session.getPacketBufferSize());
// 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());
char c = (char)(28);
String msg = "OK" + c;
dummy = ByteBuffer.wrap(msg.getBytes());
// dummy = ByteBuffer.allocate(0);
initialHandshake = true;
test.close();
}
protected int ReadExtFile(ByteBuffer buffer) throws IOException{
if (initialHandshake) {
doHandshake();
}
// Check if the stream is closed.
if (_engine.isInboundDone()) {
// We reached EOF.
return EOF;
}
// First check if there is decrypted data waiting in the buffers
if (!peerAppData.hasRemaining()) {
int appBytesProduced = readAndUnwrap();
if (appBytesProduced == EOF){
debugFile.write("Failed to read");
debugFile.close();
return appBytesProduced;
}
if (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(), buffer.remaining());
for (int i = 0; i < limit; i++) {
buffer.put(peerAppData.get());
}
return limit;
}
private int readAndUnwrap() throws IOException {
// 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 = _client.read(peerNetData);
// decoder.decode(peerNetData, charBuffer, false);
// charBuffer.flip();
System.out.println("Read bytes " + bytesRead);
if (bytesRead == EOF) {
// 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 EOF;
}
// 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;
System.out.println("Do unwrap " + bytesRead);
try{
do {
res = _engine.unwrap(peerNetData, peerAppData);
System.out.println("Read status" + res.getHandshakeStatus().toString());
// During an handshake renegotiation we might need to perform
// several unwraps to consume e handshake data.
} while (res.getStatus() == SSLEngineResult.Status.OK &&
res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
res.bytesProduced() == 0);
System.out.println("Read status" + res.getHandshakeStatus().toString());
// 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) {
initialHandshake = false;
}
// 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);
}
/*
* 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();
}
catch (Exception e){
System.out.println("Error: " + e.getMessage());
}
// 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) {
debugFile.write("Connection is being closed by peer.");
return EOF;
}
// 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)
{
debugFile.write("Rehandshaking...");
doHandshake();
}
return peerAppData.remaining();
}
public void closeSocket(boolean timeout){
super.closeSocket(timeout);
try{
debugFile.write("Close");
debugFile.close();
}catch(IOException e){}
}
/**
* Execute delegated tasks in the main thread. These are compute
* intensive tasks, so there's no point in scheduling them in a different
* thread.
*/
private void doTasks() {
Runnable task;
while ((task = _engine.getDelegatedTask()) != null) {
task.run();
}
hsStatus = _engine.getHandshakeStatus();
}
private void doHandshake() throws IOException {
while (true) {
SSLEngineResult res;
System.out.println("Handshake status: " + hsStatus.toString());
switch (hsStatus) {
case FINISHED:
initialHandshake = false;
return;
case NEED_TASK:
doTasks();
// The hs status was updated, so go back to the switch
break;
case NEED_UNWRAP:
readAndUnwrap();
return;
case NEED_WRAP:
if (netData.hasRemaining()) {
return;
}
// Prepare to write
netData.clear();
try{
res = _engine.wrap(dummy, netData);
if (res.bytesProduced() == 0){
System.out.println("No net data produced during handshake wrap.");
}
else{
System.out.println("Result status: " + res.getStatus() + "Bytes porduced: " + res.bytesProduced());
}
if (res.bytesConsumed() != 0){
System.out.println("App data consumed during handshake wrap.");
}
hsStatus = res.getHandshakeStatus();
}catch(SSLException se){
System.out.println("Error: " + se.getMessage());
System.out.println("Details: " + se.getLocalizedMessage());
throw se;
}
System.out.println(hsStatus.toString());
netData.flip();
try{
int writebytes = _client.write(netData);
System.out.println("Number of bytes sent: " + writebytes);
if (netData.hasRemaining()) {
System.out.println("netdata has remaining");
}
}
catch(Exception e){
System.out.println(e.getMessage());
}
break;
case NOT_HANDSHAKING:
assert false : "doHandshake() should never reach the NOT_HANDSHAKING state";
return;
}
}
}
}
最佳答案
SSLEngine
很难使用。您的代码有几个问题。
例如,NEED_WRAP
表示引擎要写一些东西。如果传出的网络缓冲区中有数据,它将不会写入任何内容,因此,在这种情况下,您需要将其写入以便清空缓冲区,然后进行包装。仅忽略条件是不够的。
关于FINISHED
状态也有一个微妙之处:它设置为除SSLEngine
之外的每个getHandshakeStatus()
方法返回的握手状态:即,这是一个非常短暂的条件。
可能还有更多,但我没有时间看,抱歉。我可以推荐my book中的SSLEngine
章节。它包含一个工作的SSLEngine管理器。