Passed
Push — master ( 02bea5...62dfbf )
by Rutger
03:04
created

Oauth2Cryptographer   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 23
eloc 56
c 0
b 0
f 0
dl 0
loc 166
ccs 66
cts 66
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A encryp() 0 23 5
A getDefaultKeyName() 0 9 2
A setKeys() 0 23 6
A hasKey() 0 3 1
A parseData() 0 9 2
A rotateKey() 0 13 3
A setDefaultKeyName() 0 3 1
A decrypt() 0 15 3
1
<?php
2
3
namespace rhertogh\Yii2Oauth2Server\components\encryption;
4
5
use Defuse\Crypto\Crypto;
6
use Defuse\Crypto\Exception\BadFormatException;
7
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
8
use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
9
use Defuse\Crypto\Key;
10
use rhertogh\Yii2Oauth2Server\interfaces\components\encryption\Oauth2CryptographerInterface;
11
use rhertogh\Yii2Oauth2Server\interfaces\components\factories\encryption\Oauth2EncryptionKeyFactoryInterface;
12
use Yii;
13
use yii\base\Component;
14
use yii\base\InvalidArgumentException;
15
use yii\base\InvalidConfigException;
16
use yii\helpers\Json;
17
18
class Oauth2Cryptographer extends Component implements Oauth2CryptographerInterface
19
{
20
    /**
21
     * Separator between different parts in the data. E.g. the keyName and secret.
22
     * @var string
23
     */
24
    public $dataSeparator = '::';
25
26
    /**
27
     * @var Key[]|null
28
     */
29
    protected $_keys = null;
30
31
    /**
32
     * @var string|null
33
     */
34
    protected $_defaultKeyName = null;
35
36
    /**
37
     * @inheritDoc
38
     * @throws InvalidConfigException
39
     */
40 10
    public function setKeys($keys)
41
    {
42 10
        if ($keys && is_string($keys)) {
43 4
            $keys = Json::decode($keys);
44
        }
45
46
        /** @var Oauth2EncryptionKeyFactoryInterface $keyFactory */
47 10
        $keyFactory = Yii::createObject(Oauth2EncryptionKeyFactoryInterface::class);
48 10
        $this->_keys = [];
49 10
        foreach ($keys as $keyName => $key) {
50
            try {
51 10
                $this->_keys[$keyName] = $keyFactory->createFromAsciiSafeString($key);
52 2
            } catch (BadFormatException $e) {
53 1
                throw new InvalidConfigException(
54 1
                    'Encryption key "' . $keyName . '" is malformed: ' . $e->getMessage(),
55 1
                    0,
56 1
                    $e
57 1
                );
58 1
            } catch (EnvironmentIsBrokenException $e) {
59 1
                throw new InvalidConfigException(
60 1
                    'Could not instantiate key "' . $keyName . '": ' . $e->getMessage(),
61 1
                    0,
62 1
                    $e
63 1
                );
64
            }
65
        }
66
    }
67
68
    /**
69
     * @inheritDoc
70
     */
71 6
    public function getDefaultKeyName()
72
    {
73 6
        if (empty($this->_defaultKeyName)) {
74 1
            throw new \BadMethodCallException(
75 1
                'Unable to get the defaultKeyName since it is not set.'
76 1
            );
77
        }
78
79 5
        return $this->_defaultKeyName;
80
    }
81
82
    /**
83
     * @inheritDoc
84
     */
85 9
    public function setDefaultKeyName($name)
86
    {
87 9
        $this->_defaultKeyName = $name;
88
    }
89
90
    /**
91
     * @inheritDoc
92
     */
93 4
    public function hasKey($name)
94
    {
95 4
        return array_key_exists($name, $this->_keys);
96
    }
97
98
99
    /**
100
     * @inheritDoc
101
     * @throws InvalidConfigException
102
     * @throws EnvironmentIsBrokenException
103
     */
104 6
    public function encryp($data, $keyName = null)
105
    {
106 6
        if (empty($keyName)) {
107 5
            $keyName = $this->getDefaultKeyName();
108
        }
109
110 5
        if (empty($this->_keys[$keyName])) {
111 1
            throw new \BadMethodCallException('Unable to encrypt, no key with name "' . $keyName . '" has been set.');
112
        }
113
114 4
        if (empty($this->dataSeparator)) {
115 1
            throw new InvalidConfigException('Unable to encrypt, dataSeparator is empty.');
116
        }
117
118 3
        if (strpos($keyName, $this->dataSeparator) !== false) {
119 1
            throw new \BadMethodCallException(
120 1
                'Unable to encrypt, key name "' . $keyName . '" contains dataSeparator "' . $this->dataSeparator . '".'
121 1
            );
122
        }
123
124 2
        return $keyName
125 2
            . $this->dataSeparator
126 2
            . base64_encode(Crypto::encrypt($data, $this->_keys[$keyName], true));
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132 4
    public function parseData($data)
133
    {
134 4
        $parts = explode($this->dataSeparator, $data);
135 4
        if (count($parts) !== 2) {
136 1
            throw new InvalidArgumentException(
137 1
                'Could not parse encrypted data: invalid number of parts, expected 2 got ' . count($parts)
138 1
            );
139
        }
140 3
        return array_combine(['keyName', 'ciphertext'], $parts);
141
    }
142
143
    /**
144
     * @inheritDoc
145
     * @throws EnvironmentIsBrokenException
146
     * @throws WrongKeyOrModifiedCiphertextException
147
     */
148 4
    public function decrypt($data)
149
    {
150
        try {
151 4
            ['keyName' => $keyName, 'ciphertext' => $ciphertext] = $this->parseData($data);
152 1
        } catch (\Throwable $e) {
153 1
            throw new \InvalidArgumentException(
154 1
                'Unable to decrypt, $data must be in format "keyName' . $this->dataSeparator . 'ciphertext".'
155 1
            );
156
        }
157
158 3
        if (empty($this->_keys[$keyName])) {
159 1
            throw new \BadMethodCallException('Unable to decrypt, no key with name "' . $keyName . '" has been set.');
160
        }
161
162 2
        return Crypto::decrypt(base64_decode($ciphertext), $this->_keys[$keyName], true);
163
    }
164
165
    /**
166
     * @inheritDoc
167
     * @throws InvalidConfigException
168
     * @throws EnvironmentIsBrokenException
169
     * @throws WrongKeyOrModifiedCiphertextException
170
     */
171 1
    public function rotateKey($data, $newKeyName = null)
172
    {
173 1
        if (empty($newKeyName)) {
174 1
            $newKeyName = $this->getDefaultKeyName();
175
        }
176
177 1
        [$keyName] = explode($this->dataSeparator, $data, 2);
178
179 1
        if ($keyName === $newKeyName) {
180 1
            return $data; // Key hasn't changed.
181
        }
182
183 1
        return $this->encryp($this->decrypt($data), $newKeyName);
184
    }
185
}
186