C ++ OpenSSL在非阻塞模式下接受时无法执行握手。什么是正确的方法?

时间:2016-08-13 23:37:48

标签: c++ openssl handshake

我正在尝试将OpenSSL实现到我的应用程序中,该应用程序使用原始C套接字,我遇到的唯一问题是启动KeyExchange阶段的代码的SSL_accept / SSL_connect部分,但似乎无法在服务器端完成

我已经在StackOverflow上看过无数的网站和Q& A,让我自己完成OpenSSL API,因为这基本上是我第一次尝试在应用程序中实现SSL但是我唯一不能找到如何妥善管理失败的握手。

基本上,运行作为服务器的进程A将侦听传入的连接。一旦我运行作为客户端的进程B,它将成功连接到进程A但SSL_accept(在服务器上)失败,错误代码为-2 SSL_ERROR_WANT_READ。

根据openssl handshake failed,通过在循环内调用SSL_accept直到它最终返回1(它成功连接并完成握手),“容易”解决问题。但是,我不相信这是正确的做事方式,因为它看起来像一个肮脏的把戏。之所以我认为这是一个肮脏的伎俩是因为我试图运行我在https://www.cs.utah.edu/~swalton/listings/articles/(ssl_client和ssl_server)上找到的一个小应用程序而且神奇地说,一切正常。没有多次调用SSL_accept,并且立即完成握手。

这是我接受服务器上的SSL连接的一些代码:

if (SSL_accept(conn.ssl) == -1)
            {
                fprintf(stderr, "Connection failed.\n");
                fprintf(stderr, "SSL State: %s [%d]\n", SSL_state_string_long(conn.ssl), SSL_state(conn.ssl));
                ERR_print_errors_fp(stderr);
                PrintSSLError(conn.ssl, -1, "SSL_accept");
                return -1;
            }
            else
            {
                fprintf(stderr, "Connection accepted.\n");
                fprintf(stderr, "Server -> Client handshake completed");
            }

这是PrintSSLError的输出:

SSL State: SSLv3 read client hello B [8465]
[DEBUG] SSL_accept : Failed with return -1
[DEBUG]     SSL_get_error() returned : 2
[DEBUG]     Error string : error:00000002:lib(0):func(0):system lib
[DEBUG]     ERR_get_error() returned : 0
[DEBUG]     errno returned : Resource temporarily unavailable

这是连接到服务器的客户端代码段:

if (SSL_connect(conn.ssl) == -1)
            {
                fprintf(stderr, "Connection failed.\n");
                ERR_print_errors_fp(stderr);
                PrintSSLError(conn.ssl, -1, "SSL_connect");
                return -1;
            }
            else
            {
                fprintf(stderr, "Connection established.\n");
                fprintf(stderr, "Client -> Server handshake completed");
                PrintSSLInfo(conn.ssl);
            }

连接已成功建立客户端(SSL_connect不返回-1)和PrintSSLInfo输出:

Connection established.
Cipher: DHE-RSA-AES256-GCM-SHA384
SSL State: SSL negotiation finished successfully [3]

这就是我将C Socket包装成SSL的方式:

SSLConnection conn;
        conn.fd = fd;
        conn.ctx = sslContext;

        conn.ssl = SSL_new(conn.ctx);
        SSL_set_fd(conn.ssl, conn.fd);

这里的代码片段驻留在一个函数中,该函数接受原始套接字上接受的传入连接的文件描述符和要使用的SSL上下文。

要初始化SSL上下文,我使用TLSv1_2_server_method()和TLSv1_2_client_method()。是的,我知道如果他们不支持TLS 1.2,这将阻止客户端连接,但这正是我想要的。无论如何,无论谁连接到我的应用程序都必须通过我的客户端进行。

无论哪种方式,我做错了什么?我想避免在身份验证阶段出现循环,以避免由于意外的无限循环导致应用程序出现挂起/减速,因为OpenSSL没有指定可能需要多少次尝试。

有效的解决方法是,但我想避免的是:

while ((accept = SSL_accept(conn.ssl)) != 1)

在while循环中,我检查存储在accept中的返回码。

我尝试解决SSL_ERROR_WANT_READ错误的事情:

  1. 在while循环中添加了usleep(50)(仍需要几个周期才能完成)
  2. 在SSL_connect和SSL_accept之后添加了SSL_do_handshake(conn.ssl)(最终结果没有改变任何内容)
  3. 看看roxlu.com上显示的代码(在Google上搜索“使用带内存BIO的OpenSSL - Roxlu”)来指导我完成握手阶段,但由于我是新手,我不知道在我的代码中直接使用BIO,但只是将我的本机C套接字包装成SSL,这有点令人困惑。我也无法重新编写应用程序的网络部分,因为它现在对我来说太过分了。
  4. 我已经使用openssl命令行完成了一些测试,以解决问题,但它没有出错。握手似乎是成功的,因为没有错误,例如:

    24069864:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:656
    

    出现。这是命令的整个输出

    openssl s_client -connect IP:Port -tls1_2 -prexit -msg
    

    http://pastebin.com/9u1bfuf4

    注意事项: 1.我使用的是最新的OpenSSL版本1.0.2h 2.应用程序在Unix系统上运行 3.使用自签名证书加密网络流量

    感谢所有帮助我的人。

    编辑: 我忘了提到套接字处于非阻塞模式,因为应用程序一次性为多个客户端服务。虽然,客户端他们处于阻止模式。

    EDIT2: 留待此处以备将来参考:jmarshall.com/stuff/handling-nbio-errors-in-openssl.html

1 个答案:

答案 0 :(得分:2)

您已澄清套接字问题是非阻塞的。

嗯,这是你的答案。显然,当套接字处于非阻塞模式时,握手不能立即完成。握手涉及在客户端和服务器之间交换协议分组,每个协议分组必须等待从其对等端接收响应。当套接字处于其默认阻止模式时,这可以正常工作。该库只是read()write()s,它们阻塞并等待消息成功读取或写入。当套接字处于非阻塞模式时,这显然不会发生。如果无法读取或套接字的输出缓冲区已满,read()write()会立即成功或失败。

SSL_accept()SSL-connect()的手册页说明了当底层套接字处于非阻塞模式时执行SSL握手必须实现的过程。您应该自己阅读手册页,而不是在这里重复整个过程。胶囊摘要是使用SSL_get_error()来确定握手是否实际失败,或者库是否想要读取或写入套接字;然后在相应的情况下调用poll()select(),然后再次致电SSL_accept()SSL_connect()

任何其他方法,例如在这里和那里撒上愚蠢的sleep()电话,都会导致一个不可靠的纸牌屋,会随机失败。