.NET SslStream / HttpClient.NET
错误信息
The remote certificate was rejected by the provided RemoteCertificateValidationCallback.
该文本在 .NET Core 和 .NET 5~10 中保持不变;HttpClient 会把它包装在外层异常“The SSL connection could not be established, see inner exception.”中。传给回调的 SslPolicyErrors 值可以告诉你 .NET 自身是否也发现了问题。
.NET SslStream / HttpClient — a custom validation callback returned false
含义
应用安装了自己的证书验证回调(ServerCertificateCustomValidationCallback 或 RemoteCertificateValidationCallback),而该回调返回了 false。.NET 的内置检查可能已经通过;是你的代码决定拒绝该证书。这在服务器证书续期或轮换后、使用了证书固定或公钥固定的场景中很常见。
原因
固定的证书已被替换
常见固定证书指纹的做法在每次续期时都会失效(如今公共 CA 每隔几个月就要续期一次);新证书有新的哈希值。
回调自身的逻辑拒绝了有效证书
常见回调精确比对的颁发者名称、主体或指纹列表已经不再匹配,或者判断条件没有考虑 errors 参数中报告的错误。
回调正确地拒绝了真实存在的问题
偶见返回 errors == SslPolicyErrors.None 的回调会拒绝不受信任、已过期或不匹配的证书;这时出错的是证书本身。
修复方法
- 1.
记录回调看到的内容
在回调中记录 SslPolicyErrors、主体、SHA-256 指纹和过期时间,然后更新固定值列表或修复证书。
csharp ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => { var thumbprint = cert!.GetCertHashString(HashAlgorithmName.SHA256); logger.LogWarning("TLS {Host}: errors={Errors} subject={Subject} sha256={Thumb} notAfter={NotAfter:u}", request.RequestUri?.Host, errors, cert.Subject, thumbprint, cert.NotAfter); return errors == SslPolicyErrors.None && pinnedThumbprints.Contains(thumbprint); }, - 2.
固定公钥,并保留一个备用固定值
只要续期时沿用同一密钥,SPKI 固定值就始终有效;务必至少再固定一个备用密钥,避免密钥轮换时把客户端拒之门外。
bash # Pin the public key (SPKI) rather than the certificate: it survives renewals with the same key openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \ | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64