✅ 一、你现在访问的服务端证书链(由 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

如果你贴出这两段(屏蔽掉敏感字段即可),我可以帮你确认链条的颁发方向是否一致。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐