I'm trying to implement Web push notifications to Safari using Codeigniter, I'm following the Apple Guide
and I created a library to create a Push Package based in this connorlacombe github project.
This is my library:
<?php if ( ! defined('BASEPATH')) exit("No direct script access allowed");class Apple_utils {
var $certificate_path;
var $certificate_password;
var $dir;
var $raw_files = array(
"icon.iconset/icon_16x16.png",
"icon.iconset/icon_16x16@2x.png",
"icon.iconset/icon_32x32.png",
"icon.iconset/icon_32x32@2x.png",
"icon.iconset/icon_128x128.png",
"icon.iconset/icon_128x128@2x.png",
"website.json"
);
public function __construct($config = array())
{
if (count($config) != 0){
$this->initialize($config);
}
log_message("debug", "Apple Class Initialized");
}
public function initialize($config = array())
{
if (empty($config["certificate_path"]) || empty($config["certificate_password"]) || empty($config["dir"])) return FALSE;
$this->certificate_path = $config["certificate_path"];
$this->certificate_password = $config["certificate_password"];
$this->dir = $config["dir"];
return $this;
}
// Creates the push package, and returns the path to the archive.
public function create_push_package($id)
{
list($usec, $sec) = explode(" ", microtime());
$now = ((float)$usec + (float)$sec);
//DELETING OLD DIR
$this->_delete_old_files($this->dir . "tmp/", $now);
$package_dir = $this->dir . "tmp/" . $now;
if (!mkdir($package_dir)) return FALSE;
@chmod($package_dir, 0755);
$this->_copy_raw_push_package_files($package_dir, $id);
if (!$this->_create_manifest($package_dir)) return FALSE;
if (!$this->_create_signature($package_dir)) return FALSE;
$package_path = $this->_package_raw_data($package_dir);
return $package_path;
}
private function _copy_raw_push_package_files($package_dir, $id)
{
mkdir($package_dir . "/icon.iconset");
foreach ($this->raw_files as $raw_file) {
copy($this->dir . "pushPackage/" . $raw_file, $package_dir . "/" . $raw_file);
if ($raw_file == "website.json") {
$wjson = file_get_contents($package_dir . "/". $raw_file);
unlink($package_dir . "/". $raw_file);
$ff = fopen($package_dir . "/". $raw_file, "x");
fwrite($ff, str_replace(array("{BASE_URL}", "{AUTHTOKEN}"), array(rtrim(base_url(), "/"), "authenticationToken_".$id), $wjson));
fclose($ff);
}
}
}
private function _create_manifest($package_dir)
{
// Obtain SHA1 hashes of all the files in the push package
$manifest_data = array();
foreach ($this->raw_files as $raw_file) {
$manifest_data[$raw_file] = sha1(file_get_contents($package_dir . "/" . $raw_file));
}
file_put_contents($package_dir . "/manifest.json", json_encode( (object)$manifest_data ));
return TRUE;
}
private function _create_signature($package_dir)
{
// Load the push notification certificate
$pkcs12 = file_get_contents($this->certificate_path);
$certs = array();
if(!openssl_pkcs12_read($pkcs12, $certs, $this->certificate_password)){
return FALSE;
}
$signature_path = $package_dir . "/signature";
// Sign the manifest.json file with the private key from the certificate
$cert_data = openssl_x509_read($certs["cert"]);
$private_key = openssl_pkey_get_private($certs["pkey"], $this->certificate_password);
openssl_pkcs7_sign($package_dir . "/manifest.json", $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED);
// Convert the signature from PEM to DER
$signature_pem = file_get_contents($signature_path);
$matches = array();
if (!preg_match("~Content-Disposition:[^\n]+\s*?([A-Za-z0-9+=/\r\n]+)\s*?-----~", $signature_pem, $matches)){
return FALSE;
}
$signature_der = base64_decode($matches[1]);
file_put_contents($signature_path, $signature_der);
return TRUE;
}
private function _package_raw_data($package_dir)
{
$CI = &get_instance();
$CI->load->library("zip");
$raw_files = $this->raw_files;
$raw_files[] = "manifest.json";
$raw_files[] = "signature";
foreach ($raw_files as $raw_file) {
$CI->zip->add_data($raw_file, file_get_contents($package_dir . "/" .$raw_file));
}
ob_end_clean(); //I HAVE TO PUT THIS HERE BECAUSE IF NOT THE ZIP CAN NOT BE OPENED
$CI->zip->download( "Website.pushpackage.zip" );
}
function _delete_old_files($dir, $now)
{
$expiration = 300; //seconds
$current_dir = @opendir($dir);
while ($filename = @readdir($current_dir)){
if ($filename != "." && $filename != ".." && $filename != "index.html"){
$name = str_replace(".zip", "", $filename);
if (($name + $expiration) < $now) $this->_delete_file($this->dir . "tmp/" . $filename);
}
}
@closedir($current_dir);
}
function _delete_file($file)
{
@chmod($file, 0755);
if (is_dir($file)){
$dir = @opendir($file);
while ($filename = @readdir($dir)){
if ($filename != "." && $filename != ".."){
$this->_delete_file($file . "/" . $filename);
}
}
@closedir($dir);
@rmdir($file);
}else{
@unlink($file);
}
}}
The library works, create a .zip with, website.json, signature, etc. and was working perfect until yesterday, then not work anymore, when I check the logs always say "Signature verification of push package failed" and I has been created the certificates more than one time, but still the same error .
Please help.
Thanks to Jack, his solution has solved my problem:
openssl_pkcs7_sign("$package_dir/manifest.json", $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED,"/path/to/certificate/AppleWWDRCA.pem");
To create AppleWWDRCA.pem you need:
1 - Download AppleWWDRCA.cer
2 - Execute the following command:
openssl x509 -inform der -in AppleWWDRCA.cer -out AppleWWDRCA.pem