ResponseSignatureMiddleware::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Bunq\Middleware;
4
5
use Bunq\Certificate\Certificate;
6
use Psr\Http\Message\ResponseInterface;
7
8
/**
9
 * @author Dennis de Greef (original author)
10
 * @author Mitchel Verschoof
11
 */
12
final class ResponseSignatureMiddleware
13
{
14
    const SIGNATURE_ALGORITHM = OPENSSL_ALGO_SHA256;
15
    const VERIFY_IS_VALID     = 1;
16
    const VERIFY_IS_INVALID   = 0;
17
    const VERIFY_IS_ERROR     = -1;
18
19
    /**
20
     * @var Certificate
21
     */
22
    private $publicKey;
23
24
    /**
25
     * @param Certificate $publicKey
26
     */
27
    public function __construct(Certificate $publicKey)
28
    {
29
        $this->publicKey = $publicKey;
30
    }
31
32
    /**
33
     * @param ResponseInterface $response
34
     *
35
     * @return ResponseInterface
36
     * @throws \Exception
37
     */
38
    public function __invoke(ResponseInterface $response)
39
    {
40
        $header = $response->getHeader('X-Bunq-Server-Signature');
41
42
        if (!isset($header[0])) {
43
            return $response;
44
        }
45
46
        $decodedServerSignature = base64_decode($header[0]);
47
        $signatureData          = $this->createSignatureDataFromHeaders($response);
48
49
        $verify = openssl_verify(
50
            $signatureData,
51
            $decodedServerSignature,
52
            (string)$this->publicKey,
53
            self::SIGNATURE_ALGORITHM
54
        );
55
56
        if ($verify !== self::VERIFY_IS_VALID) {
57
            throw new \Exception('Server signature does not match response');
58
        }
59
60
        return $response;
61
    }
62
63
    /**
64
     * @param ResponseInterface $response
65
     *
66
     * @return string
67
     */
68
    private function createSignatureDataFromHeaders(ResponseInterface $response)
69
    {
70
        $signatureData = $response->getStatusCode();
71
        $signatureData .= $this->convertHeadersToSignatureData($response);
72
        $signatureData .= "\n\n";
73
        $signatureData .= (string)$response->getBody();
74
75
        return $signatureData;
76
    }
77
78
    /**
79
     * @param ResponseInterface $response
80
     *
81
     * @return string
82
     */
83
    private function convertHeadersToSignatureData(ResponseInterface $response)
84
    {
85
        $headers = $response->getHeaders();
86
        ksort($headers);
87
88
        $signatureData = '';
89
        foreach ($headers as $header => $values) {
90
            // Skip the server signature itself
91
            if ($header === 'X-Bunq-Server-Signature') {
92
                continue;
93
            }
94
95
            // Skip all headers that are not X-Bunq-
96
            if (substr($header, 0, 7) !== 'X-Bunq-') {
97
                continue;
98
            }
99
100
            // Add all header data to verify signature
101
            foreach ($values as $value) {
102
                $signatureData .= PHP_EOL . $header . ': ' . $value;
103
            }
104
        }
105
106
        return $signatureData;
107
    }
108
}
109