Проверка подлинности сертификата клиента TLS в приложении UWP Windows Store

Я пытаюсь подключиться к серверу, который использует TLS с проверкой подлинности сертификата клиента. Ниже приведен фрагмент кода:

async Task TestClientCertAuth()
{
    int iWinInetError = 0;
    Uri theUri = new Uri("http://xxx-xxx");
    try
    {
        using (HttpBaseProtocolFilter baseProtocolFilter = new HttpBaseProtocolFilter())
        {
            // Task<Certificate> GetClientCertificate() displays a UI with all available 
            // certificates with and returns the user selecter certificate.  An 
            // oversimplified implementation is included for completeness.
            baseProtocolFilter.ClientCertificate = await GetClientCertificate();
            baseProtocolFilter.AllowAutoRedirect = false;
            baseProtocolFilter.AllowUI = false;
            using (HttpClient httpClient = new HttpClient(baseProtocolFilter))
            using (HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, theUri))
            using (HttpResponseMessage httpResponse = await httpClient.SendRequestAsync(httpRequest))
            {
                httpResponse.EnsureSuccessStatusCode();

                // Further HTTP calls using httpClient based on app logic.
            }
        }
    }
    catch (Exception ex)
    {
        iWinInetError = ex.HResult & 0xFFFF;
        LogMessage(ex.ToString() + " Error code: " + iWinInetError);
        throw;
    }
}

// Task<Certificate> GetClientCertificate() displays a UI with all available 
// certificates with and returns the user selecter certificate.  An 
// oversimplified implementation is included for completeness.
private async Task<Certificate> GetClientCertificate()
{
    IReadOnlyList<Certificate> certList = await CertificateStores.FindAllAsync();
    Certificate clientCert = null;
    // Always choose first enumerated certificate.  Works so long as there is only one cert
    // installed and it's the right one.
    if ((null != certList) && (certList.Count > 0))
    {
        clientCert = certList.First();
    }
    return clientCert;
}

Вызов SendRequestAsync вызывает исключение с HRESULT 0x80072F7D - я считаю, что это означает ERROR_INTERNET_SECURITY_CHANNEL_ERROR. Нет проблем с доверительным сертификатом сервера. Сертификат клиента установлен в локальном хранилище приложения, и я смог получить его, используя CertificateStores.FindAllAsync. Глядя на следы SSL, я вижу, что клиентский сертификат не отправляется.

Вышеуказанная проблема не возникает, если для HttpBaseProtocolFilter.AllowUI установлено значение true. В этом случае вызов SendRequestAsync приводит к отображению пользовательского интерфейса, запрашивающего согласие на использование сертификата клиента. После того как в этом диалоговом окне выбрано "Разрешить", я вижу, что клиентский сертификат и сертификат проверяют сообщения, отправленные в трассировках, и соединение установлено успешно.

Вопрос: Код приложения уже обрабатывает выбор сертификата пользователем. Я хотел бы знать, есть ли способ указать согласие на использование клиентского сертификата программно. Поскольку включение AllowUI вызывает другие побочные эффекты, например, если сервер повторно запускает HTTP-код 401 с заголовком WWW-Authenticate: Basic, базовый фильтр protoctol выскакивает свой собственный пользовательский интерфейс, чтобы принять учетные данные пользователя, не давая возможности вызывающей стороне справиться. Хотелось бы избежать обоих вышеуказанных интерфейсов, так как я уже выбрал сертификат клиента и получил учетные данные от пользователя с собственными интерфейсами. Спасибо

2 ответа

Решение

Эта запись в блоге Microsoft содержит информацию о том, почему эта ошибка возникает, когда AllowUI имеет значение false, и предоставляет обходной путь. Пользовательский интерфейс с сертификатом согласия не может быть обойден в любом случае, поэтому ЕС должен пройти через это. Также кажется, что поведение на Windows Phone отличается. Пробовал это решение, и оно, кажется, работает на рабочем столе и поверхности. Общая идея состоит в том, чтобы "заправить" сертификат для использования API-интерфейсами более низкого уровня в текущем сеансе приложения, пытаясь получить доступ к закрытому ключу. В этом случае мы просто пытаемся подписать некоторые фиктивные данные. Как только ЕС предоставляет доступ к сертификату, установление сеанса TLS проходит успешно. Необходимо проверить, как это ведет себя на Windows Phone, хотя.

    private async Task<bool> RequestCertificateAccess(Certificate cert)
    {
        bool signOK = false;

        try
        {
            IBuffer data = CryptographicBuffer.ConvertStringToBinary("ABCDEFGHIJKLMNOPQRSTUVWXYZ012345656789", 
                BinaryStringEncoding.Utf8);

            CryptographicKey key = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(cert,
                HashAlgorithmNames.Sha1, CryptographicPadding.RsaPkcs1V15);

            IBuffer sign = await CryptographicEngine.SignAsync(key, data);

            signOK = CryptographicEngine.VerifySignature(key, data, sign);
        }
        catch (Exception ex)
        {
            LogMessage(ex.ToString(), "Certificate access denied or sign/verify failure.");
            signOK = false;
        }
        return signOK;
    }

RequestClientCertificateAccess может быть вызван непосредственно перед установкой сертификата клиента в фильтре базового протокола.

@ Томас Карбан, спасибо за ответ. Я не использовал sharedUserCertificates, поэтому любой сертификат, который я могу перечислить, должен быть в хранилище сертификатов приложений, если я правильно понимаю. Ссылка, которой я поделился, может помочь вашему делу, если вы ее еще не видели.

Я бы сказал, что у вас нет сертификата, хранящегося в собственном хранилище сертификатов приложения. Если вы установите HttpBaseProtocolFilter.AllowUI = true и подтвердите диалоговое окно, приложение получает разрешение на использование закрытого ключа из пользовательского хранилища. Без подтверждения пользовательского интерфейса приложение может использовать только закрытые ключи из собственного хранилища сертификатов.

Ситуация на Windows 10 Mobile еще хуже - насколько я могу судить, вы не можете установить HttpBaseProtocolFilter.AllowUI в значение true (см. мой вопрос Не удается установить для HttpBaseProtocolFilter.AllowUI значение true в Windows 10 Mobile). Это оставляет единственную возможность использовать собственное хранилище сертификатов приложения.

Другие вопросы по тегам