1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace yrc\web\ncryptf; |
4
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
6
|
|
|
use ncryptf\Request; |
7
|
|
|
use ncryptf\Response; |
8
|
|
|
use ncryptf\exceptions\DecryptionFailedException; |
9
|
|
|
use yrc\models\redis\EncryptionKey; |
10
|
|
|
use yrc\web\Request as YiiRequest; |
11
|
|
|
use yii\base\Exception; |
12
|
|
|
use yii\base\InvalidParamException; |
13
|
|
|
use yii\helpers\Json; |
14
|
|
|
use yii\web\BadRequestHttpException; |
15
|
|
|
use Yii; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Parses vnd.ncryptf+json |
19
|
|
|
* @class Ncryptf JsonParser |
20
|
|
|
*/ |
21
|
|
|
class JsonParser extends \yii\web\JsonParser |
22
|
|
|
{ |
23
|
|
|
private $decryptedBody; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Returns the decrypted response |
27
|
|
|
* |
28
|
|
|
* @return string |
29
|
|
|
*/ |
30
|
|
|
public function getDecryptedBody() :? string |
31
|
|
|
{ |
32
|
|
|
return $this->decryptedBody; |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Parses vnd.ncryptf+json |
37
|
|
|
* |
38
|
|
|
* @param string $rawBody |
39
|
|
|
* @param string $contentType |
40
|
|
|
* @return mixed |
41
|
|
|
*/ |
42
|
|
|
public function parse($rawBody, $contentType) |
43
|
|
|
{ |
44
|
|
|
if ($contentType === 'application/vnd.25519+json') { |
45
|
|
|
Yii::warning([ |
46
|
|
|
'message' => '`application/vnd.25519+json` content type is deprecated. Migrate to `application/vnd.ncryptf+json' |
47
|
|
|
]); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
if ($rawBody === '') { |
51
|
|
|
$this->decryptedBody = ''; |
52
|
|
|
return []; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
$request = Yii::$app->request; |
|
|
|
|
56
|
|
|
$version = Response::getVersion(\base64_decode($rawBody)); |
57
|
|
|
$key = $this->getEncryptionKey($request, $rawBody, $version); |
|
|
|
|
58
|
|
|
|
59
|
|
|
try { |
60
|
|
|
$this->decryptedBody = $this->decryptRequest($key, $request, $rawBody, $version); |
61
|
|
|
} catch (DecryptionFailedException | InvalidArgumentException | InvalidSignatureException | InvalidChecksumException $e) { |
|
|
|
|
62
|
|
|
throw new BadRequestHttpException(Yii::t('yrc', 'Unable to decrypt response.')); |
63
|
|
|
} catch (\Exception $e) { |
64
|
|
|
Yii::warning([ |
65
|
|
|
'message' => 'An unexpected error occured when decryption the response. See attached exception', |
66
|
|
|
'exception' => $e |
67
|
|
|
]); |
68
|
|
|
|
69
|
|
|
throw new BadRequestHttpException(Yii::t('yrc', 'Unable to decrypt response.')); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
try { |
73
|
|
|
$parameters = Json::decode($this->decryptedBody, $this->asArray); |
74
|
|
|
return $parameters ?? []; |
75
|
|
|
} catch (InvalidParamException $e) { |
76
|
|
|
if ($this->throwException) { |
77
|
|
|
throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage()); |
78
|
|
|
} |
79
|
|
|
return []; |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Decrypts the request using a given encryption key and request parameters |
85
|
|
|
* |
86
|
|
|
* @param EncryptionKey $key |
87
|
|
|
* @param Request $request |
88
|
|
|
* @param string $rawBody |
89
|
|
|
* @param string $version |
90
|
|
|
* @return string |
91
|
|
|
*/ |
92
|
|
|
private function decryptRequest(EncryptionKey $key, \yrc\web\Request $request, string $rawBody, int $version) |
93
|
|
|
{ |
94
|
|
|
static $response = null; |
95
|
|
|
static $nonce = null; |
96
|
|
|
static $publicKey = null; |
97
|
|
|
|
98
|
|
|
$response = new Response( |
99
|
|
|
\base64_decode($key->secret) |
100
|
|
|
); |
101
|
|
|
|
102
|
|
|
if ($version === 1) { |
103
|
|
|
$publicKey = $request->headers->get('x-pubkey', null); |
104
|
|
|
$nonce = $request->headers->get('x-nonce', null); |
105
|
|
|
|
106
|
|
|
if ($publicKey === null || $nonce === null) { |
|
|
|
|
107
|
|
|
throw new Exception(Yii::t('yrc', 'Missing nonce or public key header. Unable to decrypt request.')); |
108
|
|
|
} |
109
|
|
|
$nonce = \base64_decode($nonce); |
|
|
|
|
110
|
|
|
$publicKey = \base64_decode($publicKey); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
$decryptedRequest = $response->decrypt( |
114
|
|
|
\base64_decode($rawBody), |
115
|
|
|
$publicKey, |
116
|
|
|
$nonce |
117
|
|
|
); |
118
|
|
|
|
119
|
|
|
if ($key->is_single_use) { |
120
|
|
|
$key->delete(); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
return $decryptedRequest; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Fetches the local encryption key from the data provided in the request |
128
|
|
|
* |
129
|
|
|
* @param yrc\web\Request $request |
|
|
|
|
130
|
|
|
* @param string $rawBody |
131
|
|
|
* @param integer $version |
132
|
|
|
* @return EncryptionKey |
133
|
|
|
*/ |
134
|
|
|
private function getEncryptionKey(\yrc\web\Request $request, string $rawBody, int $version) : EncryptionKey |
|
|
|
|
135
|
|
|
{ |
136
|
|
|
|
137
|
|
|
$lookup = $request->headers->get('x-hashid', null); |
138
|
|
|
if ($lookup === null) { |
|
|
|
|
139
|
|
|
Yii::warning([ |
140
|
|
|
'message' => 'X-HashId missing on request. Unable to decrypt response.' |
141
|
|
|
]); |
142
|
|
|
throw new Exception(Yii::t('yrc', 'Unable to decrypt response.')); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
$key = EncryptionKey::find()->where([ |
146
|
|
|
'hash' => $lookup |
147
|
|
|
])->one(); |
148
|
|
|
|
149
|
|
|
if ($key === null) { |
150
|
|
|
Yii::warning([ |
151
|
|
|
'message' => Yii::t('yrc', '{property} not found in database', [ |
152
|
|
|
'property' => $property |
|
|
|
|
153
|
|
|
]) |
154
|
|
|
]); |
155
|
|
|
|
156
|
|
|
throw new Exception(Yii::t('yrc', 'Unable to decrypt response.')); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return $key; |
|
|
|
|
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.