public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { if( channel == null || sslEngine == null || exec == null ) throw new IllegalArgumentException( "parameter must not be null" ); this.socketChannel = channel; this.sslEngine = sslEngine; this.exec = exec; readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs tasks = new ArrayList<Future<?>>( 3 ); if( key != null ) { key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); this.selectionKey = key; } createBuffers( sslEngine.getSession() ); // kick off handshake socketChannel.write( wrap( emptybuffer ) );// initializes res processHandshake(); }
private SSLEngineResult newResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { // If isOutboundDone, then the data from the network BIO // was the close_notify message and all was consumed we are not required to wait // for the receipt the peer's close_notify message -- shutdown. if (isOutboundDone()) { if (isInboundDone()) { // If the inbound was done as well, we need to ensure we return NOT_HANDSHAKING to signal we are done. hs = NOT_HANDSHAKING; // As the inbound and the outbound is done we can shutdown the engine now. shutdown(); } return new SSLEngineResult(CLOSED, hs, bytesConsumed, bytesProduced); } return new SSLEngineResult(status, hs, bytesConsumed, bytesProduced); }
private SSLEngineResult newResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { // If isOutboundDone, then the data from the network BIO // was the close_notify message and all was consumed we are not required to wait // for the receipt the peer's close_notify message -- shutdown. if (isOutboundDone()) { if (isInboundDone()) { // If the inbound was done as well, we need to ensure we return NOT_HANDSHAKING to signal we are done. hs = NOT_HANDSHAKING; // As the inbound and the outbound is done we can shutdown the engine now. shutdown(); } return new SSLEngineResult(CLOSED, hs, bytesConsumed, bytesProduced); } return new SSLEngineResult(status, hs, bytesConsumed, bytesProduced); }
@Override SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) throws SSLException { int writerIndex = out.writerIndex(); ByteBuffer inNioBuffer = toByteBuffer(in, readerIndex, len); int position = inNioBuffer.position(); final SSLEngineResult result = handler.engine.unwrap(inNioBuffer, toByteBuffer(out, writerIndex, out.writableBytes())); out.writerIndex(writerIndex + result.bytesProduced()); // This is a workaround for a bug in Android 5.0. Android 5.0 does not correctly update the // SSLEngineResult.bytesConsumed() in some cases and just return 0. // // See: // - https://android-review.googlesource.com/c/platform/external/conscrypt/+/122080 // - https://github.com/netty/netty/issues/7758 if (result.bytesConsumed() == 0) { int consumed = inNioBuffer.position() - position; if (consumed != result.bytesConsumed()) { // Create a new SSLEngineResult with the correct bytesConsumed(). return new SSLEngineResult( result.getStatus(), result.getHandshakeStatus(), consumed, result.bytesProduced()); } } return result; }
@Override SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) throws SSLException { int writerIndex = out.writerIndex(); ByteBuffer inNioBuffer = toByteBuffer(in, readerIndex, len); int position = inNioBuffer.position(); final SSLEngineResult result = handler.engine.unwrap(inNioBuffer, toByteBuffer(out, writerIndex, out.writableBytes())); out.writerIndex(writerIndex + result.bytesProduced()); // This is a workaround for a bug in Android 5.0. Android 5.0 does not correctly update the // SSLEngineResult.bytesConsumed() in some cases and just return 0. // // See: // - https://android-review.googlesource.com/c/platform/external/conscrypt/+/122080 // - https://github.com/netty/netty/issues/7758 if (result.bytesConsumed() == 0) { int consumed = inNioBuffer.position() - position; if (consumed != result.bytesConsumed()) { // Create a new SSLEngineResult with the correct bytesConsumed(). return new SSLEngineResult( result.getStatus(), result.getHandshakeStatus(), consumed, result.bytesProduced()); } } return result; }
@Test public void handshakeUsesBufferParameter() throws Exception { SocketChannel mockChannel = mock(SocketChannel.class); when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); Socket mockSocket = mock(Socket.class); when(mockChannel.socket()).thenReturn(mockSocket); when(mockSocket.isClosed()).thenReturn(false); // initial read of handshake status followed by read of handshake status after task execution when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP, NEED_WRAP); // interleaved wraps/unwraps/task-execution when(mockEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(OK, NEED_WRAP, 100, 100), new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, 0, 0), new SSLEngineResult(OK, NEED_TASK, 100, 0)); when(mockEngine.getDelegatedTask()).thenReturn(() -> { }, (Runnable) null); when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(OK, NEED_UNWRAP, 100, 100), new SSLEngineResult(BUFFER_OVERFLOW, NEED_WRAP, 0, 0), new SSLEngineResult(CLOSED, FINISHED, 100, 0)); ByteBuffer byteBuffer = ByteBuffer.allocate(netBufferSize); spyNioSslEngine.handshake(mockChannel, 10000, byteBuffer); assertThat(spyNioSslEngine.handshakeBuffer).isSameAs(byteBuffer); }
@Test public void closeWhenSocketWriteError() throws Exception { SocketChannel mockChannel = mock(SocketChannel.class); Socket mockSocket = mock(Socket.class); when(mockChannel.socket()).thenReturn(mockSocket); when(mockSocket.isClosed()).thenReturn(true); when(mockEngine.isOutboundDone()).thenReturn(Boolean.FALSE); when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenAnswer((x) -> { // give the NioSslEngine something to write on its socket channel, simulating a TLS close // message nioSslEngine.myNetData.put("Goodbye cruel world".getBytes()); return new SSLEngineResult(CLOSED, FINISHED, 0, 0); }); when(mockChannel.write(any(ByteBuffer.class))).thenThrow(new ClosedChannelException()); nioSslEngine.close(mockChannel); verify(mockChannel, times(1)).write(any(ByteBuffer.class)); }
@Test public void closeWhenUnwrapError() throws Exception { SocketChannel mockChannel = mock(SocketChannel.class); Socket mockSocket = mock(Socket.class); when(mockChannel.socket()).thenReturn(mockSocket); when(mockSocket.isClosed()).thenReturn(true); when(mockEngine.isOutboundDone()).thenReturn(Boolean.FALSE); when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(BUFFER_OVERFLOW, FINISHED, 0, 0)); assertThatThrownBy(() -> nioSslEngine.close(mockChannel)).isInstanceOf(GemFireIOException.class) .hasMessageContaining("exception closing SSL session") .hasCauseInstanceOf(SSLException.class); }
@Test(expected = IllegalStateException.class) public void checkClosedThrows() throws Exception { when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(CLOSED, FINISHED, 0, 100)); nioSslEngine.close(mock(SocketChannel.class)); nioSslEngine.checkClosed(); }
@Test public void close() throws Exception { SocketChannel mockChannel = mock(SocketChannel.class); Socket mockSocket = mock(Socket.class); when(mockChannel.socket()).thenReturn(mockSocket); when(mockSocket.isClosed()).thenReturn(false); when(mockEngine.isOutboundDone()).thenReturn(Boolean.FALSE); when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(CLOSED, FINISHED, 0, 0)); nioSslEngine.close(mockChannel); assertThatThrownBy(() -> nioSslEngine.checkClosed()).isInstanceOf(IllegalStateException.class); nioSslEngine.close(mockChannel); }
@Test public void handshakeDoesNotTerminateWithFinished() throws Exception { SocketChannel mockChannel = mock(SocketChannel.class); when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); Socket mockSocket = mock(Socket.class); when(mockChannel.socket()).thenReturn(mockSocket); when(mockSocket.isClosed()).thenReturn(false); // initial read of handshake status followed by read of handshake status after task execution when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP); // interleaved wraps/unwraps/task-execution when(mockEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(OK, NEED_WRAP, 100, 100)); when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 100, 0)); ByteBuffer byteBuffer = ByteBuffer.allocate(netBufferSize); assertThatThrownBy(() -> spyNioSslEngine.handshake(mockChannel, 10000, byteBuffer)) .isInstanceOf( SSLHandshakeException.class) .hasMessageContaining("SSL Handshake terminated with status"); }
@Test public void unwrapWithBufferUnderflow() throws Exception { ByteBuffer wrappedData = ByteBuffer.allocate(nioSslEngine.peerAppData.capacity()); byte[] netBytes = new byte[wrappedData.capacity() / 2]; Arrays.fill(netBytes, (byte) 0x1F); wrappedData.put(netBytes); wrappedData.flip(); // create an engine that will transfer bytes from the application buffer to the encrypted buffer TestSSLEngine testEngine = new TestSSLEngine(); testEngine.addReturnResult(new SSLEngineResult(BUFFER_UNDERFLOW, NEED_TASK, 0, 0)); spyNioSslEngine.engine = testEngine; ByteBuffer unwrappedBuffer = spyNioSslEngine.unwrap(wrappedData); unwrappedBuffer.flip(); assertThat(unwrappedBuffer.remaining()).isEqualTo(0); assertThat(wrappedData.position()).isEqualTo(netBytes.length); }
private SSLEngineResult sslReadErrorResult(int error, int stackError, int bytesConsumed, int bytesProduced) throws SSLException { // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the // BIO first or can just shutdown and throw it now. // This is needed so we ensure close_notify etc is correctly send to the remote peer. // See https://github.com/netty/netty/issues/3900 if (SSL.bioLengthNonApplication(networkBIO) > 0) { if (handshakeException == null && handshakeState != HandshakeState.FINISHED) { // we seems to have data left that needs to be transferred and so the user needs // call wrap(...). Store the error so we can pick it up later. handshakeException = new SSLHandshakeException(SSL.getErrorString(stackError)); } // We need to clear all errors so we not pick up anything that was left on the stack on the next // operation. Note that shutdownWithError(...) will cleanup the stack as well so its only needed here. SSL.clearError(); return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); } throw shutdownWithError("SSL_read", error, stackError); }
@Test public void handshake() throws Exception { SocketChannel mockChannel = mock(SocketChannel.class); when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); Socket mockSocket = mock(Socket.class); when(mockChannel.socket()).thenReturn(mockSocket); when(mockSocket.isClosed()).thenReturn(false); // initial read of handshake status followed by read of handshake status after task execution when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP, NEED_WRAP); // interleaved wraps/unwraps/task-execution when(mockEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(OK, NEED_WRAP, 100, 100), new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, 0, 0), new SSLEngineResult(OK, NEED_TASK, 100, 0)); when(mockEngine.getDelegatedTask()).thenReturn(() -> { }, (Runnable) null); when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( new SSLEngineResult(OK, NEED_UNWRAP, 100, 100), new SSLEngineResult(BUFFER_OVERFLOW, NEED_WRAP, 0, 0), new SSLEngineResult(CLOSED, FINISHED, 100, 0)); spyNioSslEngine.handshake(mockChannel, 10000, ByteBuffer.allocate(netBufferSize / 2)); verify(mockEngine, atLeast(2)).getHandshakeStatus(); verify(mockEngine, times(3)).wrap(any(ByteBuffer.class), any(ByteBuffer.class)); verify(mockEngine, times(3)).unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); verify(spyNioSslEngine, times(2)).expandWriteBuffer(any(Buffers.BufferType.class), any(ByteBuffer.class), any(Integer.class), any(DMStats.class)); verify(spyNioSslEngine, times(1)).handleBlockingTasks(); verify(mockChannel, times(3)).read(any(ByteBuffer.class)); }
@Test public void unwrapWithBufferOverflow() throws Exception { // make the application data too big to fit into the engine's encryption buffer ByteBuffer wrappedData = ByteBuffer.allocate(nioSslEngine.peerAppData.capacity() + 100); byte[] netBytes = new byte[wrappedData.capacity()]; Arrays.fill(netBytes, (byte) 0x1F); wrappedData.put(netBytes); wrappedData.flip(); // create an engine that will transfer bytes from the application buffer to the encrypted buffer TestSSLEngine testEngine = new TestSSLEngine(); spyNioSslEngine.engine = testEngine; testEngine.addReturnResult( new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, netBytes.length, netBytes.length), new SSLEngineResult(OK, FINISHED, netBytes.length, netBytes.length)); ByteBuffer unwrappedBuffer = spyNioSslEngine.unwrap(wrappedData); unwrappedBuffer.flip(); verify(spyNioSslEngine, times(2)).expandPeerAppData(any(ByteBuffer.class)); assertThat(unwrappedBuffer).isEqualTo(ByteBuffer.wrap(netBytes)); }
@Test public void wrap() throws Exception { // make the application data too big to fit into the engine's encryption buffer ByteBuffer appData = ByteBuffer.allocate(nioSslEngine.myNetData.capacity() + 100); byte[] appBytes = new byte[appData.capacity()]; Arrays.fill(appBytes, (byte) 0x1F); appData.put(appBytes); appData.flip(); // create an engine that will transfer bytes from the application buffer to the encrypted buffer TestSSLEngine testEngine = new TestSSLEngine(); testEngine.addReturnResult( new SSLEngineResult(OK, NEED_TASK, appData.remaining(), appData.remaining())); spyNioSslEngine.engine = testEngine; ByteBuffer wrappedBuffer = spyNioSslEngine.wrap(appData); verify(spyNioSslEngine, times(1)).expandWriteBuffer(any(Buffers.BufferType.class), any(ByteBuffer.class), any(Integer.class), any(DMStats.class)); appData.flip(); assertThat(wrappedBuffer).isEqualTo(appData); verify(spyNioSslEngine, times(1)).handleBlockingTasks(); }
@Test public void wrapFails() { // make the application data too big to fit into the engine's encryption buffer ByteBuffer appData = ByteBuffer.allocate(nioSslEngine.myNetData.capacity() + 100); byte[] appBytes = new byte[appData.capacity()]; Arrays.fill(appBytes, (byte) 0x1F); appData.put(appBytes); appData.flip(); // create an engine that will transfer bytes from the application buffer to the encrypted buffer TestSSLEngine testEngine = new TestSSLEngine(); testEngine.addReturnResult( new SSLEngineResult(CLOSED, NEED_TASK, appData.remaining(), appData.remaining())); spyNioSslEngine.engine = testEngine; assertThatThrownBy(() -> spyNioSslEngine.wrap(appData)).isInstanceOf(SSLException.class) .hasMessageContaining("Error encrypting data"); }
@Test public void unwrapWithDecryptionError() { // make the application data too big to fit into the engine's encryption buffer ByteBuffer wrappedData = ByteBuffer.allocate(nioSslEngine.peerAppData.capacity()); byte[] netBytes = new byte[wrappedData.capacity() / 2]; Arrays.fill(netBytes, (byte) 0x1F); wrappedData.put(netBytes); wrappedData.flip(); // create an engine that will transfer bytes from the application buffer to the encrypted buffer TestSSLEngine testEngine = new TestSSLEngine(); testEngine.addReturnResult(new SSLEngineResult(CLOSED, FINISHED, 0, 0)); spyNioSslEngine.engine = testEngine; assertThatThrownBy(() -> spyNioSslEngine.unwrap(wrappedData)).isInstanceOf(SSLException.class) .hasMessageContaining("Error decrypting data"); }
@Test public void readAtLeast() throws Exception { final int amountToRead = 150; final int individualRead = 60; final int preexistingBytes = 10; ByteBuffer wrappedBuffer = ByteBuffer.allocate(1000); SocketChannel mockChannel = mock(SocketChannel.class); // force a compaction by making the decoded buffer appear near to being full ByteBuffer unwrappedBuffer = nioSslEngine.peerAppData; unwrappedBuffer.position(unwrappedBuffer.capacity() - individualRead); unwrappedBuffer.limit(unwrappedBuffer.position() + preexistingBytes); // simulate some socket reads when(mockChannel.read(any(ByteBuffer.class))).thenAnswer(new Answer<Integer>() { @Override public Integer answer(InvocationOnMock invocation) throws Throwable { ByteBuffer buffer = invocation.getArgument(0); buffer.position(buffer.position() + individualRead); return individualRead; } }); TestSSLEngine testSSLEngine = new TestSSLEngine(); testSSLEngine.addReturnResult(new SSLEngineResult(OK, NEED_UNWRAP, 0, 0)); nioSslEngine.engine = testSSLEngine; ByteBuffer data = nioSslEngine.readAtLeast(mockChannel, amountToRead, wrappedBuffer, mockStats); verify(mockChannel, times(3)).read(isA(ByteBuffer.class)); assertThat(data.position()).isEqualTo(0); assertThat(data.limit()).isEqualTo(individualRead * 3 + preexistingBytes); }
private SSLEngineResult sslReadErrorResult(int err, int bytesConsumed, int bytesProduced) throws SSLException { // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the // BIO first or can just shutdown and throw it now. // This is needed so we ensure close_notify etc is correctly send to the remote peer. // See https://github.com/netty/netty/issues/3900 if (SSL.bioLengthNonApplication(networkBIO) > 0) { if (handshakeException == null && handshakeState != HandshakeState.FINISHED) { // we seems to have data left that needs to be transfered and so the user needs // call wrap(...). Store the error so we can pick it up later. handshakeException = new SSLHandshakeException(SSL.getErrorString(err)); } // We need to clear all errors so we not pick up anything that was left on the stack on the next // operation. Note that shutdownWithError(...) will cleanup the stack as well so its only needed here. SSL.clearError(); return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); } throw shutdownWithError("SSL_read", SSL.getErrorString(err)); }