3

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.

1 Answers1

6

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
Community
  • 1
  • 1
  • Hello @Wilder Batista González, I am continuously getting "Signature verification of push package failed" error. Applying your solution gives me "Missing file in push notification package" error!!! Could you please help me! – Mehdi Mar 07 '16 at 16:03
  • Seems to be that you are forgetting some file(s) in your directory raw before compress, check the structure of your final .zip file. You can check the right structure in the Apple guide, the link is in my question, and this script assumes that the website.json file and iconset already exist. I hope to help. – Wilder Batista González Mar 09 '16 at 08:06
  • Yes. When I am adding AppleWWDRCA.cer then the signature file missing. That means after adding the cer file openssl_pkcs7_sign can't able the generate the signature file. – Mehdi Mar 09 '16 at 09:26
  • You have to generate AppleWWDRCA.pem using AppleWWDRCA.cer, and then use it in the openssl_pkcs7_sign function – Wilder Batista González Mar 09 '16 at 10:34
  • Best tutorial in internet : https://medium.com/anantha-krishnan-k-g/safari-push-notifications-ec171bdf6ead – egemen Oct 24 '17 at 12:05