Completed
Push — master ( 51beac...801282 )
by Sam
03:19
created

NSEC3::setTypes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Badcow DNS Library.
7
 *
8
 * (c) Samuel Williams <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Badcow\DNS\Rdata;
15
16
use Badcow\DNS\Message;
17
use Badcow\DNS\Parser\Tokens;
18
use Badcow\DNS\Validator;
19
use BadMethodCallException;
20
use Base2n;
21
use DomainException;
22
use InvalidArgumentException;
23
24 1
/**
25
 * {@link https://tools.ietf.org/html/rfc5155}.
26
 */
27
class NSEC3 implements RdataInterface
28
{
29
    use RdataTrait;
30
31
    const TYPE = 'NSEC3';
32
    const TYPE_CODE = 50;
33
34
    /**
35
     * {@link https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml}.
36
     *
37
     * @var int the Hash Algorithm field identifies the cryptographic hash algorithm used to construct the hash-value
38
     */
39
    private $hashAlgorithm = 1;
40
41
    /**
42
     * @var bool
43
     */
44
    private $unsignedDelegationsCovered = false;
45
46
    /**
47
     * @var int
48
     */
49
    private $iterations;
50
51
    /**
52
     * @var string Binary encoded string
53
     */
54
    private $salt;
55
56
    /**
57
     * @var string fully qualified next owner name
58
     */
59
    private $nextOwnerName;
60
61
    /**
62
     * @var string Binary encoded hash
63
     */
64
    private $nextHashedOwnerName;
65
66
    /**
67 4
     * @var array
68
     */
69 4
    private $types = [];
70 1
71
    /**
72
     * @var Base2n
73 4
     */
74
    private static $base32;
75
76
    /**
77
     * Singleton to instantiate and return \Base2n instance for extended hex.
78
     */
79
    private static function base32(): Base2n
80
    {
81
        if (!isset(self::$base32)) {
82
            self::$base32 = new Base2n(5, '0123456789abcdefghijklmnopqrstuv', false, true, true);
83
        }
84 4
85
        return self::$base32;
86 4
    }
87
88
    public function getHashAlgorithm(): int
89 4
    {
90 4
        return $this->hashAlgorithm;
91
    }
92
93
    /**
94
     * @throws InvalidArgumentException
95
     */
96
    public function setHashAlgorithm(int $hashAlgorithm): void
97 4
    {
98
        if (!Validator::isUnsignedInteger($hashAlgorithm, 8)) {
99 4
            throw new InvalidArgumentException('Hash algorithm must be 8-bit integer.');
100 4
        }
101
        $this->hashAlgorithm = $hashAlgorithm;
102
    }
103
104
    public function isUnsignedDelegationsCovered(): bool
105
    {
106
        return $this->unsignedDelegationsCovered;
107
    }
108
109
    public function setUnsignedDelegationsCovered(bool $unsignedDelegationsCovered): void
110 4
    {
111
        $this->unsignedDelegationsCovered = $unsignedDelegationsCovered;
112 4
    }
113
114
    public function getIterations(): int
115 4
    {
116 4
        return $this->iterations;
117
    }
118
119
    /**
120
     * @throws InvalidArgumentException
121 2
     */
122
    public function setIterations(int $iterations): void
123 2
    {
124
        if (!Validator::isUnsignedInteger($iterations, 16)) {
125
            throw new InvalidArgumentException('Hash algorithm must be 16-bit integer.');
126
        }
127
        $this->iterations = $iterations;
128
    }
129 4
130
    /**
131 4
     * @return string Base16 string
132
     */
133
    public function getSalt(): string
134 4
    {
135 4
        return bin2hex($this->salt);
136
    }
137
138
    /**
139
     * @param string $salt Hexadecimal string
140 2
     */
141
    public function setSalt(string $salt): void
142 2
    {
143
        if (false === $bin = @hex2bin($salt)) {
144
            throw new InvalidArgumentException('Salt must be a hexadecimal string.');
145 4
        }
146
        $this->salt = $bin;
147 4
    }
148
149
    public function getNextOwnerName(): string
150
    {
151 4
        return $this->nextOwnerName;
152 4
    }
153
154 4
    /**
155
     * Set the next owner name.
156 4
     *
157 4
     * @param string $nextOwnerName the fully qualified next owner name
158
     *
159
     * @throws InvalidArgumentException
160
     */
161
    public function setNextOwnerName(string $nextOwnerName): void
162
    {
163
        if (!Validator::fullyQualifiedDomainName($nextOwnerName)) {
164
            throw new InvalidArgumentException(sprintf('NSEC3: Next owner "%s" is not a fully qualified domain name.', $nextOwnerName));
165
        }
166
        $this->nextOwnerName = $nextOwnerName;
167
    }
168
169
    public function getNextHashedOwnerName(): string
170
    {
171
        return $this->nextHashedOwnerName;
172
    }
173
174
    public function setNextHashedOwnerName(string $nextHashedOwnerName): void
175 2
    {
176
        $this->nextHashedOwnerName = $nextHashedOwnerName;
177 2
    }
178 2
179 2
    public function addType(string $type): void
180 2
    {
181 2
        $this->types[] = $type;
182 2
    }
183 2
184
    public function setTypes(array $types): void
185
    {
186
        $this->types = $types;
187
    }
188
189
    /**
190
     * Clears the types from the RDATA.
191
     */
192 1
    public function clearTypes(): void
193
    {
194 1
        $this->types = [];
195 1
    }
196 1
197 1
    public function getTypes(): array
198 1
    {
199
        return $this->types;
200 1
    }
201 1
202 1
    /**
203 1
     * {@inheritdoc}
204
     */
205 1
    public function toText(): string
206
    {
207
        return sprintf('%d %d %d %s %s %s',
208
            $this->hashAlgorithm,
209
            (int) $this->unsignedDelegationsCovered,
210
            $this->iterations,
211 1
            empty($this->salt) ? '-' : $this->getSalt(),
212
            self::base32encode($this->getNextHashedOwnerName()),
213 1
            implode(Tokens::SPACE, $this->types)
214 1
        );
215 1
    }
216 1
217 1
    /**
218 1
     * {@inheritdoc}
219 1
     *
220 1
     * @throws UnsupportedTypeException
221
     */
222
    public function toWire(): string
223
    {
224
        $wire = pack('CCnC',
225
            $this->hashAlgorithm,
226
            (int) $this->unsignedDelegationsCovered,
227 1
            $this->iterations,
228
            strlen($this->salt)
229 1
        );
230 1
        $wire .= $this->salt;
231 1
        $wire .= chr(strlen($this->nextHashedOwnerName));
232 1
        $wire .= $this->nextHashedOwnerName;
233 1
        $wire .= NSEC::renderBitmap($this->types);
234
235 1
        return $wire;
236 1
    }
237 1
238 1
    /**
239
     * {@inheritdoc}
240 1
     */
241 1
    public function fromText(string $text): void
242 1
    {
243 1
        $rdata = explode(Tokens::SPACE, $text);
244 1
        $this->setHashAlgorithm((int) array_shift($rdata));
245
        $this->setUnsignedDelegationsCovered((bool) array_shift($rdata));
246 1
        $this->setIterations((int) array_shift($rdata));
247 1
        $salt = (string) array_shift($rdata);
248 1
        if ('-' === $salt) {
249
            $salt = '';
250
        }
251
        $this->setSalt($salt);
252
        $this->setNextHashedOwnerName(self::base32decode(array_shift($rdata) ?? ''));
253
        array_map([$this, 'addType'], $rdata);
254
    }
255 3
256
    /**
257 3
     * {@inheritdoc}
258
     *
259
     * @throws UnsupportedTypeException|DecodeException
260
     */
261
    public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
262
    {
263
        $values = unpack('C<hashAlgo>/C<flags>/n<iterations>/C<saltLen>', $rdata, $offset);
264
        $offset += 5;
265 4
        $this->setHashAlgorithm((int) $values['<hashAlgo>']);
266
        $this->setUnsignedDelegationsCovered((bool) $values['<flags>']);
267 4
        $this->setIterations((int) $values['<iterations>']);
268
269
        $saltLen = (int) $values['<saltLen>'];
270
        $salt = unpack('H*', substr($rdata, $offset, $saltLen))[1];
271
        $this->setSalt($salt);
272
        $offset += $saltLen;
273
274
        $hashLen = ord(substr($rdata, $offset, 1));
275
        ++$offset;
276
        $hash = substr($rdata, $offset, $hashLen);
277
        $offset += $hashLen;
278
        $this->setNextHashedOwnerName($hash);
279
280
        $types = NSEC::parseBitmap($rdata, $offset);
281
        array_map([$this, 'addType'], $types);
282
    }
283
284
    /**
285
     * Encode data as a base32 string.
286
     *
287
     * @return string base32 string
288
     */
289
    public static function base32encode(string $data): string
290
    {
291
        return self::base32()->encode($data);
292
    }
293
294
    /**
295
     * Decode a base32 encoded string.
296
     *
297
     * @param string $data base32 string
298
     */
299
    public static function base32decode(string $data): string
300
    {
301
        return self::base32()->decode($data);
302
    }
303
304
    /**
305
     * Calculate and set NSEC3::nextOwnerHash. Requires NSEC3::salt, NSEC3::nextOwnerName, and NSEC3::iterations to be set.
306
     *
307
     * @throws InvalidArgumentException
308
     */
309
    public function calculateNextOwnerHash(): void
310
    {
311
        if (!isset($this->nextOwnerName) || !isset($this->salt) || !isset($this->iterations)) {
312
            throw new BadMethodCallException('NSEC3::salt, NSEC3::nextOwnerName, and NSEC3::iterations must be set.');
313
        }
314
        $nextOwner = Message::encodeName(strtolower($this->nextOwnerName));
315
        $this->nextHashedOwnerName = self::hash($this->salt, $nextOwner, $this->iterations);
316
    }
317
318
    /**
319
     * @param string $salt the salt
320
     * @param string $x    the value to be hashed
321
     * @param int    $k    the number of recursive iterations of the hash function
322
     *
323
     * @return string the hashed value
324
     *
325
     * @throws DomainException
326
     */
327
    private static function hash(string $salt, string $x, int $k = 0): string
328
    {
329
        if ($k < 0) {
330
            throw new DomainException('Number of iterations, $k, must be a positive integer greater than, or equal to, 0.');
331
        }
332
        $x = sha1($x.$salt, true);
333
        if (0 === $k) {
334
            return $x;
335
        }
336
        --$k;
337
338
        return self::hash($salt, $x, $k);
339
    }
340
}
341