下面列出了javax.net.ssl.SSLEngine#unwrap ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
static SSLEngineResult decryptSSL(final SSLEngine engine, final QpidByteBuffer src, final QpidByteBuffer dst)
throws SSLException
{
if (src instanceof SingleQpidByteBuffer)
{
ByteBuffer underlying = ((SingleQpidByteBuffer)src).getUnderlyingBuffer();
if (dst instanceof SingleQpidByteBuffer)
{
return engine.unwrap(underlying, ((SingleQpidByteBuffer) dst).getUnderlyingBuffer());
}
else if (dst instanceof MultiQpidByteBuffer)
{
return engine.unwrap(underlying, ((MultiQpidByteBuffer) dst).getUnderlyingBuffers());
}
else
{
throw new IllegalStateException("unknown QBB implementation");
}
}
else
{
throw new IllegalStateException("Source QBB can only be single byte buffer");
}
}
private void checkBufferOverflowOnUnWrap(SSLEngine wrappingEngine,
SSLEngine unwrappingEngine)
throws SSLException {
String wrapperMode = wrappingEngine.getUseClientMode() ? "client"
: "server";
String unwrapperMode = unwrappingEngine.getUseClientMode() ? "client"
: "server";
if (wrapperMode.equals(unwrapperMode)) {
throw new Error("Test error: both engines are in the same mode!");
}
System.out.println("================================================="
+ "===========");
System.out.println("Testing SSLEngine buffer overflow"
+ " on unwrap by " + unwrapperMode);
ByteBuffer app = ByteBuffer.wrap(MESSAGE.getBytes());
ByteBuffer net = ByteBuffer
.allocate(wrappingEngine.getSession().getPacketBufferSize());
SSLEngineResult r = wrappingEngine.wrap(app, net);
checkResult(r, SSLEngineResult.Status.OK);
//Making app buffer size less than required by 1 byte.
app = ByteBuffer.allocate(MESSAGE.length() - 1);
net.flip();
r = unwrappingEngine.unwrap(net, app);
checkResult(r, SSLEngineResult.Status.BUFFER_OVERFLOW);
System.out.println("Passed");
}
private void checkBufferUnderflowOnUnWrap(SSLEngine wrappingEngine,
SSLEngine unwrappingEngine)
throws SSLException {
String wrapperMode = wrappingEngine.getUseClientMode() ? "client"
: "server";
String unwrapperMode = unwrappingEngine.getUseClientMode() ? "client"
: "server";
if (wrapperMode.equals(unwrapperMode)) {
throw new Error("Test error: both engines are in the same mode!");
}
System.out.println("================================================="
+ "===========");
System.out.println("Testing SSLEngine buffer underflow"
+ " on unwrap by " + unwrapperMode);
ByteBuffer app = ByteBuffer.wrap(MESSAGE.getBytes());
ByteBuffer net = ByteBuffer
.allocate(wrappingEngine.getSession().getPacketBufferSize());
SSLEngineResult r = wrappingEngine.wrap(app, net);
checkResult(r, SSLEngineResult.Status.OK);
app = ByteBuffer.allocate(unwrappingEngine.getSession()
.getApplicationBufferSize());
net.flip();
//Making net buffer size less than size of dtls message.
net.limit(net.limit() - 1);
r = unwrappingEngine.unwrap(net, app);
checkResult(r, SSLEngineResult.Status.BUFFER_UNDERFLOW);
System.out.println("Passed");
}
/**
* DTLS decrypt byte buffer
*/
boolean diameterDTLSDecryptBuffer(SSLEngine engine, ByteBuffer source, ByteBuffer recBuffer) throws Exception {
//printHex("Received application data for Decrypt", source);
SSLEngineResult r = engine.unwrap(source, recBuffer);
recBuffer.flip();
SSLEngineResult.Status rs = r.getStatus();
if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
// the client maximum fragment size config does not work?
logger.warn("Buffer overflow: " + "incorrect server maximum fragment size");
return false;
} else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
// unlikely
logger.warn("Buffer underflow during wraping");
return false;
} else if (rs == SSLEngineResult.Status.CLOSED) {
logger.warn("SSLEngine has closed");
return false;
} else if (rs == SSLEngineResult.Status.OK) {
// OK
} else {
logger.warn("Can't reach here, result is " + rs);
return false;
}
//printHex("Produced application data by Decrypt", recBuffer);
return true;
}
public static void run() throws Exception {
SSLEngine[][] enginesToTest = getSSLEnginesToTest();
for (SSLEngine[] engineToTest : enginesToTest) {
SSLEngine clientSSLEngine = engineToTest[0];
SSLEngine serverSSLEngine = engineToTest[1];
// SSLEngine code based on RedhandshakeFinished.java
boolean dataDone = false;
ByteBuffer clientOut = null;
ByteBuffer clientIn = null;
ByteBuffer serverOut = null;
ByteBuffer serverIn = null;
ByteBuffer cTOs;
ByteBuffer sTOc;
SSLSession session = clientSSLEngine.getSession();
int appBufferMax = session.getApplicationBufferSize();
int netBufferMax = session.getPacketBufferSize();
clientIn = ByteBuffer.allocate(appBufferMax + 50);
serverIn = ByteBuffer.allocate(appBufferMax + 50);
cTOs = ByteBuffer.allocateDirect(netBufferMax);
sTOc = ByteBuffer.allocateDirect(netBufferMax);
clientOut = ByteBuffer.wrap(
"Hi Server, I'm Client".getBytes());
serverOut = ByteBuffer.wrap(
"Hello Client, I'm Server".getBytes());
SSLEngineResult clientResult;
SSLEngineResult serverResult;
while (!dataDone) {
clientResult = clientSSLEngine.wrap(clientOut, cTOs);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.wrap(serverOut, sTOc);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.flip();
sTOc.flip();
if (enableDebug) {
System.out.println("Client -> Network");
printTlsNetworkPacket("", cTOs);
System.out.println("");
System.out.println("Server -> Network");
printTlsNetworkPacket("", sTOc);
System.out.println("");
}
clientResult = clientSSLEngine.unwrap(sTOc, clientIn);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.unwrap(cTOs, serverIn);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.compact();
sTOc.compact();
if (!dataDone &&
(clientOut.limit() == serverIn.position()) &&
(serverOut.limit() == clientIn.position())) {
checkTransfer(serverOut, clientIn);
checkTransfer(clientOut, serverIn);
dataDone = true;
}
}
}
}
@Test
public void testMultipleRecordsInOneBufferWithNonZeroPositionJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
// Choose buffer size small enough that we can put multiple buffers into one buffer and pass it into the
// unwrap call without exceed MAX_ENCRYPTED_PACKET_LENGTH.
final int plainClientOutLen = 1024;
ByteBuffer plainClientOut = allocateBuffer(plainClientOutLen);
ByteBuffer plainServerOut = allocateBuffer(server.getSession().getApplicationBufferSize());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
int positionOffset = 1;
// We need to be able to hold 2 records + positionOffset
ByteBuffer combinedEncClientToServer = allocateBuffer(
encClientToServer.capacity() * 2 + positionOffset);
combinedEncClientToServer.position(positionOffset);
handshake(client, server);
plainClientOut.limit(plainClientOut.capacity());
SSLEngineResult result = client.wrap(plainClientOut, encClientToServer);
assertEquals(plainClientOut.capacity(), result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
encClientToServer.flip();
// Copy the first record into the combined buffer
combinedEncClientToServer.put(encClientToServer);
plainClientOut.clear();
encClientToServer.clear();
result = client.wrap(plainClientOut, encClientToServer);
assertEquals(plainClientOut.capacity(), result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
encClientToServer.flip();
// Copy the first record into the combined buffer
combinedEncClientToServer.put(encClientToServer);
encClientToServer.clear();
combinedEncClientToServer.flip();
combinedEncClientToServer.position(positionOffset);
// Make sure the limit takes positionOffset into account to the content we are looking at is correct.
combinedEncClientToServer.limit(
combinedEncClientToServer.limit() - positionOffset);
final int combinedEncClientToServerLen = combinedEncClientToServer.remaining();
result = server.unwrap(combinedEncClientToServer, plainServerOut);
assertEquals(0, combinedEncClientToServer.remaining());
assertEquals(combinedEncClientToServerLen, result.bytesConsumed());
assertEquals(plainClientOutLen, result.bytesProduced());
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Test
public void testInputTooBigAndFillsUpBuffersJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
ByteBuffer plainClient = allocateBuffer(MAX_PLAINTEXT_LENGTH + 100);
ByteBuffer plainClient2 = allocateBuffer(512);
ByteBuffer plainClientTotal = allocateBuffer(plainClient.capacity() + plainClient2.capacity());
plainClientTotal.put(plainClient);
plainClientTotal.put(plainClient2);
plainClient.clear();
plainClient2.clear();
plainClientTotal.flip();
// The capacity is designed to trigger an overflow condition.
ByteBuffer encClientToServerTooSmall = allocateBuffer(MAX_PLAINTEXT_LENGTH + 28);
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getApplicationBufferSize());
ByteBuffer encClientToServerTotal = allocateBuffer(client.getSession().getApplicationBufferSize() << 1);
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize() << 1);
handshake(client, server);
int plainClientRemaining = plainClient.remaining();
int encClientToServerTooSmallRemaining = encClientToServerTooSmall.remaining();
SSLEngineResult result = client.wrap(plainClient, encClientToServerTooSmall);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(plainClientRemaining - plainClient.remaining(), result.bytesConsumed());
assertEquals(encClientToServerTooSmallRemaining - encClientToServerTooSmall.remaining(),
result.bytesProduced());
result = client.wrap(plainClient, encClientToServerTooSmall);
assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus());
assertEquals(0, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
plainClientRemaining = plainClient.remaining();
int encClientToServerRemaining = encClientToServer.remaining();
result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(plainClientRemaining, result.bytesConsumed());
assertEquals(encClientToServerRemaining - encClientToServer.remaining(), result.bytesProduced());
assertEquals(0, plainClient.remaining());
final int plainClient2Remaining = plainClient2.remaining();
encClientToServerRemaining = encClientToServer.remaining();
result = client.wrap(plainClient2, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(plainClient2Remaining, result.bytesConsumed());
assertEquals(encClientToServerRemaining - encClientToServer.remaining(), result.bytesProduced());
// Concatenate the too small buffer
encClientToServerTooSmall.flip();
encClientToServer.flip();
encClientToServerTotal.put(encClientToServerTooSmall);
encClientToServerTotal.put(encClientToServer);
encClientToServerTotal.flip();
// Unwrap in a single call.
final int encClientToServerTotalRemaining = encClientToServerTotal.remaining();
result = server.unwrap(encClientToServerTotal, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(encClientToServerTotalRemaining, result.bytesConsumed());
plainServer.flip();
assertEquals(plainClientTotal, plainServer);
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
/**
* Perform the handshaking step of the TLS connection. We use the `sslEngine' along with the `channel' to exchange messages with the server to setup an
* encrypted channel.
*
* @param sslEngine
* {@link SSLEngine}
* @param channel
* {@link AsynchronousSocketChannel}
* @throws SSLException
* in case of handshake error
*/
private static void performTlsHandshake(SSLEngine sslEngine, AsynchronousSocketChannel channel) throws SSLException {
sslEngine.beginHandshake();
HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
// Create byte buffers to use for holding application data
int packetBufferSize = sslEngine.getSession().getPacketBufferSize();
ByteBuffer myNetData = ByteBuffer.allocate(packetBufferSize);
ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize);
int appBufferSize = sslEngine.getSession().getApplicationBufferSize();
ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize);
ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize);
SSLEngineResult res = null;
while (handshakeStatus != HandshakeStatus.FINISHED && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) {
switch (handshakeStatus) {
case NEED_WRAP:
myNetData.clear();
res = sslEngine.wrap(myAppData, myNetData);
handshakeStatus = res.getHandshakeStatus();
switch (res.getStatus()) {
case OK:
myNetData.flip();
write(channel, myNetData);
break;
case BUFFER_OVERFLOW:
case BUFFER_UNDERFLOW:
case CLOSED:
throw new CJCommunicationsException("Unacceptable SSLEngine result: " + res);
}
break;
case NEED_UNWRAP:
peerNetData.flip(); // Process incoming handshaking data
res = sslEngine.unwrap(peerNetData, peerAppData);
handshakeStatus = res.getHandshakeStatus();
switch (res.getStatus()) {
case OK:
peerNetData.compact();
break;
case BUFFER_OVERFLOW:
// Check if we need to enlarge the peer application data buffer.
final int newPeerAppDataSize = sslEngine.getSession().getApplicationBufferSize();
if (newPeerAppDataSize > peerAppData.capacity()) {
// enlarge the peer application data buffer
ByteBuffer newPeerAppData = ByteBuffer.allocate(newPeerAppDataSize);
newPeerAppData.put(peerAppData);
newPeerAppData.flip();
peerAppData = newPeerAppData;
} else {
peerAppData.compact();
}
break;
case BUFFER_UNDERFLOW:
// Check if we need to enlarge the peer network packet buffer
final int newPeerNetDataSize = sslEngine.getSession().getPacketBufferSize();
if (newPeerNetDataSize > peerNetData.capacity()) {
// enlarge the peer network packet buffer
ByteBuffer newPeerNetData = ByteBuffer.allocate(newPeerNetDataSize);
newPeerNetData.put(peerNetData);
newPeerNetData.flip();
peerNetData = newPeerNetData;
} else {
peerNetData.compact();
}
// obtain more inbound network data and then retry the operation
if (read(channel, peerNetData) < 0) {
throw new CJCommunicationsException("Server does not provide enough data to proceed with SSL handshake.");
}
break;
case CLOSED:
throw new CJCommunicationsException("Unacceptable SSLEngine result: " + res);
}
break;
case NEED_TASK:
sslEngine.getDelegatedTask().run();
handshakeStatus = sslEngine.getHandshakeStatus();
break;
case FINISHED:
case NOT_HANDSHAKING:
break;
}
}
}
@Test
public void testBufferUnderFlowAvoidedIfJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
ByteBuffer plainClient = allocateBuffer(1024);
plainClient.limit(plainClient.capacity());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize());
handshake(client, server);
SSLEngineResult result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), plainClient.capacity());
// Flip so we can read it.
encClientToServer.flip();
int remaining = encClientToServer.remaining();
// We limit the buffer so we have less then the header to read, this should result in an BUFFER_UNDERFLOW.
encClientToServer.limit(SslUtils.SSL_RECORD_HEADER_LENGTH - 1);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(SslUtils.SSL_RECORD_HEADER_LENGTH - 1, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// We limit the buffer so we can read the header but not the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(SslUtils.SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(1, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// We limit the buffer so we can read the header and partly the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(
SslUtils.SSL_RECORD_HEADER_LENGTH + remaining - 1 - SslUtils.SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(encClientToServer.limit() - SslUtils.SSL_RECORD_HEADER_LENGTH, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// Reset limit so we can read the full record.
encClientToServer.limit(remaining);
assertEquals(0, encClientToServer.remaining());
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.BUFFER_UNDERFLOW, result.getStatus());
assertEquals(0, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
encClientToServer.position(0);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(remaining, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
protected void handshake(SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException {
ByteBuffer cTOs = allocateBuffer(clientEngine.getSession().getPacketBufferSize());
ByteBuffer sTOc = allocateBuffer(serverEngine.getSession().getPacketBufferSize());
ByteBuffer serverAppReadBuffer = allocateBuffer(
serverEngine.getSession().getApplicationBufferSize());
ByteBuffer clientAppReadBuffer = allocateBuffer(
clientEngine.getSession().getApplicationBufferSize());
clientEngine.beginHandshake();
serverEngine.beginHandshake();
ByteBuffer empty = allocateBuffer(0);
SSLEngineResult clientResult;
SSLEngineResult serverResult;
boolean clientHandshakeFinished = false;
boolean serverHandshakeFinished = false;
do {
int cTOsPos = cTOs.position();
int sTOcPos = sTOc.position();
if (!clientHandshakeFinished) {
clientResult = clientEngine.wrap(empty, cTOs);
runDelegatedTasks(clientResult, clientEngine);
assertEquals(empty.remaining(), clientResult.bytesConsumed());
assertEquals(cTOs.position() - cTOsPos, clientResult.bytesProduced());
if (isHandshakeFinished(clientResult)) {
clientHandshakeFinished = true;
}
}
if (!serverHandshakeFinished) {
serverResult = serverEngine.wrap(empty, sTOc);
runDelegatedTasks(serverResult, serverEngine);
assertEquals(empty.remaining(), serverResult.bytesConsumed());
assertEquals(sTOc.position() - sTOcPos, serverResult.bytesProduced());
if (isHandshakeFinished(serverResult)) {
serverHandshakeFinished = true;
}
}
cTOs.flip();
sTOc.flip();
cTOsPos = cTOs.position();
sTOcPos = sTOc.position();
if (!clientHandshakeFinished) {
int clientAppReadBufferPos = clientAppReadBuffer.position();
clientResult = clientEngine.unwrap(sTOc, clientAppReadBuffer);
runDelegatedTasks(clientResult, clientEngine);
assertEquals(sTOc.position() - sTOcPos, clientResult.bytesConsumed());
assertEquals(clientAppReadBuffer.position() - clientAppReadBufferPos, clientResult.bytesProduced());
if (isHandshakeFinished(clientResult)) {
clientHandshakeFinished = true;
}
} else {
assertFalse(sTOc.hasRemaining());
}
if (!serverHandshakeFinished) {
int serverAppReadBufferPos = serverAppReadBuffer.position();
serverResult = serverEngine.unwrap(cTOs, serverAppReadBuffer);
runDelegatedTasks(serverResult, serverEngine);
assertEquals(cTOs.position() - cTOsPos, serverResult.bytesConsumed());
assertEquals(serverAppReadBuffer.position() - serverAppReadBufferPos, serverResult.bytesProduced());
if (isHandshakeFinished(serverResult)) {
serverHandshakeFinished = true;
}
} else {
assertFalse(cTOs.hasRemaining());
}
sTOc.compact();
cTOs.compact();
} while (!clientHandshakeFinished || !serverHandshakeFinished);
}
@Test
public void testMultipleRecordsInOneBufferWithNonZeroPosition() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
try {
// Choose buffer size small enough that we can put multiple buffers into one buffer and pass it into the
// unwrap call without exceed MAX_ENCRYPTED_PACKET_LENGTH.
ByteBuffer plainClientOut = allocateBuffer(1024);
ByteBuffer plainServerOut = allocateBuffer(server.getSession().getApplicationBufferSize());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
int positionOffset = 1;
// We need to be able to hold 2 records + positionOffset
ByteBuffer combinedEncClientToServer = allocateBuffer(
encClientToServer.capacity() * 2 + positionOffset);
combinedEncClientToServer.position(positionOffset);
handshake(client, server);
plainClientOut.limit(plainClientOut.capacity());
SSLEngineResult result = client.wrap(plainClientOut, encClientToServer);
assertEquals(plainClientOut.capacity(), result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
encClientToServer.flip();
// Copy the first record into the combined buffer
combinedEncClientToServer.put(encClientToServer);
plainClientOut.clear();
encClientToServer.clear();
result = client.wrap(plainClientOut, encClientToServer);
assertEquals(plainClientOut.capacity(), result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
encClientToServer.flip();
int encClientToServerLen = encClientToServer.remaining();
// Copy the first record into the combined buffer
combinedEncClientToServer.put(encClientToServer);
encClientToServer.clear();
combinedEncClientToServer.flip();
combinedEncClientToServer.position(positionOffset);
// Ensure we have the first record and a tiny amount of the second record in the buffer
combinedEncClientToServer.limit(
combinedEncClientToServer.limit() - (encClientToServerLen - positionOffset));
result = server.unwrap(combinedEncClientToServer, plainServerOut);
assertEquals(encClientToServerLen, result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Test
public void testMultipleRecordsInOneBufferBiggerThenPacketBufferSize() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
try {
ByteBuffer plainClientOut = allocateBuffer(4096);
ByteBuffer plainServerOut = allocateBuffer(server.getSession().getApplicationBufferSize());
ByteBuffer encClientToServer = allocateBuffer(server.getSession().getPacketBufferSize() * 2);
handshake(client, server);
int srcLen = plainClientOut.remaining();
SSLEngineResult result;
while (encClientToServer.position() <= server.getSession().getPacketBufferSize()) {
result = client.wrap(plainClientOut, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(srcLen, result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
plainClientOut.clear();
}
encClientToServer.flip();
result = server.unwrap(encClientToServer, plainServerOut);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertTrue(result.bytesConsumed() > 0);
assertTrue(result.bytesProduced() > 0);
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Test
public void testBufferUnderFlow() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
try {
ByteBuffer plainClient = allocateBuffer(1024);
plainClient.limit(plainClient.capacity());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize());
handshake(client, server);
SSLEngineResult result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), plainClient.capacity());
// Flip so we can read it.
encClientToServer.flip();
int remaining = encClientToServer.remaining();
// We limit the buffer so we have less then the header to read, this should result in an BUFFER_UNDERFLOW.
encClientToServer.limit(SSL_RECORD_HEADER_LENGTH - 1);
result = server.unwrap(encClientToServer, plainServer);
assertResultIsBufferUnderflow(result);
// We limit the buffer so we can read the header but not the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertResultIsBufferUnderflow(result);
// We limit the buffer so we can read the header and partly the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(SSL_RECORD_HEADER_LENGTH + remaining - 1 - SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertResultIsBufferUnderflow(result);
// Reset limit so we can read the full record.
encClientToServer.limit(remaining);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), remaining);
assertTrue(result.bytesProduced() > 0);
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
public static void run() throws Exception {
SSLEngine[][] enginesToTest = getSSLEnginesToTest();
for (SSLEngine[] engineToTest : enginesToTest) {
SSLEngine clientSSLEngine = engineToTest[0];
SSLEngine serverSSLEngine = engineToTest[1];
// SSLEngine code based on RedhandshakeFinished.java
boolean dataDone = false;
ByteBuffer clientOut = null;
ByteBuffer clientIn = null;
ByteBuffer serverOut = null;
ByteBuffer serverIn = null;
ByteBuffer cTOs;
ByteBuffer sTOc;
SSLSession session = clientSSLEngine.getSession();
int appBufferMax = session.getApplicationBufferSize();
int netBufferMax = session.getPacketBufferSize();
clientIn = ByteBuffer.allocate(appBufferMax + 50);
serverIn = ByteBuffer.allocate(appBufferMax + 50);
cTOs = ByteBuffer.allocateDirect(netBufferMax);
sTOc = ByteBuffer.allocateDirect(netBufferMax);
clientOut = ByteBuffer.wrap(
"Hi Server, I'm Client".getBytes());
serverOut = ByteBuffer.wrap(
"Hello Client, I'm Server".getBytes());
SSLEngineResult clientResult;
SSLEngineResult serverResult;
while (!dataDone) {
clientResult = clientSSLEngine.wrap(clientOut, cTOs);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.wrap(serverOut, sTOc);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.flip();
sTOc.flip();
if (enableDebug) {
System.out.println("Client -> Network");
printTlsNetworkPacket("", cTOs);
System.out.println("");
System.out.println("Server -> Network");
printTlsNetworkPacket("", sTOc);
System.out.println("");
}
clientResult = clientSSLEngine.unwrap(sTOc, clientIn);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.unwrap(cTOs, serverIn);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.compact();
sTOc.compact();
if (!dataDone &&
(clientOut.limit() == serverIn.position()) &&
(serverOut.limit() == clientIn.position())) {
checkTransfer(serverOut, clientIn);
checkTransfer(clientOut, serverIn);
dataDone = true;
}
}
}
}
public static void run() throws Exception {
SSLEngine[][] enginesToTest = getSSLEnginesToTest();
for (SSLEngine[] engineToTest : enginesToTest) {
SSLEngine clientSSLEngine = engineToTest[0];
SSLEngine serverSSLEngine = engineToTest[1];
// SSLEngine code based on RedhandshakeFinished.java
boolean dataDone = false;
ByteBuffer clientOut = null;
ByteBuffer clientIn = null;
ByteBuffer serverOut = null;
ByteBuffer serverIn = null;
ByteBuffer cTOs;
ByteBuffer sTOc;
SSLSession session = clientSSLEngine.getSession();
int appBufferMax = session.getApplicationBufferSize();
int netBufferMax = session.getPacketBufferSize();
clientIn = ByteBuffer.allocate(appBufferMax + 50);
serverIn = ByteBuffer.allocate(appBufferMax + 50);
cTOs = ByteBuffer.allocateDirect(netBufferMax);
sTOc = ByteBuffer.allocateDirect(netBufferMax);
clientOut = ByteBuffer.wrap(
"Hi Server, I'm Client".getBytes());
serverOut = ByteBuffer.wrap(
"Hello Client, I'm Server".getBytes());
SSLEngineResult clientResult;
SSLEngineResult serverResult;
while (!dataDone) {
clientResult = clientSSLEngine.wrap(clientOut, cTOs);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.wrap(serverOut, sTOc);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.flip();
sTOc.flip();
if (enableDebug) {
System.out.println("Client -> Network");
printTlsNetworkPacket("", cTOs);
System.out.println("");
System.out.println("Server -> Network");
printTlsNetworkPacket("", sTOc);
System.out.println("");
}
clientResult = clientSSLEngine.unwrap(sTOc, clientIn);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.unwrap(cTOs, serverIn);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.compact();
sTOc.compact();
if (!dataDone &&
(clientOut.limit() == serverIn.position()) &&
(serverOut.limit() == clientIn.position())) {
checkTransfer(serverOut, clientIn);
checkTransfer(clientOut, serverIn);
dataDone = true;
}
}
}
}
public static void run() throws Exception {
SSLEngine[][] enginesToTest = getSSLEnginesToTest();
for (SSLEngine[] engineToTest : enginesToTest) {
SSLEngine clientSSLEngine = engineToTest[0];
SSLEngine serverSSLEngine = engineToTest[1];
// SSLEngine code based on RedhandshakeFinished.java
boolean dataDone = false;
ByteBuffer clientOut = null;
ByteBuffer clientIn = null;
ByteBuffer serverOut = null;
ByteBuffer serverIn = null;
ByteBuffer cTOs;
ByteBuffer sTOc;
SSLSession session = clientSSLEngine.getSession();
int appBufferMax = session.getApplicationBufferSize();
int netBufferMax = session.getPacketBufferSize();
clientIn = ByteBuffer.allocate(appBufferMax + 50);
serverIn = ByteBuffer.allocate(appBufferMax + 50);
cTOs = ByteBuffer.allocateDirect(netBufferMax);
sTOc = ByteBuffer.allocateDirect(netBufferMax);
clientOut = ByteBuffer.wrap(
"Hi Server, I'm Client".getBytes());
serverOut = ByteBuffer.wrap(
"Hello Client, I'm Server".getBytes());
SSLEngineResult clientResult;
SSLEngineResult serverResult;
while (!dataDone) {
clientResult = clientSSLEngine.wrap(clientOut, cTOs);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.wrap(serverOut, sTOc);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.flip();
sTOc.flip();
if (enableDebug) {
System.out.println("Client -> Network");
printTlsNetworkPacket("", cTOs);
System.out.println("");
System.out.println("Server -> Network");
printTlsNetworkPacket("", sTOc);
System.out.println("");
}
clientResult = clientSSLEngine.unwrap(sTOc, clientIn);
runDelegatedTasks(clientResult, clientSSLEngine);
serverResult = serverSSLEngine.unwrap(cTOs, serverIn);
runDelegatedTasks(serverResult, serverSSLEngine);
cTOs.compact();
sTOc.compact();
if (!dataDone &&
(clientOut.limit() == serverIn.position()) &&
(serverOut.limit() == clientIn.position())) {
checkTransfer(serverOut, clientIn);
checkTransfer(clientOut, serverIn);
dataDone = true;
}
}
}
}
public static SSLEngineResult.HandshakeStatus doHandshake(SSLEngine tlsEngine,
ByteBuffer netDataBuf,
OutputStream out,
InputStream in){
try {
ByteBuffer empty;
/*Apparently on Android 4.4 (API_19) SSLEngine whines about BUFFER_OVERFLOW for this
buffer even though nothing ever gets written to it*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH){
empty = ByteBuffer.allocate(0);
} else {
empty = ByteBuffer.allocate(tlsEngine.getSession().getApplicationBufferSize());
}
// ClientHello -> netDataBuf
tlsEngine.wrap(empty, netDataBuf);
netDataBuf.flip();
byte[] clientHello = new byte[netDataBuf.limit()];
netDataBuf.get(clientHello);
out.write(ConnectionHelper.intToByteArray(clientHello.length));
out.write(clientHello);
// netDataBuf <- ServerHello..ServerHelloDone
int serverHelloSize = ByteBuffer.wrap(ConnectionHelper.readAll(4, in)).getInt();
byte[] serverHello = ConnectionHelper.readAll(serverHelloSize, in);
netDataBuf.clear();
netDataBuf.put(serverHello);
netDataBuf.flip();
SSLEngineResult result = tlsEngine.unwrap(netDataBuf, empty);
while (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP){
result = tlsEngine.unwrap(netDataBuf, empty);
}
Runnable task = tlsEngine.getDelegatedTask();
while (task != null){
task.run();
task = tlsEngine.getDelegatedTask();
}
// [client]Certificate*..ClientKeyExchange..Finished -> netDataBuf
netDataBuf.clear();
result = tlsEngine.wrap(empty, netDataBuf);
while (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP){
result = tlsEngine.wrap(empty, netDataBuf);
}
netDataBuf.flip();
byte[] clientKeyExchange = new byte[netDataBuf.limit()];
netDataBuf.get(clientKeyExchange);
out.write(ConnectionHelper.intToByteArray(clientKeyExchange.length));
out.write(clientKeyExchange);
// netDataBuf <- ChangeCipherSpec..Finished
int serverChangeCipherSpecSize = ByteBuffer.wrap(ConnectionHelper.readAll(4, in)).getInt();
byte[] serverChangeCipherSpec = ConnectionHelper.readAll(serverChangeCipherSpecSize, in);
netDataBuf.clear();
netDataBuf.put(serverChangeCipherSpec);
netDataBuf.flip();
result = tlsEngine.unwrap(netDataBuf, empty);
while (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP){
result = tlsEngine.unwrap(netDataBuf, empty);
}
/*On Android 8.1 (LineageOS 15.1) on a Xiaomi device (not sure about others) the
SSL_ENGINE may return NEED_WRAP here, so we do that even though no data gets written
by the SSL_ENGINE ???*/
if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
netDataBuf.clear();
result = tlsEngine.wrap(empty, netDataBuf);
// netDataBuf still empty here...
}
/*Apparently on Android 4.4 (API_19) with SSLEngine the latest call tlsEngine.unwrap(..)
that finishes the handshake returns NOT_HANDSHAKING instead of FINISHED as the result*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH){
return result.getHandshakeStatus();
} else {
if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING){
return SSLEngineResult.HandshakeStatus.FINISHED;
} else if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
// just in case
return result.getHandshakeStatus();
} else {
return null;
}
}
} catch (IOException e){
return null;
}
}
/**
* Perform the handshaking step of the TLS connection. We use the `sslEngine' along with the `channel' to exchange messages with the server to setup an
* encrypted channel.
*
* @param sslEngine
* {@link SSLEngine}
* @param channel
* {@link AsynchronousSocketChannel}
* @throws SSLException
* in case of handshake error
*/
private static void performTlsHandshake(SSLEngine sslEngine, AsynchronousSocketChannel channel) throws SSLException {
sslEngine.beginHandshake();
HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
// Create byte buffers to use for holding application data
int packetBufferSize = sslEngine.getSession().getPacketBufferSize();
ByteBuffer myNetData = ByteBuffer.allocate(packetBufferSize);
ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize);
int appBufferSize = sslEngine.getSession().getApplicationBufferSize();
ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize);
ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize);
SSLEngineResult res = null;
while (handshakeStatus != HandshakeStatus.FINISHED && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) {
switch (handshakeStatus) {
case NEED_WRAP:
myNetData.clear();
res = sslEngine.wrap(myAppData, myNetData);
handshakeStatus = res.getHandshakeStatus();
switch (res.getStatus()) {
case OK:
myNetData.flip();
write(channel, myNetData);
break;
case BUFFER_OVERFLOW:
case BUFFER_UNDERFLOW:
case CLOSED:
throw new CJCommunicationsException("Unacceptable SSLEngine result: " + res);
}
break;
case NEED_UNWRAP:
peerNetData.flip(); // Process incoming handshaking data
res = sslEngine.unwrap(peerNetData, peerAppData);
handshakeStatus = res.getHandshakeStatus();
switch (res.getStatus()) {
case OK:
peerNetData.compact();
break;
case BUFFER_OVERFLOW:
// Check if we need to enlarge the peer application data buffer.
final int newPeerAppDataSize = sslEngine.getSession().getApplicationBufferSize();
if (newPeerAppDataSize > peerAppData.capacity()) {
// enlarge the peer application data buffer
ByteBuffer newPeerAppData = ByteBuffer.allocate(newPeerAppDataSize);
newPeerAppData.put(peerAppData);
newPeerAppData.flip();
peerAppData = newPeerAppData;
} else {
peerAppData.compact();
}
break;
case BUFFER_UNDERFLOW:
// Check if we need to enlarge the peer network packet buffer
final int newPeerNetDataSize = sslEngine.getSession().getPacketBufferSize();
if (newPeerNetDataSize > peerNetData.capacity()) {
// enlarge the peer network packet buffer
ByteBuffer newPeerNetData = ByteBuffer.allocate(newPeerNetDataSize);
newPeerNetData.put(peerNetData);
newPeerNetData.flip();
peerNetData = newPeerNetData;
} else {
peerNetData.compact();
}
// obtain more inbound network data and then retry the operation
if (read(channel, peerNetData) < 0) {
throw new CJCommunicationsException("Server does not provide enough data to proceed with SSL handshake.");
}
break;
case CLOSED:
throw new CJCommunicationsException("Unacceptable SSLEngine result: " + res);
}
break;
case NEED_TASK:
sslEngine.getDelegatedTask().run();
handshakeStatus = sslEngine.getHandshakeStatus();
break;
case FINISHED:
case NOT_HANDSHAKING:
break;
}
}
}
private static HandshakeHolder doHandshakeUnwrap(final SocketChannel socketChannel, final SSLEngine sslEngine,
ByteBuffer peerAppData, ByteBuffer peerNetData, final int appBufferSize) throws IOException {
if (socketChannel == null || sslEngine == null || peerAppData == null || peerNetData == null || appBufferSize < 0) {
return new HandshakeHolder(peerAppData, peerNetData, false);
}
if (socketChannel.read(peerNetData) < 0) {
if (sslEngine.isInboundDone() && sslEngine.isOutboundDone()) {
return new HandshakeHolder(peerAppData, peerNetData, false);
}
try {
sslEngine.closeInbound();
} catch (SSLException e) {
s_logger.warn("This SSL engine was forced to close inbound due to end of stream.", e);
}
sslEngine.closeOutbound();
// After closeOutbound the engine will be set to WRAP state,
// in order to try to send a close message to the client.
return new HandshakeHolder(peerAppData, peerNetData, true);
}
peerNetData.flip();
SSLEngineResult result = null;
try {
result = sslEngine.unwrap(peerNetData, peerAppData);
peerNetData.compact();
} catch (final SSLException sslException) {
s_logger.error(String.format("SSL error caught during unwrap data: %s, for local address=%s, remote address=%s. The client may have invalid ca-certificates.",
sslException.getMessage(), socketChannel.getLocalAddress(), socketChannel.getRemoteAddress()));
sslEngine.closeOutbound();
return new HandshakeHolder(peerAppData, peerNetData, false);
}
if (result == null) {
return new HandshakeHolder(peerAppData, peerNetData, false);
}
switch (result.getStatus()) {
case OK:
break;
case BUFFER_OVERFLOW:
// Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap.
peerAppData = enlargeBuffer(peerAppData, appBufferSize);
break;
case BUFFER_UNDERFLOW:
// Will occur either when no data was read from the peer or when the peerNetData buffer
// was too small to hold all peer's data.
peerNetData = handleBufferUnderflow(sslEngine, peerNetData);
break;
case CLOSED:
if (sslEngine.isOutboundDone()) {
return new HandshakeHolder(peerAppData, peerNetData, false);
} else {
sslEngine.closeOutbound();
}
break;
default:
throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
}
return new HandshakeHolder(peerAppData, peerNetData, true);
}
public static SSLEngineResult.HandshakeStatus doHandshake(SSLEngine tlsEngine,
ByteBuffer netDataBuf,
OutputStream out,
InputStream in){
try {
ByteBuffer empty;
/*Apparently on Android 4.4 (API_19) SSLEngine whines about BUFFER_OVERFLOW for this
buffer even though nothing ever gets written to it*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH){
empty = ByteBuffer.allocate(0);
} else {
empty = ByteBuffer.allocate(tlsEngine.getSession().getApplicationBufferSize());
}
// ClientHello -> netDataBuf
tlsEngine.wrap(empty, netDataBuf);
netDataBuf.flip();
byte[] clientHello = new byte[netDataBuf.limit()];
netDataBuf.get(clientHello);
out.write(ConnectionHelper.intToByteArray(clientHello.length));
out.write(clientHello);
// netDataBuf <- ServerHello..ServerHelloDone
int serverHelloSize = ByteBuffer.wrap(ConnectionHelper.readAll(4, in)).getInt();
byte[] serverHello = ConnectionHelper.readAll(serverHelloSize, in);
netDataBuf.clear();
netDataBuf.put(serverHello);
netDataBuf.flip();
SSLEngineResult result = tlsEngine.unwrap(netDataBuf, empty);
while (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP){
result = tlsEngine.unwrap(netDataBuf, empty);
}
Runnable task = tlsEngine.getDelegatedTask();
while (task != null){
task.run();
task = tlsEngine.getDelegatedTask();
}
// [client]Certificate*..ClientKeyExchange..Finished -> netDataBuf
netDataBuf.clear();
result = tlsEngine.wrap(empty, netDataBuf);
while (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP){
result = tlsEngine.wrap(empty, netDataBuf);
}
netDataBuf.flip();
byte[] clientKeyExchange = new byte[netDataBuf.limit()];
netDataBuf.get(clientKeyExchange);
out.write(ConnectionHelper.intToByteArray(clientKeyExchange.length));
out.write(clientKeyExchange);
// netDataBuf <- ChangeCipherSpec..Finished
int serverChangeCipherSpecSize = ByteBuffer.wrap(ConnectionHelper.readAll(4, in)).getInt();
byte[] serverChangeCipherSpec = ConnectionHelper.readAll(serverChangeCipherSpecSize, in);
netDataBuf.clear();
netDataBuf.put(serverChangeCipherSpec);
netDataBuf.flip();
result = tlsEngine.unwrap(netDataBuf, empty);
while (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP){
result = tlsEngine.unwrap(netDataBuf, empty);
}
/*On Android 8.1 (LineageOS 15.1) on a Xiaomi device (not sure about others) the
SSL_ENGINE may return NEED_WRAP here, so we do that even though no data gets written
by the SSL_ENGINE ???*/
if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
netDataBuf.clear();
result = tlsEngine.wrap(empty, netDataBuf);
// netDataBuf still empty here...
}
/*Apparently on Android 4.4 (API_19) with SSLEngine the latest call tlsEngine.unwrap(..)
that finishes the handshake returns NOT_HANDSHAKING instead of FINISHED as the result*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH){
return result.getHandshakeStatus();
} else {
if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING){
return SSLEngineResult.HandshakeStatus.FINISHED;
} else if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
// just in case
return result.getHandshakeStatus();
} else {
return null;
}
}
} catch (IOException e){
return null;
}
}