Search code examples
c#.nethttpsuwpwindowsiot

UWP app HttpClient HTTPS client certificate problems


I'm writing a UWP app in C# that is eventually destined for IoT, but right now I've only been debugging locally. I'm using Windows.Web.Http.HttpClient to connect to a self-hosted WCF REST web service that I've also written and have running as a Console app on the same machine for testing. The service requires mutual authentication with certificates, so I have a CA cert, service cert, and client cert.

My UWP code works like this:

  1. Check app cert store for client cert and CA cert installed.
  2. If not, install from PFX file and CER file, respectively.
  3. Attach the Certificate to the HttpBaseProtocolFilter and add the filter to the HttpClient
  4. Call the HttpClient.PostAsync

After I call PostAsync I get the following error: An Error Occurred in the Secure Channel Support. After plenty of searching online, and by common sense, I'm pretty sure HttpClient is barfing because of a problem establishing the mutually-authenticated SSL connection. But based on my troubleshooting I can't figure why.

To troublshoot further, I've written a plain old Console app using System.Net.Http.HttpClient, attached the client certificate to the request and everything works great. Sadly, System.Net isn't fully supported on UWP. I've also tried NOT attaching the certificate to the UWP HttpClient and the app prompts me with a UI to select an installed certificate. I select the correct cert and still get the same exception (this at least lets me know the cert is installed correctly and validating properly with the CA from the app's perspective). In additon, I hit the GET on the web service from a browser, select the client cert when prompted, and am able to download a file.

I've tried using Fiddler and, I assume because of the way it proxies traffic, it seems to work a little bit further, except my web service rejects the request as Forbidden (presumably because Fiddler is not including the correct client cert in the request). I haven't hit up Wireshark yet because it's a pain to get Wireshark to work using localhost on Windows.

My next step is to start changing the web service to not require client authentication and see if that is the problem.

Two questions: Why is Windows.Web.Http.HttClient not working in this case? And, less important, any recommendations on good HTTP monitoring tools to help me debug this further?


Solution

  • This MSDN post proved to have the answer. Seems like an oversight on MS part requiring a separate, meaningless call to the API beforehand. Oh well.

    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

    Excerpt from the article:

    However, the security subsystem requires user confirmation before allowing access to a certificates private key of a certificate stored in the shared user certificates store. To complicate matters, if a client certificate is specified in code then the lower level network functions assume the application has already taken care of this and will not prompt the user for confirmation.

    If you look at the Windows Runtime classes related to certificates you won’t find any method to explicitly request access to the certificate private key, so what is the app developer to do?

    The solution is to use the selected certificate to 'Sign' some small bit of data. When an application calls CryptographicEngine.SignAsync, the underlying code requests access to the private key to do the signing at which point the user is asked if they want to allow the application to access the certificate private key. Note that you must call 'Async' version of this function because the synchronous version of the function: Sign, uses an option that blocks the display of the confirmation dialog.

    For example:

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

    You can then modify the earlier code example to call this function prior to using the client certificate in order to ensure the application has access to the certificate private key.