Skip to content

Commit 9b4db7c

Browse files
authored
Merge pull request #124 from includable/feat/add-basic-test
2 parents dddfca2 + c0675cf commit 9b4db7c

File tree

9 files changed

+180
-27
lines changed

9 files changed

+180
-27
lines changed

.github/lock.yml

Lines changed: 0 additions & 9 deletions
This file was deleted.

.github/workflows/test.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PHPUnit Tests on Multiple PHP Versions
2+
3+
on: [ push, pull_request ]
4+
5+
jobs:
6+
tests:
7+
runs-on: ubuntu-latest
8+
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
php: [ "7.4", "8.0", "8.1", "8.2" ]
13+
openssl_legacy: [ false, true ]
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v2
18+
19+
- name: Use OpenSSL legacy mode
20+
if: ${{ matrix.openssl_legacy }}
21+
run: |
22+
printf "openssl_conf = openssl_init\n[openssl_init]\nproviders = provider_sect\n[provider_sect]\ndefault = default_sect\nlegacy = legacy_sect\n[default_sect]\nactivate = 1\n[legacy_sect]\nactivate = 1" > openssl.cnf
23+
cat openssl.cnf
24+
export OPENSSL_CONF=openssl.cnf
25+
26+
- name: Set up PHP
27+
uses: shivammathur/setup-php@v2
28+
with:
29+
php-version: ${{ matrix.php }}
30+
extensions: zip, openssl
31+
32+
- name: Install Composer dependencies
33+
run: composer install --prefer-dist --no-progress --no-interaction
34+
35+
- name: Run PHPUnit
36+
run: vendor/bin/phpunit tests --coverage-text

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/vendor
22
composer.lock
3-
/Certificates.p12
3+
/Certificates.p12
4+
.phpunit.result.cache

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ left, you can select your iPhone. You will then be able to inspect any errors th
8181

8282
## Changelog
8383

84+
**Version 2.1.0 - April 2023**
85+
86+
* Add alternative method for extracting P12 contents to circumvent issues in recent versions of OpenSSL.
87+
8488
**Version 2.0.2 - October 2022**
8589

8690
* Switch to `ZipArchive::OVERWRITE` method of opening ZIP due to PHP 8 deprecation ([#120](https://github.com/includable/php-pkpass/pull/120)).

composer.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@
2020
"email": "thomas@includable.com"
2121
}
2222
],
23+
"autoload": {
24+
"psr-4": {
25+
"PKPass\\": "src"
26+
}
27+
},
2328
"require": {
24-
"php": ">=5.6",
29+
"php": ">=7.0",
2530
"ext-zip": "*",
2631
"ext-json": "*",
2732
"ext-openssl": "*"
2833
},
29-
"autoload": {
30-
"psr-4": {
31-
"PKPass\\": "src"
32-
}
34+
"require-dev": {
35+
"phpunit/phpunit": "^9.6"
3336
}
3437
}

src/PKPass.php

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -418,29 +418,82 @@ protected function convertPEMtoDER($signature)
418418
}
419419

420420
/**
421-
* Creates a signature and saves it.
421+
* Read a PKCS12 certificate string and turn it into an array.
422422
*
423-
* @param string $manifest
423+
* @return array
424424
* @throws PKPassException
425425
*/
426-
protected function createSignature($manifest)
426+
protected function readP12()
427427
{
428-
$manifest_path = tempnam($this->tempPath, 'pkpass');
429-
$signature_path = tempnam($this->tempPath, 'pkpass');
430-
file_put_contents($manifest_path, $manifest);
431-
428+
// Use the built-in reader first
432429
if (!$pkcs12 = file_get_contents($this->certPath)) {
433430
throw new PKPassException('Could not read the certificate.');
434431
}
435-
436432
$certs = [];
437-
if (!openssl_pkcs12_read($pkcs12, $certs, $this->certPass)) {
433+
if (openssl_pkcs12_read($pkcs12, $certs, $this->certPass)) {
434+
return $certs;
435+
}
436+
437+
// That failed, let's check why
438+
$error = '';
439+
while ($text = openssl_error_string()) {
440+
$error .= $text;
441+
}
442+
443+
// General error
444+
if (!strstr($error, 'digital envelope routines::unsupported')) {
438445
throw new PKPassException(
439446
'Invalid certificate file. Make sure you have a ' .
440447
'P12 certificate that also contains a private key, and you ' .
441-
'have specified the correct password!'
448+
'have specified the correct password!' . PHP_EOL . PHP_EOL .
449+
'OpenSSL error: ' . $error
450+
);
451+
}
452+
453+
// Try an alternative route using shell_exec
454+
try {
455+
$value = @shell_exec(
456+
"openssl pkcs12 -in " . escapeshellarg($this->certPath) .
457+
" -passin " . escapeshellarg("pass:" . $this->certPass) .
458+
" -passout " . escapeshellarg("pass:" . $this->certPass) .
459+
" -legacy"
442460
);
461+
if ($value) {
462+
$cert = substr($value, strpos($value, '-----BEGIN CERTIFICATE-----'));
463+
$cert = substr($cert, 0, strpos($cert, '-----END CERTIFICATE-----') + 25);
464+
$key = substr($value, strpos($value, '-----BEGIN ENCRYPTED PRIVATE KEY-----'));
465+
$key = substr($key, 0, strpos($key, '-----END ENCRYPTED PRIVATE KEY-----') + 35);
466+
if (strlen($cert) > 0 && strlen($key) > 0) {
467+
$certs['cert'] = $cert;
468+
$certs['pkey'] = $key;
469+
return $certs;
470+
}
471+
}
472+
} catch (\Throwable $e) {
473+
// no need to do anything
443474
}
475+
476+
throw new PKPassException(
477+
'Could not read certificate file. This might be related ' .
478+
'to using an OpenSSL version that has deprecated some older ' .
479+
'hashes. More info here: https://schof.link/2Et6z3m ' . PHP_EOL . PHP_EOL .
480+
'OpenSSL error: ' . $error
481+
);
482+
}
483+
484+
/**
485+
* Creates a signature and saves it.
486+
*
487+
* @param string $manifest
488+
* @throws PKPassException
489+
*/
490+
protected function createSignature($manifest)
491+
{
492+
$manifest_path = tempnam($this->tempPath, 'pkpass');
493+
$signature_path = tempnam($this->tempPath, 'pkpass');
494+
file_put_contents($manifest_path, $manifest);
495+
496+
$certs = $this->readP12();
444497
$certdata = openssl_x509_read($certs['cert']);
445498
$privkey = openssl_pkey_get_private($certs['pkey'], $this->certPass);
446499

@@ -506,11 +559,11 @@ protected function createZip($manifest, $signature)
506559
$download_file = file_get_contents($url);
507560
$zip->addFromString($name, $download_file);
508561
}
509-
562+
510563
foreach ($this->files_content as $name => $content) {
511564
$zip->addFromString($name, $content);
512565
}
513-
566+
514567
$zip->close();
515568

516569
if (!file_exists($filename) || filesize($filename) < 1) {

tests/BasicTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types=1);
2+
3+
use PHPUnit\Framework\TestCase;
4+
use PKPass\PKPass;
5+
6+
final class BasicTest extends TestCase
7+
{
8+
private function validatePass($pass, $expected_files = [])
9+
{
10+
// basic string validation
11+
$this->assertIsString($pass);
12+
$this->assertGreaterThan(100, strlen($pass));
13+
$this->assertStringContainsString('icon.png', $pass);
14+
$this->assertStringContainsString('manifest.json', $pass);
15+
16+
// try to read the ZIP file
17+
$temp_name = tempnam(sys_get_temp_dir(), 'pkpass');
18+
file_put_contents($temp_name, $pass);
19+
$zip = new ZipArchive();
20+
$res = $zip->open($temp_name);
21+
$this->assertTrue($res, 'Invalid ZIP file.');
22+
$this->assertEquals(count($expected_files), $zip->numFiles);
23+
24+
// extract zip to temp dir
25+
$temp_dir = $temp_name . '_dir';
26+
mkdir($temp_dir);
27+
$zip->extractTo($temp_dir);
28+
$zip->close();
29+
echo $temp_dir;
30+
foreach ($expected_files as $file) {
31+
$this->assertFileExists($temp_dir . DIRECTORY_SEPARATOR . $file);
32+
}
33+
}
34+
35+
public function testBasicGeneration()
36+
{
37+
$pass = new PKPass(__DIR__ . '/fixtures/example-certificate.p12', 'password');
38+
$data = [
39+
'description' => 'Demo pass',
40+
'formatVersion' => 1,
41+
'organizationName' => 'Flight Express',
42+
'passTypeIdentifier' => 'pass.com.scholica.flights', // Change this!
43+
'serialNumber' => '12345678',
44+
'teamIdentifier' => 'KN44X8ZLNC', // Change this!
45+
'barcode' => [
46+
'format' => 'PKBarcodeFormatQR',
47+
'message' => 'Flight-GateF12-ID6643679AH7B',
48+
'messageEncoding' => 'iso-8859-1',
49+
],
50+
'backgroundColor' => 'rgb(32,110,247)',
51+
'logoText' => 'Flight info',
52+
'relevantDate' => date('Y-m-d\TH:i:sP')
53+
];
54+
$pass->setData($data);
55+
$pass->addFile(__DIR__ . '/fixtures/icon.png');
56+
$value = $pass->create();
57+
58+
$this->validatePass($value, [
59+
'icon.png',
60+
'manifest.json',
61+
'pass.json',
62+
'signature',
63+
]);
64+
}
65+
}
2.53 KB
Binary file not shown.

tests/fixtures/icon.png

6.52 KB
Loading

0 commit comments

Comments
 (0)