phpcodeignitersafari-push-notifications

Apple website push: Signature verification of push package failed, but apple certificates are ok


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.


Solution

  • 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