Completed
Push — master ( c06d7d...864e1b )
by Charles
02:18
created

Json25519Parser::getKeyFromHash()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 19
rs 9.2
cc 4
eloc 9
nc 6
nop 1
1
<?php
2
3
namespace yrc\web;
4
5
use yii\helpers\Json;
6
use yii\web\JsonParser;
7
use yrc\api\models\EncryptionKey;
8
9
use yii\web\BadRequestHttpException;
10
use yii\base\InvalidParamException;
11
use Yii;
12
13
/**
14
 * Allows for requests to be encrypted and signed via Curve/Ed 25519 cryptography via libsodium
15
 * @class Json25519 Parser
16
 */
17
class Json25519Parser extends JsonParser
18
{
19
    /**
20
     * @const HASH_HEADER
21
     */
22
    const HASH_HEADER = 'x-hashid';
23
24
    /**
25
     * @const PUBLICKEY_HEADER
26
     */
27
    const PUBLICKEY_HEADER = 'x-pubkey';
28
29
    /**
30
     * @const NONCE_HEADER
31
     */
32
    const NONCE_HEADER = 'x-nonce';
33
34
    /**
35
     * Parses a HTTP request body.
36
     * @param string $rawBody the raw HTTP request body.
37
     * @param string $contentType the content type specified for the request body.
38
     * @return array parameters parsed from the request body
39
     * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`.
40
     */
41
    public function parse($rawBody, $contentType)
42
    {
43
        $key = self::getKeyFromHash(Yii::$app->request->getHeaders()->get(self::HASH_HEADER, null));
44
        $nonce = Yii::$app->request->getHeaders()->get(self::NONCE_HEADER, null);
45
        $public = Yii::$app->request->getHeaders()->get(self::PUBLICKEY_HEADER, null);
46
47
        try {
48
            $rawBody = $this->getRawBodyFromTokenAndNonce($key, $nonce, $public, $rawBody);
49
50
            if ($rawBody === false) {
51
                throw new \Exception;
52
            }
53
54
        } catch (\Exception $e) {
55
            throw new BadRequestHttpException(Yii::t('yrc', 'Invalid security headers.'));
56
        }
57
58
        try {
59
            $parameters = Json::decode($rawBody, $this->asArray);
60
            return $parameters === null ? [] : $parameters;
61
        } catch (InvalidParamException $e) {
62
            if ($this->throwException) {
63
                throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage());
64
            }
65
            return [];
66
        }
67
    }
68
69
    /**
70
     * Decrypts the raw body using EncryptionKey, the client submitted nonce and crypto_box_open
71
     * @param EncryptionKey $key
72
     * @param string $nonce
73
     * @param string $rawBody
74
     * @return string
75
     */
76
    private function getRawBodyFromTokenAndNonce($key, $nonce, $public, $rawBody)
77
    {
78
        if ($key === null || $nonce === null || $public === null) {
79
            throw new HttpException(400, Yii::t('yrc', 'Invalid security headers.'));
80
        }
81
82
        // Construct a keypair from the client_public and the server private key
83
        $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey(
84
            \base64_decode($key->secret),
85
            \base64_decode($public)
86
        );
87
88
        // Decrypt the raw body
89
        $rawBody = \Sodium\crypto_box_open(
90
            \base64_decode($rawBody),
91
            \base64_decode($nonce),
92
            $kp
93
        );
94
        
95
        // The key is deleted upon consumption
96
        $key->delete();
97
98
        return $rawBody;
99
    }
100
101
    /**
102
     * Helper method to retrieve and valdiate the token
103
     * @return Token
104
     */
105
    public static function getKeyFromHash($hash = null)
106
    {
107
        if ($hash === null) {
108
            $hash = Yii::$app->request->getHeaders()->get(self::HASH_HEADER, null);
109
        }
110
111
        // Fetch the hash from the header
112
        if ($hash === null) {
113
            throw new BadRequestHttpException(Yii::t('yrc', 'Missing x-hashid header'));
114
        }
115
116
        $token = EncryptionKey::find()->where(['hash' => $hash])->one();
117
118
        if ($token === null) {
119
            throw new BadRequestHttpException(Yii::t('yrc', 'Invalid x-hashid header.'));
120
        }
121
122
        return $token;
123
    }
124
}