Passed
Push — master ( 3c151b...b5e885 )
by Charles
03:04
created

JsonRequestParser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php declare(strict_types=1);
2
3
namespace ncryptf\middleware;
4
5
use Exception;
6
7
use ncryptf\Token;
8
use ncryptf\Response;
9
10
use Psr\SimpleCache\CacheInterface;
11
use Psr\Http\Message\StreamInterface;
12
use Psr\Http\Message\ResponseInterface;
13
14
use Psr\Http\Server\MiddlewareInterface;
15
use Fig\Http\Message\StatusCodeInterface;
0 ignored issues
show
Bug introduced by
The type Fig\Http\Message\StatusCodeInterface 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...
16
use Psr\Http\Message\ServerRequestInterface;
17
use Psr\Http\Server\RequestHandlerInterface;
18
use Psr\SimpleCache\InvalidArgumentException;
19
20
use ncryptf\middleware\EncryptionKeyInterface;
21
22
final class JsonRequestParser implements MiddlewareInterface
23
{
24
    /**
25
     * @var array $contentType
26
     */
27
    protected $contentType = [
28
        'application/vnd.25519+json',
29
        'application/vnd.ncryptf+json'
30
    ];
31
32
    /**
33
     * Constructor
34
     * @param CacheInterface $cache     A PSR-16 CacheInterface
35
     */
36
    public function __construct(CacheInterface $cache)
37
    {
38
        $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...
39
    }
40
41
    /**
42
     * Processes the request
43
     * @param ServerRequestInterface $request
44
     * @param RequestHandlerInterface $handler
45
     */
46
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
47
    {
48
        if ($this->checkRequest($request)) {
49
            try {
50
                $rawBody = \base64_decode($request->getBody()->getContents());
51
                if ($rawBody === '') {
52
                    $request = $request->withParsedBody([])
53
                        ->withAttribute('ncryptf-decrypted-body', '')
54
                        ->withAttribute('ncryptf-version', 2)
55
                        ->withAttribute('ncryptf-request-public-key', \base64_decode($request->getHeaderLine('x-pubkey')));
56
                } else {
57
                    $version = Response::getVersion($rawBody);
58
                    $key = $this->getEncryptionKey($request);
59
60
                    $body = $this->decryptRequest($key, $request, $rawBody, $version);
61
62
                    // If we're on V2 or greater of the request, and a token is defined, verify that the signature was signed by the user who issued the request
63
                    if ($version >= 2 && $request->getAttribute('ncryptf-token') instanceof Token) {
64
                        $token = $request->getAttribute('ncryptf-token');
65
                        $publicKey = Response::getSigningPublicKeyFromResponse($rawBody);
66
                        if (\sodium_compare($publicKey, $token->getSignaturePublicKey()) !== 0) {
67
                            throw new Exception('Signing key mismatch.');
68
                        }
69
                    }
70
                    
71
                    $request = $request->withParsedBody(\json_decode($body, true))
72
                        ->withAttribute('ncryptf-decrypted-body', $body)
73
                        ->withAttribute('ncryptf-version', $version)
74
                        ->withAttribute('ncryptf-request-public-key', $version === 2 ? Response::getPublicKeyFromResponse($rawBody) : \base64_decode($request->getHeaderLine('x-pubkey')));
75
                }
76
            } catch (DecryptionFailedException | InvalidArgumentException | InvalidSignatureException | InvalidChecksumException | Exception $e) {
0 ignored issues
show
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...
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\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...
77
                return $handler->handle($request)
78
                        ->withStatus(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
            return $this->cache->get($hashKey);
143
        } catch (InvalidArgumentException $e) {
144
            throw new Exception('Unable to decrypt request.', null, $e);
145
        }
146
    }
147
148
    /**
149
     * Check whether the request payload need to be processed
150
     * @param ServerRequestInterface $request
151
     * @return bool
152
     */
153
    private function checkRequest(ServerRequestInterface $request): bool
154
    {
155
        $contentType = $request->getHeaderLine('Content-Type');
156
        foreach ($this->contentType as $allowedType) {
157
            if (\stripos($contentType, $allowedType) === 0) {
158
                return true;
159
            }
160
        }
161
        return false;
162
    }
163
}
164