diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f55fdebb..cb0e9603 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,11 @@ if (ISAL_LIBRARIES) add_definitions(-DWITH_ISAL) endif (ISAL_LIBRARIES) +find_package(OpenSSL) +if (OPENSSL_FOUND) + add_definitions(-DWITH_OPENSSL) +endif (OPENSSL_FOUND) + add_custom_target(build_tests) add_custom_target(test COMMAND diff --git a/src/http_client.cpp b/src/http_client.cpp index d0ec9a49..05c3acda 100644 --- a/src/http_client.cpp +++ b/src/http_client.cpp @@ -27,10 +27,19 @@ static void parse_http_headers(std::string & res, http_response_t *parsed); struct http_co_t { +#ifdef WITH_OPENSSL + static SSL_CTX *ssl_ctx = NULL; + SSL *ssl_cli = NULL; + BIO *ssl_rbio = NULL; + BIO *ssl_wbio = NULL; + std::vector encrypted_out; +#endif + timerfd_manager_t *tfd; std::function response_callback; int request_timeout = 0; + bool ssl = false; std::string host; std::string request; std::string ws_outbox; @@ -46,7 +55,7 @@ struct http_co_t int timeout_id = -1; int epoll_events = 0; int sent = 0; - std::vector rbuf; + std::vector rbuf; iovec read_iov, send_iov; msghdr read_msg = { 0 }, send_msg = { 0 }; http_response_t parsed; @@ -259,6 +268,12 @@ void http_response_t::parse_json_response(std::string & error, json11::Json & r) http_co_t::~http_co_t() { +#ifdef WITH_OPENSSL + if (ssl_cli) + { + SSL_free(ssl_cli); + } +#endif close_connection(); } @@ -275,6 +290,16 @@ void http_co_t::close_connection() close(peer_fd); peer_fd = -1; } +#ifdef WITH_OPENSSL + if (ssl_ctx) + { + // Frees context, client and bios at once + SSL_free(ssl_ctx); + ssl_rbio = NULL; + ssl_wbio = NULL; + ssl_cli = NULL; + } +#endif state = HTTP_CO_CLOSED; connected_host = ""; response = ""; @@ -304,6 +329,27 @@ void http_co_t::start_connection() } fcntl(peer_fd, F_SETFL, fcntl(peer_fd, F_GETFL, 0) | O_NONBLOCK); epoll_events = 0; +#ifdef WITH_OPENSSL + // https://wiki.openssl.org/index.php/Hostname_validation + if (ssl) + { + if (!ssl_ctx) + ssl_ctx = SSL_CTX_new(TLS_method()); + ssl_rbio = BIO_new(BIO_s_mem()); + ssl_wbio = BIO_new(BIO_s_mem()); + ssl_cli = SSL_new(ssl_ctx); + if (!ssl_ctx || !ssl_cli || !ssl_rbio || !ssl_wbio) + { + parsed = { .error = std::string("openssl initialization failed: ")+ERR_get_error(NULL) }; + response_callback(&parsed); + response_callback = NULL; + stackout(); + return; + } + SSL_set_connect_state(ssl_cli); + SSL_set_bio(ssl_cli, ssl_rbio, ssl_wbio); + } +#endif // Finally call connect int r = ::connect(peer_fd, (sockaddr*)&addr, sizeof(addr)); if (r < 0 && errno != EINPROGRESS) @@ -432,11 +478,11 @@ void http_co_t::submit_read(bool check_timeout) stackin(); int res; again: - if (rbuf.size() != READ_BUFFER_SIZE) + if (rbuf.capacity()-rbuf.size() < READ_BUFFER_SIZE) { - rbuf.resize(READ_BUFFER_SIZE); + rbuf.reserve(rbuf.size() + READ_BUFFER_SIZE); } - read_iov = { .iov_base = rbuf.data(), .iov_len = READ_BUFFER_SIZE }; + read_iov = { .iov_base = rbuf.data()+rbuf.size(), .iov_len = READ_BUFFER_SIZE }; read_msg.msg_iov = &read_iov; read_msg.msg_iovlen = 1; res = recvmsg(peer_fd, &read_msg, 0); @@ -466,22 +512,177 @@ again: else if (res <= 0) { // < 0 means error, 0 means EOF - epoll_events = epoll_events & ~EPOLLIN; - if (state == HTTP_CO_HEADERS_RECEIVED) - std::swap(parsed.body, response); - close_connection(); - if (res < 0) - parsed = { .error = std::string("recvmsg: ")+strerror(-res) }; - run_cb_and_clear(); + on_read_error(res); } else { - response += std::string(rbuf.data(), res); + if (ssl) + handle_ssl_read(rbuf); + else + response += std::string((char*)rbuf.data(), res); + rbuf.resize(0); handle_read(); } stackout(); } +void http_co_t::on_read_error(int res) +{ + epoll_events = epoll_events & ~EPOLLIN; + if (state == HTTP_CO_HEADERS_RECEIVED) + std::swap(parsed.body, response); + close_connection(); + if (res < 0) + parsed = { .error = std::string("recvmsg: ")+strerror(-res) }; + run_cb_and_clear(); +} + +int http_co_t::do_ssl_handshake() +{ + stackin(); + int r; + while (1) + { + r = SSL_do_handshake(ssl_cli); + if (r == SSL_ERROR_WANT_WRITE) + { + r = ssl_encrypt(); + if (r >= 0) + submit_send(); + else + { + r = -r; + break; + } + } + else + { + if (r == SSL_ERROR_WANT_READ || r == SSL_ERROR_NONE) + { + // OK or wait until we have more incoming data + r = 0; + } + break; + } + } + stackout(); + return r; +} + +// Enqueue outbound encrypted TLS data +int http_co_t::ssl_encrypt() +{ + stackin(); + int queued = 0; + while (true) + { + if (encrypted_out.size() >= encrypted_out.capacity()/2) + encrypted_out.reserve(encrypted_out.size() < READ_BUFFER_SIZE ? encrypted_out.size() + READ_BUFFER_SIZE : 2*encrypted_out.size()); + int r = BIO_read(ssl_wbio, encrypted_out.data()+encrypted_out.size(), encrypted_out.capacity()-encrypted_out.size()); + if (r > 0) + { + queued += r; + encrypted_out.resize(encrypted_out.size()+r); + } + else + { + if (!BIO_should_retry(ssl_wbio)) + queued = r; + break; + } + } + stackout(); + return queued; +} + +void http_co_t::handle_ssl_write() +{ + stackin(); + int r = 0; + while (sent < request.size()) + { + if (!SSL_is_init_finished(ssl_cli)) + { + if (do_ssl_handshake() != 0) + { + on_read_error(-EIO); + break; + } + if (!SSL_is_init_finished(ssl_cli)) + break; + } + else + { + int n = SSL_write(ssl_cli, request.data()+sent, request.size()-sent); + if (n > 0) + sent += n; + else if (get_sslstatus(ssl_cli, n) == SSLSTATUS_FAIL) + { + on_read_error(-EIO); + break; + } + else + break; + } + r = ssl_encrypt(); + if (r >= 0) + submit_send(); + else + { + on_read_error(-EIO); + break; + } + } + stackout(); +} + +// Process incoming encrypted TLS data +void http_co_t::handle_ssl_read() +{ + stackin(); + int size = rbuf.size(); + int done = 0; + while (done < size) + { + int n = BIO_write(ssl_rbio, rbuf.data()+done, size-done); + if (n > 0) + { + done += n; + } + if (n <= 0) + { + on_read_error(-EIO); + break; + } + if (!SSL_is_init_finished(ssl_cli)) + { + if (do_ssl_handshake() != 0) + { + on_read_error(-EIO); + break; + } + if (!SSL_is_init_finished(ssl_cli)) + break; + } + do + { + if (response.capacity() - response.size() < READ_BUFFER_SIZE) + response.reserve(2*response.size() < response.size() + READ_BUFFER_SIZE ? response.size() + READ_BUFFER_SIZE : 2*response.size()); + n = SSL_read(ssl_cli, response.data() + response.size(), READ_BUFFER_SIZE); + if (n <= 0) + { + n = SSL_get_error(ssl_cli, n); + if (n == SSL_ERROR_WANT_READ) + break; + } + } while (n > 0); + } + if (done < size) + memmove(rbuf.data(), rbuf.data()+done, size-done); + rbuf.resize(size-done); + stackout(); +} + bool http_co_t::handle_read() { stackin();