Friday, April 10, 2009

APNS Client Development Certificate Available Now



(1) You need to create an App ID without .* in the Program Portal (that means one cert for one app)

(2) Generate a certificate signing request from your Mac's keychain and save to disk

(3) Upload the CertificateSigningRequest.certSigningRequest to the Program Portal

(4) Wait for the generation of cert (about 1 min). Download the certificate (aps_developer_identity.cer) from the Program Portal (If you need to renew this cert, it is under the App ID that you created in step 1, and choose Action Configure)

(5) Keep (or rename them if you prefer) these 2 files (steps 2 and 4) in a safe place. You might need the CertificateSigningRequest.certSigningRequest file to request a new cert for a new app in the future or renew the old cert when expired.

(6) Suppose you have imported the aps_developer_identity.cer to the keychain. Then you have to export these new cert and the private key of this cert (not the public key) and saved as .p12 files.

(7) Then you use these commands to generate the cert and key in Mac's Terminal for PEM format (Privacy Enhanced Mail Security Certificate)

openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12
openssl pkcs12 -nocerts -out key.pem -in key.p12


(8) The cert.pem and key.pem files will be used by your own php script communicating with APNS.

(9) If you want to remove the passphase of private key in key.pem, do this

openssl rsa -in key.pem -out key.unencrypted.pem


Then combine the certificate and key

cat cert.pem key.unencrypted.pem > ck.pem


But please set the file permission of this unencrypted key by using chmod 400 and is only readable by root in a sever configuration.

(10) The testing APNS is at ssl://gateway.sandbox.push.apple.com:2195

(11) For the source codes to push payload message to the APNS, you can find them in the Developer Forum. This is the one that I used, for php script. Run this (after obtaining the device token from the testing device and with iPhone Client program setup)
php -f apns.php "My Message" 2

or if you put this php script and the ck.pem in a local web server, you can use this to test
http://127.0.0.1/apns/apns.php?message=test%20from%20javacom&badge=2&sound=received5.caf

Please be patient to get message from the sandbox server. Normally, you need 10 minutes+ to get the first message from push notification testing.

apns.php Select all

#!/usr/bin/env php
<?php
$deviceToken = '02da851dXXXXXXXXb4f2b5bfXXXXXXXXce198270XXXXXXXX0d3dac72bc87cd60'; // masked for security reason
// Passphrase for the private key (ck.pem file)
// $pass = '';

// Get the parameters from http get or from command line
$message = $_GET['message'] or $message = $argv[1] or $message = 'Message received from javacom';
$badge = (int)$_GET['badge'] or $badge = (int)$argv[2];
$sound = $_GET['sound'] or $sound = $argv[3];

// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;


/* End of Configurable Items */

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);

$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
// for production change the server to ssl://gateway.push.apple.com:2195
if (!$fp) {
print "Failed to connect $err $errstr\n";
return;
}
else {
print "Connection OK\n";
}

$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
print "sending message :" . $payload . "\n";
fwrite($fp, $msg);
fclose($fp);
?>


(12) For iPhone Client Program, you need to edit the bundle identifier to the App ID that you created and imported the new provisioning profile for that APP ID to the XCode and iPhone. And codesign with that new provisioning profile. Then implement the following methods in AppDelegate to Build & Go

AppDelegate.m Select all

- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSLog(@"Registering Remote Notications");

[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

// Override point for customization after app launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}


// Delegation methods
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
const void *devTokenBytes = [devToken bytes];
// self.registered = YES;
NSLog(@"deviceToken: %@", devToken);
// [self sendProviderDeviceToken:devTokenBytes]; // custom method
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(@"Error in registration. Error: %@", err);
}



(13) Additional tips for sandbox testing
- The feedback service is feedback.sandbox.push.apple.com:2195
- Send your messages to gateway.sandbox.push.apple.com:2195



Here is the feedback server request php code. For the sandbox feedback server, you have to create a second dummy app to make the first one works. May be the sandbox feedback server is buggy as the production push and feedback servers does not have this problem.

php -f feedback.php

feedback.php Select all

#!/usr/bin/env php
<?php

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);


$fp = stream_socket_client('ssl://feedback.sandbox.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $ctx);
// production server is ssl://feedback.push.apple.com:2196

if (!$fp) {
print "Failed to connect feedback server: $err $errstr\n";
return;
}
else {
print "Connection to feedback server OK\n";
}

print "APNS feedback results\n";
while ($devcon = fread($fp, 38))
{
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
print "TIMESTAMP:" . $feedbackDate . "\n";
print "DEVICE ID:" . $feedbackDeviceToken. "\n\n";
}
fclose($fp);
?>



Hints: If you want to test the production push and feedback servers, use the adhoc distribution certificate and adhoc build to your devices. Moreover, the device tokens are different for the same device under sandbox and production servers.





43 comments:

Unknown said...

How do I send the payload to the server? I can't find the code anywhere :(

javacom said...

The apns.php is the php code to send the payload to the APNS server

greg Hartstein said...

I get repeated errors when using terminal, and when using browser I get through once every 9 or 10 tries.

Here are errors

Warning: stream_socket_client() [function.stream-socket-client]: Unable to set local cert chain file `ck.pem'; Check that your cafile/capath settings include details of your certificate and its issuer in /Library/WebServer/Documents/notifyTest/anps.php on line 33

Warning: stream_socket_client() [function.stream-socket-client]: failed to create an SSL handle in /Library/WebServer/Documents/notifyTest/anps.php on line 33

Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in /Library/WebServer/Documents/notifyTest/anps.php on line 33

Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error) in /Library/WebServer/Documents/notifyTest/anps.php on line 33

I added a few comments so the line # no longer match up. The line referred to is:

$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);

any ideas?

Thanks

Greg

javacom said...

You should put the ck.pem file under the same folder of apns.php script

or may be you can put the actual path of the file in the php script

Moreover, please check your generation of ck.pem, may be the file is not generated correctly.

rleger said...

Hello, This worked fine with OS3.0 beta 2, but now I get an error with beta 3:


UIApplication may not respond to -registerForRemoteNotifications


Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIApplication registerForRemoteNotifications]: unrecognized selector sent to instance 0xd139e0'

Does anyone have an idea how to solve this?
Thank you

javacom said...

The API has changed in beta3, see the documentation

[[UIApplication sharedApplication]
registerForRemoteNotificationsTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound)];

rleger said...

yes I noticed but even with the new one I get the same error. I tried copy pasting from apple doc, doesn't work either.
(I have no knowledg of objc I simply use apns to get important messages from my website).

Right now the code I have looks like this:
any idea what I should do to make it work?

#import "apnsAppDelegate.h"

@implementation apnsAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSLog(@"Registering Remote Notications");
[window makeKeyAndVisible];
[[UIApplication sharedApplication] registerForRemoteNotificationsTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];

// other codes here
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(@"deviceToken: %@", deviceToken);
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Error in registration. Error: %@", error);
}


- (void)dealloc {
[window release];
[super dealloc];
}


@end


Thanks for your help!

rleger said...

Finally figured it out...

there is a typing mistake on apple push doc...

registerForRemoteNotificationTypes takes no 'S' after notification....
it used to be registerForRemoteNotifications so I guess that's why.

Tim Smith said...

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"%@",[[[launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"] objectForKey:@"aps"] objectForKey:@"alert"]);
}

gives me "warning control reaches end of non-void function"

Anonymous said...

How to send the payload to the application after submitting to app store. I mean , after testing the application and submitting to app store, how to send the payload from php to all device using the application?

Discovering Tech said...

This is the example I wanted :). Thanks a lot for this article and providing the source code for sending payload to server. Just like your last article, this article is also useful :)

Kapil
www.makeurownrules.com

Anonymous said...

My certificate will not export as a p12. What am I doing wrong?

Jamie said...

Worked great, thanks!

Sebastian said...

You are saying there:

(6) Suppose you have imported the aps_developer_identity.cer to the keychain. Then you have to export these new cert and the private key of this cert (not the public key) and saved as .p12 files.

Okay, I did the first step. But how do I export the file and its private key in the keychain application? I cannot select .p12 as output file format? Only .pem, .cer and .p7b are available. Any ideas?

Thanks a lot.

Best regards,
Sebastian

nithin said...

A very helpful tutorial, appreciate the efforts.

This tutorial is for a distribution profile rt? Cant we test the push notification with the developer profile and test it on the simulator or the testing device?
If so, can some tell me the detailed step in doing so..
I have generated the APNs certificate and provisioning profile and installed them on the mac n the iphone, i have also save the .p12 on my mac. Can someone guide me from here?

Thank u,
NR

Anonymous said...

Hi all,

Works fine on a non-jailbroken 3GS but fails on a jailbroken 3G.

Has anyone managed to make this work on a jailbroken device ?

Im my case, "didRegisterForRemoteNotificationsWithDeviceToken" is never called so that I can retrieve the device token.

Thanks in advance for any help...

Anonymous said...

Please,

I'd like to know the steep of push notification deployment.
How I configure and communicate between my iphone application and the provider ?

And if I wish to have a notification at the fixed time (for exemple at 12'o clock)

Anonymous said...

Hi... Is it possible to have only one button in the alert (say "ok") that will launch the application when pressed...
As per the Apple document, we can get one button as "Ok" but if pressed it just dismisses the alert and will not launch application.

Please help...

javacom said...

you put null string "" in alert, if you want one button only.

Vinod said...

Great tutorial, thanks!

How do you send emoji through APNS?

pRaMoD said...

$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;

I would like to know what does this format specifies.

Md. Faisal Rahman said...

hi,
nice tutorial

I can not retrieve device token.
The two delegate method for device token found do not invoked.
plz help.

Anonymous said...

I get these errors

Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Connection timed out) in /home/content/o/r/a/orangeslide8/html/sendpush.php on line 15
Connection timed out
Warning: fwrite(): supplied argument is not a valid stream resource in /home/content/o/r/a/orangeslide8/html/sendpush.php on line 20

Fatal error: Call to undefined function socket_close() in /home/content/o/r/a/orangeslide8/html/sendpush.php on line 22

Anonymous said...

I’m getting this error

Warning: stream_socket_client() [function.stream-socket-client]: Unable to set private key file `apns-dev.pem’ in /home/bryan/sendpush.php on line 14

Warning: stream_socket_client() [function.stream-socket-client]: failed to create an SSL handle in /home/bryan/sendpush.php on line 14

Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in /home/bryan/sendpush.php on line 14

Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error) in /home/bryan/sendpush.php on line 14

Warning: fwrite(): supplied argument is not a valid stream resource in /home/bryan/sendpush.php on line 17

Warning: socket_close() expects parameter 1 to be resource, boolean given in /home/bryan/sendpush.php on line 19

Warning: fclose(): supplied argument is not a valid stream resource in /home/bryan/sendpush.php on line 20

Anonymous said...

What do you mean by "second dummy app"?

About when does the feedback service send anything? I can send messages now but the feedback service is always empty.

Peter said...

There is an open source PHP/MySQL back-end for APNS. So if you want your own integration of push notifications on your own server, here you go :)

Main Link: http://www.easyapns.com
Google Code: http://code.google.com/p/easyapns/
Google Group: http://groups.google.com/group/easyapns

nithin said...

I'm successful in sending notifications in the development environment. Now i need to deploy this in the production environment. What are the necessary changes that needs to be done except that i need to change the gateway url to gateway.push.apple.com .
Do i need to generate a new ck.pem file with aps_production_identity.cer? and place it on the server?

javacom said...

You have to generate and download the production cert from the portal and generate the new ck.pem to the server.

Anonymous said...

Thanks very much for this post

Warren said...

In step 11, the following code will fail because device tokens can contain NUL bytes (two consecutive zeros), and this will result in your packed string being truncated before the

pack('H*', str_replace(' ', '', $deviceToken))

Instead, you should use something like this:

pack('H'.$device_token_length, str_replace(' ', '', $deviceToken))

where $device_token_length is 64, the length of a device token in nybbles.

Vinay Vaidya said...

For the error "Unable to set local cert chain file", uncomment the line "extension=php_curl.dll" in php.ini. Ensure that php_curl.dll is located in php/ext/ folder.

Restart apache & php, the problem will be gone.

zahur said...

Dear Concerned,
I can send the request for push message successfully from my MAC terminal.
Terminal Output:
$ php -f apns.php "My Apns Test" 2


Connection OK
sending message :{"aps":{"alert":" Apns Test","badge":2}}
//end of output

I have a valid device token id.But I can't receive any notifications on my device.
Please guide me..

Anonymous said...

Thanks,

I make the apns server in first attempt using the code and description.

Dinesh said...

I got this error while executing apns.php. But it is working perfectly in the local server and not in the remote server. Please advice me on this.

Warning: stream_socket_client() [function.stream-socket-client]: Unable to set local cert chain file `apns-dev.pem'; Check that your cafile/capath settings include details of your certificate and its issuer in /var/www/pushv2/apns.php on line 26

Warning: stream_socket_client() [function.stream-socket-client]: failed to create an SSL handle in /var/www/pushv2/apns.php on line 26

Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in /var/www/pushv2/apns.php on line 26

Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error) in /var/www/pushv2/apns.php on line 26
Failed to connect 0

Unknown said...

Hi,

In apns.php, from where do I get the device token.

Thanks.

Umaid said...

I am facing error with PHP server code in index.php file.

Warning: stream_socket_client() [function.stream-socket-client]: Unable to set local cert chain file `ck.pem'; Check that your cafile/capath settings include details of your certificate and its issuer in http://localhost/apns/index.php on line 51

Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in
http://localhost/apns/index.php on line 51

Please assist me where I am lacking. I have generate ck.pem from 2 different mac and aswell deployed via windows server also but having above errors.

Anonymous said...

someone know how the change of december 22 will affect this script?

Umaid said...

I have successfully implemented code in your tutorial but it doesn't work, So my query is that Will it work for Jailbroken and unlock Iphone also

hoang nam said...

i received this message when i run your code at localhost:
Connection OK
sending message :{"aps":{"alert":"Nam1","badge":2,"sound":"beep.wav"}}

Is this successful?
My device did not receive any message.
How can i know that my message pushed to the server?

Paresh Rathod said...

Great article...
I was able to successfully receive push notification.
Simple description, easy to follow.

Paresh Rathod

Anonymous said...

Thanks for this superb post..It helped me many times.am referring this blog whenever am doing push notification for my apps

Anonymous said...

Thanks for Gr8 Post !!!!!!

Sunil said...

In the step 7 you say cert.p12 and key.p12 where do i get these two different p12's...Where will i get them from...