OpenSSL BIO:仅使用TLS 1.0在两个SSL记录中中断单个SSL_write

时间:2016-09-05 10:01:17

标签: c++ sockets ssl tcp openssl

我想发送此消息" Hello world! \ n再见世界!" 使用以下代码。此代码在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 ------------------- 
     */

}

1 个答案:

答案 0 :(得分:4)

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

虽然它可能看起来像一个错误但实际上是为了对抗BEAST和使用SSL 3.0和TLS 1.0中与CBC密码相关的协议漏洞的类似攻击。通过插入导致第一个SSL帧的空数据片段来实现这种保护,该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_OP_DONT_INSERT_EMPTY_FRAGMENTS

您可以使用SSL_OP_ALL而不是 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> <version>2.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> ,其中包括针对错误TLS实施的所有解决方法,包括此方法。