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.
EDIT 2: Investigating further. Tried to connect to an open HTTP2 capable web server. I used https://nghttp2.org/ as an example.
string url = "https://nghttp2.org/";
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
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.