Приложение .NET не может отправить сертификат клиента - Win 7 vs Win XP? - программирование
Подтвердить что ты не робот

Приложение .NET не может отправить сертификат клиента - Win 7 vs Win XP?

Я разрабатываю веб-приложение ASP.NET, которое отправляет запрос на другой сервер с помощью HttpWebRequest. Он отправляет запрос через HTTPS, а удаленному серверу требуется сертификат клиента. Запрос не работает в приложении .NET, видимо, не может отправить правильный сертификат клиента. Я могу успешно подключить и отправить сертификат клиента, если просто перейду к URL-адресу с помощью веб-браузера (специально для Chrome).

Нижеприведенный код представляет собой простое воспроизведение с простым базовым запросом GET.

var r = WebRequest.Create(url) as HttpWebRequest;
r.ClientCertificates = new X509CertificateCollection { myX509Cert };
using (var resp = r.GetResponse() as HttpWebResponse) {
    ...
}

Я получаю наше любимое исключение: "Не удалось создать безопасный канал SSL/TLS". Как правило, эти типы проблем указывают на проблемы с разрешениями на ваш закрытый ключ сертификата. Я пробовал все, что мог придумать, чтобы убедиться, что все настроено правильно, но, возможно, я что-то пропустил. Короче говоря, удаленный сервер отправляет TLS CertificateRequest со списком, который, похоже, правильно идентифицирует мой сертификат клиента, но мое приложение не отвечает ни на какой сертификат клиента.

Вот моя настройка:

  • Профессиональный 64-разрядный Windows 7
  • Возможность воспроизвести проблему в приложении ASP.NET MVC 3/.NET 4, запущенном на сервере разработчика Visual Studio, приложении ASP.NET WebForms/.NET 3.5, запущенном в локальном IIS, и в консольном приложении /.NET 4
  • Недавно была установлена ​​Microsoft.NET Framework 4.5. Еще не изучили, может ли это быть проблемой.

Вот все, что я пробовал, и то, что знаю:

  • Этот код работал нормально при работе на компьютере под управлением Windows XP.
  • Я гарантировал, что мой клиентский сертификат импортируется в хранилище локальных компьютеров, личных сертификатов, а разрешения Private Key правильно настроены для меня и всех соответствующих пользователей IIS.
  • Я попытался переустановить сертификат на своей машине пару раз.
  • Я подтвердил, что приложение .NET может получить доступ к сертификату X509 и HasPrivateKey= true
  • Убедитесь, что мой сертификат клиента действителен. Это фактически сертификат SSL для веб-сервера, на котором будет работать это приложение.
  • Я установил PreAuthenticate = true в объект запроса. Не имеет значения.
  • Я пробовал настройку ServicePointManager.Expect100Continue = false, не имел значения
  • Я попытался установить ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3, но, видимо, для удаленного сервера требуется TLS, чтобы не помогать
  • Я установил делегат ServicePointManager.ServerCertificateValidationCallback, чтобы всегда возвращать true. Но запрос не выполняется ранее в рукопожатии TLS, прежде чем он даже дойдет до вызова этого делегата.
  • Когда я перехожу к URL-адресу в Chrome, он просит предоставить клиентский сертификат, представляет правильный сертификат клиента в качестве выбора, я выбираю этот сертификат и получаю действительный ответ. Также работает правильно, когда я создаю запрос в Fiddler. Таким образом, определенно это определенно проблема с моим кодом .NET, а не с самим сертификатом или с удаленным сервером и т.д.
  • Поставщик, с которым я работаю, имеет тот же сервис, настроенный на другом удаленном сервере, и для этого сервиса требуется другой клиентский сертификат. Поэтому я попробовал то же самое с разными URL-адресами и сертификатом клиента и получил тот же отказ

Я добавил трассировку System.Net и увидел это:

SecureChannel # 26717201 - У нас есть сертификаты, предоставленные пользователем. Сервер указал 6 эмитентов. Поиск сертификатов, соответствующих любому из эмитентов.

SecureChannel # 26717201 - Осталось 0 сертификатов клиентов на выбор.

...

InitializeSecurityContext (In-Buffers count = 2, Out-Buffer length = 0, возвращенный код = CertUnknown).

Я включил полнофункциональный журнал SCHANNEL и увидел это предупреждение:

Удаленный сервер запросил аутентификацию клиента SSL, но подходящий сертификат клиента не найден. Будет предпринята попытка анонимного соединения. Этот запрос SSL-соединения может быть успешным или неудачным, в зависимости от настроек политики сервера.

Я запустил Wireshark и увидел, что удаленный сервер отправил CertificateRequest, и он, похоже, имеет запись Distinguished Name с значениями CN\OU\O моего сертификата клиента, указанным точно. После этого мое приложение отправляет ответ сертификата без сертификатов.

Кажется, что я что-то упустил, настроив этот сертификат на правильную работу с .NET-приложениями в Windows 7. Мое лучшее предположение заключается в том, что на моей новой машине с Windows 7 есть что-то другое по сравнению с машиной XP, то есть в результате чего это терпит неудачу. На данный момент у меня нет доступа к среде Windows XP, чтобы подтвердить это; Я за пару дней, но мне очень хотелось бы разрешить это как можно скорее.

Любые идеи будут высоко оценены. Спасибо!

EDIT Как описано выше, при подключении к URL-адресу в Chrome браузер запрашивает у меня сертификат клиента, и я могу предоставить правильный сертификат и успешно подключиться. Однако я не могу сделать это успешно в Internet Explorer (9). Я просто получаю "Internet Explorer не может отображать веб-страницу", без каких-либо других подсказок или объяснений. Мне сказали, что это может быть актуальным, поскольку WebRequest.Create имеет аналогичное поведение с IE. Я смотрю, что это может означать, но будет ценить любые мысли по этому поводу.

EDIT Кроме того, следует отметить, что удаленный сервер использует самоподписанный сертификат SSL. Первоначально я думал, что это может быть проблемой, поэтому я добавил сертификат в качестве доверенного корня в MMC, чтобы сертификат выглядел как допустимый на моей машине. Это не устранило проблему.

4b9b3361

Ответ 1

Это оказалось довольно простой проблемой, но было трудно обнаружить. Удаленный сервер, в котором мое приложение имело мой клиентский сертификат в хранилище ключей, но не какие-либо из корневых сертификатов в цепочке доверия доверенных клиентов.

Я смог использовать свой код для успешной отправки запроса на другой сервер, требующий сертификатов клиентов. Я взял захват в Wireshark при отправке этого успешного запроса, а также взял захват при отправке сбойного запроса на другой сервер. В захвате Wireshark я нашел "Server Hello" и сравнивал сообщения, отправленные с удаленных серверов. "Хороший" удаленный сервер отправлял мой сертификат клиента, а также его корневой сертификат в разделе "Запрос сертификата" этого сообщения. "Плохой" удаленный сервер отправлял только мой клиентский сертификат.

Это напомнило мне, что диагностическая трассировка System.Net читает "Сервер указал 6 эмитентов (ы)... Оставил с 0 клиентскими сертификатами на выбор". Таким образом, оказывается, что "эмитент" является ключевым термином здесь. Первоначально было легко упустить из виду, потому что в моем первоначальном анализе рукопожатия TLS сервер отправил мой сертификат клиента в запрос сертификата. Оглядываясь назад, имеет смысл, что сервер должен отправлять корневой сертификат, а не сам клиентский сертификат.

Ответ 2

Я хотел бы добавить еще одно "решение" проблемы.

Сервер не сможет отправить правильный список эмитентов, если этот список слишком длинный. Это, похоже, ограничение Microsoft. http://support.microsoft.com/kb/933430

Источник: http://netsekure.org/2011/04/tls-client-authentication-and-trusted-issuers-list/

Решение состоит в том, чтобы попросить сервер не отправлять список вообще. (Путем редактирования значения реестра)

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL

Значение: SendTrustedIssuerList Тип значения: REG_DWORD Данные значения: 0 (False)

Ответ 3

Для меня эти точные симптомы были вызваны использованием TLS 1.0, который сервер не разрешал. Это можно найти в журналах трассировки как таковых:

Информация о System.Net: 0: ProcessAuthentication (Protocol = Tls, Cipher = TripleDes 168 бит, Hash = Sha1 160 бит, Key Exchange = RsaKeyX 2048 бит).

TLS 1.0 по умолчанию используется для .NET 4.5, но его можно переопределить, установив:

ServicePointManager.SecurityProtocol = 
    SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

Существуют также некоторые флаги реестра, которые позволяют установить это без изменения существующего кода.