beast: SSL through proxy, "handshake: wrong version number"

Version of Beast : 189

Hello everyone,

I’m trying to establish a TLS connection through my proxy server.

Setting up the tunnel using HTTP CONNECT works as expected. The code I use as a demo is:

net::io_context ioc{};

std::thread([&] {
    ioc.run();
 }).detach();

ssl::context ssl_context_{ssl::context::tls};
ssl_context_.set_default_verify_paths();
ssl_context_.add_verify_path("/opt/misc/certificates");

auto websocket_secure_ = std::make_shared<websocket::stream<ssl::stream<tcp::socket>>>(ioc, ssl_context_);

tcp::resolver resolver_{ioc};
auto const resolve_results = resolver_.resolve("www-my.proxy.int", "8080");
net::connect(websocket_secure_->next_layer().next_layer(), resolve_results.begin(), resolve_results.end());

const std::string HOST_TO_CONNECT_TO = "my.host.com:443";

http::request<http::string_body> request_connect{http::verb::connect, HOST_TO_CONNECT_TO, 11};
request_connect.insert(http::field::proxy_authorization, "Basic dXNlcm5hbWU6cGFzc3dvcmQ=");
request_connect.insert(http::field::host, HOST_TO_CONNECT_TO);
boost::beast::http::write(websocket_secure_->next_layer().next_layer(), request_connect);

As expected, Wireshark shows a HTTP CONNECT followed by a 200 response.

However, attempting to do the SSL handshake afterwards results in a thrown error: “handshake: wrong version number”.

try {
    websocket_secure_->next_layer().handshake(ssl::stream_base::client);
  } catch (boost::system::system_error& error) {
    std::cout << error.what();
    throw error;
  }

Wireshark shows a Client Hello, followed by a Server Hello, Certificate, Server Key Exchange, Certificate Request, Server Hello Done. The trace looks fine to me and doesn’t differ from a request of my browser to that same website.

The SSL handshake works just fine if I don’t have to tunnel the requests through my proxy.

Why am i getting the thrown error inside my application? Thanks in advance for any help!

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

Ahh… I see. You have to tell Beast that you are not expecting a body, otherwise Beast has no way to know that you are reading the response to a CONNECT request. See: https://www.boost.org/doc/libs/1_71_0/libs/beast/doc/html/beast/ref/boost__beast__http__basic_parser/skip/overload2.html

Ahh I see, that would’ve taken me ages to find 😄 Thank you very much - it’s working as intended (for now) 😃

 http::request<http::string_body> request_connect{http::verb::connect, HOST_TO_CONNECT_TO, 11};
  request_connect.insert(http::field::proxy_authorization, PROXY_AUTHENTICATION_CREDENTIALS);
  request_connect.insert(http::field::host, HOST_TO_CONNECT_TO);
  request_connect.insert(http::field::proxy_connection, "keep-alive");
  request_connect.insert(http::field::connection, "keep-alive");
  boost::beast::http::write(websocket_secure_->next_layer().next_layer(), request_connect);

  http::response<http::empty_body> res;
  http::parser<false, http::empty_body> p(res);
  p.skip(true);
  boost::beast::flat_buffer buffer;
  http::read(websocket_secure_->next_layer().next_layer(), buffer, p);

Ahh… I see. You have to tell Beast that you are not expecting a body, otherwise Beast has no way to know that you are reading the response to a CONNECT request. See: https://www.boost.org/doc/libs/1_71_0/libs/beast/doc/html/beast/ref/boost__beast__http__basic_parser/skip/overload2.html

I was using the approach you suggested, calling the http::read posted above right after writing the CONNECT request to the proxy server. However, read never returns, so I’m wondering if I might be using the wrong arguments to that function call? I was expecting to read back the 200 code.

  http::request<http::string_body> request_connect{http::verb::connect, HOST_TO_CONNECT_TO, 11};
  request_connect.insert(http::field::proxy_authorization, PROXY_AUTHENTICATION_CREDENTIALS);
  request_connect.insert(http::field::host, HOST_TO_CONNECT_TO);
  request_connect.insert(http::field::proxy_connection, "keep-alive");
  request_connect.insert(http::field::connection, "keep-alive");
  boost::beast::http::write(websocket_secure_->next_layer().next_layer(), request_connect);

  std::cout << "Reading...\n";
  beast::flat_buffer buffer{};
  http::response<http::dynamic_body> res{};
  boost::beast::http::read(websocket_secure_->next_layer().next_layer(), buffer, res);
  // Application never reaches 'DONE'
  std::cout << "DONE!\n";

Well, that’s why your program is failing. The SSL implementation is expecting to see a TLS record but instead, it is seeing the HTTP response from the proxy. You have to read the responds from the stream before you perform the TLS handshake.