Skip to content

Commit d14a456

Browse files
author
Daniel Mewes
committed
Squashed commit of @jlhawn's TLS patch from #5381
Also fixed compilation in DEBUG mode (including unit tests).
1 parent a8728db commit d14a456

29 files changed

+1044
-141
lines changed

configure

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ init () {
2424
must_fetch_list='bluebird v8'
2525
please_fetch_list="handlebars gtest re2 $must_fetch_list"
2626

27-
optional_libs="gtest termcap boost_system ssl"
28-
required_libs="protobuf v8 re2 z crypto curl"
27+
optional_libs="gtest termcap boost_system"
28+
required_libs="protobuf v8 re2 z crypto ssl curl"
2929
other_libs="unwind tcmalloc jemalloc"
3030
all_libs="$required_libs $optional_libs $other_libs"
3131
default_static="tcmalloc jemalloc"
@@ -863,6 +863,7 @@ int main(){
863863
864864
#include <openssl/evp.h>
865865
#include <openssl/crypto.h>
866+
#include <openssl/ssl.h>
866867
#include <openssl/bn.h>
867868
int main(){ return 0; }
868869

src/arch/io/network.cc

Lines changed: 342 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,330 @@ void linux_tcp_conn_t::on_event(int /* events */) {
691691
event_watcher->stop_watching_for_errors();
692692
}
693693

694+
tls_conn_wrapper_t::tls_conn_wrapper_t(SSL_CTX *tls_ctx)
695+
THROWS_ONLY(linux_tcp_conn_t::connect_failed_exc_t) {
696+
ERR_clear_error();
697+
698+
conn = SSL_new(tls_ctx);
699+
if (nullptr == conn) {
700+
unsigned long err_code = ERR_get_error();
701+
702+
throw linux_tcp_conn_t::connect_failed_exc_t(
703+
err_code, ERR_error_string(err_code, nullptr));
704+
}
705+
706+
// Add support for partial writes.
707+
SSL_set_mode(conn, SSL_MODE_ENABLE_PARTIAL_WRITE);
708+
}
709+
710+
tls_conn_wrapper_t::~tls_conn_wrapper_t() {
711+
SSL_free(conn);
712+
}
713+
714+
// Set the underlying IO.
715+
void tls_conn_wrapper_t::set_fd(fd_t sock)
716+
THROWS_ONLY(linux_tcp_conn_t::connect_failed_exc_t) {
717+
if (0 == SSL_set_fd(conn, sock)) {
718+
unsigned long err_code = ERR_get_error();
719+
throw linux_tcp_conn_t::connect_failed_exc_t(
720+
err_code, ERR_error_string(err_code, nullptr));
721+
}
722+
}
723+
724+
/* This is the client version of the constructor. The base class constructor
725+
will establish a TCP connection to the peer at the given host:port and then we
726+
wrap the tcp connection in TLS using the configuration in the given tls_ctx. */
727+
linux_secure_tcp_conn_t::linux_secure_tcp_conn_t(
728+
SSL_CTX *tls_ctx, const ip_address_t &host, int port,
729+
signal_t *interruptor, int local_port) THROWS_ONLY(connect_failed_exc_t, interrupted_exc_t) :
730+
linux_tcp_conn_t(host, port, interruptor, local_port),
731+
conn(tls_ctx) {
732+
733+
conn.set_fd(sock.get());
734+
SSL_set_connect_state(conn.get());
735+
perform_handshake(interruptor);
736+
}
737+
738+
/* This is the server version of the constructor */
739+
linux_secure_tcp_conn_t::linux_secure_tcp_conn_t(
740+
SSL_CTX *tls_ctx, fd_t _sock, signal_t *interruptor)
741+
THROWS_ONLY(connect_failed_exc_t, interrupted_exc_t) :
742+
linux_tcp_conn_t(_sock),
743+
conn(tls_ctx) {
744+
745+
conn.set_fd(sock.get());
746+
SSL_set_accept_state(conn.get());
747+
perform_handshake(interruptor);
748+
}
749+
750+
linux_secure_tcp_conn_t::~linux_secure_tcp_conn_t() THROWS_NOTHING {
751+
assert_thread();
752+
753+
if (is_open()) shutdown();
754+
}
755+
756+
void linux_secure_tcp_conn_t::perform_handshake(signal_t *interruptor)
757+
THROWS_ONLY(linux_tcp_conn_t::connect_failed_exc_t, interrupted_exc_t) {
758+
// Perform TLS handshake.
759+
while (true) {
760+
ERR_clear_error();
761+
int ret = SSL_do_handshake(conn.get());
762+
763+
if (ret > 0) {
764+
return; // Successful TLS handshake.
765+
}
766+
767+
if (ret == 0) {
768+
// The handshake failed but the connection shut down cleanly.
769+
throw linux_tcp_conn_t::connect_failed_exc_t(
770+
0, "TLS handshake failed, shutdown cleanly");
771+
}
772+
773+
switch (SSL_get_error(conn.get(), ret)) {
774+
case SSL_ERROR_WANT_READ:
775+
/* The handshake needs to read data, but the underlying I/O has no data
776+
ready to read. Wait for it to be ready or for an interrupt signal. */
777+
{
778+
linux_event_watcher_t::watch_t watch(get_event_watcher(), poll_event_in);
779+
wait_interruptible(&watch, interruptor);
780+
}
781+
break;
782+
case SSL_ERROR_WANT_WRITE:
783+
/* The handshake needs to write data, but the underlying I/O is not ready
784+
to write. Wait for it to be ready or for an interrupt signal. */
785+
{
786+
linux_event_watcher_t::watch_t watch(get_event_watcher(), poll_event_out);
787+
wait_interruptible(&watch, interruptor);
788+
}
789+
break;
790+
default:
791+
// Some other error with the underlying I/O.
792+
unsigned long err_code = ERR_get_error();
793+
throw linux_tcp_conn_t::connect_failed_exc_t(
794+
err_code, ERR_error_string(err_code, nullptr));
795+
}
796+
797+
if (interruptor->is_pulsed()) {
798+
// The handshake cannot continue because we need to shutdown now.
799+
throw interrupted_exc_t();
800+
}
801+
802+
/* Go around the loop and try to complete the handshake. */
803+
}
804+
}
805+
806+
size_t linux_secure_tcp_conn_t::read_internal(void *buffer, size_t size)
807+
THROWS_ONLY(tcp_conn_read_closed_exc_t) {
808+
assert_thread();
809+
rassert(!closed.is_pulsed());
810+
811+
while(true) {
812+
ERR_clear_error();
813+
814+
int ret = SSL_read(conn.get(), buffer, size);
815+
816+
if (ret > 0) {
817+
return ret; // Operation successful, returns number of bytes read.
818+
}
819+
820+
switch (SSL_get_error(conn.get(), ret)) {
821+
case SSL_ERROR_ZERO_RETURN:
822+
// Indicates that the peer has sent the "close notify" alert. The
823+
// shutdown state is currently SSL_RECEIVED_SHUTDOWN. We must now
824+
// send our "close notify" alert.
825+
shutdown();
826+
throw tcp_conn_read_closed_exc_t();
827+
case SSL_ERROR_WANT_READ:
828+
/* The underlying I/O has no data ready to read. Wait for it to be
829+
ready or for someone to send a close signal. */
830+
{
831+
linux_event_watcher_t::watch_t watch(
832+
get_event_watcher(), poll_event_in);
833+
wait_any_t waiter(&watch, &closed);
834+
waiter.wait_lazily_unordered();
835+
}
836+
break;
837+
case SSL_ERROR_WANT_WRITE:
838+
/* Though we are reading, a TLS renegotiation may occur at any time
839+
requiring a write. Wait for the underyling I/O to be ready for a
840+
write, or for someone to send a close signal. */
841+
{
842+
linux_event_watcher_t::watch_t watch(
843+
get_event_watcher(), poll_event_out);
844+
wait_any_t waiter(&watch, &closed);
845+
waiter.wait_lazily_unordered();
846+
}
847+
break;
848+
default:
849+
// Some other error. Assume that the connection is unusable.
850+
on_shutdown();
851+
throw tcp_conn_read_closed_exc_t();
852+
}
853+
854+
if (closed.is_pulsed()) {
855+
/* We were closed for whatever reason. Whatever signalled us has
856+
already called on_shutdown(). */
857+
throw tcp_conn_read_closed_exc_t();
858+
}
859+
860+
/* Go around the loop and try to read again */
861+
}
862+
}
863+
864+
void linux_secure_tcp_conn_t::perform_write(const void *buffer, size_t size) {
865+
assert_thread();
866+
867+
if (closed.is_pulsed()) {
868+
/* The connection was closed, but there are still operations in the
869+
write queue; we are one of those operations. Just don't do anything. */
870+
return;
871+
}
872+
873+
// Loop for retrying if the underlying socket would block and to retry on
874+
// partial writes.
875+
while (size > 0) {
876+
ERR_clear_error();
877+
878+
int ret = SSL_write(conn.get(), buffer, size);
879+
880+
if (ret > 0) {
881+
// Operation successful, returns number of bytes written.
882+
rassert(static_cast<size_t>(ret) <= size);
883+
size -= ret;
884+
885+
// Slide down the buffer.
886+
buffer = reinterpret_cast<const void *>(
887+
reinterpret_cast<const char *>(buffer) + ret);
888+
889+
if (write_perfmon) write_perfmon->record(ret);
890+
891+
// Go around the loop again if there is more data to write.
892+
continue;
893+
}
894+
895+
switch (SSL_get_error(conn.get(), ret)) {
896+
case SSL_ERROR_ZERO_RETURN:
897+
// Indicates that the peer has sent the "close notify" alert. The
898+
// shutdown state is currently SSL_RECEIVED_SHUTDOWN. We must now
899+
// send our "close notify" alert.
900+
shutdown();
901+
return;
902+
case SSL_ERROR_WANT_READ:
903+
/* Though we are writing, a TLS renegotiation may occur at any time
904+
requiring a read. Wait for the underyling I/O to be ready for a
905+
read, or for someone to send a close signal. */
906+
{
907+
linux_event_watcher_t::watch_t watch(get_event_watcher(), poll_event_in);
908+
wait_any_t waiter(&watch, &closed);
909+
waiter.wait_lazily_unordered();
910+
}
911+
break;
912+
case SSL_ERROR_WANT_WRITE:
913+
/* The underlying I/O is not ready to accept a write. Wait for it
914+
to be ready or for someone to send a close signal. */
915+
{
916+
linux_event_watcher_t::watch_t watch(
917+
get_event_watcher(), poll_event_out);
918+
wait_any_t waiter(&watch, &closed);
919+
waiter.wait_lazily_unordered();
920+
}
921+
break;
922+
default:
923+
// Some other error. Assume that the connection is unusable.
924+
on_shutdown();
925+
return;
926+
}
927+
928+
if (closed.is_pulsed()) {
929+
/* We were closed for whatever reason. Whatever signalled
930+
us has already called on_shutdown(). */
931+
throw tcp_conn_read_closed_exc_t();
932+
}
933+
934+
/* Go around the loop and try to read again */
935+
}
936+
}
937+
938+
void linux_secure_tcp_conn_t::shutdown() {
939+
assert_thread();
940+
941+
// Wait at most 5 seconds for the orderly shutdown. If it doesn't complete by then,
942+
// we simply shutdown the socket.
943+
signal_timer_t shutdown_timeout(5000);
944+
945+
bool skip_shutdown = false; // Set to true if TLS shutdown encounters an error.
946+
947+
// If we have not already received a "close notify" alert from the peer,
948+
// then we should send one. If we have received one, this loop will respond
949+
// with our own "close notify" alert.
950+
while (!(skip_shutdown || shutdown_timeout.is_pulsed())) {
951+
ERR_clear_error();
952+
953+
int ret = SSL_shutdown(conn.get());
954+
955+
if (ret > 0) {
956+
// "close notify" has been sent and received.
957+
break;
958+
}
959+
960+
if (ret == 0) {
961+
// "close notify" has been sent but not yet received from peer.
962+
continue;
963+
}
964+
965+
switch(SSL_get_error(conn.get(), ret)) {
966+
case SSL_ERROR_WANT_READ:
967+
{
968+
/* The shutdown needs to read data, but the underlying I/O has no data
969+
ready to read. Wait for it to be ready or for a timeout. */
970+
linux_event_watcher_t::watch_t watch(get_event_watcher(), poll_event_in);
971+
wait_any_t waiter(&watch, &shutdown_timeout);
972+
waiter.wait_lazily_unordered();
973+
}
974+
continue;
975+
case SSL_ERROR_WANT_WRITE:
976+
{
977+
/* The handshake needs to write data, but the underlying I/O is not ready
978+
to write. Wait for it to be ready or for a timeout. */
979+
linux_event_watcher_t::watch_t watch(
980+
get_event_watcher(), poll_event_out);
981+
wait_any_t waiter(&watch, &shutdown_timeout);
982+
waiter.wait_lazily_unordered();
983+
}
984+
continue;
985+
default:
986+
// Unable to perform clean shutdown. Just skip it.
987+
skip_shutdown = true;
988+
}
989+
}
990+
991+
// Shutdown the underlying TCP connection.
992+
int res = ::shutdown(sock.get(), SHUT_RDWR);
993+
if (res != 0 && get_errno() != ENOTCONN) {
994+
logERR(
995+
"Could not shutdown socket for reading and writing: %s",
996+
errno_string(get_errno()).c_str());
997+
}
998+
999+
on_shutdown();
1000+
}
1001+
1002+
/* It is not possible to close only the read or write side of a TLS connection
1003+
so we use only a single shutdown method which attempts to shutdown the TLS
1004+
before shutting down the underlying tcp connection */
1005+
void linux_secure_tcp_conn_t::on_shutdown() {
1006+
assert_thread();
1007+
1008+
rassert(!closed.is_pulsed());
1009+
rassert(!read_closed.is_pulsed());
1010+
rassert(!write_closed.is_pulsed());
1011+
1012+
closed.pulse();
1013+
read_closed.pulse();
1014+
write_closed.pulse();
1015+
}
1016+
1017+
6941018
linux_tcp_conn_descriptor_t::linux_tcp_conn_descriptor_t(fd_t fd) : fd_(fd) {
6951019
rassert(fd != -1);
6961020
}
@@ -699,13 +1023,27 @@ linux_tcp_conn_descriptor_t::~linux_tcp_conn_descriptor_t() {
6991023
rassert(fd_ == -1);
7001024
}
7011025

702-
void linux_tcp_conn_descriptor_t::make_overcomplicated(scoped_ptr_t<linux_tcp_conn_t> *tcp_conn) {
703-
tcp_conn->init(new linux_tcp_conn_t(fd_));
1026+
void linux_tcp_conn_descriptor_t::make_server_connection(
1027+
SSL_CTX *tls_ctx, scoped_ptr_t<linux_tcp_conn_t> *tcp_conn, signal_t *closer
1028+
) THROWS_ONLY(linux_tcp_conn_t::connect_failed_exc_t, interrupted_exc_t) {
1029+
fd_t sock = fd_;
7041030
fd_ = -1;
1031+
1032+
if (tls_ctx == nullptr) {
1033+
tcp_conn->init(new linux_tcp_conn_t(sock));
1034+
} else {
1035+
tcp_conn->init(new linux_secure_tcp_conn_t(tls_ctx, sock, closer));
1036+
}
7051037
}
7061038

707-
void linux_tcp_conn_descriptor_t::make_overcomplicated(linux_tcp_conn_t **tcp_conn_out) {
708-
*tcp_conn_out = new linux_tcp_conn_t(fd_);
1039+
void linux_tcp_conn_descriptor_t::make_server_connection(
1040+
SSL_CTX *tls_ctx, linux_tcp_conn_t **tcp_conn_out, signal_t *closer
1041+
) THROWS_ONLY(linux_tcp_conn_t::connect_failed_exc_t, interrupted_exc_t) {
1042+
if (tls_ctx == nullptr) {
1043+
*tcp_conn_out = new linux_tcp_conn_t(fd_);
1044+
} else {
1045+
*tcp_conn_out = new linux_secure_tcp_conn_t(tls_ctx, fd_, closer);
1046+
}
7091047
fd_ = -1;
7101048
}
7111049

0 commit comments

Comments
 (0)