cURL 报错 tlsv1 alert unknown ca 如何解决?
结论
tlsv1 alert unknown ca 表示:银行服务器已经收到客户端证书,但无法建立可信的证书链,因此拒绝了该证书。
这里的 unknown ca 通常说明银行服务器不信任客户端证书的签发机构,或者请求只发送了客户端证书,缺少必要的中间 CA 证书。这通常与 TLS 版本无关,也不能通过关闭 CURLOPT_SSL_VERIFYPEER 解决。
建议依次检查:
.crt与.key是否匹配。- 证书是否为当前 cURL/OpenSSL 能读取的 PEM 格式。
- 客户端证书和中间 CA 证书是否已按顺序组成完整证书链。
- 银行是否已在对应环境中登记并信任该证书或签发 CA。
- 是否已恢复服务器证书校验。支付系统中不应关闭 TLS 验证。
为什么关闭证书校验仍然报错
下面两个选项只控制客户端是否验证银行服务器的证书:
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false,
当前错误发生在另一个方向:
客户端 银行服务器
|------ 客户端证书 ---------->|
|<----- unknown ca -----------|
也就是说,银行服务器验证你的客户端证书时失败了。关闭本地验证不会改变银行服务器的信任配置。
另外,tlsv1 alert 只是 TLS 警报名称的一部分,并不表示服务器只支持 TLS 1.0。
1. 检查 .crt 和 .key 是否匹配
分别从证书和私钥中提取公钥并计算摘要:
openssl x509 -in client.crt -pubkey -noout | openssl dgst -sha256
openssl pkey -in client.key -pubout | openssl dgst -sha256
两条命令输出的摘要必须完全一致。如果不一致,说明 .crt 不是由该 .key 对应的 CSR 签发的,需要找到正确的私钥。
不要重新生成私钥。新私钥无法与银行已经签发的证书匹配。
如果私钥有密码,可以运行:
openssl pkey -in client.key -check
程序还必须通过 CURLOPT_SSLKEYPASSWD 提供正确的密码。如果私钥没有密码,可以将其设置为空字符串。
2. 检查证书格式
PEM 格式的证书通常如下:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
私钥可能采用以下格式:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
也可能是:
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
可以用下面的命令确认 OpenSSL 能否解析证书和私钥:
openssl x509 -in client.crt -noout -subject -issuer -dates
openssl pkey -in client.key -noout -check
如果银行提供的是 DER 格式,即使文件扩展名为 .crt,也需要先转换:
openssl x509 -inform DER -in client.crt -out client.pem
如果文件实际采用 PKCS#7、PKCS#12 或其他格式,则需要按照它的真实格式处理。仅修改扩展名无法完成格式转换。
3. 配置完整的客户端证书链
银行提供的 .crt 可能只包含终端客户端证书。TLS 握手时,还需要发送签发该证书的中间 CA 证书。
创建一个 PEM 文件,按以下顺序写入证书:
-----BEGIN CERTIFICATE-----
客户端证书内容
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
第一层中间 CA 证书内容
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
更上一级中间 CA 证书内容
-----END CERTIFICATE-----
证书链的顺序是:
client certificate
↓
intermediate CA
↓
higher intermediate CA
通常不需要加入根 CA 证书,因为根证书应当已经存在于银行服务器的信任库中。是否需要包含根证书,应以银行提供的接入文档为准。
假设组合后的文件名是:
client-fullchain.pem
此时,CURLOPT_SSLCERT 应指向这个完整的证书链文件,而不是仅包含客户端证书的 .crt 文件。
4. PHP cURL 配置示例
$twpg_gateway_url = 'https://bank.example.com';
$twpg_cert_file = __DIR__ . '/cert/client-fullchain.pem';
$twpg_key_file = __DIR__ . '/cert/client.key';
$twpg_key_password = '';
$curl = curl_init();
$options = [
CURLOPT_URL => $twpg_gateway_url . '/Exec',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $request,
CURLOPT_HTTPHEADER => [
'Content-Type: text/xml',
],
CURLOPT_TIMEOUT => 30,
// 验证银行服务器证书
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
// 客户端证书及其对应私钥
CURLOPT_SSLCERT => $twpg_cert_file,
CURLOPT_SSLCERTTYPE => 'PEM',
CURLOPT_SSLKEY => $twpg_key_file,
CURLOPT_SSLKEYTYPE => 'PEM',
CURLOPT_SSLKEYPASSWD => $twpg_key_password,
];
curl_setopt_array($curl, $options);
$response = curl_exec($curl);
if ($response === false) {
$errorNumber = curl_errno($curl);
$errorMessage = curl_error($curl);
curl_close($curl);
throw new RuntimeException(
"cURL error {$errorNumber}: {$errorMessage}"
);
}
$statusCode = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
return $response;
如果 XAMPP 使用的 CA 信任库配置不正确,还需要单独指定用于验证银行服务器证书的 CA 文件:
CURLOPT_CAINFO => __DIR__ . '/cert/server-ca-bundle.pem',
这几个选项的作用不同:
CURLOPT_SSLCERT:发送给银行的客户端证书和中间证书链。CURLOPT_SSLKEY:与客户端证书对应的私钥。CURLOPT_CAINFO:本地用于验证银行服务器证书的 CA 集合。
CURLOPT_CAINFO 不能代替客户端证书链,也无法让银行服务器信任你的客户端证书。
5. 检查证书链关系
先查看客户端证书的签发者:
openssl x509 -in client.crt -noout -subject -issuer
再查看中间 CA 证书:
openssl x509 -in intermediate-ca.crt -noout -subject -issuer
客户端证书的 issuer 应与中间 CA 证书的 subject 对应。也可以使用 CA 文件验证证书链:
openssl verify -CAfile root-ca.pem -untrusted intermediate-ca.pem client.crt
验证成功时会返回:
client.crt: OK
这个结果只能证明本地可以建立证书链,不能证明银行服务器已经信任同一个根 CA。
6. 仍然报错时需要银行确认的事项
如果证书、私钥和中间证书链都正确,问题很可能出在银行服务器端。需要请银行技术支持确认以下事项:
- 客户端证书是否已经登记并启用。
- 银行是否信任签发该证书的根 CA 和中间 CA。
- 证书是否登记在正确的商户号下。
- 是否将测试证书用于生产环境,或将生产证书用于测试环境。
- 银行是否要求使用指定 CA 签发的客户端证书。
- 提交的 CSR 是否与当前使用的私钥对应。
- 证书是否已经生效、过期或被吊销。
- 证书用途是否允许 TLS 客户端认证,例如是否包含合适的
Extended Key Usage。 - 网关域名、端口和接口路径是否属于正确的环境。
如果银行只信任其内部 CA,使用公开 CA、自签名 CA 或其他环境签发的证书都会收到 unknown ca。PHP 代码无法绕过这种限制,只能改用银行认可的证书,或者由银行更新服务器端的信任配置。
注意事项
不要在支付接口中长期使用以下配置:
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false,
关闭这些校验后,客户端无法确认连接的是否为真正的银行服务器,会产生中间人攻击风险。正确配置应为:
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => true,
同时,不要将 .key 文件放在 Web 可访问目录中。应限制文件权限,并避免在日志、异常信息或代码仓库中暴露私钥及其密码。
备注:内容仅供参考。
