• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paynl / request-signing / 7738391667

01 Feb 2024 08:33AM UTC coverage: 98.649% (-1.4%) from 100.0%
7738391667

Pull #6

github

web-flow
Merge 2b6003b71 into 2e1d10bdc
Pull Request #6: Add fallback verification method to the HMAC implementation

21 of 22 new or added lines in 1 file covered. (95.45%)

73 of 74 relevant lines covered (98.65%)

3.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.83
/src/Methods/HmacSignature.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PayNL\RequestSigning\Methods;
6

7
use PayNL\RequestSigning\Constant\SignatureMethodEnum;
8
use PayNL\RequestSigning\Exception\SignatureKeyNotFoundException;
9
use PayNL\RequestSigning\Exception\UnsupportedHashingAlgorithmException;
10
use PayNL\RequestSigning\Repository\HmacSignatureKeyRepositoryInterface;
11
use PayNL\RequestSigning\ValueObject\SignatureData;
12
use Psr\Http\Message\RequestInterface;
13
use Throwable;
14

15
final class HmacSignature implements RequestSigningMethodInterface
16
{
17
    public const METHOD_NAME = SignatureMethodEnum::HMAC;
18

19
    private HmacSignatureKeyRepositoryInterface $signatureKeyRepository;
20

21
    public function __construct(HmacSignatureKeyRepositoryInterface $signatureKeyRepository)
22
    {
23
        $this->signatureKeyRepository = $signatureKeyRepository;
10✔
24
    }
25

26
    /**
27
     * Generate a signature for a given request using the specified key and algorithm.
28
     *
29
     * @param RequestInterface $request
30
     * @param string $keyId The ID of the key used for signing the request.
31
     * @param string $algorithm The hashing algorithm to be used for signing the request.
32
     *
33
     * @throws UnsupportedHashingAlgorithmException If the specified algorithm is not supported.
34
     * @throws SignatureKeyNotFoundException if the key for the given key id cannot be found
35
     *
36
     * @return RequestInterface The generated signature data.
37
     */
38
    public function sign(RequestInterface $request, string $keyId, string $algorithm): RequestInterface
39
    {
40
        $signatureData = $this->generateSignature((string)$request->getBody(), $keyId, $algorithm);
3✔
41

42
        return $request
2✔
43
            ->withHeader(RequestSigningMethodInterface::SIGNATURE_HEADER, $signatureData->getSignature())
2✔
44
            ->withHeader(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER, $signatureData->getKeyId())
2✔
45
            ->withHeader(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER, $signatureData->getMethod())
2✔
46
            ->withHeader(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER, $signatureData->getAlgorithm());
2✔
47
    }
48

49
    /**
50
     * Generate a signature using the given body, key ID, and algorithm.
51
     *
52
     * @param string $body The body to sign.
53
     * @param string $keyId The ID of the key to use for signing.
54
     * @param string $algorithm The hashing algorithm to use for signing.
55
     *
56
     * @throws UnsupportedHashingAlgorithmException if the provided algorithm is not supported.
57
     * @throws SignatureKeyNotFoundException if the key for the given key id cannot be found
58
     * @return SignatureData The generated signature data.
59
     */
60
    private function generateSignature(string $body, string $keyId, string $algorithm): SignatureData
61
    {
62
        $key = $this->signatureKeyRepository->findOneById($keyId);
9✔
63

64
        $algorithm = strtolower($algorithm);
8✔
65

66
        if (in_array($algorithm, hash_hmac_algos()) === false) {
8✔
67
            throw UnsupportedHashingAlgorithmException::forAlgorithm($algorithm);
2✔
68
        }
69

70
        return new SignatureData(
6✔
71
            hash_hmac($algorithm, $body, $key->getSecret()),
6✔
72
            $keyId,
6✔
73
            $algorithm,
6✔
74
            self::METHOD_NAME
6✔
75
        );
6✔
76
    }
77

78
    /**
79
     * Verifies the signature of a request.
80
     *
81
     * @param RequestInterface $request The request to verify.
82
     *
83
     * @return bool Returns true if the signature is valid, false otherwise.
84
     */
85
    public function verify(RequestInterface $request): bool
86
    {
87
        try {
88
            $keyId = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER);
6✔
89
            $signature = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_HEADER);
6✔
90
            $algorithm = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER);
6✔
91

92
            $generatedSignature = $this
6✔
93
                ->generateSignature((string)$request->getBody(), $keyId, $algorithm)
6✔
94
                ->getSignature();
6✔
95

96
            return hash_equals($signature, $generatedSignature) || $this->doFallbackVerification($request);
4✔
97
        } catch (Throwable $throwable) {
2✔
98
            // We do nothing with the exception. The request stays marked as "invalid".
99
        }
100

101
        return false;
2✔
102
    }
103

104
    public function supports(string $method): bool
105
    {
106
        return strtolower(self::METHOD_NAME) === strtolower($method);
3✔
107
    }
108

109
    /**
110
     * Since we've moved away from hash() signing in favor of hash_hmac we should (for now) support the
111
     * hash verification method to keep older implementations valid
112
     *
113
     * @throws UnsupportedHashingAlgorithmException
114
     * @throws SignatureKeyNotFoundException
115
     */
116
    private function doFallbackVerification(RequestInterface $request): bool
117
    {
118
        $keyId = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER);
2✔
119
        $signature = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_HEADER);
2✔
120
        $algorithm = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER);
2✔
121

122
        $key = $this->signatureKeyRepository->findOneById($keyId);
2✔
123

124
        $algorithm = strtolower($algorithm);
2✔
125

126
        if (in_array($algorithm, hash_algos()) === false) {
2✔
NEW
127
            throw UnsupportedHashingAlgorithmException::forAlgorithm($algorithm);
×
128
        }
129

130
        $generatedSignature = hash(
2✔
131
            $algorithm,
2✔
132
            implode(
2✔
133
                ':',
2✔
134
                [
2✔
135
                    $key->getId(),
2✔
136
                    $key->getSecret(),
2✔
137
                    (string)$request->getBody(),
2✔
138
                ]
2✔
139
            )
2✔
140
        );
2✔
141

142
        return hash_equals($signature, $generatedSignature);
2✔
143
    }
144
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc