Search code examples
c#.netapple-push-notificationsdotnet-httpclienthttp2

Sending HTTP/2 request to Apple Push Notification Service using HttpClient, Client Certificate and WinHttpHandler raises HttpRequestException


I am trying to write some code to send a push notification to an iPhone using C# .NET and HttpClient over HTTP with client certificate authentication.

Code compiles but always yields a:

HttpRequestException with Message "Error while copying content to a stream."

InnerException IOException Message "The write operation failed, see inner exception."

InnerException WinHttpException Message "{"Error 12152 calling WinHttpWriteData, 'The server returned an invalid or unrecognized response'."}"

I am looking for why the code is failing?

Is it possible to debu/troubleshoot this in some way?

Here is my code

class Program {
    private const string cert = "MIINKwI....<hidden>";
    private const string certpw = "password";
    private const string devicetoken = "921c95b6494828f973512f2327478b0<hidden>";
    private const string bundleid = "se.jensolsson.testapp";

    private static HttpClient client = null;
    static async Task Main(string[] args) {
        try {

            byte[] certificateData = Convert.FromBase64String(cert);
            X509Certificate2 certificate = new X509Certificate2(certificateData, certpw, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

            string url = "https://api.push.apple.com:443/3/device/";

            string payload = @"{""aps"":{""alert"":""Test""}}";

            var handler = new WinHttpHandler();
            handler.ClientCertificates.Add(certificate);

            if (client == null)
                client = new HttpClient(handler);

            using (var request = new HttpRequestMessage(HttpMethod.Post, url + devicetoken)) {
                var messageGuid = Guid.NewGuid().ToString();
                request.Version = new Version("2.0");
                request.Content = new StringContent(payload);
                request.Headers.Add("apns-id", messageGuid);
                request.Headers.Add("apns-push-type", "alert");
                request.Headers.Add("apns-priority", "10");
                request.Headers.Add("apns-topic", bundleid);


                using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) {
                    HttpStatusCode statusCode = response.StatusCode;
                    string reasonPhrase = response.ReasonPhrase;
                    bool success = response.IsSuccessStatusCode;
                }
            }

        }
        catch (ArgumentNullException anu) { }
        catch (InvalidOperationException ioe) { }
        catch (HttpRequestException hre) { }
        catch (TaskCanceledException tce) { }
        catch (Exception e) { }        
    }
}

EDIT @jdweng actually tried Wireshark but since it is over SSL and encrypted I would expect it to be unreadable from Wireshark? What I can see is that the connection is reset from our client, must be done by the underlying framework.

Wireshark

EDIT 2: Investigating further. Tried to connect to an open HTTP2 capable web server. I used https://nghttp2.org/ as an example.

  1. Replaced url line to string url = "https://nghttp2.org/";
  2. replaced request line to using (var request = new HttpRequestMessage(HttpMethod.Get, url)) {.

A very interesting finding is that even though i send a HTTP/2 request and it seem to give a reply, looking at the response object, Version is set to 1.1.

If I also remove the handler.ClientCertificates.Add(certificate); line and retry, looking at the response object, Version is correctly set to 2.0.

So it seem like WinHttpHandler drops HTTP/2 support as soon as a client certificate is added to the handler.

EDIT 3: I now assume this is a bug in the framework and I have posted a bug report here: https://github.com/dotnet/runtime/issues/40794


Solution

  • Turned out that this is not supported. Mainly because of missing functionality in the Windows version. According to a comment in github in the corefx team this should work from Windows version 2004, build 19573+ github discussion. The problem with this version is that it is not released yet and there is no plan on when it will be.