我正在尝试将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错误的事情:
我已经使用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
注意事项: 1.我使用的是最新的OpenSSL版本1.0.2h 2.应用程序在Unix系统上运行 3.使用自签名证书加密网络流量
感谢所有帮助我的人。
编辑: 我忘了提到套接字处于非阻塞模式,因为应用程序一次性为多个客户端服务。虽然,客户端他们处于阻止模式。
EDIT2: 留待此处以备将来参考:jmarshall.com/stuff/handling-nbio-errors-in-openssl.html
答案 0 :(得分:2)
您已澄清套接字问题是非阻塞的。
嗯,这是你的答案。显然,当套接字处于非阻塞模式时,握手不能立即完成。握手涉及在客户端和服务器之间交换协议分组,每个协议分组必须等待从其对等端接收响应。当套接字处于其默认阻止模式时,这可以正常工作。该库只是read()
和write()s
,它们阻塞并等待消息成功读取或写入。当套接字处于非阻塞模式时,这显然不会发生。如果无法读取或套接字的输出缓冲区已满,read()
或write()
会立即成功或失败。
SSL_accept()
和SSL-connect()
的手册页说明了当底层套接字处于非阻塞模式时执行SSL握手必须实现的过程。您应该自己阅读手册页,而不是在这里重复整个过程。胶囊摘要是使用SSL_get_error()
来确定握手是否实际失败,或者库是否想要读取或写入套接字;然后在相应的情况下调用poll()
或select()
,然后再次致电SSL_accept()
和SSL_connect()
。
任何其他方法,例如在这里和那里撒上愚蠢的sleep()
电话,都会导致一个不可靠的纸牌屋,会随机失败。