c++ Boost ASIO SSL握手失败

ryoqjall  于 2023-01-28  发布在  其他
关注(0)|答案(2)|浏览(260)

尝试使用Boost ASIO安全连接到远程IMAP服务器时,服务器握手在每个连接上都失败。异常消息如下:

handshake: unregistered scheme (STORE routines) [asio.ssl:369098857]

我的代码如下(url是包含主机URL的std::string_view):

using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
using SSLSocket = ssl::stream<tcp::socket>;

boost::asio::io_context context;
ssl::context ssl_context(ssl::context::tls);
SSLSocket socket(context, ssl_context);

ssl_context.set_default_verify_paths();

tcp::resolver resolver(context);
auto endpoints = resolver.resolve(url, "993");
boost::asio::connect(socket.next_layer(), endpoints);
socket.set_verify_mode(ssl::verify_peer);
socket.set_verify_callback(ssl::host_name_verification(url.data()));
socket.handshake(SSLSocket::client);

代码立即在最后一行抛出异常,这是一个阻塞同步握手。
前两行设置了主机名验证,类似于in the official ASIO tutorial的做法,但是这些检查似乎会导致一个问题,因为当它们被删除时,握手会成功,显然,这不是一个好的解决方案。
在逐步了解ASIO的一些内部结构之后,我发现上面代码段的最后三行可以替换为:

SSL_set_verify(socket.native_handle(), SSL_VERIFY_PEER, nullptr);
socket.handshake(SSLSocket::client);

SSL_set_verify是一个OpenSSL函数,直接设置空回调会导致同样的问题,这让我认为问题出在我的系统的OpenSSL环境中,而不是ASIO或主机名验证回调。但是,我无法确定错误的确切含义和原因。
以下是我在故障诊断过程中尝试过的操作列表:
1.显式加载系统的证书(.pem)文件我想可能是ASIO和/或OpenSSL无法加载正确的证书来进行验证,所以我在/private/etc/ssl/cert.pem找到了系统(Mac)的证书文件。

ssl_context.load_verify_file("/private/etc/ssl/cert.pem");

set_default_verify_paths()被调用之后,我的程序加载这个证书文件时没有任何抱怨,但是它没有修复握手错误。
1.使用不同版本的OpenSSL起初我使用的是苹果系统版本的OpenSSL(实际上是LibreSSL 2.8.3)。然后我使用Homebrew软件包管理器版本的OpenSSL(OpenSSL 3.0.4)重建了我的代码。这也没有解决问题,即使我尝试像第1点那样调用load_verify_file
1.使用OpenSSL命令行工具进行健全性检查为确保网络连接和URL/端口号正确,我尝试使用以下命令通过SSL连接到IMAP服务器:

openssl s_client -connect my.url.com:993 -crlf -verify 1

而且它工作得很好,连接到IMAP服务器,使我能够发送/接收IMAP响应。
有人在使用OpenSSL和ASIO时遇到过类似的问题吗?我对设置SSL/TLS连接不太熟悉,但我不知道是什么原因导致了这个问题。
谢谢你的帮忙!

omqzjyyz

omqzjyyz1#

考虑到openssl s_client -connect my.url.com:993 -crlf -verify 1的成功,似乎没有太多错误。有一件事引起了我的注意:我会在从上下文构造SSL流 * 之前 * 配置上下文:

ssl::context ssl_context(ssl::context::tls);

ssl_context.set_default_verify_paths();

SSLSocket socket(context, ssl_context);

此外,openssl可能使用SNI extensions

// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(socket.native_handle(), hostname.c_str()))
{
    throw boost::system::system_error(
        ::ERR_get_error(), boost::asio::error::get_ssl_category());
}

最后,确保url字符串视图包含正确的数据,特别是它是一个有效的主机名空终止的字符串。在这种情况下,我更喜欢使用一个保证空终止的字符串表示:

总结

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
using SSLSocket = ssl::stream<tcp::socket>;

int main() {
    boost::asio::io_context context;
    ssl::context ssl_context(ssl::context::tls);

    ssl_context.set_default_verify_paths();

    SSLSocket socket(context, ssl_context);

    tcp::resolver r(context);
    std::string hostname = "www.example.com";
    auto endpoints = r.resolve(hostname, "443");
    boost::asio::connect(socket.next_layer(), endpoints);
    socket.set_verify_mode(ssl::verify_peer);
    socket.set_verify_callback(ssl::host_name_verification(hostname));

    // Set SNI Hostname (many hosts need this to handshake successfully)
    if(! SSL_set_tlsext_host_name(socket.native_handle(), hostname.c_str()))
    {
        throw boost::system::system_error(
            ::ERR_get_error(), boost::asio::error::get_ssl_category());
    }

    socket.handshake(SSLSocket::client);
}
bvpmtnay

bvpmtnay2#

在我的情况下,我设法使它工作与此:

ssl_context.load_verify_file("/etc/ssl/certs/ca-bundle.crt");

因此指向ca-bundle文件而不是cert文件。

相关问题