UWP app HttpClient HTTPS客户端证书问题

时间:2016-07-08 11:38:07

标签: c# .net https uwp windowsiot

我正在用C#编写一个UWP应用程序,最终用于物联网,但是现在我只是在本地进行调试。我正在使用Windows.Web.Http.HttpClient连接到我自己编写的自托管WCF REST Web服务,并在同一台机器上作为控制台应用程序运行以进行测试。该服务需要与证书进行相互身份验证,因此我拥有CA证书,服务证书和客户端证书。

我的UWP代码的工作原理如下:

  1. 检查应用程序证书存储区是否已安装客户端证书和CA证书。
  2. 如果没有,请分别从PFX文件和CER文件安装。
  3. Certificate附加到HttpBaseProtocolFilter并将过滤器添加到HttpClient
  4. 致电HttpClient.PostAsync
  5. 致电PostAsync后,我收到以下错误:An Error Occurred in the Secure Channel Support。经过大量的在线搜索,并且通过常识,我很确定HttpClient因为建立相互认证的SSL连接时出现问题而被禁止。但根据我的故障排除,我无法理解为什么。

    为了进一步麻烦,我使用System.Net.Http.HttpClient编写了一个普通的旧控制台应用程序,将客户端证书附加到请求中,一切正常。遗憾的是,UWP并未完全支持System.Net。我还尝试不将证书附加到UWP HttpClient,应用程序提示我使用UI来选择已安装的证书。我选择了正确的证书并仍然得到相同的例外(这至少让我知道证书已正确安装并从应用程序的角度与CA正确验证)。另外,我从浏览器点击Web服务的GET,在提示时选择客户端证书,并且能够下载文件。

    我尝试过使用Fiddler,我认为由于它代理流量的方式,它似乎工作得更远,除了我的Web服务拒绝请求为Forbidden(可能是因为Fiddler不包括正确的客户端证书在请求中)。我还没有打过Wireshark,因为在Windows上使用localhost让Wireshark工作很痛苦。

    我的下一步是开始更改Web服务,不需要客户端身份验证,看看是否存在问题。

    两个问题:为什么Windows.Web.Http.HttClient在这种情况下不起作用?而且,不太重要,有关良好HTTP监控工具的任何建议可以帮助我进一步调试吗?

2 个答案:

答案 0 :(得分:3)

这个MSDN帖子证明了答案。似乎是对MS部分的疏忽,需要事先对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运行时类,您将找不到任何显式请求访问证书私钥的方法,那么应用程序开发人员应该做什么?

解决方案是使用所选证书来签署'一些小数据。当应用程序调用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;
}

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

答案 1 :(得分:0)

  1. 添加项目
  2. 的证书文件
  3. 将证书添加到Manifested文件(在附件中提供文件路径)
  4. Ur Project中的Frist Service Call用于忽略证书验证以下Code最适合登录功能。
  5. 试             {

                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();
            }