PushSharp - ios - StopAllServices() hangs with no errors - c#

I am trying to send a push notification to IOS device via PushSharp. For android, it works. For IOS, the call to StopAllServices() hangs forever, without calling any exception handlers.
Could the problem be that I was given a .pem certificate file, and pushsharp requires a .p12 file?
The code is the following:
var br = new PushBroker();
br.OnNotificationSent += br_OnNotificationSent;
br.OnNotificationFailed += br_OnNotificationFailed;
br.OnChannelException += br_OnChannelException;
br.OnServiceException += br_OnServiceException;
br.OnDeviceSubscriptionChanged += br_OnDeviceSubscriptionChanged;
br.OnDeviceSubscriptionExpired += br_OnDeviceSubscriptionExpired;
br.OnChannelCreated += br_OnChannelCreated;
br.OnChannelDestroyed += br_OnChannelDestroyed;
var appleCert = Resource1.ck; // this is a pem file, not a p12 file!!! could this be the problem?
var sandbox = true;
br.RegisterAppleService(new ApplePushChannelSettings(!sandbox, appleCert, "223684"));
// password given to me by ios developer
var deviceIds = new string[] { "09eddcb8b89494adf802a0caf97d5daaa789a53f52d8c544dbdcf39f2c0b619a" };
foreach (var did in deviceIds)
{
br.QueueNotification(
new AppleNotification()
.ForDeviceToken(did)//the recipient device id
.WithAlert("test: " + DateTime.Now.ToString())//the message
.WithBadge(1)
.WithSound("sound.caf"));
}
br.StopAllServices(waitForQueuesToFinish: true); // hangs forever, no callbacks are called
I am using PushSharp taken via Git, and compiled by myself with Visual Studio 2013, as of yesterday.
The hang happens both if the code is in a console application and in an asp.net application.
I am using the sandbox, because I was told to. If I use the production server, I get an exception telling me that the certificate is for the sandbox.
Thanks for any hint as to the cause of the freeze.

We spent a whole day trying to guess the problem!
In the end it was in the wrong Newtonsoft.Json version
Some of the projects in our solution were dependant on the older version of this library as a result we had bad luck to get wrong version in the /bin folder of the Web project.

You can wait few seconds for br_OnNotificationFailed or any other event probably. It should contain some error description.
Nevertheless, I've found out PushSharp has strict requirements about certificates usage. PEM should be OK but it is not enough, even if you import it from file - you should have all necessary certificates in Windows certificates store (pem itself and its dependecies):
Import your PEM to Local Machine\Root storage and give read access rights of its private key to the user of your running application
Import from Apple site certificates Apple Worldwide Developer Relations Certification Authority and Apple Root CA into Local Machine\Trusted Root Certification Authorities
Import Entrust Secure CA certificate (for SSL as described in iOS Developer Library) into Local Machine\Trusted Root Certification Authorities

In the end it was a certificate problem. The .pem I was given is not accepted by PushSharp. Only when I was given a .p12 created with this guide
https://code.google.com/p/apns-sharp/wiki/HowToCreatePKCS12Certificate
, the problem was solved.
However, PushSharp should have raised an exception instead of hanging.

An ASP.NET application is NOT the ideal place to use PushSharp. You'd be better off using a Windows Service, or some other infrastructure if at all possible. The reason is that in an ASP.NET application, the Application Pool (AppPool) can be restarted on you and is usually not under your direct control, which means all the Queued notifications that PushSharp may be in the process of sending could be lost if PushSharp is not cleaned up gracefully.
If you MUST run PushSharp in an ASP.NET application, the best way is to create a singleton PushBroker instance in your Global.asax file. You should keep this singleton instance around for the lifespan of your web application, including a call to pushBroker.StopAllServices() when your Application is ending (Application_End in global.asax).
You can help mitigate losing messages due to unforeseen App Pool terminations or restarts by persisting notifications you want to send in some other way, and only removing them from that persistent storage once the OnNotificationSent event has fired. This is still not perfect (you may risk multiple notification deliveries), but it's probably adequate for most.
You should not be creating and destroying instances of PushBroker each time you send a notification, as this uses unnecessary resources and if you're using Apple APNS, they require you to keep the connection to their servers open as long as possible when sending notifications. You should also call pushBroker.StopAllServices() in your Application_Ended event in your Global.asax. Keep in mind that PushSharp works.
Ref: https://github.com/Redth/PushSharp/issues/240

Related

Error when using X509KeyStorageFlags.EphemeralKeySet in Azure App Service Web Job

I'm trying to instantiate a X509Certificate2 object in a Web Job, in an Azure App Service. The certificate is a PFX file.
When I try to instantiate like this, it fails to use the object in a WS call:
new X509Certificate2(byteArray, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.EphemeralKeySet)
By failing, I mean it starts throwing:
System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.
But when I try like this, the WS works correctly:
new X509Certificate2(byteArray, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.UserKeySet)
The only difference is the user of X509KeyStorageFlags.EphemeralKeySet. The app is running on .Net Framework 4.7.2. Does anybody know why this happens?
A little explanation: we've had a issue with disk space in the App Service and we've read in some articles and in some SO questions/answers that this could have been caused by the fact that Windows writes to disk all certificates read, thus consuming a lot of space.
One example is this question.
SslStream, on Windows, can't work with EphemeralKeySet keys. The underlying reason is that Windows doesn't do TLS in-proc, but does all of the crypto operations in a different process. Their current functionality doesn't try to export/transport ephemeral keys to that other process, so it fails on the other side with "I can't find the private key".

CngKey.Import on azure

var rawData = Convert.FromBase64String(_signingKey);
var cng = CngKey.Import(rawData, CngKeyBlobFormat.Pkcs8PrivateBlob);
I use this code to extract key, from embedded base64 string.
It works fine when I test it locally but when I publish on azure I get following exception:
WindowsCryptographicException: The system cannot find the file specified
(once again I'm not reading from any file)
I need this to communicate with apple apns for push notifications, is there any workaround?
And this happens only on free service plan, if I switch to basic plan it's working.
I ran into the same error after publishing an existing application to Azure. In my case the problem was solved after I set WEBSITE_LOAD_USER_PROFILE = 1 in App Services / App Name / Application Settings.
Setting WEBSITE_LOAD_USER_PROFILE to equal 1 in the Azure App Service configuration definitely got my remote iOS notifications working. Using dotAPNS for C# .NET I also needed to omit apns.UseSandbox().
It seems that it causes by there is no certificate attached in your Azure Mobile App. If it is that case, we need to upload the "Development" or "Distribution" SSL certificate to the WebApp. More info about how to send push notifications to iOS App, please refer to the azure document.
I've had a similar error trying to construct a X509Certificate2 from a byte array - worked fine locally but once I deploy to Azure Web App, I got the same and VERY misleading file not found exception.
The real issue turned out to be that there was no user store associated with the web service account. You can also get a similar error if there are permission-related errors with accessing the certificate store on Windows.
In any case - In my scenario I fixed the problem by using MachineKeySet:
new X509Certificate2(certRawBytes, default(string), X509KeyStorageFlags.MachineKeySet);
So, in your scenario, try something like:
var keyParams = new CngKeyCreationParameters
{
KeyCreationOptions = CngKeyCreationOptions.MachineKey,
};
CngKey.Create(CngAlgorithm.Rsa, keyName, keyParams);
Note: You may have to set a few parameters to get the above working. The Import method doesn't seem to support MachineKey - but you should be able to achieve similar outcome by using the Create method.
To add to #strohmsn's answer, you can also set the App Service settings with this value directly within Visual Studio on the Publish page for web apps: Right click on web app and select Publish, then select App Service Settings, and you can add setting properties there: WEBSITE_LOAD_USER_PROFILE = 1 in this case. See screenshot:
For making it works, I needed TWO things in AzureWebApp..
So my code is :
//I load the PrivateKey here
ReadedByte = System.IO.File.ReadAllBytes(strPathPrivateKey);
//create the RSA thing
RSA rsa = System.Security.Cryptography.RSA.Create();
//import the key. It crashed HERE with the 'System cannot find file specified'
rsa.ImportPkcs8PrivateKey(source: ReadedByte,bytesRead: out int _);
It works perfectly locally. But, to make it WORK on Azure Web App, I had to have those TWO requirements :
1 - the WEBSITE_LOAD_USER_PROFILE = 1 spoken in the discussion above and below
2 - The App Service Plan must include "Custom domains / SSL" !
...so No 'F1 Share Infrastructure' nor 'D1 Share Infrastructure'. The lowest Service plan that worked for me was 'B1 - 100 Total Acu'.
Maybe I have something wrong somewhere else in my code, or my 'RSA' choice is bad..anyway...
It now works!

SSL certification on C# service over TCP

I have 2 server side (dummy) programs which creates a TCP server (TCPListener) and then tries to authenticate the server with X509 certificates (BeginAuthenticateAsServer).
The client program is ready.
The difference between the two server side program is that one of them is a simple console application while the other is a Windows service.
For some reason client can connect to the console application but not to the service. Design is the same in both program.
I'm using the following line to describe the certificate I will use:
serverCertificate = new X509Certificate( "C:\\Users\\Tom\\workspace\\ServerSSL.cer", "12345678" );
I think something is fishy about privilages with the service program but I could not figured out in the last couple of days. Of course, I have the The server mode SSL must use a certificate with the associated private key. error. When I tried to search for answer as help, I got results in topics of IIS / webservices but I'm using a simple Windows 7 Pro.
May I ask your help?
The file ServerSSL.cer most likely contains only certificate, not the private key. That's what the error message tells you.
Try to find pfx or p12 file. Or if you have separate file that contains private key (i.e. .key) you need to make a pfx (p12) file from both of them (.key and .cer). You could use openssl or xca to do that.

Xamarin/Mono Mac - Handshake failure in AppStore build

I have an application which now needs to be deployed to the app store, as it is slowly becoming unavoidable thanks to Gatekeeper.
Only problem is that web requests seem to fail, in the sense that they aren't even being fired.
The following code snippet has been pulled from a Xamarin Bugzilla article, and succeeds when built for Release and Debug;
try
{
WebClient test = new WebClient();
Console.WriteLine("Testing SSL GET...");
string testresponse = test.DownloadString(checkFileUrl);
Console.WriteLine("testresponse = " + testresponse);
} catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.InnerException.Message);
}
However, when I flip over to AppStore build, with sandboxing and Network IO Entitlements, the request never gets sent out, as verified by Charles in Non-SSL decryption mode. The following gets spat out from the console;
Testing SSL GET...
Error getting response stream (Write: The authentication or decryption has failed.): SendFailure
The authentication or decryption has failed.
This seems to be the problem, as we use SOAP calls made to an IIS service to perform actions, the first of which is logging in. For Debug and Release, login works fine, as the calls are completed. Once again, the AppStore build doesn't even attempt to make contact.
Certificates are valid, and CA's installed in my keychain.
Leading up to this, I was getting some exceptions in the code (in Debug) such as;
System.Exception..ctor (message="invalid encoding specification.") in /private/tmp/source/bockbuild-mono-3.2.6/profiles/mono-mac-xamarin/build-root/mono-3.2.6/mcs/class/corlib/System/Exception.cs:81
and
System.Exception..ctor (message="Store Root doesn't exists.") in /private/tmp/source/bockbuild-mono-3.2.6/profiles/mono-mac-xamarin/build-root/mono-3.2.6/mcs/class/corlib/System/Exception.cs:81
and
System.Exception..ctor (message="Store CA doesn't exists.") in /private/tmp/source/bockbuild-mono-3.2.6/profiles/mono-mac-xamarin/build-root/mono-3.2.6/mcs/class/corlib/System/Exception.cs:81
which still leads me to believe it is a Certificate issue. The test URL is an S3 link, and the login server is an EC2 instance with valid Certificates.
Cheers.
Check how your application is being packaged.
By default, when building your project (either in Xamarin Studio or Visual Studio), it will call a tool called mtouch that includes a linker for managed code. This tool is used to remove features from the class libraries that the application is not using.
Or so mtouch would like you to believe.
The default option of the linker behaviour is to Link all assembiles. This will use mtouch to try to make the application as small as possible by modifying user code. This can and will break code that uses features in a way that mtouch cannot detect (such as webservices, reflection or serialisation).
The workaround that I have used is to disable linking. By changing the linker behaviour to Don't Link, this will make sure that no assemblies are modified.
You can find the menu to do this by right-clicking on the relevant project, and selecting Options:
Xamarin Studio - Project Options window
Try changing the linker behaviour to Don't Link (as shown above) and rebuild.
More information
Xamarin Guides - Linker with iOS

Push Notifications with PushSharp - the basics

I need to push notifications to tens of thousands of iOS devices that my app installed. I'm trying to do it with PushSharp, but I'm missing some fundamental concepts here. At first I tried to actually run this in a Windows service, but couldn't get it work - getting null reference errors coming from _push.QueueNotification() call. Then I did exactly what the documented sample code did and it worked:
PushService _push = new PushService();
_push.Events.OnNotificationSendFailure += new ChannelEvents.NotificationSendFailureDelegate(Events_OnNotificationSendFailure);
_push.Events.OnNotificationSent += new ChannelEvents.NotificationSentDelegate(Events_OnNotificationSent);
var cert = File.ReadAllBytes(HttpContext.Current.Server.MapPath("..pathtokeyfile.p12"));
_push.StartApplePushService(new ApplePushChannelSettings(false, cert, "certpwd"));
AppleNotification notification = NotificationFactory.Apple()
.ForDeviceToken(deviceToken)
.WithAlert(message)
.WithSound("default")
.WithBadge(badge);
_push.QueueNotification(notification);
_push.StopAllServices(true);
Issue #1:
This works perfectly and I see the notification pop up on the iPhone. However, since it's called a Push Service, I assumed it would behave like a service - meaning, I instantiate it and call _push.StartApplePushService() within a Windows service perhaps. And I thought to actually queue up my notifications, I could do this on the front-end (admin app, let's say):
PushService push = new PushService();
AppleNotification notification = NotificationFactory.Apple()
.ForDeviceToken(deviceToken)
.WithAlert(message)
.WithSound("default")
.WithBadge(badge);
push.QueueNotification(notification);
Obviously (and like I already said), it didn't work - the last line kept throwing a null reference exception.
I'm having trouble finding any other kind of documentation that would show how to set this up in a service/client manner (and not just call everything at once). Is it possible or am I missing the point of how PushSharp should be utilized?
Issue #2:
Also, I can't seem to find a way to target many device tokens at once, without looping through them and queuing up notifications one at a time. Is that the only way or am I missing something here as well?
Thanks in advance.
#baramuse explained it all, if you wish to see a service "processor" you can browse through my solution on https://github.com/vmandic/DevUG-PushSharp where I've implemented the workflow you seek for, i.e. a win service, win processor or even a web api ad hoc processor using the same core processor.
From what I've read and how I'm using it, the 'Service' keyword may have mislead you...
It is a service in a way that you configure it once and start it.
From this point, it will wait for you to push new notifications inside its queue system and it will raise events as soon as something happens (delivery report, delivery error...). It is asynchronous and you can push (=queue) 10000 notifications and wait for the results to come back later using the event handlers.
But still it's a regular object instance you will have to create and access as a regular one. It doesn't expose any "outside listener" (http/tcp/ipc connection for example), you will have to build that.
In my project I created a small selfhosted webservice (relying on ServiceStack) that takes care about the configuration and instance lifetime while only exposing the SendNotification function.
And about the Issue #2, there indeed isn't any "batch queue" but as the queue function returns straight away (enqueue and push later) it's just a matter of a looping into your device tokens list...
public void QueueNotification(Notification notification)
{
if (this.cancelTokenSource.IsCancellationRequested)
{
Events.RaiseChannelException(new ObjectDisposedException("Service", "Service has already been signaled to stop"), this.Platform, notification);
return;
}
notification.EnqueuedTimestamp = DateTime.UtcNow;
queuedNotifications.Enqueue(notification);
}

Categories