用crypto/tls模拟证书过期需构造NotAfter早于当前时间的测试证书,并在VerifyPeerCertificate中传入过期CurrentTime,触发x509.CertificateInvalidError{InvalidReason: x509.Expired}。

如何用 crypto/tls 模拟证书过期并触发 tls.Handshake 失败
Go 的 crypto/tls 默认不主动校验证书有效期是否“已过期”,而是把校验权交给底层 x509.VerifyOptions。真正报错发生在 conn.Handshake() 阶段,错误类型是 x509.CertificateInvalidError,且 Err.Error() 包含 "certificate has expired" 字样。
关键点在于:你不能只改系统时间或依赖真实过期证书——测试必须可控、可重复、不污染环境。推荐做法是构造一个「人为过期」的证书(NotAfter 设为过去时间),再用它启动服务端或配置客户端验证器。
- 服务端测试:用自签名过期证书启动
http.Server或裸tls.Listener,再用标准http.Client访问,观察是否返回x509: certificate has expired - 客户端测试:用
tls.Config.VerifyPeerCertificate手动调用cert.Verify(),传入自定义time.Now()模拟过期时刻 - 避免用
time.Sleep等待真实过期——不可靠、慢、难并行
生成测试用过期证书的最小可行代码(crypto/x509 + crypto/rsa)
不要依赖外部工具(如 openssl)或预生成 PEM 文件。直接在测试中用 Go 原生库生成:NotBefore 设为 1 小时前,NotAfter 设为 1 分钟前,确保它在测试运行时必然过期。
注意:生成私钥和证书必须在测试函数内完成,避免复用导致并发失败;rand.Reader 必须用 crypto/rand.Reader,不能用 math/rand。
立即学习“go语言免费学习笔记(深入)”;
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(-1 * time.Minute), // ← 关键:已过期
Subject: pkix.Name{CommonName: "test.local"},
}
derBytes, _ := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
cert, _ := x509.ParseCertificate(derBytes)
客户端验证逻辑中注入时间偏移(绕过系统时钟)
默认 cert.Verify() 使用 time.Now(),但你可以通过 x509.VerifyOptions.CurrentTime 强制指定一个过去的时间点,从而让有效证书也“变过期”。这比改系统时间安全得多,且不影响其他 goroutine。
典型误用:直接修改 tls.Config.Time ——这个字段只影响 SNI 和 OCSP 时间戳,不控制证书链校验。
- 正确方式:在
VerifyPeerCertificate回调里调用cert.Verify(),并传入CurrentTime: time.Now().Add(-24*time.Hour) - 若使用
http.Client,需设置Transport.TLSClientConfig.VerifyPeerCertificate - 注意:该回调必须显式调用
cert.Verify(),不能只返回 nil,否则跳过校验
测试断言要捕获具体错误类型,而非字符串匹配
只检查错误信息是否包含 "certificate has expired" 是脆弱的——不同 Go 版本或语言环境可能微调提示。应直接断言错误是否为 *x509.CertificateInvalidError,且 InvalidReason == x509.Expired。
示例断言(用 testify/assert 或原生 if err != nil):
if _, ok := err.(x509.CertificateInvalidError); !ok {
t.Fatal("expected CertificateInvalidError")
}
if certErr, ok := err.(x509.CertificateInvalidError); ok {
if certErr.InvalidReason != x509.Expired {
t.Fatalf("expected Expired reason, got %v", certErr.InvalidReason)
}
}
最易被忽略的是:服务端 TLS 握手失败时,错误出现在客户端(比如 http.Get() 返回的 net/http.ErrServerClosed 不是你要找的),而服务端日志往往静默;务必确认你是在发起连接的一方捕获并检查错误。


















