Если попытаться соединиться с HTTPS-сервером, имеющим самоподписанный сертификат, то HttpWebRequest
сгенерирует исключение WebException
Базовое соединение закрыто: Не удалось установить доверительные отношения для защищенного канала SSL/TLS.
(The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
). Оно и понятно, HttpWebRequest
не смог проверить сертификат узла и закономерно нас послал.
Решение заключается в переопределении глобального обработчика System.Net.ServicePointManager.ServerCertificateValidationCallback
.
Внимание! Изменение ServerCertificateValidationCallback
повлияет на все соединения в программе, пока обработчик не будет установлен по умолчанию, надо не забывать это, и возвращать обработчик к стандартному значению, после того, как работа с сервером, имеющим самоподписанный сертификат, завершена.
Предположим, есть класс-обертка над HttpWebRequest
, надо его дополнить так, чтоб можно было работать с серверами с self-signed сертификатами.
Самый простой способ, но не самый безопасный. ServerCertificateValidationCallback
передается делегат, который всегда возвращает true
. Добавляем в класс функцию:
public void EnableIgnoreCertError() { ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; }
И функцию, которая возвращает ServerCertificateValidationCallback
к значению по умолчанию:
public void DisableIgnoreCertError() { ServicePointManager.ServerCertificateValidationCallback = null; }
Начали работать с сервером — вызвали первую, закончили — вызвали вторую.
Немного более сложный, но более безопасный способ. Необходимо заранее либо иметь сам сертификат, либо знать его хэш. В следующем примере будем проверять как раз хэш SHA1 сертификата.
1. Подключим в References’ах System.Security.Cryptography.
2. Подключим необходимые пространства имен:
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
3. Заведем в классе поле, куда из вызывающей программы будем передавать заранее известный хэш сертификата:
public string CertHashString { get; set; }
4. Заведем две функции, для включения и выключения самостоятельной проверки сертификата:
public void EnableValidateCert() { ServicePointManager.ServerCertificateValidationCallback = ValidateCert; } public void DisableValidateCert() { ServicePointManager.ServerCertificateValidationCallback = null; }
В первой функции вместо делегата, возвращающего true
, указываем нашу функцию проверки.
Функция проверки сертификата обязательно должна иметь следующий заголовок:
bool FunctionName (object, X509Certificate, X509Chain, SslPolicyErrors)
, т.е., например:
private bool ValidateCert(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors)
В функции:
1. Проверяем наличие ошибок SSL, если их нет, значит сертификат уже был опознан:
if (sslPolicyErrors == SslPolicyErrors.None) { return true; }
2. Если известный хэш сертификата не был передан вызывающей программой, значит и проверять нечего:
if (string.IsNullOrEmpty(CertHashString)) { return false; }
3. Получаем хэш сертификата сервера в виде строки:
string hashstring = cert.GetCertHashString();
4. Если хэши полученного и известного сертификата совпадают — все ок, возвращаем true
, иначе false
.
if (hashstring == CertHashString) { return true; } return false;
Функция целиком:
private bool ValidateCert(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) { //все и так хорошо if (sslPolicyErrors == SslPolicyErrors.None) { return true; } //не передан хэш сертификата //значит и проверять нечего if (string.IsNullOrEmpty(CertHashString)) { return false; } //получаем хэш сертификата сервера в виде строки string hashstring = cert.GetCertHashString(); //если хэши полученного и известного //сертификата совпадают - все ок. if (hashstring == CertHashString) { return true; } return false; }