1: <?php
2: namespace Worldline\Connect\Sdk\Webhooks;
3:
4: /**
5: * Class SignatureValidator
6: *
7: * @package Worldline\Connect\Sdk\Webhooks
8: */
9: class SignatureValidator
10: {
11: /**
12: * @var SecretKeyStore
13: */
14: private SecretKeyStore $secretKeyStore;
15:
16: /**
17: * @param SecretKeyStore $secretKeyStore
18: */
19: public function __construct(SecretKeyStore $secretKeyStore)
20: {
21: $this->secretKeyStore = $secretKeyStore;
22: }
23:
24: /**
25: * Validates the given body using the given request headers.
26: *
27: * @param string $body
28: * @param array $requestHeaders
29: *
30: * @return void
31: * @throws SignatureValidationException
32: */
33: public function validate(string $body, array $requestHeaders): void
34: {
35: $this->validateBody($body, $requestHeaders);
36: }
37:
38: // utility methods
39:
40: private function validateBody(string $body, array $requestHeaders): void
41: {
42: $signature = $this->getHeaderValue($requestHeaders, 'X-GCS-Signature');
43: $keyId = $this->getHeaderValue($requestHeaders, 'X-GCS-KeyId');
44: $secretKey = $this->secretKeyStore->getSecretKey($keyId);
45:
46: $expectedSignature = base64_encode(hash_hmac("sha256", $body, $secretKey, true));
47:
48: $isValid = hash_equals($signature, $expectedSignature);
49: if (!$isValid) {
50: throw new SignatureValidationException("failed to validate signature '$signature'");
51: }
52: }
53:
54: // general utility methods
55:
56: private function getHeaderValue(array $requestHeaders, string $headerName): string
57: {
58: $lowerCaseHeaderName = strtolower($headerName);
59: foreach ($requestHeaders as $name => $value) {
60: if ($lowerCaseHeaderName === strtolower($name)) {
61: return $value;
62: }
63: }
64: throw new SignatureValidationException("could not find header '$headerName'");
65: }
66: }
67: