其实,最直接也最可靠的办法,就是直接比较证书的 NotAfter 属性和当前的 UTC 时间。因为 NotBefore 和 NotAfter 本身就是 UTC 时间,类型标识也是 DateTimeKind.Utc,这样就能有效避免本地时钟偏差带来的误判。

如何在 C# 中检查 X509Certificate2 是否已过期
直接看证书的 NotBefore 和 NotAfter 属性是最可靠、最轻量的方式,不需要发起网络请求,也无需依赖系统时间同步服务。
常见错误是只比对 DateTime.Now,却忽略了本地时钟偏差,或者没注意到证书使用的是 UTC 时间。其实 NotBefore 和 NotAfter 返回值已经是 DateTime 类型,且 Kind 属性为 DateTimeKind.Utc,直接用 DateTime.UtcNow 比较才准确。
- 用
cert.NotAfter < DateTime.UtcNow判断是否已过期,但要留意,证书在NotAfter当天 23:59:59 之前仍然有效,不能单纯用大于等于来判断。 - 用
cert.NotBefore > DateTime.UtcNow判断是否尚未生效,比如刚签发但起效时间设在未来。 - 如果非要兼容本地时区,也可以统一转成 UTC,比如
DateTime.Now.ToUniversalTime(),但坦白说,不推荐这么做,容易引入不必要的歧义。
HttpClient 默认 SSL 验证失败时怎么拿到具体错误原因
默认情况下,HttpClient 一旦遇到 SSL 验证失败,会直接抛出 HttpRequestException,里面几乎不包含任何有用信息。你根本不知道是证书过期、域名不匹配,还是根证书缺失。这时,必须通过 HttpClientHandler.ServerCertificateCustomValidationCallback 这个回调来拦截原始验证过程。
这个回调的第四个参数 sslPolicyErrors 是关键,它是一个位掩码,可能包含:SslPolicyErrors.None、SslPolicyErrors.RemoteCertificateNotA vailable、SslPolicyErrors.RemoteCertificateNameMismatch、SslPolicyErrors.RemoteCertificateChainErrors 等。
- 当
sslPolicyErrors包含RemoteCertificateChainErrors时,可以进一步检查certificate.GetIssuerName()和certificate.Verify()的结果。 - 如果想在调试阶段只放行“过期”错误,可以写一个表达式:
return (sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.None || sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors;——但生产环境千万别这么干。 - 值得留意的是,在 .NET 5+ 中,
HttpClientHandler的ServerCertificateCustomValidationCallback默认是null,也就是使用系统默认验证。一旦你给它赋值,你就完全接管了验证逻辑,连本该通过的合法证书也可能被拦截。
为什么调用 HttpClient.SendAsync 后抛出“Authentication failed because the remote party has closed the transport stream”
这个错误并不是证书过期的典型报错,而是 TLS 握手阶段被服务器主动断开了。通常是因为服务端配置了严格的证书策略,比如只接受特定 CA,或者禁用了旧版 TLS 协议,而客户端没有跟上。
最常见的诱因是,.NET Framework 默认使用 TLS 1.0 或 1.1,但现在大部分 HTTPS 服务都要求 TLS 1.2 以上。虽然 .NET Core 和 .NET 5+ 默认支持 TLS 1.2,但如果项目降级到旧框架,或者跑在老旧 Windows 上,仍然可能出问题。
- 强制启用 TLS 1.2 可以全局生效,建议放在应用启动处:
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - 检查服务器实际支持的协议,可以用
openssl s_client -connect example.com:443 -tls1_2来验证。 - 如果使用
HttpClientHandler实例,还可以设置handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;(.NET Core 2.1+)。 - 另外,Windows Server 2008 R2 默认不支持 TLS 1.2,需要打补丁或升级系统。
自签名证书或私有 CA 证书怎么让 HttpClient 接受
千万不要为了省事而直接关闭验证,比如返回 true,那等于彻底放弃了 HTTPS 的安全性。正确的做法,是把私有根证书导入到当前用户或机器的证书存储,确保 X509Chain 能构建出完整的信任链。
如果没法导入系统存储,比如容器环境或者没有管理员权限,也可以手动加载根证书参与链验证:
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, certChain, sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None) return true;
var chain = new X509Chain();
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(yourRootCert); // yourRootCert 是 X509Certificate2 实例
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // 内网环境常关闭吊销检查
return chain.Build(cert);
};
但有一个容易被忽略的细节:自签名证书没有颁发者,chain.Build() 会直接失败。这时需要单独判断,如果证书的颁发者和使用者相同,就跳过链验证,或者直接比对证书指纹。