Update 22.01.2012: Thanks to asmera I discovered, that Apple seems to be stricter with the JSON you send and that since a few days the Apple Servers do not accept the output from the example given here. I changed the code in here as well as in the downloadable example to make it work again.
-----------------------------------------
Today I will show you a quick implementation of an Apple Push Notification Server provider in C. (Now updated with the new Entrust CA cert and instructions how to use it. For information on how to trust it go here) Some parts of this (like creating the needed certificates) are taken from this very good tutorial about building the same thing in php. For a good intro on the topic and how APNs works internally you should first read this.
You're done? Good, then let's get started:
First you need to create the Push Certificates and enable your application to get notified:
1. Log in to the iPhone Developer Connection Portal and click App IDs Ensure you have created an App ID without a wildcard. Wildcard IDs cannot use the push notification service. For example, our iPhone application ID looks something like AB123346CD.com.serverdensity.iphone
2. Click Configure next to your App ID and then click the button to generate a Push Notification certificate. A wizard will appear guiding you through the steps to generate a signing authority and then upload it to the portal, then download the newly generated certificate. This step is also covered in the Apple documentation.
3. Import your aps_developer_identity.cer into your Keychain by double clicking the .cer file. Launch Keychain Assistant from your local Mac and from the login keychain, filter by the Certificates category. You will see an expandable option called “Apple Development Push Services”
4. Expand this option then right click on “Apple Development Push Services” > Export “Apple Development Push Services ID123″. Save this as apns-dev-cert.p12 file somewhere you can access it.
5. Do the same again for the “Private Key” that was revealed when you expanded “Apple Development Push Services” ensuring you save it as apns-dev-key.p12 file.
6. These files now need to be converted to the PEM format by executing this command from the terminal:
openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
openssl pkcs12 -nocerts -out apns-dev-key-enc.pem -in apns-dev-key.p12
Then to remove the passphrase execute:
openssl rsa -in apns-dev-key-enc.pem -out apns-dev-key.pem
Once we are finished, this little example program will help us testing if we did everything right.
#include
#include
#include
#include "Helper/RemoteNotification.h"
/* MAIN Function */
int main(int argc, char *argv[])
{
int err;
/* Phone specific Payload message as well as hex formated device token */
const char *deviceTokenHex = NULL;
if(argc == 1)
{
deviceTokenHex = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
}
else
{
deviceTokenHex = argv[1];
}
if(strlen(deviceTokenHex) < 64 || strlen(deviceTokenHex) > 70)
{
("Device Token is to short or to long. Length without spaces should be 64 chars...\n");
exit(1);
}
Payload *payload = (Payload*)malloc(sizeof(Payload));
init_payload(payload);
// This is the message the user gets once the Notification arrives
payload->message = "Message to print out";
// This is the red numbered badge that appears over the Icon
payload->badgeNumber = 1;
// This is the Caption of the Action key on the Dialog that appears
payload->actionKeyCaption = "Caption of the second Button";
// These are two dictionary key-value pairs with user-content
payload->dictKey[0] = "Key1";
payload->dictValue[0] = "Value1";
payload->dictKey[1] = "Key2";
payload->dictValue[1] = "Value2";
/* Send the payload to the phone */
("Sending APN to Device with UDID: %s\n", deviceTokenHex);
send_remote_notification(deviceTokenHex, payload);
return 0;
}
send_remote_notification does everything we need. It first prepares the payload, which is encapsulated into a struct:
typedef struct {
/* The Message that is displayed to the user */
char *message;
/* The name of the Sound which will be played back */
char *soundName;
/* The Number which is plastered over the icon, 0 disables it */
int badgeNumber;
/* The Caption of the Action Key the user needs to press to launch the Application */
char *actionKeyCaption;
/* Custom Message Dictionary, which is accessible from the Application */
char* dictKey[5];
char* dictValue[5];
} Payload;
#define DEVICE_BINARY_SIZE 32
#define MAXPAYLOAD_SIZE 256
From the given Information JSON is generated (JSON is described ) and then sent. It is implemented as follows:
int send_remote_notification(const char *deviceTokenHex, Payload *payload)
{
char messageBuff[MAXPAYLOAD_SIZE];
char tmpBuff[MAXPAYLOAD_SIZE];
char badgenumBuff[3];
strcpy(messageBuff, "{\"aps\":{");
if(payload->message != NULL)
{
strcat(messageBuff, "\"alert\":");
if(payload->actionKeyCaption != NULL)
{
sprintf(tmpBuff, "{\"body\":\"%s\",\"action-loc-key\":\"%s\"},", payload->message, payload->actionKeyCaption);
strcat(messageBuff, tmpBuff);
}
else
{
sprintf(tmpBuff, "{\"%s\"},", payload->message);
strcat(messageBuff, tmpBuff);
}
}
if(payload->badgeNumber > 99 || payload->badgeNumber < 0)
payload->badgeNumber = 1;
sprintf(badgenumBuff, "%d", payload->badgeNumber);
strcat(messageBuff, "\"badge\":");
strcat(messageBuff, badgenumBuff);
strcat(messageBuff, ",\"sound\":\"");
strcat(messageBuff, payload->soundName == NULL ? "default" : payload->soundName);
strcat(messageBuff, "\"},");
int i = 0;
while(payload->dictKey[i] != NULL && i < 5)
{
sprintf(tmpBuff, "\"%s\":\"%s\"", payload->dictKey[i], payload->dictValue[i]);
strcat(messageBuff, tmpBuff);
if(i < 4 && payload->dictKey[i + 1] != NULL)
{
strcat(messageBuff, ",");
}
i++;
}
strcat(messageBuff, "}");
("Sending %s\n", messageBuff);
send_payload(deviceTokenHex, messageBuff, strlen(messageBuff));
}
It allows up to 5 custom key-value pairs in a custom dictionary. After the method is done formatting, the sending process is initiated by calling the send_payload function. The payload can be sent either through the development push server or the production push server, depending on the used certificate. For this purpose a define is used, which selects the right type depending on the build.
#define CA_CERT_PATH "/path/to/the/ca/cert"
#if defined(IS_DEVELOPMENT_VERSION)
/* Development Certificates */
#define RSA_CLIENT_CERT "Certs/apns-dev-cert.pem"
#define RSA_CLIENT_KEY "Certs/apns-dev-key.pem"
/* Development Connection Infos */
#define APPLE_HOST "gateway.sandbox.push.apple.com"
#define APPLE_PORT 2195
#define APPLE_FEEDBACK_HOST "feedback.sandbox.push.apple.com"
#define APPLE_FEEDBACK_PORT 2196
#else
/* Distribution Certificates */
#define RSA_CLIENT_CERT "Certs/apns-dis-cert.pem"
#define RSA_CLIENT_KEY "Certs/apns-dis-key.pem"
/* Release Connection Infos */
#define APPLE_HOST "gateway.push.apple.com"
#define APPLE_PORT 2195
#define APPLE_FEEDBACK_HOST "feedback.push.apple.com"
#define APPLE_FEEDBACK_PORT 2196
#endif
int send_payload(const char *deviceTokenHex, const char *payloadBuff, size_t payloadLength)
{
int rtn = 0;
SSL_Connection *sslcon = ssl_connect(APPLE_HOST, APPLE_PORT, RSA_CLIENT_CERT,RSA_CLIENT_KEY, CA_CERT_PATH);
if(sslcon == NULL)
{
("Could not allocate memory for SSL Connection");
exit(1);
}
if (sslcon && deviceTokenHex && payloadBuff && payloadLength)
{
uint8_t command = 0; /* command number */
char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint16_t) + DEVICE_BINARY_SIZE +sizeof(uint16_t) + MAXPAYLOAD_SIZE];
/* message format is, |COMMAND|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD| */
char *binaryMessagePt = binaryMessageBuff;
uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE);
uint16_t networkOrderPayloadLength = htons(payloadLength);
/* command */
*binaryMessagePt++ = command;
/* token length network order */
memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t));
binaryMessagePt += sizeof(uint16_t);
/* Convert the Device Token */
int i = 0;
int j = 0;
int tmpi;
char tmp[3];
char deviceTokenBinary[DEVICE_BINARY_SIZE];
while(i < strlen(deviceTokenHex))
{
if(deviceTokenHex[i] == ' ')
{
i++;
}
else
{
tmp[0] = deviceTokenHex[i];
tmp[1] = deviceTokenHex[i + 1];
tmp[2] = '\0';
sscanf(tmp, "%x", &tmpi);
deviceTokenBinary[j] = tmpi;
i += 2;
j++;
}
}
/* device token */
memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE);
binaryMessagePt += DEVICE_BINARY_SIZE;
/* payload length network order */
memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t));
binaryMessagePt += sizeof(uint16_t);
/* payload */
memcpy(binaryMessagePt, payloadBuff, payloadLength);
binaryMessagePt += payloadLength;
if (SSL_write(sslcon->ssl, binaryMessageBuff, (binaryMessagePt - binaryMessageBuff)) >0)
rtn = 1;
}
ssl_disconnect(sslcon);
return rtn;
}
We're nearly done with the server part, the last (and hardest ) puzzle piece is the SSL connection, which is wrapped in a neat structure:
typedef struct {
/* SSL Vars */
SSL_CTX *ctx;
SSL *ssl;
SSL_METHOD *meth;
X509 *server_cert;
EVP_PKEY *pkey;
/* Socket Communications */
struct sockaddr_in server_addr;
struct hostent *host_info;
int sock;
} SSL_Connection;
So let's get right to using it:
SSL_Connection *ssl_connect(const char *host, int port, const char *certfile, const char*keyfile, const char* capath)
{
int err;
SSL_Connection *sslcon = NULL;
sslcon = (SSL_Connection *)malloc(sizeof(SSL_Connection));
if(sslcon == NULL)
{
("Could not allocate memory for SSL Connection");
exit(1);
}
/* Load encryption & hashing algorithms for the SSL program */
SSL_library_init();
/* Load the error strings for SSL & CRYPTO APIs */
SSL_load_error_strings();
/* Create an SSL_METHOD structure (choose an SSL/TLS protocol version) */
sslcon->meth = SSLv3_method();
/* Create an SSL_CTX structure */
sslcon->ctx = SSL_CTX_new(sslcon->meth);
if(!sslcon->ctx)
{
("Could not get SSL Context\n");
exit(1);
}
/* Load the CA from the Path */
if(SSL_CTX_load_verify_locations(sslcon->ctx, NULL, capath) <= 0)
{
/* Handle failed load here */
("Failed to set CA location...\n");
ERR_print_errors_fp(stderr);
exit(1);
}
/* Load the client certificate into the SSL_CTX structure */
if (SSL_CTX_use_certificate_file(sslcon->ctx, certfile, SSL_FILETYPE_PEM) <= 0) {
("Cannot use Certificate File\n");
ERR_print_errors_fp(stderr);
exit(1);
}
/* Load the private-key corresponding to the client certificate */
if (SSL_CTX_use_PrivateKey_file(sslcon->ctx, keyfile, SSL_FILETYPE_PEM) <= 0) {
("Cannot use Private Key\n");
ERR_print_errors_fp(stderr);
exit(1);
}
/* Check if the client certificate and private-key matches */
if (!SSL_CTX_check_private_key(sslcon->ctx)) {
("Private key does not match the certificate public key\n");
exit(1);
}
/* Set up a TCP socket */
sslcon->sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sslcon->sock == -1)
{
("Could not get Socket\n");
exit(1);
}
memset (&sslcon->server_addr, '\0', sizeof(sslcon->server_addr));
sslcon->server_addr.sin_family = AF_INET;
sslcon->server_addr.sin_port = htons(port); /* Server Port number */
sslcon->host_info = gethostbyname(host);
if(sslcon->host_info)
{
/* Take the first IP */
struct in_addr *address = (struct in_addr*)sslcon->host_info->h_addr_list[0];
sslcon->server_addr.sin_addr.s_addr = inet_addr(inet_ntoa(*address)); /* Server IP */
}
else
{
("Could not resolve hostname %s\n", host);
return NULL;
}
/* Establish a TCP/IP connection to the SSL client */
err = connect(sslcon->sock, (struct sockaddr*) &sslcon->server_addr, sizeof(sslcon->server_addr));
if(err == -1)
{
("Could not connect\n");
exit(1);
}
/* An SSL structure is created */
sslcon->ssl = SSL_new(sslcon->ctx);
if(!sslcon->ssl)
{
("Could not get SSL Socket\n");
exit(1);
}
/* Assign the socket into the SSL structure (SSL and socket without BIO) */
SSL_set_fd(sslcon->ssl, sslcon->sock);
/* Perform SSL Handshake on the SSL client */
err = SSL_connect(sslcon->ssl);
if(err <= 0)
{
("Could not connect to SSL Server\n");
exit(1);
}
return sslcon;
}
void ssl_disconnect(SSL_Connection *sslcon)
{
int err;
if(sslcon == NULL)
{
return;
}
/* Shutdown the client side of the SSL connection */
err = SSL_shutdown(sslcon->ssl);
if(err == -1)
{
("Could not shutdown SSL\n");
exit(1);
}
/* Terminate communication on a socket */
err = close(sslcon->sock);
if(err == -1)
{
("Could not close socket\n");
exit(1);
}
/* Free the SSL structure */
SSL_free(sslcon->ssl);
/* Free the SSL_CTX structure */
SSL_CTX_free(sslcon->ctx);
/* Free the sslcon */
if(sslcon != NULL)
{
free(sslcon);
sslcon = NULL;
}
}
Remember, as I told in the beginning, this is a quick implementation, so don't count on it being flawless. But if you still want it, you can download the complete Project from (updated 22.01.2012)
You can use this code and modify it as you see fit, but if you find it useful or found something wrong or missing, a comment would be nice.