Skip to content

Commit cdb00c2

Browse files
authored
Merge pull request #149 from JonErickson/support-mutual-tls
Adds support for mutual TLS
2 parents 275cae5 + 9fd4678 commit cdb00c2

File tree

4 files changed

+100
-4
lines changed

4 files changed

+100
-4
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,25 @@ WebhookCall::create()
292292
...
293293
```
294294

295+
### Using mutual TLS authentication
296+
297+
To safeguard the integrity of webhook data transmission, it's critical to authenticate the intended recipient of your webhook payload.
298+
Mutual TLS authentication serves as a robust method for this purpose. Contrary to standard TLS, where only the client verifies the server,
299+
mutual TLS requires both the webhook endpoint (acting as the client) and the webhook provider (acting as the server) to authenticate each other.
300+
This is achieved through an exchange of certificates during the TLS handshake, ensuring that both parties confirm each other's identity.
301+
302+
> Note: If you need to include your own certificate authority, pass the certificate path to the `verifySsl()` method.
303+
304+
```php
305+
WebhookCall::create()
306+
->mutualTls(
307+
certPath: storage_path('path/to/cert.pem'),
308+
certPassphrase: 'optional_cert_passphrase',
309+
sslKeyPath: storage_path('path/to/key.pem'),
310+
sslKeyPassphrase: 'optional_key_passphrase'
311+
)
312+
```
313+
295314
The proxy specification follows the [guzzlehttp proxy format](https://docs.guzzlephp.org/en/stable/request-options.html#proxy)
296315

297316
### Verifying the SSL certificate of the receiving app

src/CallWebhookJob.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,21 @@ class CallWebhookJob implements ShouldQueue
3333

3434
public int $requestTimeout;
3535

36+
public ?string $cert = null;
37+
38+
public ?string $certPassphrase = null;
39+
40+
public ?string $sslKey = null;
41+
42+
public ?string $sslKeyPassphrase = null;
43+
3644
public string $backoffStrategyClass;
3745

3846
public ?string $signerClass = null;
3947

4048
public array $headers = [];
4149

42-
public bool $verifySsl;
50+
public string|bool $verifySsl;
4351

4452
public bool $throwExceptionOnFailure;
4553

@@ -132,14 +140,20 @@ protected function createRequest(array $body): Response
132140
{
133141
$client = $this->getClient();
134142

135-
return $client->request($this->httpVerb, $this->webhookUrl, array_merge([
143+
return $client->request($this->httpVerb, $this->webhookUrl, array_merge(
144+
[
136145
'timeout' => $this->requestTimeout,
137146
'verify' => $this->verifySsl,
138147
'headers' => $this->headers,
139148
'on_stats' => function (TransferStats $stats) {
140149
$this->transferStats = $stats;
141150
},
142-
], $body, is_null($this->proxy) ? [] : ['proxy' => $this->proxy]));
151+
],
152+
$body,
153+
is_null($this->proxy) ? [] : ['proxy' => $this->proxy],
154+
is_null($this->cert) ? [] : ['cert' => [$this->cert, $this->certPassphrase]],
155+
is_null($this->sslKey) ? [] : ['ssl_key' => [$this->sslKey, $this->sslKeyPassphrase]]
156+
));
143157
}
144158

145159
protected function shouldBeRemovedFromQueue(): bool

src/WebhookCall.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ public function maximumTries(int $tries): self
117117
return $this;
118118
}
119119

120+
public function mutualTls(string $certPath, string $sslKeyPath, ?string $certPassphrase = null, ?string $sslKeyPassphrase = null): self
121+
{
122+
$this->callWebhookJob->cert = $certPath;
123+
$this->callWebhookJob->certPassphrase = $certPassphrase;
124+
$this->callWebhookJob->sslKey = $sslKeyPath;
125+
$this->callWebhookJob->sslKeyPassphrase = $sslKeyPassphrase;
126+
127+
return $this;
128+
}
129+
120130
public function useBackoffStrategy(string $backoffStrategyClass): self
121131
{
122132
if (! is_subclass_of($backoffStrategyClass, BackoffStrategy::class)) {
@@ -160,7 +170,7 @@ public function withHeaders(array $headers): self
160170
return $this;
161171
}
162172

163-
public function verifySsl(bool $verifySsl = true): self
173+
public function verifySsl(bool|string $verifySsl = true): self
164174
{
165175
$this->callWebhookJob->verifySsl = $verifySsl;
166176

tests/CallWebhookJobTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,59 @@ function baseGetRequest(array $overrides = []): array
184184
->assertRequestsMade([$baseRequest]);
185185
});
186186

187+
it('will use mutual TLS without passphrases', function () {
188+
baseWebhook()
189+
->mutualTls('foobar', 'barfoo')
190+
->dispatch();
191+
192+
$baseRequest = baseRequest();
193+
194+
$baseRequest['options']['cert'] = ['foobar', null];
195+
$baseRequest['options']['ssl_key'] = ['barfoo', null];
196+
197+
artisan('queue:work --once');
198+
199+
$this
200+
->testClient
201+
->assertRequestsMade([$baseRequest]);
202+
});
203+
204+
it('will use mutual TLS with passphrases', function () {
205+
baseWebhook()
206+
->mutualTls('foobar', 'barfoo', 'foobarpassword', 'barfoopassword')
207+
->dispatch();
208+
209+
$baseRequest = baseRequest();
210+
211+
$baseRequest['options']['cert'] = ['foobar', 'foobarpassword'];
212+
$baseRequest['options']['ssl_key'] = ['barfoo', 'barfoopassword'];
213+
214+
artisan('queue:work --once');
215+
216+
$this
217+
->testClient
218+
->assertRequestsMade([$baseRequest]);
219+
});
220+
221+
it('will use mutual TLS with certificate authority', function () {
222+
baseWebhook()
223+
->mutualTls('foobar', 'barfoo')
224+
->verifySsl('foofoo')
225+
->dispatch();
226+
227+
$baseRequest = baseRequest();
228+
229+
$baseRequest['options']['cert'] = ['foobar', null];
230+
$baseRequest['options']['ssl_key'] = ['barfoo', null];
231+
$baseRequest['options']['verify'] = 'foofoo';
232+
233+
artisan('queue:work --once');
234+
235+
$this
236+
->testClient
237+
->assertRequestsMade([$baseRequest]);
238+
});
239+
187240
it('will use a proxy', function () {
188241
baseWebhook()
189242
->useProxy('https://proxy.test')

0 commit comments

Comments
 (0)