WebApi HttpClient не отправляет сертификат клиента
Я пытаюсь защитить свой сервис RESTful WebApi с помощью аутентификации ssl и клиента с помощью клиентских сертификатов.
Проверить; Я создал самоподписанный сертификат и размещен на локальной машине, доверенной корневой папке сертификации, и я создал сертификаты "сервер" и "клиент".
Стандартный https для сервера работает без проблем.
Однако у меня есть код на сервере для проверки сертификата, который никогда не вызывается при подключении с использованием моего тестового клиента, который поставляет мой клиентский сертификат, а тестовому клиенту возвращается статус запрещенного 403.
Это указывает на то, что сервер не выполнил мой сертификат, прежде чем он достигнет моего кода проверки.
Однако, если я запускаю скрипач, он знает, что требуется сертификат клиента, и просит его предоставить его в "Мои документы \Fiddler2". Я дал ему тот же клиентский сертификат, который я использую в своем тестовом клиенте, и теперь мой сервер работает и получил сертификат клиента, которого я ожидаю!
Это означает, что клиент WebApi не отправляет сертификат надлежащим образом, мой код клиента ниже примерно такой же, как и другие примеры, которые я нашел.
static async Task RunAsync()
{
try
{
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(GetClientCert());
handler.ServerCertificateValidationCallback += Validate;
handler.UseProxy = false;
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri("https://hostname:10001/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var response = await client.GetAsync("api/system/");
var str = await response.Content.ReadAsStringAsync();
Console.WriteLine(str);
}
} catch(Exception ex)
{
Console.Write(ex.Message);
}
}
Любые идеи, почему это будет работать у скрипача, но не на моем тестовом клиенте?
Изменить: Вот код GetClientCert()
private static X509Certificate GetClientCert()
{
X509Store store = null;
try
{
store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Integration Client Certificate", true);
if (certs.Count == 1)
{
var cert = certs[0];
return cert;
}
}
finally
{
if (store != null)
store.Close();
}
return null;
}
Предоставлено, что тестовый код не обрабатывает нулевой сертификат, но я отлаживаю его, чтобы убедиться, что находится правильный сертификат.
Ответы
Ответ 1
Является ли ваш код выше в той же учетной записи пользователя, в которой работает Fiddler? В противном случае он может иметь доступ к файлу сертификата, но не к правильному закрытому ключу. Аналогично, что возвращает ваша функция GetClientCert()
? В частности, имеет ли он свойство свойства PrivateKey
?
Ответ 2
Существует два типа сертификатов. Первый - это публичный файл .cer, который отправляется вам от владельца сервера. Этот файл представляет собой просто длинную строку символов. Второй - это сертификат хранилища ключей, это самоподписанный сертификат, который вы создаете и отправляете на сервер, на который вы звоните, и устанавливаете его. В зависимости от того, сколько у вас безопасности, вам может потребоваться добавить один или оба из них клиенту (обработчик в вашем случае). Я видел только сертификат хранилища ключей, используемый на одном сервере, где безопасность ОЧЕНЬ безопасна. Этот код получает оба сертификата из папки bin/deployed:
#region certificate Add
// KeyStore is our self signed cert
// TrustStore is cer file sent to you.
// Get the path where the cert files are stored (this should handle running in debug mode in Visual Studio and deployed code) -- Not tested with deployed code
string executableLocation = Path.GetDirectoryName(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
#region Add the TrustStore certificate
// Get the cer file location
string pfxLocation = executableLocation + "\\Certificates\\TheirCertificate.cer";
// Add the certificate
X509Certificate2 theirCert = new X509Certificate2();
theirCert.Import(pfxLocation, "Password", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(theirCert);
#endregion
#region Add the KeyStore
// Get the location
pfxLocation = executableLocation + "\\Certificates\\YourCert.pfx";
// Add the Certificate
X509Certificate2 YourCert = new X509Certificate2();
YourCert.Import(pfxLocation, "PASSWORD", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(YourCert);
#endregion
#endregion
Кроме того, вам нужно обрабатывать ошибки сертификата (обратите внимание: это BAD - он говорит, что все проблемы с сертификатом в порядке), вы должны изменить этот код для обработки определенных проблем с сертификатами, таких как "Несоответствие имен". это в моем списке. Есть много примеров того, как это сделать.
Этот код в верхней части вашего метода
// Ignore Certificate errors need to fix to only handle
ServicePointManager.ServerCertificateValidationCallback = MyCertHandler;
Метод где-то в вашем классе
private bool MyCertHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors error)
{
// Ignore errors
return true;
}
Ответ 3
В коде, который вы используете store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
.
Сертификаты клиентов не выбраны из LocalMachine
, вместо этого следует использовать StoreLocation.CurrentUser
.
Проверяя MMC -> File -> Add or Remove Snap-ins -> Certificates -> My user account
, вы увидите сертификат, который использует скрипт. Если вы удалите его из My user account
и импортируете только в Computer account
, вы увидите, что Fiddler не может его забрать.
Обратите внимание, что при поиске сертификатов вы также должны обращаться к культуре.
Пример:
var certificateSerialNumber= "83 c6 62 0a 73 c7 b1 aa 41 06 a3 ce 62 83 ae 25".ToUpper().Replace(" ", string.Empty);
//0 certs
var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, certificateSerialNumber, true);
//null
var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString() == certificateSerialNumber);
//1 cert
var cert1 = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x =>
x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));