title: Apple website push: Signature verification of push package failed
body: <p>Following the <a href="https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/PushNotifications/PushNotifications.html" rel="nofollow">Apple Website Push Notification guide</a>, I'm getting a post-back to <code>/endpoint/v1/log</code> with the following error: <code>Signature verification of push package failed.</code></p>
  
  <p>However, when I download and verify manually using</p>
  
  <pre><code>curl -O $endpoint/push/v1/pushPackages/x/y
  unzip y
  openssl smime -verify -in signature -inform der -content manifest.json -noverify
  </code></pre>
  
  <p>I get <code>Verification successful</code>.</p>
  
  <p>The certificate is correctly added under the Apple Developer console, and generation of the ZIP is pretty straight-forward:</p>
  
  <pre><code>$z = new ZipArchive();
  $z-&gt;open($tmp, ZIPARCHIVE::CREATE);
  $manifest = [];
  $data = json_encode([
      'websiteName' =&gt; $name,
      'websitePushId' =&gt; $pushId,
      'allowedDomains' =&gt; $domains,
      'webServiceURL' =&gt; "$endpoint/push"
  ]);
  $manifest['website.json'] = sha1($data);
  $z-&gt;addFromString('website.json', $data);
  $data = file_get_contents('static/favicon.png');
  foreach (['16x16','16x16@2x','32x32','32x32@2x','128x128','128x128@2x'] as $f) {
      $f = 'icon.iconset/icon_'.$f.'.png';
      $manifest[$f] = sha1($data);
      $z-&gt;addFromString($f, $data);
  }
  file_put_contents('/tmp/manifest.json', json_encode($manifest));
  $z-&gt;addFile('/tmp/manifest.json','manifest.json');
  $pem = file_get_contents('etc/aweb.pem');
  $cert = openssl_x509_read($pem);
  $pk = openssl_pkey_get_private($pem, 'developer');
  openssl_pkcs7_sign(
      '/tmp/manifest.json', '/tmp/signature',
      $cert, $pk, array(), PKCS7_BINARY | PKCS7_DETACHED
  );
  // PEM to DER
  $pem = file_get_contents('/tmp/signature');
  preg_match('~Content-Disposition:[^\n]+\s*?([A-Za-z0-9+=/\r\n]+)\s*?-----~', $pem, $matches);
  $data = base64_decode($matches[1]);
  $z-&gt;addFromString('signature', $data);
  $z-&gt;close();
  </code></pre>
  
  <p>Note the use of <code>-noverify</code> because, despite attempts, I cannot get the certificate chain setup right:</p>
  
  <pre><code>curl -L https://www.apple.com/appleca/AppleIncRootCertificate.cer | openssl x509 -inform der &gt; certs.pem
  curl -L http://developer.apple.com/certificationauthority/AppleWWDRCA.cer | openssl x509 -inform der &gt;&gt; certs.pem
  openssl smime -verify -in signature -inform der -content manifest.json -CAfile certs.pem
  </code></pre>
  
  <p>yields</p>
  
  <pre><code>Verification failure
  140184513181352:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:pk7_smime.c:342:Verify error:unsupported certificate purpose
  </code></pre>
  
  <p>An additional aside: I found that the documentation is <em>incredibly</em> unclear on whether the ZIP file should contain a root directory (and if so, what it should be named). After many hours of <code>Missing files</code> errors, I finally figured the ZIP should <em>not</em> contain a root directory.</p>