Completed
Push — master ( c57370...c02a5b )
by Charles
03:10
created

Response::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php declare(strict_types=1);
2
3
namespace ncryptf;
4
5
use ncryptf\exceptions\DecryptionFailedException;
6
use ncryptf\exceptions\InvalidChecksumException;
7
use ncryptf\exceptions\InvalidSignatureException;
8
use Exception;
9
use InvalidArgumentException;
10
use SodiumException;
11
12
class Response
13
{
14
    /**
15
     * Sodium CryptoBox Keypair
16
     *
17
     * @var string
18
     */
19
    private $keypair;
20
21
    /**
22
     * Secret key
23
     * 
24
     * @var string
25
     */
26
    private $secretKey;
27
28
    /**
29
     * Constructor
30
     *
31
     * @param string $secretKey The 32 byte secret key
32
     * @param string $publicKey The 32 byte public key (required for v1, optional for v2)
33
     * 
34
     * @throws InvalidArguementException
35
     */
36
    public function __construct(string $secretKey, string $publicKey = null)
37
    {
38
        try {
39
            $this->secretKey = $secretKey;
40
            if ($publicKey !== null) {
41
                $this->keypair = new Keypair(
0 ignored issues
show
Documentation Bug introduced by
It seems like new ncryptf\Keypair($secretKey, $publicKey) of type ncryptf\Keypair is incompatible with the declared type string of property $keypair.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
42
                    $secretKey,
43
                    $publicKey
44
                );
45
            }
46
        } catch (SodiumException $e) {
47
            throw new InvalidArgumentException($e->getMessage());
48
        }
49
    }
50
51
    /**
52
     * Decrypts a payload using the response and an optional nonce
53
     * Nonce is not required for v2 type signatures, but is required for v1 signatures
54
     * 
55
     * @param string $response  The encrypted HTTP response, as a multi-byte string
56
     * @param string $nonce     The 32 byte nonce
57
     * 
58
     * @throws InvalidArguementException
59
     */
60
    public function decrypt(string $response, string $nonce = null)
61
    {
62
        $version = $this->getVersion($response);
63
        if ($version === 2) {
64
            $nonce = \substr($response, 4, 24);
65
66
            // Determine the payload size sans the 64 byte checksum at the end
67
            $payload = \substr($response, 0, \strlen($response) - 64);
68
            $checksum = \substr($response, -64);
69
70
            // Verify the checksum to ensure the headers haven't been tampered with
71
            if ($checksum !== \sodium_crypto_generichash($payload, $nonce, 64)) {
72
                throw new InvalidChecksumException;
73
            }
74
            
75
            $publicKey = \substr($response, 28, 32);
76
            $signature = \substr($payload, -64);
77
            $payload = \substr($payload, 0, -64);
78
            $sigPubKey = \substr($payload, -32);
79
            $payload = \substr($payload, 0, -32);
80
            $body = \substr($payload, 60, \strlen($payload));
81
82
            $this->keypair = new Keypair(
0 ignored issues
show
Documentation Bug introduced by
It seems like new ncryptf\Keypair($this->secretKey, $publicKey) of type ncryptf\Keypair is incompatible with the declared type string of property $keypair.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
83
                $this->secretKey,
84
                $publicKey
85
            );
86
87
            $decryptedPayload = $this->decrypt($body, $nonce);
88
            if (!$decryptedPayload) {
89
                throw new DecryptionFailedException;
90
            }
91
            if (!$this->isSignatureValid($decryptedPayload, $signature, $sigPubKey)) {
92
                throw new InvalidSignatureException;
93
            }
94
95
            return $decryptedPayload;
96
        }
97
98
        if ($nonce === null) {
99
            throw new InvalidArgumentException('Nonce is required to decrypt v1 requests.');
100
        }
101
102
        return $this->decryptBody($response, $nonce);
103
    }
104
105
    /**
106
     * Decrypts a given response with a nonce
107
     * This will return the decrypted string of decrypt was successful, and false otherwise
108
     *
109
     * @param string $response  The encrypted HTTP response, as a multi-byte string
110
     * @param string $nonce     The 32 byte nonce
111
     * @return string
112
     * 
113
     * @throws InvalidArguementException
114
     */
115
    private function decryptBody(string $response, string $nonce)
116
    {
117
        try {
118
            if ($this->keypair === null) {
119
                throw new InvalidArguementException('Keypair not available');
0 ignored issues
show
Bug introduced by
The type ncryptf\InvalidArguementException 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...
120
            }
121
122
            return \sodium_crypto_box_open(
123
                $response,
124
                $nonce,
125
                $this->keypair->getSodiumKeypair()
126
            );
127
        } catch (SodiumException $e) {
128
            throw new InvalidArgumentException($e->getMessage());
129
        }
130
    }
131
132
    /**
133
     * Returns true if the signature validates the response
134
     *
135
     * @param string $response  The raw http response, after decoding
136
     * @param string $signature The raw multi-byte signature
137
     * @param string $publicKey The signing public key
138
     * @return bool
139
     * 
140
     *  @throws InvalidArguementException
141
     */
142
    public function isSignatureValid(string $response, string $signature, string $publicKey) : bool
143
    {
144
        try {
145
            return \sodium_crypto_sign_verify_detached(
146
                $signature,
147
                $response,
148
                $publicKey
149
            );
150
        } catch (SodiumException $e) {
151
            throw new InvalidArgumentException($e->getMessage());
152
        }
153
    }
154
155
    /**
156
     * Extracts the version from the response
157
     * 
158
     * @param string $response  The encrypted http response
159
     * @return int
160
     */
161
    private function getVersion(string $response) : int
162
    {
163
        $header = \substr($response, 0, 4);
164
        if (\strtoupper(\unpack("h*", $header)[1]) === 'DE259002') {
165
            return 2;
166
        }
167
168
        return 1;
169
    }
170
}
171