Skip to content

Commit 407ea4d

Browse files
committed
feat!: enforce VAPID subject validation
Added stricter validation for the VAPID 'subject' field to require a valid mailto: email or https: URL.
1 parent 5d154c2 commit 407ea4d

File tree

3 files changed

+48
-8
lines changed

3 files changed

+48
-8
lines changed

src/VAPID.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,35 @@ class VAPID
4646
*/
4747
public static function validate(array $vapid): array
4848
{
49-
if (!isset($vapid['subject'])) {
49+
// The subject is optional according to the RFC but no browser vendor accept VAPID without it.
50+
// https://www.rfc-editor.org/rfc/rfc8292#section-2.1
51+
if (!isset($vapid['subject']) || !is_string($vapid['subject'])) {
52+
throw new \ErrorException('[VAPID] You must provide a subject as string that is either a mailto: or a URL.');
53+
}
54+
$subject = $vapid['subject'];
55+
if (str_starts_with($subject, 'mailto:')) {
56+
$email = substr($subject, 7);
57+
if (false === filter_var($email, FILTER_VALIDATE_EMAIL)) {
58+
throw new \ErrorException('[VAPID] subject: mailto email is not valid.');
59+
}
60+
} elseif (str_starts_with($subject, 'https:')) {
61+
if (false === filter_var($subject, FILTER_VALIDATE_URL)) {
62+
throw new \ErrorException('[VAPID] subject: url is not valid.');
63+
}
64+
} else {
5065
throw new \ErrorException('[VAPID] You must provide a subject that is either a mailto: or a URL.');
5166
}
5267

5368
if (isset($vapid['pemFile'])) {
54-
$vapid['pem'] = file_get_contents($vapid['pemFile']);
69+
$filename = $vapid['pemFile'];
70+
if (!file_exists($filename)) {
71+
throw new \ErrorException('Error loading PEM file: does not exist.');
72+
}
73+
if (!is_readable($filename)) {
74+
throw new \ErrorException('Error loading PEM file: not readable.');
75+
}
76+
77+
$vapid['pem'] = file_get_contents($filename);
5578

5679
if (!$vapid['pem']) {
5780
throw new \ErrorException('[VAPID] Error loading PEM file.');
@@ -93,7 +116,7 @@ public static function validate(array $vapid): array
93116
}
94117

95118
return [
96-
'subject' => $vapid['subject'],
119+
'subject' => $subject,
97120
'publicKey' => $publicKey,
98121
'privateKey' => $privateKey,
99122
];

tests/PushServiceTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class PushServiceTest extends PHPUnit\Framework\TestCase
2626
private static int $portNumber = 9012;
2727
private static string $testServiceUrl;
2828
public static array $vapidKeys = [
29-
'subject' => 'http://test.com',
29+
'subject' => 'https://test.com',
3030
'publicKey' => 'BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
3131
'privateKey' => '-3CdhFOqjzixgAbUSa0Zv9zi-dwDVmWO7672aBxSFPQ',
3232
];

tests/VAPIDTest.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Minishlink\WebPush\VAPID;
1414
use PHPUnit\Framework\Attributes\CoversClass;
1515
use PHPUnit\Framework\Attributes\DataProvider;
16+
use PHPUnit\Framework\Attributes\TestWith;
1617

1718
#[CoversClass(VAPID::class)]
1819
final class VAPIDTest extends PHPUnit\Framework\TestCase
@@ -23,24 +24,24 @@ public static function vapidProvider(): array
2324
[
2425
'http://push.com',
2526
[
26-
'subject' => 'http://test.com',
27+
'subject' => 'https://test.com',
2728
'publicKey' => 'BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
2829
'privateKey' => '-3CdhFOqjzixgAbUSa0Zv9zi-dwDVmWO7672aBxSFPQ',
2930
],
3031
ContentEncoding::aesgcm,
3132
1475452165,
32-
'WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwOi8vcHVzaC5jb20iLCJleHAiOjE0NzU0NTIxNjUsInN1YiI6Imh0dHA6Ly90ZXN0LmNvbSJ9.4F3ZKjeru4P9XM20rHPNvGBcr9zxhz8_ViyNfe11_xcuy7A9y7KfEPt6yuNikyW7eT9zYYD5mQZubDGa-5H2cA',
33+
'WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwOi8vcHVzaC5jb20iLCJleHAiOjE0NzU0NTIxNjUsInN1YiI6Imh0dHBzOi8vdGVzdC5jb20ifQ.JFr6qZp7_1tXtAbkdEFjZtGYAeAyQvQPOJQu7FQcbuvA2JwHsb65YlMUOFPG2qGImaESrHdO-G7blkUP5XHOYw',
3334
'p256ecdsa=BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
3435
], [
3536
'http://push.com',
3637
[
37-
'subject' => 'http://test.com',
38+
'subject' => 'https://test.com',
3839
'publicKey' => 'BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
3940
'privateKey' => '-3CdhFOqjzixgAbUSa0Zv9zi-dwDVmWO7672aBxSFPQ',
4041
],
4142
ContentEncoding::aes128gcm,
4243
1475452165,
43-
'vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwOi8vcHVzaC5jb20iLCJleHAiOjE0NzU0NTIxNjUsInN1YiI6Imh0dHA6Ly90ZXN0LmNvbSJ9.4F3ZKjeru4P9XM20rHPNvGBcr9zxhz8_ViyNfe11_xcuy7A9y7KfEPt6yuNikyW7eT9zYYD5mQZubDGa-5H2cA, k=BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
44+
'vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwOi8vcHVzaC5jb20iLCJleHAiOjE0NzU0NTIxNjUsInN1YiI6Imh0dHBzOi8vdGVzdC5jb20ifQ.kXXd2JaK1583Le1mheFKEKSF1I4rYFKvF0HKNXO8et-w2UYSc3d0pbsbN_sP17PvcsO_zT8XJZ-gbKWlCOGksw, k=BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
4445
null,
4546
],
4647
];
@@ -89,4 +90,20 @@ public function testCreateVapidKeys(): void
8990
$this->assertGreaterThanOrEqual(86, strlen($keys['publicKey']));
9091
$this->assertGreaterThanOrEqual(42, strlen($keys['privateKey']));
9192
}
93+
94+
#[TestWith([[]])]
95+
#[TestWith([['subject' => '']])]
96+
#[TestWith([['subject' => 'test']])]
97+
#[TestWith([['subject' => 'mailto:']])]
98+
#[TestWith([['subject' => 'mailto:localhost']])]
99+
#[TestWith([['subject' => 'https://']])]
100+
#[TestWith([['subject' => 'https://example.com', 'pemFile' => '']])]
101+
#[TestWith([['subject' => 'https://example.com', 'pemFile' => 'abc.pem']])]
102+
#[TestWith([['subject' => 'https://example.com', 'pem' => '']])]
103+
#[TestWith([['subject' => 'https://example.com', 'publicKey' => '']])]
104+
public function testValidateException(array $vapid): void
105+
{
106+
$this->expectException(Exception::class);
107+
VAPID::validate($vapid);
108+
}
92109
}

0 commit comments

Comments
 (0)