Я пытаюсь вызвать API через HTTPS. API ожидает отправки сертификата клиента, который затем будет проверен. API не возвращает некоторый список принятых CA или около того (как в стандартном потоке TLS веб-сервера), он просто ожидает проверки сертификата.
На клиентском компьютере, на котором запускается мое приложение, сертификат клиента устанавливается на локальном компьютере \ в личном хранилище (где он также выглядит нормально, отметив его в certlm.msc
). Я могу получить сертификат в магазине и добавить его в мой HttpWebRequest
, например, request.ClientCertificates.Add(cert)
. Проверка сертификата в коде приложения во время выполнения имеет закрытый ключ (HasPrivateKey
- это true
, а свойство PrivateKey
содержит данные). В целях тестирования я предоставил everyone
доступ на чтение к сертификату, чтобы убедиться, что их закрытый ключ может быть получен вместе с сертификатом из приложения, похоже, работает.
Однако API жалуется, что сертификат не был отправлен. Проверка запроса с Wireshark показывает TLSv1.2
рукопожатие типа Certificate
, однако Certificate Length: 0
. Так что сертификат действительно не отправлен.
Использование Postman для вызова точно такого же API с точно таким же сертификатом (из файла, поскольку Postman нужен файл, файл .pfx
, из которого сертификат был импортирован в машину \ личный магазин) отправляет сертификат. Wireshark показывает Certificate Length: 770
, и я могу просмотреть информацию о сертификате.
Похоже, по какой-то причине, хотя я прикрепляю сертификат к HttpWebRequest.ClientCertificates
, он не отправляется из кода приложения. Там нет исключений, он просто не будет отправлен.
Подведем итог:
HttpWebRequest.ClientCertificates
содержит сертификат, полученный от машины \ личного магазина
- сертификат имеет закрытый ключ
- сертификат (в
certlm.msc
) выглядит хорошо (доверенный CA и т. Д.)
- сертификат не настроен на API
- однако тот же сертификат настраивается при вызове API от почтальона
- приложение является приложением .Net 4.6.1 wpf
Есть идеи, что мне не хватает? Спасибо!
(позднее редактировать)
Я отладил класс SecureChannel
в платформе, который обрабатывает отправку сертификатов, и обнаружил некоторые неприятные вещи. Сначала несколько комментариев:
Here is how we work:
case 0: Cert Selection delegate is present
Alwasys use its result as the client cert answer.
Try to use cached credential handle whenever feasible.
Do not use cached anonymous creds if the delegate has returned null
and the collection is not empty (allow responding with the cert later).
case 1: Certs collection is empty
Always use the same statically acquired anonymous SSL Credential
case 2: Before our Connection with the Server
If we have a cached credential handle keyed by first X509Certificate
**content** in the passed collection, then we use that cached
credential and hoping to restart a session.
Otherwise create a new anonymous (allow responding with the cert later).
case 3: After our Connection with the Server (ie during handshake or re-handshake)
The server has requested that we send it a Certificate then
we Enumerate a list of server sent Issuers trying to match against
our list of Certificates, the first match is sent to the server.
Once we got a cert we again try to match cached credential handle if possible.
This will not restart a session but helps miminizing the number of handles we create.
Отладка кода, комментарии, похоже, содержат:
- сначала он проверяет делегата выбора сертификата: его нет, и коллекция сертификатов содержит мой сертификат, поэтому он переходит к шагу 2
- здесь он берет мой сертификат, проверяет действительный закрытый ключ, успешен, затем приходит новый комментарий
// (see VsWhidbey#363953) For some (probably good) reason IIS does not renegotiate a restarted session if client cert is needed.
// So we don't want to reuse **anonymous** cached credential for a new SSL connection if the client has passed some certificate.
// The following block happens if client did specify a certificate but no cached creds were found in the cache
// Since we don't restart a session the server side can still challenge for a client cert.
... после чего этот код ...
if ((object)clientCertificate != (object)selectedCert)
selectedCert.Reset();
guessedThumbPrint = null;
selectedCert = null;
clientCertificate = null;
Таким образом, в основном это будет null
ссылка на сертификат, который я передал. Если я правильно понимаю, это обойдет некоторую проблему IIS, и это означает, что он ожидает, что сервер отправит обратно список эмитентов для фильтрации сертификаты (это будет случай 3 из комментария), что действительно то, что я вижу в коде. Только если этот список эмитентов будет получен с сервера, код будет проходить через клиентские сертификаты и фильтровать их по эмитентам, а затем отправлять соответствующие сертификаты.
Теперь у меня возникает вопрос: можно ли отправлять клиентские сертификаты только в том случае, если сервер запрашивает список эмитентов? Потому что в моем случае вызываемый API не делает этого, он просто ожидает сертификат. Надеюсь, я ошибаюсь, и кто-то может пролить свет на это.
(редактировать позже)
API, который я сейчас вызываю, возвращает список CA. Мой тестовый CA был добавлен туда, и он возвращается во время рукопожатия SSL. Теперь отладка SecureChannel
показывает, что мои предыдущие предположения были верны:
- при первом прохождении добавленный сертификат не отправляется
- однако после получения списка CA от сервера он проверяется на наличие одного, соответствующего издателю моего сертификата, и совпадение действительно найдено!
- после успешного совпадения
SecureChannel
успешно устанавливает m_SelectedClientCertificate
для моего сертификата, но ... - ... к сожалению, он все еще не отправляется на сервер со следующим шагом протокола: (
Как только SecureChannel.m_SelectedClientCertificate
установлен, он, похоже, больше не используется из SecureChannel
, но выставлен:
internal X509Certificate LocalClientCertificate {
get {
return m_SelectedClientCertificate;
}
}
К сожалению, это свойство никогда не вызывается в моем случае: (