I've developed an iOS app, that receives Push Notifications. I'm sending them from a .NET environment using PushSharp. Everything went beautifully while developing, and the Pushs were successfully sent:
var push = new PushBroker();
var appleCert = File.ReadAllBytes(#"Utils\Cert.Development.p12");
push.RegisterAppleService(new ApplePushChannelSettings(false, appleCert, "*******"));
push.QueueNotification(new AppleNotification()
.ForDeviceToken(token)
.WithContentAvailable(1)
);
push.StopAllServices();
Now, the app has been approved, and it's at AppStore. I have generate the correct production certificate:
var push = new PushBroker();
var appleCert = File.ReadAllBytes(#"Utils\Cert.Production.p12");
push.RegisterAppleService(new ApplePushChannelSettings(true, appleCert, "*******"));
push.QueueNotification(new AppleNotification()
.ForDeviceToken(token)
.WithContentAvailable(1)
);
push.StopAllServices();
but when I try to send the push from PushSharp, it throws the following exception:
You have selected the Production server, yet your Certificate does not appear to be the Production certificate! Please check to ensure you have the correct certificate!
I'm pretty sure I've followed all the steps. I've downloaded the production certificate that was binded with the provisioning file set in the app to publish. Opened it and exported the .p12.
I'm also sure I'm not using a development one by accident, because, if I set PushSharp for development, using this last certificate, it throws the following error:
You have selected the Development/Sandbox (Not production) server, yet your Certificate does not appear to be the Development/Sandbox (Not production) certificate! Please check to ensure you have the correct certificate!
How can the certificate be neither Development, nor Production?
Is there somewhere I can validate the file? Please give me some insigth on this matter, as I have no clue where to start
Apple has changed the name. Please go to ApplePushChannelSettings.cs and change the name as below.
From
if (production && !subjectName.Contains("Apple Production IOS Push Services"))
To
if (production && !subjectName.Contains("Apple Push Services"))
I need to do this when I renewing my expired cert yesterday. Change the name, rebuild it and upload to server, then it's working again.
Apple has introduced a new universal push certificate that enables connection to both the APNs Production and Development environments.That's why the production certificate common name has been changed from Apple Production IOS Push Services to Apple Push Services.
You should change the code on the provider push server to be compatible with the new common name.
When you create production certificate (.p12) for .NET, Always export like selecting the certificate only. see the attached image
http://davidbits.blogspot.in/2016/02/error-you-have-selected-production.html
Issue was in PushSharp Library just update it to Version .3 this is becuase apple has changed Push Certificate Name From (Apple Production Push Service) to (Apple Push Service)
and pushSharp check the name of Certificate :
(production && !subjectName.Contains("Apple Push Services")).
Error: You have selected the Production server, yet your Certificate
does not appear to be the Production certificate! Please check to
ensure you have the correct certificate!
Solution: (production && !subjectName.Contains("Apple Push Services"))
Invoke Following snippet Send Device Token & Message
public void PendingNotification(string DeviceToken,string message)
{
try
{
int port = 2195;
//Developer
String hostname = "gateway.sandbox.push.apple.com";
//Production
//String hostname = "gateway.push.apple.com";
String certificatePassword = "XXXXXX";
string certificatePath = Server.MapPath("~/Cert.p12");
TcpClient client = new TcpClient(hostname, port);
X509Certificate2 clientCertificate = new X509Certificate2(System.IO.File.ReadAllBytes(certificatePath), certificatePassword);
X509Certificate2Collection certificatesCollection = new X509Certificate2Collection(clientCertificate);
SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
sslStream.AuthenticateAsClient(hostname, certificatesCollection, SslProtocols.Tls, false);
//String DeviceToken = "a5062b62aacbe6a499e02351c3f233ce87004574ff01965dff5f6bb8f15cae13";
String LoginName = "Name";
int Counter = 1; //Badge Count;
String Message = message;
String UID = "your choice UID";
string payload = "{\"aps\":{\"alert\":\"" + Message + "\",\"badge\":" + Counter + ",\"sound\":\"default\"},\"UID\":\"" + UID + "\",\"LoginName\":\"" + LoginName + "\"}";
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write((byte)32);
writer.Write(HexStringToByteArray(DeviceToken.ToUpper()));
writer.Write((byte)0);
writer.Write((byte)payload.Length);
byte[] b1 = System.Text.Encoding.UTF8.GetBytes(payload);
writer.Write(b1);
writer.Flush();
byte[] array = memoryStream.ToArray();
sslStream.Write(array);
}
catch (Exception ex)
{
//Response.Write(ex.Message);
}
}
public static byte[] HexStringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
Follow the steps in the following link to generate Production SSL Certificate:
How To Create APNS Certificate
If that didn't work,double check on your code,make sure you're reading from the correct certificate file, and you're passing True to ApplePushChannelSettings
I'd recommend using something from the 3.0 nuget preview releases. This issue is fixed in these releases and will not be backported to 2.x.
PushSharp 2.x > Push.Apple > ApplePushChannelSettings.cs
On ApplePushChannelSettings.cs find DetectProduction() and CheckProductionCertificateMatching() and replace '.Contains("Apple Production IOS Push Services")' by '.Contains("Apple Push Services")'
It occurs because apple changed the certificate name from "Apple Production IOS Push Services" to "Apple Push Services", the PushSharp identify the type(production/development) by the certificate name.
Related
I'm trying to set-up push notifications for my app.
I'm so far that, when the app is installed through Xcode (in development mode basically), I can successfully receive push notifications. However, as soon as I install the app from TestFlight and try to use the new device token, the APN answers with BadDeviceToken.
I did do my own research, but all the questions on this seem to be outdated: While these used *.pem and *.p12 certificates, I'm using a *.p8 certificate. As far as I understood, p8 certificates are for both development and production, so I don't see the problem here?
I'm using the edamov\pushok library from Github with this code:
<?php
require '../../vendor/autoload.php';
use Pushok\AuthProvider;
use Pushok\Client;
use Pushok\Notification;
use Pushok\Payload;
use Pushok\Payload\Alert;
$options = [
'key_id' => 'xxx', // The Key ID obtained from Apple developer account
'team_id' => 'xxx', // The Team ID obtained from Apple developer account
'app_bundle_id' => 'xxx', // The bundle ID for app obtained from Apple developer account
'private_key_path' => 'xxx (p8 certificate path)', // Path to private key
'private_key_secret' => null // Private key secret
];
$authProvider = AuthProvider\Token::create($options);
$alert = Alert::create()->setTitle('Hello!');
$alert = $alert->setBody('First push notification');
$payload = Payload::create()->setAlert($alert);
//set notification sound to default
$payload->setSound('default');
//add custom value to your notification, needs to be customized
$payload->setCustomValue('key', 'value');
$deviceTokens = ['xxx (device token from TestFlight installation)'];
$notifications = [];
foreach ($deviceTokens as $deviceToken) {
$notifications[] = new Notification($payload,$deviceToken);
}
$client = new Client($authProvider, $production = false);
$client->addNotifications($notifications);
$responses = $client->push(); // returns an array of ApnsResponseInterface (one Response per Notification)
foreach ($responses as $response) {
echo($response->getApnsId());
echo($response->getStatusCode());
echo($response->getReasonPhrase());
echo($response->getErrorReason());
echo($response->getErrorDescription());
}
So how can I setup the APN with a p8 certificate for production mode? Do I need to create a production certificate and somehow include it somewhere?
While trying to send push if app installed through Testflight did you keep the Sandbox enable or not?
I am having trouble validating the merchant in my apple pay sandbox environment. Taken from https://developer.apple.com/reference/applepayjs/applepaysession#2166532, once my server then calls the Start Session endpoint at the provided URL, I get a 500 error.
I've dug around and this 500 error is happening somewhere in the network layer. As listed on the apple page (https://developer.apple.com/reference/applepayjs/), I need the following requirements met:
All pages that include Apple Pay must be served over HTTPS. done, server has ssl/https sitewide
To enable merchant validation, your server must allow access over HTTPS (TCP over port 443) to the Apple Pay IP addresses provided in Listing 1 below. done, server is open to all ips on port 443
Your server must support the Transport Layer Security (TLS) 1.2 protocol and one of the cipher suites listed in Table 1. server does support tls 1.2, since I send requests on tls 1.2 to apple pay's development server (below)
I've been using Wireshark to check what's going on, and I seem to be failing once the server is in the ChangeCipherSpec phase, after the server sends back the cipher spec to the client. (Reference for ssl procedure: https://support.f5.com/csp/article/K15292). As you can see from my image, I'm communicating to the apple pay sandbox server, passing in the same supported tls protocol and cipher suite that the error would suggest -> Handshake Failure (40), so something else is going on and I don't know where to look
If you look at the ServerHello message, you can see the server found and accepted the cipher suite that matches the client, which also matches one of the required ciphers that apple pay supports
I can add other details as necessary
The issue was that our server did not have TLS 1.2 enabled by default. Enabling TLS 1.2 and disabling TLS 1.0 fixed the issue - Win 2008
edit
There are a few things that needed to happen. Our server was on .net 4.5, which does not use tls 1.2 by default (apple requires tls 1.2 to be used). So, we upgraded our solution to .net 4.6, and also forced tls 1.2 for our request. Additionally, we have to include the merchant id certificate in our request to apple (which isn't mentioned in the docs very well).
You can find the github repo of the source I used here (https://github.com/justeat/ApplePayJSSample), but here is my code that I needed to put in my solution to make things work (I also had to export my merchant certificate from my mac's keychain that gave me a .p12 file. I imported this .p12 file into my server's computer certificate store)
[System.Web.Http.HttpPost]
public async Task<ContentResult> GetApplePaySession([FromBody] string url)
{
// http://stackoverflow.com/a/36912392/1837080
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// Load the merchant certificate for two-way TLS authentication with the Apple Pay server.
var certificate = LoadMerchantCertificate();
// Get the merchant identifier from the certificate to send in the validation payload.
var merchantIdentifier = GetMerchantIdentifier(certificate);
// Create the JSON payload to POST to the Apple Pay merchant validation URL.
var payload = new ApplePayRequest()
{
merchantIdentifier = merchantIdentifier,
domainName = System.Web.HttpContext.Current.Request.Url.Host,
displayName = "[display name from apple developer portal]"
};
JObject merchantSession;
// Create an HTTP client with the merchant certificate
// for two-way TLS authentication over HTTPS.
using (var httpClient = CreateHttpClient(certificate))
{
var jsonPayload = JsonConvert.SerializeObject(payload);
using (var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"))
{
// POST the data to create a valid Apple Pay merchant session.
using (var response = await httpClient.PostAsync(url, content))
{
response.EnsureSuccessStatusCode();
// Read the opaque merchant session JSON from the response body.
var merchantSessionJson = await response.Content.ReadAsStringAsync();
merchantSession = JObject.Parse(merchantSessionJson);
}
}
}
// Return the merchant session as JSON.
return Content(merchantSession.ToString(), "application/json");
}
#region Apple Pay helper methods
private X509Certificate2 LoadMerchantCertificate()
{
X509Certificate2 certificate;
// Load the certificate from the current user's certificate store. This
// is useful if you do not want to publish the merchant certificate with
// your application, but it is also required to be able to use an X.509
// certificate with a private key if the user profile is not available,
// such as when using IIS hosting in an environment such as Microsoft Azure.
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
// when using thumbprint from mmc, look at:
// http://stackoverflow.com/a/14852713
// there is a hidden character that you must delete
var certificates = store.Certificates.Find(
X509FindType.FindByThumbprint,
"[thumbprint]",
validOnly: false);
if (certificates.Count < 1)
{
throw new InvalidOperationException(
// ReSharper disable once UseStringInterpolation
string.Format(
"Could not find Apple Pay merchant certificate with thumbprint '{0}' from store '{1}' in location '{2}'.",
"[thumpprint]", store.Name, store.Location));
}
certificate = certificates[0];
}
return certificate;
}
private string GetMerchantIdentifier(X509Certificate2 certificate)
{
// This OID returns the ASN.1 encoded merchant identifier
var extension = certificate.Extensions["1.2.840.113635.100.6.32"];
// Convert the raw ASN.1 data to a string containing the ID
return extension == null ? string.Empty : Encoding.ASCII.GetString(extension.RawData).Substring(2);
}
private HttpClient CreateHttpClient(X509Certificate2 certificate)
{
var handler = new WebRequestHandler();
handler.ClientCertificates.Add(certificate);
return new HttpClient(handler, disposeHandler: true);
}
#endregion
I just recently went through this. For me I had to merge the PEM and KEY files into a PFX. Then I was able to run the start session call from ubuntu 16.04 using .net core 2.1
private HttpClient CreateHttpClient()
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(new X509Certificate2(#"/path/yourcombinedpfx.pfx"));
return new HttpClient(handler); ;
}
We have an Azure Notification Hub set up, with APNS Configured in Production Mode, using our Production APNS Certificate.
We register our test devices (using TestFlight / Production build, and certificate) to APNS, and then to ANH with a tag dealer-1. We can send notifications using our production certificate and registered Device ID with success when using APNS directly, however, when we use 'Test Send' we get a 'Successful send' to 1 device (or however many we have registered). The notification is not received. If we then run 'Test Send' again, the are 0 devices to send to.
In the logs, we see 'APNS Errors' per device, per test send. I cannot see any way to view what the errors actually are though so this is an absolutely useless metric.
I have ran through all the troubleshooting steps and confirmed many times that everything is setup in 'Production'.
Having reviewed other questions, the answers have been along the lines of:
.. registering a sandbox certificate and then changing it to production. Unfortunately we created this hub from scratch as Production in an attempt to work around that potential issue.
.. registering sandbox devices (and thus tokens) against the production certificate. Unfortunately I have controlled this closely and ensured that we are only registering TestFlight builds (thus Production) against the ANH.
.. uploading the wrong certificate. I have confirmed with the Push Notification Tester, as above, that the certificate is correct (thumbprint confirmed, re-uploaded, etc) and works to send to the devices via Production APNS endpoint.
The resource name is: eight-technology/react-push-notification-hub
In-app registration process is as follows:
Device registers for push notifications
Registration event is handled in iOS Project (AppDelegate event)..
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
//base.RegisteredForRemoteNotifications(application, deviceToken);
App.ConfigurePushNotifications(deviceToken.ToString());
}
ConfigurePushNotifications is in the XF Shared Project..
public static void ConfigurePushNotifications(string deviceToken)
{
var azureComm = DependencyService.Get<Interop.IAzureCommunication>();
azureComm.RegisterForPushTags(
"sb://eight-technology.servicebus.windows.net/",
".. token ..",
"react-push-notification-hub",
deviceToken,
StateManager.SelectedNodes.Select(m => "dealer-" + m).ToArray());
}
The implementation is pretty much as per the sample code provided (contained in iOS project)
public class AzureCommunication : DealerwebReact.Interop.IAzureCommunication
{
public void RegisterForPushTags(string url, string key, string hubName, string deviceToken, string[] tags)
{
var cs = SBConnectionString.CreateListenAccess(new NSUrl(url), key);
var hub = new SBNotificationHub(cs, hubName);
hub.RegisterNativeAsync(deviceToken, new NSSet(tags), err =>
{
if (err != null)
Console.WriteLine("Error: " + err.Description);
else
Console.WriteLine("Success");
});
}
}
After a frustrating few days, and thanks to the help of Nikita G. and hvaughan3 I finally got to the root cause of my issue. As anticipated it wasn't any of the issues actually outlined, but was to do with the way we handled the cross-plat aspect of the registrations with Xamarin Forms.
That is, we stored our token in a class as a string. The NSData that is received as part of the iOS Device registration in RegisteredForRemoteNotifications has a ToString() method that is incompatible with sending to ANH. Furthermore, RegisterNativeAsync method from the Azure library requires an NSData which I assume Xamarin can morph a string into without warning or error, hence it was unknown that the two were somewhat incompatible.
Basically, to maintain cross platform functionality, we are now simply passing the token around as an object and performing the translation back to the original type in the platform-specific implementation of our push configuration method.
Our registration method now looks like this, note the explicit use of the NSData type so that it remains untouched whilst passing through the Xamarin Forms layer:
public void RegisterForPushTags(string url, string key, string hubName, object deviceToken, string[] tags)
{
var cs = SBConnectionString.CreateListenAccess(new NSUrl(url), key);
var hub = new SBNotificationHub(cs, hubName);
hub.RegisterNativeAsync((NSData)deviceToken, new NSSet(tags), err =>
{
if (err != null)
Console.WriteLine("Error: " + err.Description);
else
Console.WriteLine("Success");
});
}
Is this the guide you used for troubleshooting?
Is there a chance you somehow do any string (or any other type of) processing on the APN handle before you register your device? The 'APNS errors' you're seeing seem to be 'invalid token size'. Since I don't know what's going on in your code, it's hard to suggest what exactly might it be, but maybe would help you.
A similar thing happened to me when the device would register correctly but as soon as a notification was sent, the devices would disappear from the list. It always turned out to be an issue with the APNS certificate that was configured within the Notification Hub was not connected properly to the App ID and/or the app was not being signed with the correct provisioning profile.
I'm integrating push notification for iOS using the gem houston on Ruby on Rails as the web service but whenever I try to send a push notification, it only returns the number "94". Tested it a few hours ago, it returned the number "83" instead. What do you think is causing this and what does those numbers mean?
Code:
token = params[:user]["device_token"]
certificate = File.read("../flux-ws/public/certificates/#{PEM_FILE}")
passphrase = ""
connection = Houston::Connection.new(GATEWAY_URI, certificate, passphrase)
connection.open
notification = Houston::Notification.new(device: token)
notification.alert = {
:body => "HELLO WORLD"
}
notification.badge = 57
pushed_data = connection.write(notification.message)
connection.close
Return value:
{"pushed_data":94,"status":200}
Nevermind, guys. I solved it. The bundle id on the pem file didn't match with the bundle id on my project. That was what caused it and after I matched both, it delivered the push notification successfully.
I need to implement iOS push notification in my app. I ve been trying this for a while and have done the iOS side code changes required. I'm able to get the device token from APNS. But im stuck with the provider side implementation.
I'm using a java provider and using JAVAPNS library to implement the provider side logic. What puzzles me is the certificate installation and stuff required at the provider.
My provider runs on unix/windows machine and I am not sure how to proceed with the SSL certificate installation here.
I have obtained SSL certificate for push notification from the apple developer site. But how do I proceed with the p12 file?
I do not find many explanations on this on the net though there are a great deal of discussions and tutorial about the iOS side implementation of push notification.
The SSL certificate is used to establish the socket connection to gateway.push.apple.com from your server side code. Here is some Ruby code as an example:
APN_SSL_KEY_FILE = 'lib/SSLCert_Private_Key.pem'
APN_SSL_HOST = 'gateway.push.apple.com'
APN_SSL_PORT = 2195
APN_SSL_PASSWORD = '<password>'
def configure_apn_cert
##apn_cert = File.read(File.join(RAILS_ROOT, APN_SSL_KEY_FILE))
##apn_context = OpenSSL::SSL::SSLContext.new
##apn_context.key = OpenSSL::PKey::RSA.new(##apn_cert, APN_SSL_PASSWORD)
##apn_context.cert = OpenSSL::X509::Certificate.new(##apn_cert)
end
def create_and_configure_apn_server
configure_apn_cert if not ##apn_cert
puts "APN Service: Configuring APN SOCKET and SSL connection"
#apn_socket = TCPSocket.new(APN_SSL_HOST, APN_SSL_PORT)
#apn_ssl = OpenSSL::SSL::SSLSocket.new(#apn_socket, ##apn_context)
#apn_ssl.sync = true
#apn_ssl.connect
#apn_is_active = false; # reopen the TCP/SSL sockets for now
end
You get a .pem file with:
$ openssl pkcs12 -in myfile.p12 -out myfile.pem
If you're using JavaPNS, it's as simple as :
import javapns.Push;
public class PushTest {
public static void main(String[] args) {
Push.alert("Hello World!", "keystore.p12", "keystore_password", false, "Your token");
}
}