C++ OpenSSL 在非阻塞模式下接受时无法执行握手。什么是正确的方法?
C++ OpenSSL Fails to perform handshake when accepting in non-blocking mode. What is the proper way?
我正在尝试将 OpenSSL 实施到我使用原始 C 套接字的应用程序中,我遇到的唯一问题是开始 KeyExchange 阶段的代码的 SSL_accept / SSL_connect 部分但似乎没有在服务器端完成它。
我查看了无数网站和 Whosebug 上的问答以了解 OpenSSL API 因为这基本上是我第一次尝试在应用程序中实现 SSL 但唯一的我还没有找到的是如何正确管理失败的握手。
基本上,运行作为服务器的宁进程A将监听传入的连接。一旦我 运行 作为客户端的进程 B,它将成功连接到进程 A 但 SSL_accept(在服务器上)失败,错误代码为 -2 SSL_ERROR_WANT_READ.
根据 openssl handshake failed, the problem is "easily" worked around by calling SSL_accept within a loop until it finally returns 1 (It successfully connects and completes the handshake). However, I do not believe that this is the proper way of doing things as it looks like a dirty trick. The reason for why I believe it is a dirty trick is because I tried to run a small application I found on 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 没有 return -1)并且 PrintSSLInfo 输出:
Connection established.
Cipher: DHE-RSA-AES256-GCM-SHA384
SSL State: SSL negotiation finished successfully [3]
这就是我将 C 套接字包装到 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 未指定可能需要多少次尝试而导致意外无限循环导致应用程序挂起 ups/slow。
可行但我想避免的解决方法是:
while ((accept = SSL_accept(conn.ssl)) != 1)
在 while 循环中,我检查存储在 accept 中的 return 代码。
我尝试解决 SSL_ERROR_WANT_READ 错误的方法:
- 在 while 循环中添加了 usleep(50)(仍然需要几个循环才能完成)
- 在 SSL_connect 和 SSL_accept 之后添加 SSL_do_handshake(conn.ssl)(最终结果没有任何改变)
- 查看了 roxlu.com 上显示的代码(在 Google 上搜索 "Using OpenSSL with memory BIOs - Roxlu")以指导我完成握手阶段,但由于我是新手,并且我不直接在我的代码中使用 BIO,而是简单地将我的本机 C 套接字包装到 SSL 中,这有点令人困惑。我也无法重写应用程序的网络部分,因为现在对我来说工作量太大了。
我也用 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系统上的应用程序运行s
3. 使用自签名证书加密网络流量
感谢所有愿意帮助我的人。
编辑:
我忘了提到套接字处于非阻塞模式,因为应用程序一次性为多个客户端提供服务。虽然,客户端他们处于阻塞模式。
编辑2:
留在这里供将来参考:jmarshall.com/stuff/handling-nbio-errors-in-openssl.html
你已经说明了socket问题是非阻塞的
嗯,这就是你的答案。显然,当socket处于非阻塞模式时,握手并不能立即完成。握手涉及客户端和服务器之间的协议数据包交换,每个人都必须等待接收来自其对等方的响应。当套接字处于其默认阻塞模式时,这可以正常工作。该库只是 read()
s 和 write()s
,它会阻塞并等待消息成功读取或写入。当套接字处于非阻塞模式时,这显然不会发生。 read()
或 write()
立即成功,或失败,如果没有可读取的内容或套接字的输出缓冲区已满。
SSL_accept()
和 SSL-connect()
的手册页解释了当底层套接字处于非阻塞模式时执行 SSL 握手必须执行的过程。不要在这里重复整个过程,您应该自己阅读手册页。 capsule总结就是用SSL_get_error()
来判断握手是否真的失败了,或者库是否要读或者写to/from套接字;并相应地调用 poll()
或 select()
,然后再次调用 SSL_accept()
和 SSL_connect()
。
任何其他方法,例如在各处散布愚蠢的 sleep()
调用,都会导致纸牌屋不可靠,随机失败。
我正在尝试将 OpenSSL 实施到我使用原始 C 套接字的应用程序中,我遇到的唯一问题是开始 KeyExchange 阶段的代码的 SSL_accept / SSL_connect 部分但似乎没有在服务器端完成它。
我查看了无数网站和 Whosebug 上的问答以了解 OpenSSL API 因为这基本上是我第一次尝试在应用程序中实现 SSL 但唯一的我还没有找到的是如何正确管理失败的握手。
基本上,运行作为服务器的宁进程A将监听传入的连接。一旦我 运行 作为客户端的进程 B,它将成功连接到进程 A 但 SSL_accept(在服务器上)失败,错误代码为 -2 SSL_ERROR_WANT_READ.
根据 openssl handshake failed, the problem is "easily" worked around by calling SSL_accept within a loop until it finally returns 1 (It successfully connects and completes the handshake). However, I do not believe that this is the proper way of doing things as it looks like a dirty trick. The reason for why I believe it is a dirty trick is because I tried to run a small application I found on 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 没有 return -1)并且 PrintSSLInfo 输出:
Connection established.
Cipher: DHE-RSA-AES256-GCM-SHA384
SSL State: SSL negotiation finished successfully [3]
这就是我将 C 套接字包装到 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 未指定可能需要多少次尝试而导致意外无限循环导致应用程序挂起 ups/slow。
可行但我想避免的解决方法是:
while ((accept = SSL_accept(conn.ssl)) != 1)
在 while 循环中,我检查存储在 accept 中的 return 代码。
我尝试解决 SSL_ERROR_WANT_READ 错误的方法:
- 在 while 循环中添加了 usleep(50)(仍然需要几个循环才能完成)
- 在 SSL_connect 和 SSL_accept 之后添加 SSL_do_handshake(conn.ssl)(最终结果没有任何改变)
- 查看了 roxlu.com 上显示的代码(在 Google 上搜索 "Using OpenSSL with memory BIOs - Roxlu")以指导我完成握手阶段,但由于我是新手,并且我不直接在我的代码中使用 BIO,而是简单地将我的本机 C 套接字包装到 SSL 中,这有点令人困惑。我也无法重写应用程序的网络部分,因为现在对我来说工作量太大了。
我也用 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系统上的应用程序运行s 3. 使用自签名证书加密网络流量
感谢所有愿意帮助我的人。
编辑: 我忘了提到套接字处于非阻塞模式,因为应用程序一次性为多个客户端提供服务。虽然,客户端他们处于阻塞模式。
编辑2: 留在这里供将来参考:jmarshall.com/stuff/handling-nbio-errors-in-openssl.html
你已经说明了socket问题是非阻塞的
嗯,这就是你的答案。显然,当socket处于非阻塞模式时,握手并不能立即完成。握手涉及客户端和服务器之间的协议数据包交换,每个人都必须等待接收来自其对等方的响应。当套接字处于其默认阻塞模式时,这可以正常工作。该库只是 read()
s 和 write()s
,它会阻塞并等待消息成功读取或写入。当套接字处于非阻塞模式时,这显然不会发生。 read()
或 write()
立即成功,或失败,如果没有可读取的内容或套接字的输出缓冲区已满。
SSL_accept()
和 SSL-connect()
的手册页解释了当底层套接字处于非阻塞模式时执行 SSL 握手必须执行的过程。不要在这里重复整个过程,您应该自己阅读手册页。 capsule总结就是用SSL_get_error()
来判断握手是否真的失败了,或者库是否要读或者写to/from套接字;并相应地调用 poll()
或 select()
,然后再次调用 SSL_accept()
和 SSL_connect()
。
任何其他方法,例如在各处散布愚蠢的 sleep()
调用,都会导致纸牌屋不可靠,随机失败。