简体   繁体   中英

OpenSSL BIO: single SSL_write broken in two SSL records with TLS 1.0 only

I wanna send this message "Hello world! \\n Bye world!" using following code. This code send the message in 2 SSL record but I need to send it in 1 SSL record.

I read something about base64 here and here but I don't how to use them in my code! Can any one help me to solve my problem.

Problem: I want to send message in one part not 2 part that separated by "\\n"!

More Description: I use OpenSSL s_client to connect it, I forced it to use TLSv1 and sniff networks using wireshark. Actually this code is simplified of a bigger project for being Database Proxy. In main project we need send a message to a server that is not under our control and it can only process 1 SSL record.

My OpenSSL Command:

s_client -connect 127.0.0.1:9999 -tls1

Wireshark Result for Message:

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

My Simulation Code:

#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 ------------------- 
     */

}

Very interesting question indeed. What happens here is that a single SSL_write results in two Application Data SSL frames (type 0x17). And this happens only with TLS 1.0, ie not with TLS 1.1 or TLS 1.2.

While it might look like a bug it is actually done on purpose to fight the BEAST and similar attacks which use a protocol vulnerability in SSL 3.0 and TLS 1.0 in connection with CBC ciphers. This protection is done by inserting empty data fragments which leads to the first SSL frame which effectively contains no real data. Similar protections like this 0/n split exist in other TLS stacks in the form of 1/n-1 splits.

Since this attack does not affect TLS 1.1 and TLS 1.2 the protection is not enabled there. For more details about how this protection works see Why does Firefox split HTTPS request? and Is BEAST really fixed in all modern browsers? .

But since you are burdened with some buggy application which cannot deal with this behavior you need to find a way to disable it. This can be done by choosing a cipher which is not CBC (but there are no good ones left in TLS 1.0) or by simply disabling the protection:

SSL_CTX_set_options(context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);

Instead of SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS you could also use SSL_OP_ALL which includes all workaround for buggy TLS implementations including this one.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM