Passed
Push — master ( 2a728a...34eaf6 )
by Charles
03:31
created

RequestParser::decryptRequest()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 31
rs 9.3554
c 0
b 0
f 0
cc 5
nc 5
nop 4
1
<?php declare(strict_types=1);
2
3
namespace ncryptf\middleware;
4
5
use Exception;
6
7
use ncryptf\Response;
8
9
use Psr\SimpleCache\CacheInterface;
10
use Psr\Http\Message\StreamInterface;
11
use Psr\Http\Message\ResponseInterface;
12
13
use Psr\Http\Server\MiddlewareInterface;
14
use Fig\Http\Message\StatusCodeInterface;
15
use Psr\Http\Message\ServerRequestInterface;
16
use Psr\Http\Server\RequestHandlerInterface;
17
use Psr\SimpleCache\InvalidArgumentException;
18
19
use ncryptf\middleware\EncryptionKeyInterface;
20
21
final class RequestParser implements MiddlewareInterface
22
{
23
    /**
24
     * @var array $contentType
25
     */
26
    protected $contentType = [
27
        'application/vnd.25519+json',
28
        'application/vnd.ncryptf+json'
29
    ];
30
31
    /**
32
     * Constructor
33
     * @param CacheInterface $cache     A PSR-16 CacheInterface
34
     */
35
    public function __construct(CacheInterface $cache)
36
    {
37
        $this->cache = $cache;
0 ignored issues
show
Bug Best Practice introduced by
The property cache does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
38
    }
39
40
    /**
41
     * Processes the request
42
     * @param ServerRequestInterface $request
43
     * @param RequestHandlerInterface $handler
44
     */
45
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
46
    {
47
        if ($this->checkRequest($request)) {
48
            try {
49
                $rawBody = \base64_decode($request->getBody()->getContents());
50
                if ($rawBody === '') {
51
                    $request = $request->withParsedBody([]);
52
                    $request->withAttribute('ncryptf-decrypted-body', '');
53
                } else {
54
                    $version = Response::getVersion($rawBody);
55
                    $key = $this->getEncryptionKey($request);
56
57
                    $body = $this->decryptRequest($key, $request, $rawBody, $version);
58
                    $request = $request->withParsedBody(\json_decode($body, true, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION))
59
                        ->withAttribute('ncryptf-decrypted-body', $body)
60
                        ->withAttribute('ncryptf-version', $version)
61
                        ->withAttribute('ncryptf-request-public-key', $version === 2 ? Response::getPublicKeyFromResponse($rawBody) : $request->getHeaderLine('x-pubkey'));
62
                }
63
            } catch (DecryptionFailedException | InvalidArgumentException | InvalidSignatureException | InvalidChecksumException | Exception $e) {
0 ignored issues
show
Bug introduced by
The type ncryptf\middleware\InvalidChecksumException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type ncryptf\middleware\DecryptionFailedException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type ncryptf\middleware\InvalidSignatureException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
64
                return $handler->handle($request)
65
                        ->withStatus(401);
66
            }
67
        }
68
69
        return $handler->handle($request);
70
    }
71
72
    /**
73
     * Decrypts a request
74
     * @param EncryptionKeyInterface $key
75
     * @param ServerRequestInterface $request
76
     * @param string $rawBody
77
     * @param int $version
78
     * @return string|null
79
     */
80
    private function decryptRequest(EncryptionKeyInterface $key, ServerRequestInterface $request, string $rawBody, int $version) :? string
81
    {
82
        static $response = null;
83
        static $nonce = null;
84
        static $publicKey = null;
85
86
        $response = new Response(
87
            $key->getBoxSecretKey()
88
        );
89
90
        if ($version === 1) {
91
            if (!$request->hasHeader('x-pubkey') || !$request->hasHeader('x-nonce')) {
92
                throw new Exception('Missing nonce or public key header. Unable to decrypt response.');
93
            }
94
95
            $publicKey = \base64_decode($request->getHeaderLine('x-pubkey'));
96
            $nonce = \base64_decode($request->getHeaderLine('x-nonce'));
97
        }
98
99
        $decryptedRequest = $response->decrypt(
100
            $rawBody,
101
            $publicKey,
102
            $nonce
103
        );
104
105
        $hashKey = $request->getHeaderLine('x-hashid');
106
        if ($key->isEphemeral()) {
107
            $this->cache->delete($hashKey);
108
        }
109
110
        return $decryptedRequest;
111
    }
112
113
    /**
114
     * Determines the key from the X-HashId header
115
     * @param ServerRequestInterface
116
     * @return EncryptionKeyInterface
117
     */
118
    private function getEncryptionKey(ServerRequestInterface $request) : EncryptionKeyInterface
119
    {
120
        if (!$request->hasHeader('x-hashid')) {
121
            throw new Exception('Unable to decrypt request.');
122
        }
123
124
        $hashKey = $request->getHeaderLine('x-hashid');
125
126
        try {
127
            return $this->cache->get($hashKey);
128
        } catch (InvalidArgumentException $e) {
129
            throw new Exception('Unable to decrypt request.', null, $e);
130
        }
131
    }
132
133
    /**
134
     * Check whether the request payload need to be processed
135
     */
136
    private function checkRequest(ServerRequestInterface $request): bool
137
    {
138
        $contentType = $request->getHeaderLine('Content-Type');
139
        foreach ($this->contentType as $allowedType) {
140
            if (stripos($contentType, $allowedType) === 0) {
141
                return true;
142
            }
143
        }
144
        return false;
145
    }
146
}
147