UWP HttpRequestMessage.TransportInformation 缺失
UWP HttpRequestMessage.TransportInformation missing
我的 UWP 应用程序连接到 http(s) 服务器以执行一些数据传输。
在建立 TLS 连接时,如果由于握手失败而失败,HttpRequestMessage.TransportInformation 会提供有关特定错误和服务器证书的信息,我们使用这些信息向最终用户显示消息。
在我将我的开发机器升级到 Windows 10 build 1607 并将应用程序重新定位到 "Windows 10 Anniversary Edition (10.0; Build 14393)" 之前,以上所有内容都运行良好。早些时候这是 "Windows 10 (10.0; Build 10586)"。
此更改后,HttpRequestMessage.TransportInformation 中的所有字段均为空。但是,异常和相应的 HRESULT 清楚地表明了 SSL 错误(在这种情况下是不受信任的服务器证书)。
我尝试使用 StreamSocket,果然出现了 SSL 握手错误,但是 StreamSocket.Information 属性 已正确填写所有字段(服务器证书、错误等),因此它们可以检查。
备案,服务器证书是自签名的并使用 SHA1 thumbprint/signature 算法。
在下面的代码片段中,req.TransprtInformation 在 ConnectToServerHttpAsync 中从不提供服务器证书,而 streamSock.Information 在 ConnectToServerAsync 中提供服务器证书详细信息。
问题:这是较新 SDK 中的错误,还是我必须对 build 14393 上的 HttpClient 执行一些不同的操作才能获取传输信息?未在 MSDN 或 SO 上找到有关此行为的任何信息,因此发布。
private async Task ConnectToServerHttpAsync(Uri connectUri)
{
HttpRequestMessage req = null;
try
{
using (HttpBaseProtocolFilter bpf = new HttpBaseProtocolFilter())
{
bpf.AllowUI = false;
using (HttpClient httpClient = new HttpClient(bpf))
{
req = new HttpRequestMessage(HttpMethod.Get, connectUri);
using (HttpResponseMessage res = await httpClient.SendRequestAsync(req))
{
Status = ((int)(res.StatusCode)) + " " + res.ReasonPhrase;
}
}
}
}
catch (Exception ex)
{
SocketErrorStatus eSocketErrorStatus = SocketError.GetStatus(ex.HResult);
Status = eSocketErrorStatus.ToString();
Status = req?.TransportInformation?.ServerCertificate?.ToString() ?? "No server certificate.";
}
req?.Dispose();
}
private async Task ConnectToServerAsync(Uri uriToConnect)
{
StreamSocket streamSock = new StreamSocket();
HostName hostName = new HostName(uriToConnect.Host);
try
{
await streamSock.ConnectAsync(hostName, uriToConnect.Port.ToString(), SocketProtectionLevel.Tls12);
Status = "Connected.";
streamSock.Dispose();
}
catch (Exception ex)
{
SocketErrorStatus eSocketErrorStatus = SocketError.GetStatus(ex.HResult);
Status = eSocketErrorStatus.ToString();
Status = "Certificate details:";
Status = "Friendly name: " + streamSock.Information.ServerCertificate.FriendlyName;
Status = "Issuer: " + streamSock.Information.ServerCertificate.Issuer;
Status = "SignatureAlgorithmName: " + streamSock.Information.ServerCertificate.SignatureAlgorithmName;
Status = "SignatureHashAlgorithmName: " + streamSock.Information.ServerCertificate.SignatureHashAlgorithmName;
Status = "Subject: " + streamSock.Information.ServerCertificate.Subject;
Status = "ValidFrom: " + streamSock.Information.ServerCertificate.ValidFrom.ToString();
Status = "ValidTo: " + streamSock.Information.ServerCertificate.ValidTo.ToString();
ServerCert = streamSock.Information.ServerCertificate;
}
}
看来我可能找到了解决方法(由于 MS 似乎没有很好地记录 API 行为更改,因此需要)。显然,如果 OS 默认证书验证失败,服务器证书详细信息将不再在 HttpRequestMessage::TransportInformation 属性 中可用。要获取服务器证书详细信息,需要添加(内部版本 13493 中的新功能)ServerCustomValidationRequested 事件的处理程序。对于我的具体情况(自签名、不受信任的证书),我还必须将 ChainValidationResult.Untrusted 添加到 HttpBaseProtocolFilter 中的 IgnorableServerCertificateErrors 属性。此后,ServerCustomValidationRequested 被触发,我能够获取服务器证书的详细信息。修改后的函数如下
顺便说一下,我还注意到一旦处理了 ServerCustomValidationRequested 并且证书被拒绝,使用 HttpServerCustomValidationRequestedEventArgs::Reject,HttpRequestMessage.TransportInformation 确实会被填充。
如果 MS 更好地记录了这种行为变化,我本可以避免很多时间浪费和悲伤。希望这对其他人有帮助。
private async Task ConnectToServerHttpAsync(Uri connectUri)
{
HttpRequestMessage req = null;
try
{
using (HttpBaseProtocolFilter bpf = new HttpBaseProtocolFilter())
{
bpf.AllowUI = false;
bpf.ServerCustomValidationRequested += ServerCustomValidationRequested;
bpf.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
using (HttpClient httpClient = new HttpClient(bpf))
{
req = new HttpRequestMessage(HttpMethod.Get, connectUri);
using (HttpResponseMessage res = await httpClient.SendRequestAsync(req))
{
Status = ((int)(res.StatusCode)) + " " + res.ReasonPhrase;
}
}
}
}
catch (Exception ex)
{
SocketErrorStatus eSocketErrorStatus = SocketError.GetStatus(ex.HResult);
Status = eSocketErrorStatus.ToString();
Status = req?.TransportInformation?.ServerCertificate?.ToString() ?? "No server certificate.";
}
req?.Dispose();
}
private void ServerCustomValidationRequested(HttpBaseProtocolFilter sender, HttpServerCustomValidationRequestedEventArgs customValidationArgs)
{
Status = "-----ServerCustomValidationRequested-----";
Status = "Certificate details:";
Status = "Friendly name: " + customValidationArgs.ServerCertificate.FriendlyName;
Status = "Issuer: " + customValidationArgs.ServerCertificate.Issuer;
Status = "SignatureAlgorithmName: " + customValidationArgs.ServerCertificate.SignatureAlgorithmName;
Status = "SignatureHashAlgorithmName: " + customValidationArgs.ServerCertificate.SignatureHashAlgorithmName;
Status = "Subject: " + customValidationArgs.ServerCertificate.Subject;
Status = "ValidFrom: " + customValidationArgs.ServerCertificate.ValidFrom.ToString();
Status = "ValidTo: " + customValidationArgs.ServerCertificate.ValidTo.ToString();
ServerCert = customValidationArgs.ServerCertificate;
// Validate the server certificate as required.
// customValidationArgs.Reject();
}
我的 UWP 应用程序连接到 http(s) 服务器以执行一些数据传输。 在建立 TLS 连接时,如果由于握手失败而失败,HttpRequestMessage.TransportInformation 会提供有关特定错误和服务器证书的信息,我们使用这些信息向最终用户显示消息。
在我将我的开发机器升级到 Windows 10 build 1607 并将应用程序重新定位到 "Windows 10 Anniversary Edition (10.0; Build 14393)" 之前,以上所有内容都运行良好。早些时候这是 "Windows 10 (10.0; Build 10586)"。
此更改后,HttpRequestMessage.TransportInformation 中的所有字段均为空。但是,异常和相应的 HRESULT 清楚地表明了 SSL 错误(在这种情况下是不受信任的服务器证书)。
我尝试使用 StreamSocket,果然出现了 SSL 握手错误,但是 StreamSocket.Information 属性 已正确填写所有字段(服务器证书、错误等),因此它们可以检查。
备案,服务器证书是自签名的并使用 SHA1 thumbprint/signature 算法。
在下面的代码片段中,req.TransprtInformation 在 ConnectToServerHttpAsync 中从不提供服务器证书,而 streamSock.Information 在 ConnectToServerAsync 中提供服务器证书详细信息。
问题:这是较新 SDK 中的错误,还是我必须对 build 14393 上的 HttpClient 执行一些不同的操作才能获取传输信息?未在 MSDN 或 SO 上找到有关此行为的任何信息,因此发布。
private async Task ConnectToServerHttpAsync(Uri connectUri)
{
HttpRequestMessage req = null;
try
{
using (HttpBaseProtocolFilter bpf = new HttpBaseProtocolFilter())
{
bpf.AllowUI = false;
using (HttpClient httpClient = new HttpClient(bpf))
{
req = new HttpRequestMessage(HttpMethod.Get, connectUri);
using (HttpResponseMessage res = await httpClient.SendRequestAsync(req))
{
Status = ((int)(res.StatusCode)) + " " + res.ReasonPhrase;
}
}
}
}
catch (Exception ex)
{
SocketErrorStatus eSocketErrorStatus = SocketError.GetStatus(ex.HResult);
Status = eSocketErrorStatus.ToString();
Status = req?.TransportInformation?.ServerCertificate?.ToString() ?? "No server certificate.";
}
req?.Dispose();
}
private async Task ConnectToServerAsync(Uri uriToConnect)
{
StreamSocket streamSock = new StreamSocket();
HostName hostName = new HostName(uriToConnect.Host);
try
{
await streamSock.ConnectAsync(hostName, uriToConnect.Port.ToString(), SocketProtectionLevel.Tls12);
Status = "Connected.";
streamSock.Dispose();
}
catch (Exception ex)
{
SocketErrorStatus eSocketErrorStatus = SocketError.GetStatus(ex.HResult);
Status = eSocketErrorStatus.ToString();
Status = "Certificate details:";
Status = "Friendly name: " + streamSock.Information.ServerCertificate.FriendlyName;
Status = "Issuer: " + streamSock.Information.ServerCertificate.Issuer;
Status = "SignatureAlgorithmName: " + streamSock.Information.ServerCertificate.SignatureAlgorithmName;
Status = "SignatureHashAlgorithmName: " + streamSock.Information.ServerCertificate.SignatureHashAlgorithmName;
Status = "Subject: " + streamSock.Information.ServerCertificate.Subject;
Status = "ValidFrom: " + streamSock.Information.ServerCertificate.ValidFrom.ToString();
Status = "ValidTo: " + streamSock.Information.ServerCertificate.ValidTo.ToString();
ServerCert = streamSock.Information.ServerCertificate;
}
}
看来我可能找到了解决方法(由于 MS 似乎没有很好地记录 API 行为更改,因此需要)。显然,如果 OS 默认证书验证失败,服务器证书详细信息将不再在 HttpRequestMessage::TransportInformation 属性 中可用。要获取服务器证书详细信息,需要添加(内部版本 13493 中的新功能)ServerCustomValidationRequested 事件的处理程序。对于我的具体情况(自签名、不受信任的证书),我还必须将 ChainValidationResult.Untrusted 添加到 HttpBaseProtocolFilter 中的 IgnorableServerCertificateErrors 属性。此后,ServerCustomValidationRequested 被触发,我能够获取服务器证书的详细信息。修改后的函数如下
顺便说一下,我还注意到一旦处理了 ServerCustomValidationRequested 并且证书被拒绝,使用 HttpServerCustomValidationRequestedEventArgs::Reject,HttpRequestMessage.TransportInformation 确实会被填充。
如果 MS 更好地记录了这种行为变化,我本可以避免很多时间浪费和悲伤。希望这对其他人有帮助。
private async Task ConnectToServerHttpAsync(Uri connectUri)
{
HttpRequestMessage req = null;
try
{
using (HttpBaseProtocolFilter bpf = new HttpBaseProtocolFilter())
{
bpf.AllowUI = false;
bpf.ServerCustomValidationRequested += ServerCustomValidationRequested;
bpf.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
using (HttpClient httpClient = new HttpClient(bpf))
{
req = new HttpRequestMessage(HttpMethod.Get, connectUri);
using (HttpResponseMessage res = await httpClient.SendRequestAsync(req))
{
Status = ((int)(res.StatusCode)) + " " + res.ReasonPhrase;
}
}
}
}
catch (Exception ex)
{
SocketErrorStatus eSocketErrorStatus = SocketError.GetStatus(ex.HResult);
Status = eSocketErrorStatus.ToString();
Status = req?.TransportInformation?.ServerCertificate?.ToString() ?? "No server certificate.";
}
req?.Dispose();
}
private void ServerCustomValidationRequested(HttpBaseProtocolFilter sender, HttpServerCustomValidationRequestedEventArgs customValidationArgs)
{
Status = "-----ServerCustomValidationRequested-----";
Status = "Certificate details:";
Status = "Friendly name: " + customValidationArgs.ServerCertificate.FriendlyName;
Status = "Issuer: " + customValidationArgs.ServerCertificate.Issuer;
Status = "SignatureAlgorithmName: " + customValidationArgs.ServerCertificate.SignatureAlgorithmName;
Status = "SignatureHashAlgorithmName: " + customValidationArgs.ServerCertificate.SignatureHashAlgorithmName;
Status = "Subject: " + customValidationArgs.ServerCertificate.Subject;
Status = "ValidFrom: " + customValidationArgs.ServerCertificate.ValidFrom.ToString();
Status = "ValidTo: " + customValidationArgs.ServerCertificate.ValidTo.ToString();
ServerCert = customValidationArgs.ServerCertificate;
// Validate the server certificate as required.
// customValidationArgs.Reject();
}