PAYATHON 2026

cURL 报错 tlsv1 alert unknown ca 如何解决?

支付小周

结论

tlsv1 alert unknown ca 表示:银行服务器已经收到客户端证书,但无法建立可信的证书链,因此拒绝了该证书。

这里的 unknown ca 通常说明银行服务器不信任客户端证书的签发机构,或者请求只发送了客户端证书,缺少必要的中间 CA 证书。这通常与 TLS 版本无关,也不能通过关闭 CURLOPT_SSL_VERIFYPEER 解决。

建议依次检查:

  1. .crt 与 .key 是否匹配。
  2. 证书是否为当前 cURL/OpenSSL 能读取的 PEM 格式。
  3. 客户端证书和中间 CA 证书是否已按顺序组成完整证书链。
  4. 银行是否已在对应环境中登记并信任该证书或签发 CA。
  5. 是否已恢复服务器证书校验。支付系统中不应关闭 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 可访问目录中。应限制文件权限,并避免在日志、异常信息或代码仓库中暴露私钥及其密码。

备注:内容仅供参考。