Использование самозаверяющего сертификата с помощью .СЕТЕВОЙ HttpWebRequest /Ответ
-
22-08-2019 - |
Вопрос
Я пытаюсь подключиться к API, который использует самозаверяющий SSL-сертификат.Я делаю это, используя .Сетевые объекты HttpWebRequest и HttpWebResponse .И я получаю исключение, которое:
Базовое соединение было закрыто:Не удалось установить доверительные отношения для защищенного канала SSL / TLS.
Я понимаю, что это значит.И я понимаю почему .NET считает, что он должен предупредить меня и закрыть соединение.Но в этом случае я бы все равно хотел просто подключиться к API, будь прокляты атаки типа "человек посередине".
Итак, как мне добавить исключение для этого самозаверяющего сертификата?Или это подход, позволяющий указать HttpWebRequest / Response вообще не проверять сертификат?Как бы я это сделал?
Решение
@Домстер:это работает, но вы можете захотеть немного повысить безопасность, проверив, соответствует ли хэш сертификата тому, что вы ожидаете.Итак, расширенная версия выглядит примерно так (на основе некоторого используемого нами живого кода):
static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};
/// <summary>
/// Somewhere in your application's startup/init sequence...
/// </summary>
void InitPhase()
{
// Override automatic validation of SSL server certificates.
ServicePointManager.ServerCertificateValidationCallback =
ValidateServerCertficate;
}
/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private static bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
// Good certificate.
return true;
}
log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);
bool certMatch = false; // Assume failure
byte[] certHash = cert.GetCertHash();
if (certHash.Length == apiCertHash.Length)
{
certMatch = true; // Now assume success.
for (int idx = 0; idx < certHash.Length; idx++)
{
if (certHash[idx] != apiCertHash[idx])
{
certMatch = false; // No match
break;
}
}
}
// Return true => allow unauthenticated server,
// false => disallow unauthenticated server.
return certMatch;
}
Другие советы
Оказывается, если вы просто хотите полностью отключить проверку сертификата, вы можете изменить ServerCertificateValidationCallback в ServicePointManager, например:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Это позволит проверить все сертификаты (включая недействительные, с истекшим сроком действия или самоподписанные).
Обратите внимание, что в .NET 4.5 вы можете переопределить проверку SSL для самого HttpWebRequest (а не через глобальный делегат, который влияет на все запросы):
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };
Добавьте самозаверяющий сертификат в доверенные корневые центры сертификации локального компьютера
Вы можете импортировать сертификат, запустив MMC от имени администратора.
Область обратного вызова проверки, используемая в Ответ Домстера может быть ограничено конкретным запросом с использованием параметра sender на ServerCertificateValidationCallback
делегировать.Следующий простой класс области видимости использует этот метод для временного подключения обратного вызова проверки, который выполняется только для данного объекта запроса.
public class ServerCertificateValidationScope : IDisposable
{
private readonly RemoteCertificateValidationCallback _callback;
public ServerCertificateValidationScope(object request,
RemoteCertificateValidationCallback callback)
{
var previous = ServicePointManager.ServerCertificateValidationCallback;
_callback = (sender, certificate, chain, errors) =>
{
if (sender == request)
{
return callback(sender, certificate, chain, errors);
}
if (previous != null)
{
return previous(sender, certificate, chain, errors);
}
return errors == SslPolicyErrors.None;
};
ServicePointManager.ServerCertificateValidationCallback += _callback;
}
public void Dispose()
{
ServicePointManager.ServerCertificateValidationCallback -= _callback;
}
}
Приведенный выше класс может использоваться для игнорирования всех ошибок сертификата для конкретного запроса следующим образом:
var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
request.GetResponse();
}
Чтобы добавить в качестве возможной помощи кого-то другого...Если вы хотите, чтобы он предлагал пользователю установить самозаверяющий сертификат, вы можете использовать этот код (измененный выше).
Не требует прав администратора, устанавливается в доверенные профили локальных пользователей:
private static bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
// Good certificate.
return true;
}
Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors));
try
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.Add(new X509Certificate2(cert));
store.Close();
}
return true;
}
catch (Exception ex)
{
Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message));
}
return false;
}
Кажется, это хорошо работает для нашего приложения, и если пользователь нажмет "Нет", связь не будет работать.
Обновить:2015-12-11 - Изменено имя хранилища.Root на StoreName.My - My будет устанавливаться в хранилище локальных пользователей вместо Root.Root в некоторых системах не будет работать, даже если вы "запускаетесь от имени администратора".
Просто отталкиваюсь от ответа от девстуфф чтобы включить тему и эмитента ... комментарии приветствуются...
public class SelfSignedCertificateValidator
{
private class CertificateAttributes
{
public string Subject { get; private set; }
public string Issuer { get; private set; }
public string Thumbprint { get; private set; }
public CertificateAttributes(string subject, string issuer, string thumbprint)
{
Subject = subject;
Issuer = issuer;
Thumbprint = thumbprint.Trim(
new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste
);
}
public bool IsMatch(X509Certificate cert)
{
bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture);
bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture);
bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2")));
return subjectMatches && issuerMatches && thumbprintMatches;
}
}
private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> {
new CertificateAttributes( // can paste values from "view cert" dialog
"CN = subject.company.int",
"CN = issuer.company.int",
"f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4")
};
private static bool __createdSingleton = false;
public SelfSignedCertificateValidator()
{
lock (this)
{
if (__createdSingleton)
throw new Exception("Only a single instance can be instanciated.");
// Hook in validation of SSL server certificates.
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate;
__createdSingleton = true;
}
}
/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true; // Good certificate.
Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors);
return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert));
}
}
Одна вещь, которую следует иметь в виду, - это наличие ServicePointManager.ServerCertificateValidationCallback, по-видимому, не означает, что проверка CRL и проверка имени сервера не выполнены, она лишь предоставляет средство для переопределения их результата.Таким образом, вашему сервису все еще может потребоваться некоторое время, чтобы получить CRL, и только потом вы узнаете, что он не прошел некоторые проверки.
Я столкнулся с той же проблемой, что и OP, когда веб-запрос выдавал именно это исключение.Я думал, что у меня все настроено правильно, сертификат установлен, я могу легко найти его в машинном хранилище и прикрепить к веб-запросу, и я отключил проверку сертификатов в контексте запроса.
Оказалось, что я работал под своей учетной записью пользователя и что сертификат был установлен в машинном хранилище.Это привело к тому, что веб-запрос выдал это исключение.Чтобы решить проблему, мне пришлось либо работать от имени администратора, либо установить сертификат в хранилище пользователей и прочитать его оттуда.
Казалось бы, C # способен найти сертификат в машинном хранилище, даже если он не может быть использован с веб-запросом, и что это приводит к возникновению исключения OP после выдачи веб-запроса.