简体   繁体   中英

UWP HttpRequestMessage.TransportInformation missing

My UWP app connects to an http(s) server to perform some data transfer. While establishing a TLS connection, if a failure occurs due to a handshake failure, the HttpRequestMessage.TransportInformation provides information regarding the specific errors and the server certificate, which we were using to display a message to the end user.

All of the above worked perfectly until I upgraded my dev machine to Windows 10 build 1607 and retargeted the app to "Windows 10 Anniversary Edition (10.0; Build 14393)". Earlier this was "Windows 10 (10.0; Build 10586)".

After this change, all the fields in the HttpRequestMessage.TransportInformation are null. However, the exception and corresponding HRESULT indicate the SSL error clearly (untrusted server certificate in this case).

I tried using StreamSocket, and sure enough an SSL handshake error came up, but the StreamSocket.Information property had all the fields (Server certificate, errors etc.) properly filled in, so they could be examined.

For the record, the server certificate is self signed and uses SHA1 thumbprint/signature algorithm.

In the code snippets below, req.TransprtInformation in ConnectToServerHttpAsync never gives the server certificate, whereas streamSock.Information provides server certificate details in ConnectToServerAsync.

Question: Is this a bug in the newer SDK or do I have to do something different with the HttpClient on build 14393 to obtain Transport information? Did not find anything on MSDN or SO on this behavior hence posting.

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

It appears I might have found a workaround (required due to what appears to be a not very well documented API behavior change by MS). Apparently, if the OS default validation of the certificate fails, the server certificate details are no longer available in the HttpRequestMessage::TransportInformation property. To get the server certificate details, a handler for the (new in build 13493) ServerCustomValidationRequested event is required to be added. For my specific case (self-signed, untrusted certificate), I also had to add ChainValidationResult.Untrusted to the IgnorableServerCertificateErrors property in HttpBaseProtocolFilter. After this, the ServerCustomValidationRequested is fired and I am able to get the server certificate details. The modified function is as below.

Incidentally, I also noticed once the ServerCustomValidationRequested is handled and the certificate is rejected, using HttpServerCustomValidationRequestedEventArgs::Reject, the HttpRequestMessage.TransportInformation does get populated.

Had MS documented this behavior change better, I could have avoided much time wastage and grief. Hope this is helpful to others.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM