NSEC3   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 89.72%

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 6
dl 0
loc 271
ccs 96
cts 107
cp 0.8972
rs 9.6
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A getHashAlgorithm() 0 4 1
A setHashAlgorithm() 0 7 2
A isUnsignedDelegationsCovered() 0 4 1
A setUnsignedDelegationsCovered() 0 4 1
A getIterations() 0 4 1
A setIterations() 0 7 2
A getSalt() 0 4 1
A setSalt() 0 7 2
A getNextOwnerName() 0 4 1
A setNextOwnerName() 0 7 2
A getNextHashedOwnerName() 0 4 1
A setNextHashedOwnerName() 0 4 1
A addType() 0 4 1
A setTypes() 0 4 1
A clearTypes() 0 4 1
A getTypes() 0 4 1
A toText() 0 11 2
A toWire() 0 15 1
A fromText() 0 14 2
A fromWire() 0 26 3
A calculateNextOwnerHash() 0 8 4
A hash() 0 13 3
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 Base32\Base32Hex;
21
use DomainException;
22
use InvalidArgumentException;
23
24
/**
25
 * {@link https://tools.ietf.org/html/rfc5155}.
26
 */
27
class NSEC3 implements RdataInterface
28
{
29 1
    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
     * @var array
68
     */
69
    private $types = [];
70
71
    public function getHashAlgorithm(): int
72
    {
73
        return $this->hashAlgorithm;
74
    }
75
76
    /**
77
     * @throws InvalidArgumentException
78
     */
79 8
    public function setHashAlgorithm(int $hashAlgorithm): void
80
    {
81 8
        if (!Validator::isUnsignedInteger($hashAlgorithm, 8)) {
82 1
            throw new InvalidArgumentException('Hash algorithm must be 8-bit integer.');
83
        }
84
        $this->hashAlgorithm = $hashAlgorithm;
85 8
    }
86
87
    public function isUnsignedDelegationsCovered(): bool
88
    {
89
        return $this->unsignedDelegationsCovered;
90
    }
91
92
    public function setUnsignedDelegationsCovered(bool $unsignedDelegationsCovered): void
93
    {
94
        $this->unsignedDelegationsCovered = $unsignedDelegationsCovered;
95
    }
96 4
97
    public function getIterations(): int
98 4
    {
99
        return $this->iterations;
100
    }
101 4
102 4
    /**
103
     * @throws InvalidArgumentException
104 2
     */
105
    public function setIterations(int $iterations): void
106 2
    {
107
        if (!Validator::isUnsignedInteger($iterations, 16)) {
108
            throw new InvalidArgumentException('Hash algorithm must be 16-bit integer.');
109 8
        }
110
        $this->iterations = $iterations;
111 8
    }
112 8
113
    /**
114 2
     * @return string Base16 string
115
     */
116 2
    public function getSalt(): string
117
    {
118
        return bin2hex($this->salt);
119
    }
120
121
    /**
122 8
     * @param string $salt Hexadecimal string
123
     */
124 8
    public function setSalt(string $salt): void
125
    {
126
        if (false === $bin = @hex2bin($salt)) {
127 8
            throw new InvalidArgumentException('Salt must be a hexadecimal string.');
128 8
        }
129
        $this->salt = $bin;
130
    }
131
132
    public function getNextOwnerName(): string
133 4
    {
134
        return $this->nextOwnerName;
135 4
    }
136
137
    /**
138
     * Set the next owner name.
139
     *
140
     * @param string $nextOwnerName the fully qualified next owner name
141 8
     *
142
     * @throws InvalidArgumentException
143 8
     */
144
    public function setNextOwnerName(string $nextOwnerName): void
145
    {
146 8
        if (!Validator::fullyQualifiedDomainName($nextOwnerName)) {
147 8
            throw new InvalidArgumentException(sprintf('NSEC3: Next owner "%s" is not a fully qualified domain name.', $nextOwnerName));
148
        }
149
        $this->nextOwnerName = $nextOwnerName;
150
    }
151
152
    public function getNextHashedOwnerName(): string
153
    {
154
        return $this->nextHashedOwnerName;
155
    }
156
157
    public function setNextHashedOwnerName(string $nextHashedOwnerName): void
158
    {
159
        $this->nextHashedOwnerName = $nextHashedOwnerName;
160
    }
161 4
162
    public function addType(string $type): void
163 4
    {
164
        $this->types[] = $type;
165
    }
166 4
167 4
    public function setTypes(array $types): void
168
    {
169 6
        $this->types = $types;
170
    }
171 6
172
    /**
173
     * Clears the types from the RDATA.
174 4
     */
175
    public function clearTypes(): void
176 4
    {
177 4
        $this->types = [];
178
    }
179 4
180
    public function getTypes(): array
181 4
    {
182 4
        return $this->types;
183
    }
184 6
185
    public function toText(): string
186 6
    {
187 6
        return sprintf('%d %d %d %s %s %s',
188
            $this->hashAlgorithm,
189
            (int) $this->unsignedDelegationsCovered,
190
            $this->iterations,
191
            empty($this->salt) ? '-' : $this->getSalt(),
192
            Base32Hex::encode($this->getNextHashedOwnerName()),
193
            implode(Tokens::SPACE, $this->types)
194
        );
195
    }
196
197 2
    /**
198
     * @throws UnsupportedTypeException
199 2
     */
200
    public function toWire(): string
201
    {
202 4
        $wire = pack('CCnC',
203
            $this->hashAlgorithm,
204 4
            (int) $this->unsignedDelegationsCovered,
205 4
            $this->iterations,
206 4
            strlen($this->salt)
207 4
        );
208 4
        $wire .= $this->salt;
209 4
        $wire .= chr(strlen($this->nextHashedOwnerName));
210 4
        $wire .= $this->nextHashedOwnerName;
211
        $wire .= NSEC::renderBitmap($this->types);
212
213
        return $wire;
214
    }
215
216
    public function fromText(string $text): void
217 2
    {
218
        $rdata = explode(Tokens::SPACE, $text);
219 2
        $this->setHashAlgorithm((int) array_shift($rdata));
220 2
        $this->setUnsignedDelegationsCovered((bool) array_shift($rdata));
221 2
        $this->setIterations((int) array_shift($rdata));
222 2
        $salt = (string) array_shift($rdata);
223 2
        if ('-' === $salt) {
224
            $salt = '';
225 2
        }
226 2
        $this->setSalt($salt);
227 2
        $this->setNextHashedOwnerName(Base32Hex::decode(array_shift($rdata) ?? ''));
228 2
        array_map([$this, 'addType'], $rdata);
229
    }
230 2
231
    /**
232
     * @throws UnsupportedTypeException|DecodeException
233 2
     */
234
    public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
235 2
    {
236 2
        if (false === $values = unpack('C<hashAlgo>/C<flags>/n<iterations>/C<saltLen>', $rdata, $offset)) {
237 2
            throw new DecodeException(static::TYPE, $rdata);
238 2
        }
239 2
        $offset += 5;
240 2
        $this->setHashAlgorithm((int) $values['<hashAlgo>']);
241 1
        $this->setUnsignedDelegationsCovered((bool) $values['<flags>']);
242
        $this->setIterations((int) $values['<iterations>']);
243 2
244 2
        $saltLen = (int) $values['<saltLen>'];
245 2
        if (false === $salt = unpack('H*', substr($rdata, $offset, $saltLen))) {
246 2
            throw new DecodeException(static::TYPE, $rdata);
247
        }
248
        $this->setSalt($salt[1]);
249
        $offset += $saltLen;
250
251 2
        $hashLen = ord(substr($rdata, $offset, 1));
252
        ++$offset;
253 2
        $hash = substr($rdata, $offset, $hashLen);
254 2
        $offset += $hashLen;
255 2
        $this->setNextHashedOwnerName($hash);
256 2
257 2
        $types = NSEC::parseBitmap($rdata, $offset);
258
        array_map([$this, 'addType'], $types);
259 2
    }
260 2
261 2
    /**
262 2
     * Calculate and set NSEC3::nextOwnerHash. Requires NSEC3::salt, NSEC3::nextOwnerName, and NSEC3::iterations to be set.
263
     *
264 2
     * @throws InvalidArgumentException
265 2
     */
266 2
    public function calculateNextOwnerHash(): void
267 2
    {
268 2
        if (!isset($this->nextOwnerName) || !isset($this->salt) || !isset($this->iterations)) {
269
            throw new BadMethodCallException('NSEC3::salt, NSEC3::nextOwnerName, and NSEC3::iterations must be set.');
270 2
        }
271 2
        $nextOwner = Message::encodeName(strtolower($this->nextOwnerName));
272 2
        $this->nextHashedOwnerName = self::hash($this->salt, $nextOwner, $this->iterations);
273
    }
274
275
    /**
276
     * @param string $salt the salt
277
     * @param string $x    the value to be hashed
278
     * @param int    $k    the number of recursive iterations of the hash function
279 4
     *
280
     * @return string the hashed value
281 4
     *
282
     * @throws DomainException
283
     */
284
    private static function hash(string $salt, string $x, int $k = 0): string
285
    {
286
        if ($k < 0) {
287
            throw new DomainException('Number of iterations, $k, must be a positive integer greater than, or equal to, 0.');
288
        }
289 4
        $x = sha1($x.$salt, true);
290
        if (0 === $k) {
291 4
            return $x;
292
        }
293
        --$k;
294
295
        return self::hash($salt, $x, $k);
296
    }
297
}
298