1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace yrc\web; |
4
|
|
|
|
5
|
|
|
use yrc\web\JsonResponseFormatter; |
6
|
|
|
use yii\web\NotAcceptableHttpException; |
7
|
|
|
use yrc\web\Json25519Parser; |
8
|
|
|
use yrc\models\redis\EncryptionKey; |
9
|
|
|
use yii\web\HttpException; |
10
|
|
|
|
11
|
|
|
use Yii; |
12
|
|
|
|
13
|
|
|
class Json25519ResponseFormatter extends JsonResponseFormatter |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* Take the response generated by JsonResponseFormatter and anonymously encrypt it |
17
|
|
|
* @param array $response |
18
|
|
|
*/ |
19
|
|
|
protected function formatJson($response) |
20
|
|
|
{ |
21
|
|
|
// Generate a new encryption key |
22
|
|
|
$key = EncryptionKey::generate(); |
23
|
|
|
$public = Yii::$app->request->getHeaders()->get(Json25519Parser::PUBLICKEY_HEADER, null); |
24
|
|
|
|
25
|
|
|
if ($public === null) { |
26
|
|
|
$response->statusCode = 400; |
27
|
|
|
$response->content = ''; |
28
|
|
|
$response->getHeaders('x-reason', Yii::t('yrc', 'Accept: application/vnd.25519+json requires x-pubkey header to be set.')); |
|
|
|
|
29
|
|
|
return; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
$rawPublic = \base64_decode($public); |
33
|
|
|
if (strlen($rawPublic) !== 32) { |
34
|
|
|
$response->statusCode = 400; |
35
|
|
|
$response->getHeaders()->set('x-reason', Yii::t('yrc', 'Public key is not 32 bytes in length.')); |
|
|
|
|
36
|
|
|
return; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
parent::formatJson($response); |
|
|
|
|
40
|
|
|
$response->getHeaders()->set('Content-Type', 'application/vnd.25519+json; charset=UTF-8'); |
|
|
|
|
41
|
|
|
|
42
|
|
|
// Calculate the keypair |
43
|
|
|
$keyPair = \sodium_crypto_box_keypair_from_secretkey_and_publickey( |
44
|
|
|
\base64_decode($key->secret), |
|
|
|
|
45
|
|
|
$rawPublic |
46
|
|
|
); |
47
|
|
|
|
48
|
|
|
// Encrypt the content |
49
|
|
|
$nonce = \random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES); |
50
|
|
|
$content = \sodium_crypto_box( |
51
|
|
|
$response->content, |
52
|
|
|
$nonce, |
53
|
|
|
$keyPair |
54
|
|
|
); |
55
|
|
|
|
56
|
|
|
// If the user is authenticated, we can sign the request using the signature associated to the session. |
57
|
|
|
// If they are not authenticated, we can skip the signature generation |
58
|
|
|
if (!Yii::$app->user->isGuest) { |
59
|
|
|
$token = Yii::$app->user->getIdentity()->getToken(); |
60
|
|
|
if ($token === null) { |
61
|
|
|
$response->statusCode = 406; |
62
|
|
|
$response->content = ''; |
63
|
|
|
Yii::warning([ |
64
|
|
|
'message' => 'Could not fetch token keypair. Unable to generate encrypted response.' |
65
|
|
|
]); |
66
|
|
|
return; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
// Sign the request using the new authentication key |
70
|
|
|
$signature = \sodium_crypto_sign_detached( |
71
|
|
|
$content, |
72
|
|
|
\base64_decode($token->secret_sign_kp) |
73
|
|
|
); |
74
|
|
|
|
75
|
|
|
// Sign the raw response and send the signature alongside the header |
76
|
|
|
$response->getHeaders()->set('x-sigpubkey', \base64_encode($token->getSignPublicKey())); |
|
|
|
|
77
|
|
|
$response->getHeaders()->set('x-signature', \base64_encode($signature)); |
|
|
|
|
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
$response->getHeaders()->set('x-nonce', \base64_encode($nonce)); |
|
|
|
|
81
|
|
|
$response->getHeaders()->set('x-pubkey', \base64_encode($key->getBoxPublicKey())); |
|
|
|
|
82
|
|
|
$response->content = \base64_encode($content); |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
|
Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.