C++ OpenSSL:libssl 无法验证 Windows 上的证书

C++ OpenSSL: libssl fails to verify certificates on Windows

我四处寻找了很多,但我似乎找不到解决这个问题的合适方法。许多 Whosebug 帖子都与 Ruby 有关,但我或多或少直接(通过 https://gitlab.com/eidheim/Simple-Web-Server 库)将 OpenSSL 用于 C++ application/set 库,并且需要弄清楚如何为用户完全透明地解决此问题(他们不需要连接任何自定义证书验证文件即可使用该应用程序)。

在 Windows,当我尝试使用 SimpleWeb HTTPS 客户端时,如果我打开了证书验证,连接会失败,因为连接证书无法验证。 Linux 的情况并非如此,验证工作正常。

我被建议按照 this solution 将 Windows 根证书导入 OpenSSL,以便验证例程可以使用它们。但是,据我所知,这似乎没有任何区别。我深入研究了 libssl 验证函数的内部结构,试图准确理解发生了什么,尽管上面的答案建议将 Windows 根证书添加到新的 X509_STORE,但 SSL 连接上下文有自己的存储区,它是在连接初始化时设置的。这让我觉得简单地创建一个新的 X509_STORE 并在那里添加证书没有帮助,因为连接实际上并不使用该存储。

很可能是我花了太多时间调试 libssl 的细节,以至于我错过了解决这个问题的实际方法。 OpenSSL 是否提供了一种规范的方式来查找我没有设置的系统证书?或者,问题可能是 SimpleWeb library/ASIO 初始化 OpenSSL 的方式吗?我知道该库允许您为证书的“验证文件”提供路径,但我觉得这不是一个合适的解决方案,因为我作为开发人员应该使用在最终用户系统上找到的证书,而不是比我自己硬编码。

编辑:对于上下文,这是我在一个小示例应用程序中使用的代码:

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

static void LoadSystemCertificates()
{
    HCERTSTORE hStore;
    PCCERT_CONTEXT pContext = nullptr;
    X509 *x509 = nullptr;
    X509_STORE *store = X509_STORE_new();

    hStore = CertOpenSystemStore(NULL, "ROOT");

    if (!hStore)
    {
        return;
    }

    while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr)
    {
        const unsigned char* encodedCert = reinterpret_cast<const unsigned char*>(pContext->pbCertEncoded);
        x509 = d2i_X509(nullptr, &encodedCert, pContext->cbCertEncoded);

        if (x509)
        {
            X509_STORE_add_cert(store, x509);
            X509_free(x509);
        }
    }

    CertCloseStore(hStore, 0);
}

static void MakeRequest(const std::string& address)
{
    using Client = SimpleWeb::Client<SimpleWeb::HTTPS>;

    Client httpsClient(address);
    httpsClient.io_service = std::make_shared<asio::io_service>();

    std::cout << "Making request to: " << address << std::endl;

    bool hasResponse = false;
    httpsClient.request("GET", [address, &hasResponse](std::shared_ptr<Client::Response> response, const SimpleWeb::error_code& error)
    {
        hasResponse = true;

        if ( error )
        {
            std::cerr << "Got error from " << address << ": " << error.message() << std::endl;
        }
        else
        {
            std::cout << "Got response from " << address << ":\n" << response->content.string() << std::endl;
        }
    });

    while ( !hasResponse )
    {
        httpsClient.io_service->poll();
        httpsClient.io_service->reset();

        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    }
}

int main(int, char**)
{
    LoadSystemCertificates();
    MakeRequest("google.co.uk");

    return 0;
}

电话returns我:Got error from google.co.uk: certificate verify failed

好的,这就是我解决这个问题的方法,对于将来可能有所帮助的任何人。 对相关问题有帮助。

事实证明,问题确实是 SSL 上下文没有使用我设置的证书存储。其他一切正常,但缺少的部分是对 SSL_CTX_set_cert_store() 的调用,它获取证书存储并将其提供给 SSL 上下文。

在 SimpleWeb 库的上下文中,执行此操作的最简单方法似乎是 class 子 SimpleWeb::Client<SimpleWeb::HTTPS> class 并将以下内容添加到构造函数中:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <wincrypt.h>

class MyClient : public SimpleWeb::Client<SimpleWeb::HTTPS>
{
public:
    MyClient( /* ... */ ) :
        SimpleWeb::Client<SimpleWeb::HTTPS>( /* ... */ )
    {
        AddWindowsRootCertificates();
    }

private:
    using OpenSSLContext = asio::ssl::context::native_handle_type;

    void AddWindowsRootCertificates()
    {
        // Get the SSL context from the SimpleWeb class.
        OpenSSLContext sslContext = context.native_handle();
        
        // Get a certificate store populated with the Windows root certificates.
        // If this fails for some reason, the function returns null.
        X509_STORE* certStore = GetWindowsCertificateStore();

        if ( sslContext && certStore )
        {
            // Set this store to be used for the SSL context.
            SSL_CTX_set_cert_store(sslContext, certStore);
        }
    }

    static X509_STORE* GetWindowsCertificateStore()
    {
        // To avoid populating the store every time, we keep a static
        // pointer to the store and just initialise it the first time
        // this function is called.
        static X509_STORE* certificateStore = nullptr;

        if ( !certificateStore )
        {
            // Not initialised yet, so do so now.

            // Try to open the root certificate store.
            HCERTSTORE rootStore = CertOpenSystemStore(0, "ROOT");

            if ( rootStore )
            {
                // The new store is reference counted, so we can create it
                // and keep the pointer around for later use.
                certificateStore = X509_STORE_new();
                
                PCCERT_CONTEXT pContext = nullptr;

                while ( (pContext = CertEnumCertificatesInStore(rootStore, pContext)) != nullptr )
                {
                    // d2i_X509() may modify the pointer, so make a local copy.
                    const unsigned char* content = pContext->pbCertEncoded;
                    
                    // Convert the certificate to X509 format.
                    X509 *x509 = d2i_X509(NULL, &content, pContext->cbCertEncoded);

                    if ( x509 )
                    {
                        // Successful conversion, so add to the store.
                        X509_STORE_add_cert(certificateStore, x509);
                        
                        // Release our reference.
                        X509_free(x509);
                    }
                }

                // Make sure to close the store.
                CertCloseStore(rootStore, 0);
            }
        }

        return certificateStore;
    }
};

如果您的 class 需要在多个平台上编译,显然 GetWindowsCertificateStore() 需要抽象到某个地方 platform-specific。