C++ Curl使用Let's Encrypt证书连接失败:CA证书验证异常
Peer certificate cannot be authenticated with given CA certificates 我有一台装了Let's Encrypt SSL证书的服务器,当前生效的证书文件包括:cert.pem、privkey.pem、fullchain.pem、chain.pem。
我的Curl代码里的证书相关配置是这样的:
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, *sslctx);
对应的sslctx函数如下,现在纠结mypem应该填哪个PEM文件的内容:
static CURLcode sslctx(CURL *curl, void *sslctx, void *parm) { X509_STORE *store; X509 *cert = NULL; BIO *bio; const char *mypem = // 此处应填入哪个PEM文件内容? "-----BEGIN CERTIFICATE-----\n" "XXXXXX WHICH PEM FILE? XXXX\n" "-----END CERTIFICATE-----"; bio = BIO_new_mem_buf(mypem, -1); PEM_read_bio_X509(bio, &cert, 0, NULL); if (cert == NULL) { cout << "Failed reading SSL-certificate" << endl; Exit(false); } store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx); if (X509_STORE_add_cert(store, cert) == 0) { cout << "Error adding SSL-certificate" << endl; Exit(false); } X509_free(cert); BIO_free(bio); return CURLE_OK; }
现在通过HTTPS连接服务器时,Curl返回错误:Peer certificate cannot be authenticated with given CA certificates。禁用CURLOPT_SSL_VERIFYPEER能成功请求,但有安全风险,不符合要求。
补充信息:
我的Apache配置里,/etc/letsencrypt/options-ssl-apache.conf包含基础安全配置,而启用的站点SSL配置/etc/apache2/sites-enabled/{site}-le-ssl.conf里指定了:
Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateFile /etc/letsencrypt/live/{site}/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/{site}/privkey.pem
嘿,这个问题其实很明确,我来给你拆解清楚:
1. 你需要填入的是**chain.pem**的内容
Let's Encrypt的这几个文件分工很清晰:
cert.pem:仅服务器自己的证书privkey.pem:服务器的私钥(绝对不能暴露给客户端)fullchain.pem:服务器证书 + 中间证书的完整链(服务器端用这个)chain.pem:仅Let's Encrypt的中间证书(客户端验证时需要信任这个)
你的代码逻辑是往Curl的SSL上下文里添加可信任的CA证书,用来验证服务器的证书链。服务器发送的fullchain.pem里包含了中间证书,客户端需要信任这个中间证书(加上系统默认信任的根证书),才能完成整个证书链的验证。所以把chain.pem的内容填到mypem里就对了。
2. 更简单可靠的替代方案:直接指定CA证书文件
其实你完全不用自己写sslctx函数这么麻烦,Curl本身就支持直接指定CA证书文件的路径,用CURLOPT_CAINFO参数就行,代码更简洁也不容易出错:
// 直接指定chain.pem的绝对路径 curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/letsencrypt/live/{site}/chain.pem"); // 或者也可以用fullchain.pem,效果一样,因为它包含了中间证书 // curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/letsencrypt/live/{site}/fullchain.pem");
这样你甚至可以删掉CURLOPT_SSL_CTX_FUNCTION相关的配置,代码更清爽。
3. 为什么之前会报错?
如果之前你填的是cert.pem或者其他错误的文件,那相当于告诉Curl要信任服务器的证书本身,而不是用来验证它的CA证书。这样Curl无法构建有效的信任链,自然就会抛出Peer certificate cannot be authenticated的错误了。
另外提一句,现在大多数系统已经默认信任Let's Encrypt的根证书了,如果你不想指定本地的证书文件,也可以尝试直接依赖系统的CA存储,但为了兼容性(比如某些老系统),还是指定chain.pem更稳妥。
内容的提问来源于stack exchange,提问作者Thomas van Hesteren

