Сообщение об ошибке
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