【问题标题】:OpenSSL BIO: single SSL_write broken in two SSL records with TLS 1.0 onlyOpenSSL BIO:仅使用 TLS 1.0 的两个 SSL 记录中的单个 SSL_write 中断
【发布时间】:2017-01-12 16:18:57
【问题描述】:

我想使用以下代码发送此消息“Hello world!\n Bye world!”。此代码在 2 个 SSL 记录中发送消息,但我需要在 1 个 SSL 记录中发送它。

我读过一些关于 base64 herehere 的内容,但我不知道如何在我的代码中使用它们!谁能帮我解决我的问题。

问题:我想用“\n”分隔的部分而不是2部分发送消息!

更多说明: 我使用 OpenSSL s_client 连接它,我强制它使用 TLSv1 并使用wireshark 嗅探网络。实际上,此代码是作为数据库代理的更大项目的简化。在主项目中,我们需要向不受我们控制的服务器发送消息,它只能处理 1 个 SSL 记录。

我的 OpenSSL 命令:

s_client -connect 127.0.0.1:9999 -tls1

Wireshark 消息结果:

17 03 01 00 24 db f3 59 37 98 78 3b b6 06 b0 c1 66 0c 78 04 4d 50 60 54 19 37 fe 
77 65 27 7f 4e e8 4e 9a d7 94 66 3f 0d 03 17 03 01 00 34 83 a8 39 b3 3e 9a 35 7b 
a2 64 07 35 9b c5 d7 d0 c9 03 3c 43 ac d8 1c ad d5 0f 55 34 10 6c 99 3e 57 b1 51 
d4 a5 36 6a 8e 23 08 e0 2d 33 c1 53 63 4e d2 bd bd

我的模拟代码:

#include <cstdlib>
#include <iostream>

#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 

#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <unistd.h>

using namespace std;

static void ssl_set_sys_error(int ssl_error)
{
    int error = 0;

    switch (ssl_error) {
    case SSL_ERROR_ZERO_RETURN:
        error = 0;
        break;
    case SSL_ERROR_WANT_READ:
    case SSL_ERROR_WANT_WRITE:
#ifdef SSL_ERROR_WANT_CONNECT
    case SSL_ERROR_WANT_CONNECT:
#endif
#ifdef SSL_ERROR_WANT_ACCEPT
    case SSL_ERROR_WANT_ACCEPT:
#endif
        error = 1;
        break;
    case SSL_ERROR_SSL:
        /* Protocol error. */
#ifdef EPROTO
        error = EPROTO;
#else
        error = SOCKET_ECONNRESET;
#endif
        break;
    case SSL_ERROR_SYSCALL:
    case SSL_ERROR_NONE:
    default:
        break;
    };
}

int main()
{
    /* 
    ------------------ START Initialize Server ------------------
     */

    int serverfd, clientfd;
    struct sockaddr_in vir_serv_addr, cli_addr;

    serverfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero((char *) &vir_serv_addr, sizeof (vir_serv_addr));

    vir_serv_addr.sin_family = AF_INET;
    vir_serv_addr.sin_addr.s_addr = INADDR_ANY;
    vir_serv_addr.sin_port = htons(9999);

    bind(serverfd, (struct sockaddr *) &vir_serv_addr, sizeof (vir_serv_addr));
    listen(serverfd, 5);
    socklen_t client = sizeof (cli_addr);
    clientfd = accept(serverfd, (struct sockaddr*) &cli_addr, (socklen_t*) & client);

    /*
    ------------------- END Initialize Server ------------------- 
     */

    /* 
    ------------------ START SSL ------------------
     */
    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();

    SSL_CTX* context = SSL_CTX_new(TLS_server_method());
    SSL_CTX_set_ecdh_auto(context, 1);
    SSL_CTX_use_certificate_file(context, "server-cert.pem", SSL_FILETYPE_PEM);
    SSL_CTX_use_PrivateKey_file(context, "server-key.pem", SSL_FILETYPE_PEM);
    SSL_CTX_load_verify_locations(context, "ca-cert.pem", NULL);
    SSL_CTX_check_private_key(context);

    SSL* ssl = SSL_new(context);
    BIO *rbio = BIO_new(BIO_s_mem());
    BIO *wbio = BIO_new(BIO_s_mem());
    SSL_set_bio(ssl, rbio, wbio);

    char* buffer[8192];
    int n;

    SSL_set_accept_state(ssl);

    while (!SSL_is_init_finished(ssl)) {
        n = recv(clientfd, buffer, 8192, 0);
        n = BIO_write(rbio, buffer, n);
        int r = SSL_do_handshake(ssl);
        n = BIO_read(wbio, buffer, 8192);
        n = send(clientfd, buffer, n, 0);

        bzero(buffer, 8192);
        BIO_flush(rbio);
        BIO_flush(wbio);

        if (r != 1) {
            ERR_print_errors_fp(stderr);
            int err_SSL_get_error = SSL_get_error(ssl, r);
            switch (err_SSL_get_error) {
            case SSL_ERROR_NONE:
            case SSL_ERROR_SSL:
                return 0;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
                continue;
            default:
                return 0;
            }
        }
    }

    string message = "Hello world! \n Bye world!";
    n = SSL_write(ssl, message.c_str(), message.size());
    n = BIO_read(wbio, buffer, 8192);
    n = send(clientfd, buffer, n, 0);

    /*
    ------------------- END SSL ------------------- 
     */

}

【问题讨论】:

  • 您如何检查消息是否在 2 条记录中发送?因为我怀疑这一点。也许您尝试逐行阅读?
  • 为什么发送数据时使用多少 SSL 记录很重要?数据是任意的。 TCP 将确保它以一种或另一种方式可靠地发送,并且接收器的 SSL 引擎将以连贯的方式将其重新组合在一起。 SSL 可以随意使用它想要的许多记录来发送应用程序数据。此外,即使 SSL 不拆分数据,TCP 仍然可以。您的要求不现实。
  • @SteffenUllrich:我使用了wireshark。在 TLSv1.1 和 TLSv1.2 中它只是一条 SSL 记录,但在 TLSv1 中它是 2 条记录,我必须使用这个版本的 TLS。
  • @RemyLebeau:实际上这段代码是一个更大的数据库代理项目的简化。在主项目中,我们需要向不受我们控制的服务器发送消息,它只能处理 1 个 SSL 记录。
  • @Heil_Me 但是为什么呢?这是服务器方面的错误实现。您的代理不应该迎合这样的错误。 AFAIK,您不能强制 OpenSSL 按照您的要求行事。改为修复服务器。或者停止一次发送如此多的数据,以至于 OpenSSL 不得不将其拆分为多条记录。

标签: c++ sockets ssl tcp openssl


【解决方案1】:

确实非常有趣的问题。这里发生的是单个SSL_write 导致两个应用程序数据 SSL 帧(类型 0x17)。这只发生在 TLS 1.0 上,即不会发生在 TLS 1.1 或 TLS 1.2 上。

虽然它可能看起来像一个错误,但实际上是为了对抗BEAST 和类似攻击,这些攻击使用 SSL 3.0 和 TLS 1.0 中的协议漏洞与 CBC 密码相关。这种保护是通过插入导致第一个 SSL 帧实际上不包含实际数据的空数据片段来完成的。像这种 0/n 拆分的类似保护以 1/n-1 拆分的形式存在于其他 TLS 堆栈中。

由于此攻击不影响 TLS 1.1 和 TLS 1.2,因此此处未启用保护。有关此保护如何工作的更多详细信息,请参阅 Why does Firefox split HTTPS request?Is BEAST really fixed in all modern browsers?

但是由于您有一些无法处理这种行为的错误应用程序,您需要找到一种方法来禁用它。这可以通过选择不是 CBC 的密码(但 TLS 1.0 中没有好的密码)或简单地禁用保护来完成:

SSL_CTX_set_options(context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);

除了SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS,您还可以使用SSL_OP_ALL,其中包括所有有问题的TLS实现的解决方法,包括这个。

【讨论】:

    猜你喜欢
    • 2015-04-03
    • 2019-06-27
    • 2012-07-03
    • 2016-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-09
    相关资源
    最近更新 更多