Преамбула
Если попытаться соединиться с 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;
}
Класс целиком на PasteBin
На GitHub
Источник