我正在用C#编写一个UWP应用程序,最终用于物联网,但是现在我只是在本地进行调试。我正在使用Windows.Web.Http.HttpClient
连接到我自己编写的自托管WCF REST Web服务,并在同一台机器上作为控制台应用程序运行以进行测试。该服务需要与证书进行相互身份验证,因此我拥有CA证书,服务证书和客户端证书。
我的UWP代码的工作原理如下:
Certificate
附加到HttpBaseProtocolFilter
并将过滤器添加到HttpClient
HttpClient.PostAsync
致电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监控工具的任何建议可以帮助我进一步调试吗?
答案 0 :(得分:3)
这个MSDN帖子证明了答案。似乎是对MS部分的疏忽,需要事先对API进行单独的,毫无意义的调用。哦,好吧。
摘自文章:
但是,在允许访问存储在共享用户证书存储中的证书的证书私钥之前,安全子系统需要用户确认。更复杂的是,如果在代码中指定了客户端证书,那么较低级别的网络功能会假定应用程序已经处理了此问题,并且不会提示用户进行确认。
如果查看与证书相关的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)
试 {
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();
}