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。
我四处寻找了很多,但我似乎找不到解决这个问题的合适方法。许多 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。