JsonRequestParser::process()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 29
rs 9.3222
c 0
b 0
f 0
cc 5
nc 9
nop 2
1
<?php declare(strict_types=1);
2
3
namespace ncryptf\middleware;
4
5
use Exception;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
10
use Psr\Http\Message\StreamInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
14
use Psr\SimpleCache\CacheInterface;
15
use Psr\SimpleCache\InvalidArgumentException;
16
use ncryptf\Response;
17
18
use ncryptf\Token;
19
use ncryptf\exceptions\DecryptionFailedException;
20
use ncryptf\exceptions\InvalidChecksumException;
21
use ncryptf\exceptions\InvalidSignatureException;
22
23
use ncryptf\middleware\EncryptionKeyInterface;
24
25
final class JsonRequestParser implements MiddlewareInterface
26
{
27
    use \Middlewares\Utils\Traits\HasResponseFactory;
28
    
29
    /**
30
     * @var CacheInterface $cache
31
     */
32
    protected $cache;
33
34
    /**
35
     * @var array $contentType
36
     */
37
    protected $contentType = [
38
        'application/vnd.25519+json',
39
        'application/vnd.ncryptf+json'
40
    ];
41
42
    /**
43
     * Constructor
44
     * @param CacheInterface $cache     A PSR-16 CacheInterface
45
     */
46
    public function __construct(CacheInterface $cache)
47
    {
48
        $this->cache = $cache;
49
    }
50
51
    /**
52
     * Processes the request
53
     * @param ServerRequestInterface $request
54
     * @param RequestHandlerInterface $handler
55
     */
56
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
57
    {
58
        if ($this->checkRequest($request)) {
59
            try {
60
                $rawBody = \base64_decode($request->getBody()->getContents());
61
                if ($rawBody === '') {
62
                    $request = $request->withParsedBody([])
63
                        ->withAttribute('ncryptf-decrypted-body', '')
64
                        ->withAttribute('ncryptf-version', 2)
65
                        ->withAttribute('ncryptf-request-public-key', \base64_decode($request->getHeaderLine('x-pubkey')));
66
                } else {
67
                    $version = Response::getVersion($rawBody);
68
                    $key = $this->getEncryptionKey($request);
69
70
                    $body = $this->decryptRequest($key, $request, $rawBody, $version);
71
72
                    $request = $request->withParsedBody(\json_decode($body, true))
73
                        ->withAttribute('ncryptf-decrypted-body', $body)
74
                        ->withAttribute('ncryptf-version', $version)
75
                        ->withAttribute('ncryptf-request-public-key', $version === 2 ? Response::getPublicKeyFromResponse($rawBody) : \base64_decode($request->getHeaderLine('x-pubkey')));
76
                }
77
            } catch (DecryptionFailedException | InvalidArgumentException | InvalidSignatureException | InvalidChecksumException | Exception $e) {
78
                return $this->createResponse(400);
79
            }
80
        }
81
82
        // Only attempt to process this request if it is a vnd.25519+json or vnd.ncryptf+json request
83
        // Otherwise, continue with normal processing
84
        return $handler->handle($request);
85
    }
86
87
    /**
88
     * Decrypts a request
89
     * @param EncryptionKeyInterface $key
90
     * @param ServerRequestInterface $request
91
     * @param string $rawBody
92
     * @param int $version
93
     * @return string|null
94
     */
95
    private function decryptRequest(EncryptionKeyInterface $key, ServerRequestInterface $request, string $rawBody, int $version) :? string
96
    {
97
        static $response = null;
98
        static $nonce = null;
99
        static $publicKey = null;
100
101
        $response = new Response(
102
            $key->getBoxSecretKey()
103
        );
104
105
        if ($version === 1) {
106
            if (!$request->hasHeader('x-pubkey') || !$request->hasHeader('x-nonce')) {
107
                throw new Exception('Missing nonce or public key header. Unable to decrypt response.');
108
            }
109
110
            $publicKey = \base64_decode($request->getHeaderLine('x-pubkey'));
111
            $nonce = \base64_decode($request->getHeaderLine('x-nonce'));
112
        }
113
114
        $decryptedRequest = $response->decrypt(
115
            $rawBody,
116
            $publicKey,
117
            $nonce
118
        );
119
120
        $hashKey = $request->getHeaderLine('x-hashid');
121
        if ($key->isEphemeral()) {
122
            $this->cache->delete($hashKey);
123
        }
124
125
        return $decryptedRequest;
126
    }
127
128
    /**
129
     * Determines the key from the X-HashId header
130
     * @param ServerRequestInterface
131
     * @return EncryptionKeyInterface
132
     */
133
    private function getEncryptionKey(ServerRequestInterface $request) : EncryptionKeyInterface
134
    {
135
        if (!$request->hasHeader('x-hashid')) {
136
            throw new Exception('Unable to decrypt request.');
137
        }
138
139
        $hashKey = $request->getHeaderLine('x-hashid');
140
141
        try {
142
            $result = $this->cache->get($hashKey);
143
144
            if (!$result) {
145
                throw new Exception('Unable to extract key from cache.');
146
            }
147
148
            if (\function_exists('igbinary_unserialize')) {
149
                return \igbinary_unserialize($result);
150
            }
151
152
            return \unserialize($result);
153
        } catch (InvalidArgumentException $e) {
154
            throw new Exception('Unable to decrypt request.', null, $e);
155
        }
156
    }
157
158
    /**
159
     * Check whether the request payload need to be processed
160
     * @param ServerRequestInterface $request
161
     * @return bool
162
     */
163
    private function checkRequest(ServerRequestInterface $request): bool
164
    {
165
        $contentType = $request->getHeaderLine('Content-Type');
166
        foreach ($this->contentType as $allowedType) {
167
            if (\stripos($contentType, $allowedType) === 0) {
168
                return true;
169
            }
170
        }
171
        return false;
172
    }
173
}
174