4

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

我的 UWP 代码是这样工作的:

  1. 检查应用程序证书商店是否安装了客户端证书和 CA 证书。
  2. 如果没有,请分别从 PFX 文件和 CER 文件安装。
  3. 附加CertificateHttpBaseProtocolFilter并将过滤器添加到HttpClient
  4. 调用HttpClient.PostAsync

在我打电话后,PostAsync我收到以下错误:An Error Occurred in the Secure Channel Support. 经过大量的在线搜索,根据常识,我很确定HttpClient是因为建立相互身份验证的 SSL 连接的问题而在呕吐。但根据我的故障排除,我不知道为什么。

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

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

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

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

4

2 回答 2

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 时,底层代码请求访问私钥以进行签名,此时会询问用户是否要允许应用程序访问证书私钥。请注意,您必须调用此函数的“异步”版本,因为该函数的同步版本: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;
}

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

于 2016-07-15T07:22:01.713 回答
0
  1. 将证书文件添加到您的项目
  2. 将证书添加到清单文件(在附件中提供文件路径)
  3. Ur 项目中的第一个服务调用用于忽略证书验证以下代码最适合登录功能。

尝试 {

            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();
        }
于 2018-01-11T08:31:30.197 回答