UWP应用程序HttpClient HTTPS客户端证书问题

4
我正在使用C#编写一个UWP应用程序,最终将被用于物联网,但现在我只在本地调试。我使用`Windows.Web.Http.HttpClient`连接到自托管的WCF REST Web服务,该服务也是我编写并运行在同一台机器上进行测试的控制台应用程序。该服务需要证书的相互认证,因此我有CA证书、服务证书和客户端证书。
我的UWP代码工作方式如下:
  1. 检查应用程序证书存储是否安装了客户端证书和CA证书。
  2. 如果没有,则从PFX文件和CER文件中分别安装。
  3. 将"证书"附加到"HttpBaseProtocolFilter"中,并将过滤器添加到"HttpClient"中。
  4. 调用"HttpClient.PostAsync"。
在我调用了"PostAsync"之后,我收到以下错误信息: "安全通道支持中发生了错误"。经过大量在线搜索和常识推断,我相信"HttpClient"由于建立相互认证的SSL连接时出现问题而失败。但根据我的故障排除,我无法弄清楚原因。
为了进一步排除故障,我编写了一个普通的控制台应用程序,使用"System.Net.Http.HttpClient",将客户端证书附加到请求中,一切正常。可惜,在UWP上不完全支持"System.Net"。我还尝试了不将证书附加到UWP的"HttpClient"上,应用程序提示我选择已安装的证书。我选择了正确的证书,仍然得到相同的异常(这至少让我知道证书从应用程序的角度正确安装并且可以与CA正确验证)。此外,我通过浏览器访问Web服务上的GET请求时,选择客户端证书后,可以下载文件。
我尝试使用Fiddler,我认为由于代理流量的方式,它似乎可以进一步工作,但是我的Web服务拒绝该请求,因为Fiddler没有在请求中包含正确的客户端证书(可能是这个原因)。我尚未使用Wireshark,因为使用Windows上的localhost进行Wireshark工作很麻烦。
我的下一步是开始更改Web服务,使其不需要客户端认证,看看是否出现问题。
两个问题:为什么在这种情况下`Windows.Web.Http.HttClient`无法工作?而且,不太重要,有没有建议好的HTTP监视工具来帮助我进一步调试?

这些是自签名证书吗? - Brendan Green
我使用了 makecert 工具创建了一个 CA 证书,然后用该 CA 证书颁发了客户端和服务端证书。 - koopaking3
可能相关:https://code.msdn.microsoft.com/windowsapps/How-to-ignore-Self-Signed-e50b89b6 - Brendan Green
我已经尝试过了,但无济于事。顺便说一下,证书是被信任的并且安装在正确的证书存储中。无论如何还是谢谢。 - koopaking3
2个回答

3
这篇MSDN文章提供了答案。看起来微软需要在调用API之前进行一个单独且无意义的调用,这是一个疏忽。嗯,好吧。

http://blogs.msdn.com/b/wsdevsol/archive/2015/03/26/how-to-use-a-shared-user-certificate-for-https-authentication-in-an-enterprise-application.aspx

文章摘录:
然而,安全子系统在允许访问存储在共享用户证书存储中的证书的私钥之前需要用户确认。更为复杂的是,如果在代码中指定了客户端证书,则较低级别的网络功能会认为应用程序已经处理了这个问题,并且不会提示用户进行确认。
如果查看与证书相关的Windows Runtime类,您将找不到任何明确请求访问证书私钥的方法,那么应用程序开发人员该怎么办呢?
解决方案是使用所选的证书对一些小数据进行“签名”。当应用程序调用CryptographicEngine.SignAsync时,底层代码请求访问私钥以进行签名,此时会询问用户是否允许应用程序访问证书私钥。请注意,您必须调用此函数的“Async”版本,因为该函数的同步版本:Sign,使用一个选项来阻止显示确认对话框。
例如:
public static async Task<bool> VerifyCertificateKeyAccess(Certificate selectedCertificate)
{
    bool VerifyResult = false;  // default to access failure
    CryptographicKey keyPair = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(
                                        selectedCertificate, HashAlgorithmNames.Sha1, 
                                        CryptographicPadding.RsaPkcs1V15);
    String buffer = "Data to sign";
    IBuffer Data = CryptographicBuffer.ConvertStringToBinary(buffer, BinaryStringEncoding.Utf16BE);

    try
    {
        //sign the data by using the key
        IBuffer Signed = await CryptographicEngine.SignAsync(keyPair, Data);
        VerifyResult = CryptographicEngine.VerifySignature(keyPair, Data, Signed);
    }
    catch (Exception exp)
    {
        System.Diagnostics.Debug.WriteLine("Verification Failed. Exception Occurred : {0}", exp.Message);
        // default result is false so drop through to exit.
    }

    return VerifyResult;
}

您可以修改之前的代码示例,在使用客户端证书之前调用此函数,以确保应用程序可以访问证书的私钥。

0
  1. 将证书文件添加到您的项目中
  2. 将证书添加到清单文件中(在附件中给出文件路径)
  3. 在您的项目中第一次服务调用时,使用以下代码忽略证书验证。下面的代码最适合登录功能。

尝试 {

            var filter = new HttpBaseProtocolFilter();
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationFailure);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationInformationMissing);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.WrongUsage);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.IncompleteChain);

            Windows.Web.Http.HttpClient client = new Windows.Web.Http.HttpClient(filter);
            TimeSpan span = new TimeSpan(0, 0, 60);
            var cts = new CancellationTokenSource();
            cts.CancelAfter(span);
            var request = new Windows.Web.Http.HttpRequestMessage()
            {
                RequestUri = new Uri(App.URL + "/oauth/token"),
                Method = Windows.Web.Http.HttpMethod.Post,
            };
            //request.Properties. = span;
            string encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(Server_Username + ":" + Server_Password));
            var values = new Dictionary<string, string>
                { { "grant_type", "password" },{ "username",  Uname}, { "password", Pwd }};
            var content = new HttpFormUrlEncodedContent(values);
            request.Headers.Add("Authorization", "Basic " + encoded);
            request.Content = content;
            User root = new User();
            using (Windows.Web.Http.HttpResponseMessage response = await client.SendRequestAsync(request).AsTask(cts.Token))
            {
                HttpStatusCode = (int)response.StatusCode;
                if (HttpStatusCode == (int)HttpCode.OK)
                {
                    using (IHttpContent content1 = response.Content)
                    {
                        var jsonString = await content1.ReadAsStringAsync();
                        root = JsonConvert.DeserializeObject<User>(jsonString);
                        App.localSettings.Values["access_token"] = root.Access_token;
                        App.localSettings.Values["refresh_token"] = root.Refresh_token;
                        App.localSettings.Values["expires_in"] = root.Expires_in;
                        var json = JsonConvert.SerializeObject(root.Locations);
                        App.localSettings.Values["LocationList"] = json;
                        App.localSettings.Values["LoginUser"] = Uname;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接