Send Push with .p8 and PHP

3.7k views Asked by At

I need to send Push notifications through PHP script by using .p8 file and found following code in similar question asked here.

<?php

  $keyfile = 'AuthKey_AABBCC1234.p8';               # <- Your AuthKey file
  $keyid = 'AABBCC1234';                            # <- Your Key ID
  $teamid = 'AB12CD34EF';                           # <- Your Team ID (see Developer Portal)
  $bundleid = 'com.company.YourApp';                # <- Your Bundle ID
  $url = 'https://api.development.push.apple.com';  # <- development url, or use http://api.push.apple.com for production environment
  $token = 'e2c48ed32ef9b018........';              # <- Device Token

  $message = '{"aps":{"alert":"Hi there!","sound":"default"}}';

  $key = openssl_pkey_get_private('file://'.$keyfile);

  $header = ['alg'=>'ES256','kid'=>$keyid];
  $claims = ['iss'=>$teamid,'iat'=>time()];

  $header_encoded = base64($header);
  $claims_encoded = base64($claims);

  $signature = '';
  openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
  $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);

  // only needed for PHP prior to 5.5.24
  if (!defined('CURL_HTTP_VERSION_2_0')) {
      define('CURL_HTTP_VERSION_2_0', 3);
  }

  $http2ch = curl_init();
  curl_setopt_array($http2ch, array(
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
    CURLOPT_URL => "$url/3/device/$token",
    CURLOPT_PORT => 443,
    CURLOPT_HTTPHEADER => array(
      "apns-topic: {$bundleid}",
      "authorization: bearer $jwt"
    ),
    CURLOPT_POST => TRUE,
    CURLOPT_POSTFIELDS => $message,
    CURLOPT_RETURNTRANSFER => TRUE,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HEADER => 1
  ));

  $result = curl_exec($http2ch);
  if ($result === FALSE) {
    throw new Exception("Curl failed: ".curl_error($http2ch));
  }

  $status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
  echo $status;

  function base64($data) {
    return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
  }

?>

However, I found that openssl_pkey_get_private doesn't read the key file and it gives following error when I debug it.

$key = openssl_pkey_get_private('file://'.$keyfile);
if ($key === false) {
    var_dump(openssl_error_string());
}

error :

'error:02001002:system library:fopen:No such file or directory'

Please note that there is no issue with curl as HTTP2 was enabled for the curl and I am using PHP7. In testing phase I'm using the script and file on the same folder to avoid any path issues.

Any clue where it went wrong ?

2

There are 2 answers

0
John On

Please refer to next URL if did not read yet.

https://www.php.net/manual/en/function.openssl-pkey-get-private.php

To narrow down your issue, please use same directory for your php file and key file and try this working code.

Working code

$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absolute path
$key = openssl_pkey_get_private($keyfile);

if ($key === false) {
    var_dump(openssl_error_string());
}else{
    var_dump($key);
}

The following might be an issue.

  1. Path

Following path styles should work.

$keyfile="file:///home/john/php/key.p8"; // unix absoulute path
$keyfile="file://C:\\users\\john\\php\\key.p8"; // windows absoulute path
$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absoulute path for unix, windows
$keyfile="file://key.p8"; // relative path, unix, windows, (php,key files in same directory)

$key = openssl_pkey_get_private($keyfile);

If path does not exist, error will be like

"error:02001002:system library:fopen:No such file or directory"

  1. Web environment

    Check your web root and web user access permission to the folder and key file.

    To reduce issues, test it on php build-in web server env rather than WAMP env.

>php -S localhost:80
  1. Corrupted key file

saved as certain type which include whitespaces. This can occur error like next.

"error:0906D06C:PEM routines:PEM_read_bio:no start line"

in my case, key file was saved as UTF-8 with BOM(whitespaces)

DEBUG key file 1 - READ FROM VARIABLE

This code should work. I got key file from http://micmap.org/php-by-example/en/function/openssl_pkey_get_private

Please replace $str to yours.

$str = <<<EOF
-----BEGIN RSA PRIVATE KEY----- 
MIIEogIBAAKCAQEA0llCeBjy18RylTdBih9GMUSZIC3GzeN0vQ9W8E3nwy2jdeUn 
H3GBXWpMo3F43V68zM2Qz5epRNmlLSkY/PJUfJIC8Yc1VEokT52q87hH/XJ5eS8h 
eZnjuSlPAGi8oZ3ImVbruzV7XmlD+QsCSxJW7tBv0dqJ71e1gAAisCXK2m7iyf/u 
l6rT0Zz0ptYH4IZfwc/hQ9JcMg69uM+3bb4oBFsixMmEQwxKZsXk3YmO/YRjRbay 
+6+79bSV/frW+lWhknyGSIJp2CJArYcOdbK1bXx1dRWpbNSExo7dWwuPC0Y7a5AE 
eoZofieQPPBhXlp1hPgLYGat71pDqBjKLvF5GwIDAQABAoIBACPItYsSy3UzYT7L 
OKYTrfBBuD8GKpTqBfkHvAWDa1MD15P92Mr7l0NaCxGfAy29qSa6LdFy/oPM9tGY 
9TxKyV6rxD5sfwEI3+Z/bw6pIe4W5F1eTDaQnHHqehsatkRUQET9yXp+na8w/zRF 
0C0PQKS95tfvcpm59RGCdGQ8+aZw+cIy/xez75W8IS/hagMxe7xYPjpkOkSCCEJU 
zmbVq6AyWodASV0p4H9p8I+c0vO2hJ/ELJ167w6T+2/GlZg979rlyHoTW8jK2BbG 
IRGaPo+c2GANXa686tdpbkPd6oJliXwBSNolxmXShvlveBbPFAJJACzCmbXNj9kH 
6/K+SWkCgYEA7FNudcTkRPV8TzKhJ1AzDjw3VcnraYhY8IlNxbk7RVHLdkoUtwk/ 
mImeBlEfCoz9V+S/gRgeQ+1Vb/BCbS24+bN/+IGoNRFMRcOieFt6lQUpj7a9NeSo 
IEclGgUiU7QR3xH73SB4GC3rgSPeHJhJZC5EJq5TzYjXTPGPpBD3zicCgYEA49wz 
zfMDYIH8h4L65r/eJYIbLwpvgktgaYvhijO3qfZSWW+Y19jCBn55f65YOhPGQBHA 
my0f+tVxFNZ/OupbrAIIzogxlCIYHNBawDhoHN/sB3/lSBAjifySNLyRlA62oA0w 
wXvXVLVWMa3aXim3c9AlnLF1fHwcvwpOKSfdye0CgYBb1mBKq+T5V1yjek1d9bCh 
i40FbZ5qOG43q2Ppvn3mBk9G/KroJlPsdy5NziB9/SRGj8JL7I92Xjihc4Cc5PPJ 
NZQ5gklXtg0p30i39PTCDGuGScFlvCIJyRwF7JDWblezlE2INSH2Y4HtgX7DJfr/ 
T2t0jLJMYS0p3YWwgFeMaQKBgHUIe/8y6zAdc5QynSX5tGL1gXrW1FFK39k2RICU 
cag1YTSYkhuDNJzbRxJifORPlcsAkzngooVWLb+zMCQVjUI6xUU3RKe+Hz5lccc6 
8ZarGHL9qMkrqOVNudamZ+tw5zIrtDgcoIvcm8nmbrtgl94/MaJar2ph4O3qoByZ 
Ylw9AoGAIdS79s0VKkj4VVXqK47ZcI7jGL4V4C8ujU8YcMNV88xwCoDg9ZIFprWA 
P5p/cnvj6aHnqL58XiH0+bE0Lt3J+U6N6JelQQevgBHooMFh4FpDXcVda7xB3rK3 
woqbi8fNhr827H2maxIZPtVG95/mvR4k5z1Jrdnr34ZUmtC6U5Q= 
-----END RSA PRIVATE KEY-----
EOF;

$key = openssl_pkey_get_private($str);
if ($key === false) {
    var_dump(openssl_error_string());
}else{
    var_dump($key);
}

OUTPUT

resource(4) of type (OpenSSL key)

DEBUG key file 2 - READ FROM FILE

copy your key strings($str) to key file like "key.p8".

$str = <<<EOF
-----BEGIN RSA PRIVATE KEY----- 
...YOUR KEY STINGS HERE...
-----END RSA PRIVATE KEY-----
EOF;

$str2 = file_get_contents("key.p8");

$len1 = strlen ($str);
$len2 = strlen ($str2);
if($len1 !== $len2) echo "File has been corrupted.";

$key = openssl_pkey_get_private($str2);

if ($key === false) {
    var_dump(openssl_error_string());
}else{
    var_dump($key);
}

2
Arun P On

This script can be used to send a push to IOS using .p8 certificate. Make sure the location of the certificate is correct


<?php

      $keyfile = 'AuthKey_AABBCC1234.p8';               // Your p8 Key file
      $keyid = 'AABBCC1234';                            // Your Key ID
      $teamid = 'AB12CD34EF';                           // Your Team ID (see Developer Portal)
      $bundleid = 'com.company.YourApp';                // Your Bundle ID
      $url = 'https://api.development.push.apple.com';  // development url, or use http://api.push.apple.com for production environment
      $token = 'e2c48ed32ef9b018........';              // Device Token

      $message = '{"aps":{"alert":"Hi there!","sound":"default"}}';

      $key = openssl_pkey_get_private('file://'.$keyfile);

      $header = ['alg'=>'ES256','kid'=>$keyid];
      $claims = ['iss'=>$teamid,'iat'=>time()];

      $header_encoded = base64($header);
      $claims_encoded = base64($claims);

      $signature = '';
      openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
      $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);

      // only needed for PHP prior to 5.5.24
      if (!defined('CURL_HTTP_VERSION_2_0')) {
          define('CURL_HTTP_VERSION_2_0', 3);
      }

      $http2ch = curl_init();
      curl_setopt_array($http2ch, array(
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
        CURLOPT_URL => "$url/3/device/$token",
        CURLOPT_PORT => 443,
        CURLOPT_HTTPHEADER => array(
          "apns-topic: {$bundleid}",
          "authorization: bearer $jwt"
        ),
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => $message,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HEADER => 1
      ));

      $result = curl_exec($http2ch);
      if ($result === FALSE) {
        throw new Exception("Curl failed: ".curl_error($http2ch));
      }

      $status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
      echo $status;

      function base64($data) {
        return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
      }

    ?>```