Go HTTPS客户端证书链验证失败解决方案
✅ 一、你现在访问的服务端证书链(由 openssl 验证成功)
*.akieni.tech
│
├─ Go Daddy Secure Certificate Authority - G2
│
└─ Go Daddy Root Certificate Authority - G2
这是一条 标准的 GoDaddy 公有链。
它的根证书 “Go Daddy Root Certificate Authority - G2” 已经内置在 Go 的系统信任列表中。
换句话说:
🚫 你其实 不需要自己加载
ca-chain.pem,Go 自带信任这条链。
✅ 二、curl 能成功,但 Go 报错的原因
在你使用 curl 时:
--cacert ca-chain.pem
你加载的是 你自己颁发的内部 CA,而不是 GoDaddy 那条公链。
而服务器在握手时:
Acceptable client certificate CA names:
CN = Akieni Tech Intermediate CA
O = Akieni SA
👉 说明服务端要求客户端提供的证书(client-cert.pem)
必须由 Akieni Tech Intermediate CA 签发,而不是 GoDaddy。
因此:
| 角色 | 证书来源 | 问题 |
|---|---|---|
| 服务端 | 公共 CA(GoDaddy) | ✅ Go 自带信任,无需 ca-chain.pem |
| 客户端 | 私有 CA(Akieni Tech CA)签发 | ❗ 需要你自己提供完整链 |
所以问题不是 “服务端证书不被信任”,
而是 “客户端的证书链验证失败 —— 你的 client-cert.pem 没包含颁发链”。
✅ 三、解决方案
你需要让 Go 能验证你自己的 client 证书链。
curl 能成功是因为它把 3 个文件组合起来:
--cert client-cert.pem
--key client-key.pem
--cacert ca-chain.pem
Go 的等价逻辑应如下:
✅ 推荐最终 Go 代码(可正常双向认证)
package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
)
func main() {
url := "https://dev-apipos.akieni.tech/api/invoices/report/tfc"
jsonData := []byte(`{"test": "ok"}`)
// 加载 client cert+key
cert, err := tls.LoadX509KeyPair("client-cert.pem", "client-key.pem")
if err != nil {
panic(fmt.Errorf("加载client证书失败: %v", err))
}
// 加载 CA 链(你的 Akieni 私有 CA)
caCert, err := os.ReadFile("ca-chain.pem")
if err != nil {
panic(fmt.Errorf("读取ca失败: %v", err))
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
panic("CA证书加载失败")
}
// 构造 TLS 配置
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: x509.NewCertPool(), // 服务端证书 Go 自带信任,所以保留系统默认
ClientCAs: caCertPool, // 告诉服务器我们信任 Akieni CA
ServerName: "dev-apipos.akieni.tech",
MinVersion: tls.VersionTLS12,
}
client := &http.Client{
Transport: &http.Transport{TLSClientConfig: tlsConfig},
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
panic(fmt.Errorf("创建请求失败: %v", err))
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
panic(fmt.Errorf("请求失败: %v", err))
}
body, _ := io.ReadAll(resp.Body)
defer resp.Body.Close()
fmt.Println("响应状态:", resp.Status)
fmt.Println("响应内容:", string(body))
}
✅ 四、若仍然报 “unknown authority”:
请检查以下几点:
| 检查项 | 操作方式 |
|---|---|
✅ ca-chain.pem 是否包含了 Akieni Intermediate + Akieni Root 两段 |
cat intermediate.pem root.pem > ca-chain.pem |
✅ client-cert.pem 是否是由 Akieni Intermediate CA 签发 |
openssl x509 -in client-cert.pem -noout -issuer -subject |
✅ Go 程序运行时 ServerName 是否精确匹配 |
必须是 "dev-apipos.akieni.tech" |
🧩 可快速验证链完整性的命令:
openssl verify -CAfile ca-chain.pem client-cert.pem
输出应为:
client-cert.pem: OK
如果不是 OK(比如 “unable to get local issuer certificate”),
那就说明 ca-chain.pem 不完整。
结论总结:
| 问题点 | 原因 | 解决 |
|---|---|---|
| Go 报 unknown authority | Go 在验证 client 证书链失败 | 确保 ca-chain.pem 完整并加到 ClientCAs |
| curl 能成功 | curl 同时加载了 client+key+ca | Go 需要显式配置 TLS |
| 服务端使用 GoDaddy 公链 | Go 内置信任 | 无需自行配置 RootCAs |
error 2 at 1 depth lookup: unable to get issuer certificate
意思是:
你的客户端证书(
mtlsClientCertificate.pem)是由 Akieni Tech Intermediate CA 签发的,
但mtlsClientIssuingCa.pem里 没有包含颁发它的上级 CA(Root CA),
所以链条断了。
🧩 一、当前证书链关系
| 层级 | 名称 |
|---|---|
| 🌐 Root CA | Akieni Tech Root CA (顶层) |
| 🧩 Intermediate CA | Akieni Tech Intermediate CA |
| 🪪 Client Cert | mtlsClientCertificate.pem |
你现在的 mtlsClientIssuingCa.pem 里只放了 “Intermediate”,
但少了最顶层 “Root”。
所以验证时 openssl 找不到“Intermediate 的上级”。
✅ 二、正确做法
你需要合并出一个完整的 CA 链文件(从中间到根):
cat mtlsClientIssuingCa.pem akieni_root_ca.pem > ca-chain.pem
📘 注意顺序:
中间证书(Intermediate)在前,根证书(Root)在后。
✅ 三、验证是否修复成功
重新执行验证:
openssl verify -CAfile ca-chain.pem mtlsClientCertificate.pem
预期输出:
mtlsClientCertificate.pem: OK
如果输出 OK,说明你的证书链是完整的。
这时候 Go 就不会再报:
tls: failed to verify certificate: x509: certificate signed by unknown authority
✅ 四、然后更新 Go 代码
保持你的 Go 代码里引用新的链文件:
caCert, err := os.ReadFile("ca-chain.pem")
不要再用 mtlsClientIssuingCa.pem。
✅ 五、原理总结
| 问题 | 原因 | 解决方式 |
|---|---|---|
| openssl verify error 2 | 缺少上级 CA | 合并中间 CA + 根 CA |
| curl 能成功 | curl 的 --cacert 包含完整链 |
Go 的 caCertPool 必须也加载完整链 |
| Go 报 unknown authority | 证书链断裂 | 重新生成 ca-chain.pem 并在 Go 中使用 |
要验证你的链确实正确,还可以打印证书链结构:
openssl x509 -in mtlsClientCertificate.pem -noout -issuer -subject
openssl x509 -in mtlsClientIssuingCa.pem -noout -issuer -subject
如果你贴出这两段(屏蔽掉敏感字段即可),我可以帮你确认链条的颁发方向是否一致。
更多推荐



所有评论(0)