Completed
Push — master ( 32a55a...fb9711 )
by Mitchel
02:18
created

ResponseSignatureMiddleware   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 97
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 97
c 0
b 0
f 0
wmc 10
lcom 0
cbo 1
rs 10

4 Methods

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