如何通过HTTPS POST正确发送二进制数据?

时间:2012-02-03 11:53:48

标签: c++ ssl https boost-asio

我将二进制数据从客户端(Debian 6.0.3)发送到服务器(Windows Server 2003)。为了绕过大多数防火墙,我使用 HTTPS POST 。客户端和服务器使用 Boost.Asio OpenSSL 实现。首先,我实现了最简单的版本,并且工作正常。

HTTP标头:

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

(如果这很重要,[binary data]不是base64编码的)

然后,在另一台客户机上它失败了(连接到同一台服务器机器)。行为不稳定。连接始终建立良好(端口443)。大多数时候我通过SSL握手很好但服务器没有接收数据(几乎没有数据,有时实际收到一两个数据包)。有时我收到SSL握手错误“短读”。有时我会收到无效数据。

客户端连接到服务器,握手,发送HTTP POST标头,然后无限发送二进制数据,直到出现错误。对于测试,我使用自定义生成的SSL证书。

服务器代码:

namespace ssl = boost::asio::ssl;
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
context.use_certificate_chain_file("server.pem");
context.use_private_key_file("server.pem", boost::asio::ssl::context::pem);

ssl::stream<tcp::socket> socket(io_service, context);

// standard connection accepting

socket.async_handshake(ssl::stream_base::server, ...);
...
boost::asio::async_read_until(socket, POST_header, "\r\n\r\n", ...);
...

客户代码:

ssl::context context(io_service, ssl::context::sslv23);
context.load_verify_file("server.crt");
socket.reset(new ssl::stream<tcp::socket>(io_service, context));
socket->set_verify_mode(ssl::verify_none);

// standard connection

socket.async_handshake(ssl::stream_base::client, ...);
...

(省略错误处理以及不相关的代码)

如您所见,这是最简单的SSL连接。怎么了?原因可能是防火墙吗?

我尝试在同一个443端口上使用简单的TCP w / o SSL,这很好。

修改

尝试添加“Content-Type:application / octet-stream”,但没有帮助。

编辑2:

通常我会收到HTTP POST标头。然后我将数据块发送为chunk-size(4 bytes)chunk(chunk-size bytes)...。服务器接收chunk-size很好,但没有。客户端不会通知服务器问题(没有错误)并继续发送数据。有时服务器可以接收一两个块,有时它会收到无​​效的chunk-size,但大部分时间都没有。

编辑3:

比较客户端和服务器上捕获的流量,没有发现任何差异。

解决方案

我从一开始就被这个问题误导了。将其缩小到令人惊讶的细节:

如果我在Boost v.1.48中使用Boost.Asio多缓冲区(此时最新的那个),则通过SSL套接字发送失败。例如:

// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);

在此示例中单独发送packet_sizepacket可解决此问题。我没有把任何可疑行为称为bug,特别是如果它与Boost库有关。但这个看起来真的像个bug。尝试Boost v.1.47 - 工作正常。尝试使用通常的TCP套接字(不是SSL) - 工作正常。 Linux和Windows都是一样的。

我将在Asio邮件列表中找到有关此问题的任何报告,如果没有找到则会报告。

3 个答案:

答案 0 :(得分:7)

如果您不必在Web服务器前操作,则没有 使用HTTPS协议。从防火墙的角度来看,HTTPS看起来还是如此 另一个SSL连接,它不知道经历了什么。所以,如果 你需要的只是传递数据 - 而不是实际的web服务器,使用 只需443端口的SSL连接。

因此,只需对SSL连接进行故障排除,问题与HTTP无关。


如果您想使用HTTP Web服务器而不是自定义客户端:

两点:

  1. 您需要指定Content-Length。
  2. 如果您使用的是HTTP / 1.1,则需要指定主机标头。
  3. 最简单的是

    POST /url HTTP/1.0
    User-Agent: my custom client v.1
    Content-Type: application/octet-stream
    Content-Length: NNN
    
    Actual Content
    

    或者对于HTTP / 1.1

    POST /url HTTP/1.1
    Host: www.example.com
    User-Agent: my custom client v.1
    Content-Type: application/octet-stream
    Content-Length: NNN
    
    Actual Content
    

    注意:您无法发送无限数据。 HTTP协议需要固定的内容长度 并且大部分时间Web服务器会在将数据传递给之前先加载数据 后端。

    所以你必须按块传输数据。

答案 1 :(得分:4)

我从一开始就被这个问题误导了。将其缩小到令人惊讶的细节:

如果我在Boost v.1.48中使用Boost.Asio多缓冲区(此时最新的那个),则通过SSL套接字发送失败。例如:

// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);

在此示例中单独发送packet_sizepacket可解决此问题。我没有把任何可疑行为称为bug,特别是如果它与Boost库有关。但这个看起来真的像个bug。尝试Boost v.1.47 - 工作正常。尝试使用通常的TCP套接字(不是SSL) - 工作正常。 Linux和Windows都是一样的。

我将在Asio邮件列表中找到有关此问题的任何报告,如果没有找到则会报告。

答案 2 :(得分:1)

(编辑:我最初删除了这个,因为我已经意识到它并没有真正使用HTTP。在您认为可能有MITM代理并且应该使用正确的HTTP的评论之后,我取消删除/编辑。)< / p>

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

无论是否是二进制数据,在普通HTTP或SSL / TLS中,您都需要Content-Length标头或使用chunked transfer encoding。这是HTTP规范的this sectionContent-Type标题也很有用。

分块传输编码适用于您不一定知道流的长度的情况。 (发送数据时总是需要某种形式的分隔符,如果只是在结束时可靠地检测到。)

话虽如此,如果您获得的证书不是您的服务器,您应该能够了解是否支持在SSL / TLS之上查看应用程序层的MITM代理。如果您仍然获得了与您赢得的服务器证书的成功握手,则没有这样的代理。甚至HTTP代理也会使用CONNECT并中继所有内容,而不会改变SSL / TLS连接(因此不会在顶部更改原始的伪HTTP)。