]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/validate.cc
rec: CVE-2023-50387 and CVE-2023-50868
[thirdparty/pdns.git] / pdns / validate.cc
CommitLineData
7ada1188
OM
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
243f4780 23#include "validate.hh"
24#include "misc.hh"
25#include "dnssecinfra.hh"
3d5ebf10 26#include "dnsseckeeper.hh"
2ac8ae89 27#include "rec-lua-conf.hh"
243f4780 28#include "base32.hh"
566ab902 29#include "logger.hh"
f019f773 30
9a3ab3e4 31time_t g_signatureInceptionSkew{0};
d377bb54 32uint16_t g_maxNSEC3Iterations{0};
15e973d6
OM
33uint16_t g_maxRRSIGsPerRecordToConsider{0};
34uint16_t g_maxNSEC3sPerRecordToConsider{0};
35uint16_t g_maxDNSKEYsToConsider{0};
36uint16_t g_maxDSsToConsider{0};
243f4780 37
6f2088b4
RG
38static bool isAZoneKey(const DNSKEYRecordContent& key)
39{
40 /* rfc4034 Section 2.1.1:
41 "Bit 7 of the Flags field is the Zone Key flag. If bit 7 has value 1,
42 then the DNSKEY record holds a DNS zone key, and the DNSKEY RR's
43 owner name MUST be the name of a zone. If bit 7 has value 0, then
44 the DNSKEY record holds some other type of DNS public key and MUST
45 NOT be used to verify RRSIGs that cover RRsets."
46
47 Let's check that this is a ZONE key, even though there is no other
48 types of DNSKEYs at the moment.
49 */
50 return (key.d_flags & 256) != 0;
51}
52
53static bool isRevokedKey(const DNSKEYRecordContent& key)
54{
55 /* rfc5011 Section 3 */
56 return (key.d_flags & 128) != 0;
57}
58
d06dcda4 59static vector<shared_ptr<const DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm, const OptLog& log)
243f4780 60{
d06dcda4 61 vector<shared_ptr<const DNSKEYRecordContent>> ret;
6f2088b4
RG
62
63 for (const auto& key : keys) {
64 if (!isAZoneKey(*key)) {
a8cd5db5 65 VLOG(log, "Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" is not a zone key, skipping"<<endl;);
6f2088b4
RG
66 continue;
67 }
68
69 if (isRevokedKey(*key)) {
a8cd5db5 70 VLOG(log, "Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" has been revoked, skipping"<<endl;);
6f2088b4
RG
71 continue;
72 }
73
74 if (key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm) {
243f4780 75 ret.push_back(key);
6f2088b4
RG
76 }
77 }
78
243f4780 79 return ret;
80}
81
7ada1188 82bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash)
812c3a69 83{
7ada1188
OM
84 return ((beginHash < hash && hash < nextHash) || // no wrap BEGINNING --- HASH -- END
85 (nextHash > hash && beginHash > nextHash) || // wrap HASH --- END --- BEGINNING
86 (nextHash < beginHash && beginHash < hash) || // wrap other case END --- BEGINNING --- HASH
87 (beginHash == nextHash && hash != beginHash)); // "we have only 1 NSEC3 record, LOL!"
812c3a69
RG
88}
89
7ada1188 90bool isCoveredByNSEC3Hash(const DNSName& name, const DNSName& beginHash, const DNSName& nextHash)
bf720981 91{
7ada1188
OM
92 return ((beginHash.canonCompare(name) && name.canonCompare(nextHash)) || // no wrap BEGINNING --- HASH -- END
93 (name.canonCompare(nextHash) && nextHash.canonCompare(beginHash)) || // wrap HASH --- END --- BEGINNING
94 (nextHash.canonCompare(beginHash) && beginHash.canonCompare(name)) || // wrap other case END --- BEGINNING --- HASH
95 (beginHash == nextHash && name != beginHash)); // "we have only 1 NSEC3 record, LOL!"
bf720981
RG
96}
97
25f5783a 98bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next)
8119e5cb
RG
99{
100 return ((begin.canonCompare(name) && name.canonCompare(next)) || // no wrap BEGINNING --- NAME --- NEXT
101 (name.canonCompare(next) && next.canonCompare(begin)) || // wrap NAME --- NEXT --- BEGINNING
102 (next.canonCompare(begin) && begin.canonCompare(name)) || // wrap other case NEXT --- BEGINNING --- NAME
eb3e3d99 103 (begin == next && name != begin)); // "we have only 1 NSEC record, LOL!"
8119e5cb
RG
104}
105
00e3fef4
RG
106static bool nsecProvesENT(const DNSName& name, const DNSName& begin, const DNSName& next)
107{
108 /* if name is an ENT:
109 - begin < name
110 - next is a child of name
111 */
00be1ff6 112 return begin.canonCompare(name) && next != name && next.isPartOf(name);
00e3fef4
RG
113}
114
15e973d6 115[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context)
5374b03b
RG
116{
117 std::string result;
118
15e973d6 119 if (g_maxNSEC3Iterations != 0 && iterations > g_maxNSEC3Iterations) {
5374b03b
RG
120 return result;
121 }
122
15e973d6
OM
123 auto key = std::tuple(qname, salt, iterations);
124 auto iter = context.d_nsec3Cache.find(key);
125 if (iter != context.d_nsec3Cache.end()) {
7ada1188 126 return iter->second;
dabcae2a
RG
127 }
128
15e973d6
OM
129 if (context.d_nsec3IterationsRemainingQuota < iterations) {
130 // we throw here because we cannot take the risk that the result
131 // be cached, since a different query can try to validate the
132 // same result with a bigger NSEC3 iterations quota
133 throw pdns::validation::TooManySEC3IterationsException();
134 }
135
136 result = hashQNameWithSalt(salt, iterations, qname);
137 context.d_nsec3IterationsRemainingQuota -= iterations;
138 context.d_nsec3Cache[key] = result;
dabcae2a 139 return result;
5374b03b
RG
140}
141
15e973d6
OM
142[[nodiscard]] static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, pdns::validation::ValidationContext& context)
143{
144 return getHashFromNSEC3(qname, nsec3.d_iterations, nsec3.d_salt, context);
145}
146
9b061cf5
RG
147/* There is no delegation at this exact point if:
148 - the name exists but the NS type is not set
149 - the name does not exist
150 One exception, if the name is covered by an opt-out NSEC3
151 it doesn't prove that an insecure delegation doesn't exist.
152*/
15e973d6 153bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords, pdns::validation::ValidationContext& context)
5374b03b 154{
15e973d6 155 uint16_t nsec3sConsidered = 0;
dabcae2a 156
5374b03b
RG
157 for (const auto& record : dsrecords) {
158 if (record.d_type == QType::NSEC) {
159 const auto nsec = getRR<NSECRecordContent>(record);
160 if (!nsec) {
161 continue;
162 }
163
164 if (record.d_name == zone) {
27d4a65b 165 return !nsec->isSet(QType::NS);
5374b03b
RG
166 }
167
168 if (isCoveredByNSEC(zone, record.d_name, nsec->d_next)) {
169 return true;
170 }
171 }
172 else if (record.d_type == QType::NSEC3) {
173 const auto nsec3 = getRR<NSEC3RecordContent>(record);
174 if (!nsec3) {
175 continue;
176 }
177
15e973d6
OM
178 if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
179 return false;
180 }
181 nsec3sConsidered++;
182
183 const string hash = getHashFromNSEC3(zone, *nsec3, context);
7ada1188 184 if (hash.empty()) {
5374b03b
RG
185 return false;
186 }
187
188 const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]);
7ada1188 189 if (beginHash == hash) {
27d4a65b 190 return !nsec3->isSet(QType::NS);
5374b03b
RG
191 }
192
7ada1188 193 if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
0cbcfeda 194 return !(nsec3->isOptOut());
5374b03b
RG
195 }
196 }
197 }
198
199 return false;
200}
201
9b061cf5
RG
202/* RFC 4035 section-5.3.4:
203 "If the number of labels in an RRset's owner name is greater than the
204 Labels field of the covering RRSIG RR, then the RRset and its
205 covering RRSIG RR were created as a result of wildcard expansion."
206*/
03e5e4cb 207bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign)
78cdf520 208{
7ada1188 209 return sign.d_labels < labelCount;
78cdf520
RG
210}
211
d06dcda4 212static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
243f4780 213{
9b061cf5
RG
214 if (signatures.empty()) {
215 return false;
216 }
217
218 const auto& sign = signatures.at(0);
219 unsigned int labelsCount = owner.countLabels();
03e5e4cb 220 return isWildcardExpanded(labelsCount, *sign);
78cdf520
RG
221}
222
03e5e4cb 223bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const RRSIGRecordContent& sign)
78cdf520 224{
7ada1188
OM
225 /* this is a wildcard alright, but it has not been expanded */
226 return owner.isWildcard() && (labelCount - 1) == sign.d_labels;
9b061cf5
RG
227}
228
d06dcda4 229static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
78cdf520
RG
230{
231 if (signatures.empty()) {
232 return false;
233 }
234
235 const auto& sign = signatures.at(0);
236 unsigned int labelsCount = owner.countLabels();
03e5e4cb 237 return isWildcardExpandedOntoItself(owner, labelsCount, *sign);
78cdf520
RG
238}
239
9b061cf5
RG
240/* if this is a wildcard NSEC, the owner name has been modified
241 to match the name. Make sure we use the original '*' form. */
d06dcda4 242DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
9b061cf5
RG
243{
244 DNSName result = initialOwner;
245
246 if (signatures.empty()) {
247 return result;
248 }
249
250 const auto& sign = signatures.at(0);
251 unsigned int labelsCount = initialOwner.countLabels();
252 if (sign && sign->d_labels < labelsCount) {
253 do {
254 result.chopOff();
255 labelsCount--;
256 }
257 while (sign->d_labels < labelsCount);
258
259 result = g_wildcarddnsname + result;
260 }
261
262 return result;
263}
264
03e5e4cb 265static bool isNSECAncestorDelegation(const DNSName& signer, const DNSName& owner, const NSECRecordContent& nsec)
b580fad2 266{
03e5e4cb
RG
267 return nsec.isSet(QType::NS) &&
268 !nsec.isSet(QType::SOA) &&
b580fad2
RG
269 signer.countLabels() < owner.countLabels();
270}
271
03e5e4cb 272bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const NSEC3RecordContent& nsec3)
b580fad2 273{
03e5e4cb
RG
274 return nsec3.isSet(QType::NS) &&
275 !nsec3.isSet(QType::SOA) &&
b580fad2
RG
276 signer.countLabels() < owner.countLabels();
277}
278
a8cd5db5 279static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t& validrrsets, const OptLog& log)
82566a96 280{
5de1b16e 281 const DNSName wildcard = g_wildcarddnsname + closestEncloser;
baaa61a1 282 VLOG(log, qname << ": Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
7ada1188
OM
283 for (const auto& validset : validrrsets) {
284 VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
285 if (validset.first.second == QType::NSEC) {
286 for (const auto& record : validset.second.records) {
287 VLOG(log, ":\t"<<record->getZoneRepresentation()<<endl);
288 auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
82566a96
RG
289 if (!nsec) {
290 continue;
291 }
292
7ada1188 293 DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
5de1b16e 294 if (owner != wildcard) {
82566a96
RG
295 continue;
296 }
297
baaa61a1 298 VLOG(log, qname << ":\tWildcard matches");
03e5e4cb 299 if (qtype == 0 || isTypeDenied(*nsec, QType(qtype))) {
baaa61a1 300 VLOG_NO_PREFIX(log, " and proves that the type did not exist"<<endl);
5de1b16e 301 return true;
82566a96 302 }
baaa61a1 303 VLOG_NO_PREFIX(log, " BUT the type did exist!"<<endl);
5de1b16e 304 return false;
82566a96
RG
305 }
306 }
307 }
308
309 return false;
310}
311
57fe2038
RG
312DNSName getClosestEncloserFromNSEC(const DNSName& name, const DNSName& owner, const DNSName& next)
313{
314 DNSName commonWithOwner(name.getCommonLabels(owner));
315 DNSName commonWithNext(name.getCommonLabels(next));
316 if (commonWithOwner.countLabels() >= commonWithNext.countLabels()) {
317 return commonWithOwner;
318 }
319 return commonWithNext;
320}
321
9b061cf5 322/*
82566a96 323 This function checks whether the non-existence of a wildcard covering qname|qtype
9b061cf5 324 is proven by the NSEC records in validrrsets.
9b061cf5 325*/
a8cd5db5 326static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t & validrrsets, const OptLog& log)
9b061cf5 327{
baaa61a1 328 VLOG(log, qname << ": Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
5de1b16e 329 const DNSName wildcard = g_wildcarddnsname + closestEncloser;
7ada1188
OM
330 for (const auto& validset : validrrsets) {
331 VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
332 if (validset.first.second == QType::NSEC) {
333 for (const auto& records : validset.second.records) {
334 VLOG(log, qname << ":\t"<<records->getZoneRepresentation()<<endl);
335 auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(records);
9b061cf5
RG
336 if (!nsec) {
337 continue;
338 }
339
7ada1188 340 const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
baaa61a1 341 VLOG(log, qname << ": Comparing owner: "<<owner<<" with target: "<<wildcard<<endl);
9b061cf5 342
d7eb8c9a 343 if (qname != owner && qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
c94c8dfe
RG
344 /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
345
346 In any negative response, the NSEC or NSEC3 [RFC5155] record type
347 bitmap SHOULD be checked to see that there was no DNAME that could
348 have been applied. If the DNAME bit in the type bitmap is set and
349 the query name is a subdomain of the closest encloser that is
350 asserted, then DNAME substitution should have been done, but the
351 substitution has not been done as specified.
352 */
baaa61a1 353 VLOG(log, qname << ":\tThe qname is a subdomain of the NSEC and the DNAME bit is set"<<endl);
c94c8dfe
RG
354 return false;
355 }
356
57fe2038 357 if (wildcard != owner && isCoveredByNSEC(wildcard, owner, nsec->d_next)) {
baaa61a1 358 VLOG(log, qname << ":\tWildcard is covered"<<endl);
57fe2038 359 return true;
9b061cf5
RG
360 }
361 }
362 }
363 }
364
365 return false;
366}
367
368/*
82566a96 369 This function checks whether the non-existence of a wildcard covering qname|qtype
9b061cf5
RG
370 is proven by the NSEC3 records in validrrsets.
371 If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
372 for this qname but doesn't have this qtype.
373*/
15e973d6 374static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const qtype, const cspmap_t& validrrsets, bool* wildcardExists, const OptLog& log, pdns::validation::ValidationContext& context)
9b061cf5 375{
c94c8dfe 376 auto wildcard = g_wildcarddnsname + closestEncloser;
baaa61a1 377 VLOG(log, closestEncloser << ": Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype)<<endl);
9b061cf5 378
7ada1188
OM
379 for (const auto& validset : validrrsets) {
380 VLOG(log, closestEncloser << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
381 if (validset.first.second == QType::NSEC3) {
382 for (const auto& records : validset.second.records) {
383 VLOG(log, closestEncloser << ":\t"<<records->getZoneRepresentation()<<endl);
384 auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(records);
9b061cf5
RG
385 if (!nsec3) {
386 continue;
387 }
388
7ada1188
OM
389 const DNSName signer = getSigner(validset.second.signatures);
390 if (!validset.first.first.isPartOf(signer)) {
9b061cf5 391 continue;
c94c8dfe 392 }
9b061cf5 393
15e973d6 394 string hash = getHashFromNSEC3(wildcard, *nsec3, context);
7ada1188 395 if (hash.empty()) {
15e973d6 396 VLOG(log, closestEncloser << ": Unsupported hash, ignoring"<<endl);
9b061cf5
RG
397 return false;
398 }
7ada1188
OM
399 VLOG(log, closestEncloser << ":\tWildcard hash: "<<toBase32Hex(hash)<<endl);
400 string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
baaa61a1 401 VLOG(log, closestEncloser << ":\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
9b061cf5 402
7ada1188 403 if (beginHash == hash) {
baaa61a1 404 VLOG(log, closestEncloser << ":\tWildcard hash matches");
7ada1188 405 if (wildcardExists != nullptr) {
9b061cf5
RG
406 *wildcardExists = true;
407 }
cd4beb37
RG
408
409 /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
410 Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
411 nonexistence of any RRs below that zone cut, which include all RRs at
412 that (original) owner name other than DS RRs, and all RRs below that
413 owner name regardless of type.
414 */
7ada1188 415 if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
cd4beb37 416 /* this is an "ancestor delegation" NSEC3 RR */
baaa61a1 417 VLOG_NO_PREFIX(log, " BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
cd4beb37
RG
418 return false;
419 }
420
03e5e4cb 421 if (qtype == 0 || isTypeDenied(*nsec3, QType(qtype))) {
baaa61a1 422 VLOG_NO_PREFIX(log, " and proves that the type did not exist"<<endl);
9b061cf5
RG
423 return true;
424 }
baaa61a1 425 VLOG_NO_PREFIX(log, " BUT the type did exist!"<<endl);
9b061cf5
RG
426 return false;
427 }
428
7ada1188 429 if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
baaa61a1 430 VLOG(log, closestEncloser << ":\tWildcard hash is covered"<<endl);
9b061cf5
RG
431 return true;
432 }
433 }
434 }
435 }
436
437 return false;
438}
439
03e5e4cb 440dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner, const NSECRecordContent& nsec, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, const OptLog& log)
25f5783a
RG
441{
442 const DNSName signer = getSigner(signatures);
443 if (!name.isPartOf(signer) || !nsecOwner.isPartOf(signer)) {
cd4beb37 444 return dState::INCONCLUSIVE;
25f5783a
RG
445 }
446
447 const DNSName owner = getNSECOwnerName(nsecOwner, signatures);
448 /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
449 Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
450 nonexistence of any RRs below that zone cut, which include all RRs at
451 that (original) owner name other than DS RRs, and all RRs below that
452 owner name regardless of type.
453 */
56011cc3 454 if (name.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, nsec)) {
25f5783a 455 /* this is an "ancestor delegation" NSEC RR */
7ada1188 456 if (qtype != QType::DS || name != owner) {
baaa61a1 457 VLOG_NO_PREFIX(log, "An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
56011cc3
RG
458 return dState::NODENIAL;
459 }
25f5783a
RG
460 }
461
462 /* check if the type is denied */
463 if (name == owner) {
cd4beb37 464 if (!isTypeDenied(nsec, QType(qtype))) {
65a177b1 465 VLOG_NO_PREFIX(log, "does _not_ deny existence of type "<<QType(qtype)<<endl);
399f391d
RG
466 return dState::NODENIAL;
467 }
468
48c54748 469 if (qtype == QType::DS && signer == name) {
65a177b1 470 VLOG_NO_PREFIX(log, "the NSEC comes from the child zone and cannot be used to deny a DS"<<endl);
48c54748
RG
471 return dState::NODENIAL;
472 }
473
baaa61a1 474 VLOG_NO_PREFIX(log, "Denies existence of type "<<QType(qtype)<<endl);
25f5783a
RG
475 return dState::NXQTYPE;
476 }
477
03e5e4cb 478 if (name.isPartOf(owner) && nsec.isSet(QType::DNAME)) {
c94c8dfe
RG
479 /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
480
481 In any negative response, the NSEC or NSEC3 [RFC5155] record type
482 bitmap SHOULD be checked to see that there was no DNAME that could
483 have been applied. If the DNAME bit in the type bitmap is set and
484 the query name is a subdomain of the closest encloser that is
485 asserted, then DNAME substitution should have been done, but the
486 substitution has not been done as specified.
487 */
65a177b1 488 VLOG(log, "the DNAME bit is set and the query name is a subdomain of that NSEC");
c94c8dfe
RG
489 return dState::NODENIAL;
490 }
491
03e5e4cb
RG
492 if (isCoveredByNSEC(name, owner, nsec.d_next)) {
493 VLOG_NO_PREFIX(log, name << ": is covered by ("<<owner<<" to "<<nsec.d_next<<")");
25f5783a 494
03e5e4cb 495 if (nsecProvesENT(name, owner, nsec.d_next)) {
65a177b1 496 VLOG_NO_PREFIX(log, " denies existence of type "<<name<<"/"<<QType(qtype)<<" by proving that "<<name<<" is an ENT"<<endl);
25f5783a
RG
497 return dState::NXQTYPE;
498 }
499
500 return dState::NXDOMAIN;
501 }
502
cd4beb37 503 return dState::INCONCLUSIVE;
25f5783a
RG
504}
505
15e973d6
OM
506[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength)
507{
508 return (iterations + 1 + (saltLength > 0 ? 1 : 0)) * maxLabels;
509}
510
9b061cf5
RG
511/*
512 This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
513 in validrrsets.
98307d0f 514 - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODENIAL
9b061cf5
RG
515 if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9.
516 - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return
95b66e0d 517 NXQTYPE if the name is proven to be ENT and NXDOMAIN otherwise.
9b061cf5 518 - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is
ef2ea4bf 519 useful when we have a positive answer synthesized from a wildcard and we only need to prove that the exact
9b061cf5
RG
520 name does not exist.
521*/
15e973d6 522dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, pdns::validation::ValidationContext& context, const OptLog& log, bool needWildcardProof, unsigned int wildcardLabelsCount) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
9b061cf5
RG
523{
524 bool nsec3Seen = false;
e4894ce0 525 if (!needWildcardProof && wildcardLabelsCount == 0) {
ef2ea4bf 526 throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
e4894ce0 527 }
9b061cf5 528
15e973d6
OM
529 uint8_t numberOfLabelsOfParentZone{std::numeric_limits<uint8_t>::max()};
530 uint16_t nsec3sConsidered = 0;
7ada1188
OM
531 for (const auto& validset : validrrsets) {
532 VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
da204325 533
7ada1188
OM
534 if (validset.first.second==QType::NSEC) {
535 for (const auto& record : validset.second.records) {
536 VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
25f5783a 537
7ada1188 538 if (validset.second.signatures.empty()) {
ce454638
RG
539 continue;
540 }
541
7ada1188 542 auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
cd4beb37 543 if (!nsec) {
da204325 544 continue;
cd4beb37 545 }
da204325 546
7ada1188
OM
547 const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
548 const DNSName signer = getSigner(validset.second.signatures);
549 if (!validset.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
cd4beb37 550 continue;
ce454638 551 }
3143417d 552
be5d851d
RG
553 /* The NSEC is either a delegation one, from the parent zone, and
554 * must have the NS bit set but not the SOA one, or a regular NSEC
555 * either at apex (signer == owner) or with the SOA or NS bits clear.
556 */
557 const bool notApex = signer.countLabels() < owner.countLabels();
558 if (notApex && nsec->isSet(QType::NS) && nsec->isSet(QType::SOA)) {
baaa61a1 559 VLOG(log, qname << ": However, that NSEC is not at the apex and has both the NS and the SOA bits set!"<<endl);
be5d851d
RG
560 continue;
561 }
562
95208ae3
RG
563 /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
564 Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
565 nonexistence of any RRs below that zone cut, which include all RRs at
566 that (original) owner name other than DS RRs, and all RRs below that
567 owner name regardless of type.
568 */
03e5e4cb 569 if (qname.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, *nsec)) {
95208ae3 570 /* this is an "ancestor delegation" NSEC RR */
7ada1188 571 if (qtype != QType::DS || qname != owner) {
baaa61a1 572 VLOG(log, qname << ": An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
56011cc3
RG
573 return dState::NODENIAL;
574 }
95208ae3
RG
575 }
576
0a9dcd16 577 if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
baaa61a1 578 VLOG(log, qname << ": A NSEC RR from the child zone cannot deny the existence of a DS"<<endl);
0a9dcd16
RG
579 continue;
580 }
581
da204325 582 /* check if the type is denied */
25f5783a 583 if (qname == owner) {
03e5e4cb 584 if (!isTypeDenied(*nsec, QType(qtype))) {
baaa61a1 585 VLOG(log, qname << ": Does _not_ deny existence of type "<<QType(qtype)<<endl);
98307d0f 586 return dState::NODENIAL;
eb3e3d99
RG
587 }
588
baaa61a1 589 VLOG(log, qname << ": Denies existence of type "<<QType(qtype)<<endl);
95823c07
RG
590
591 /*
592 * RFC 4035 Section 2.3:
593 * The bitmap for the NSEC RR at a delegation point requires special
594 * attention. Bits corresponding to the delegation NS RRset and any
595 * RRsets for which the parent zone has authoritative data MUST be set
596 */
be5d851d
RG
597 if (referralToUnsigned && qtype == QType::DS) {
598 if (!nsec->isSet(QType::NS)) {
baaa61a1 599 VLOG(log, qname << ": However, no NS record exists at this level!"<<endl);
be5d851d
RG
600 return dState::NODENIAL;
601 }
95823c07
RG
602 }
603
9b061cf5
RG
604 /* we know that the name exists (but this qtype doesn't) so except
605 if the answer was generated by a wildcard expansion, no wildcard
606 could have matched (rfc4035 section 5.4 bullet 1) */
7ada1188 607 if (needWildcardProof && (!isWildcardExpanded(owner, validset.second.signatures) || isWildcardExpandedOntoItself(owner, validset.second.signatures))) {
9b061cf5
RG
608 needWildcardProof = false;
609 }
610
5de1b16e
RG
611 if (!needWildcardProof) {
612 return dState::NXQTYPE;
613 }
614
615 DNSName closestEncloser = getClosestEncloserFromNSEC(qname, owner, nsec->d_next);
10971f78 616 if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
98307d0f 617 return dState::NXQTYPE;
9b061cf5
RG
618 }
619
baaa61a1 620 VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
98307d0f 621 return dState::NODENIAL;
da204325 622 }
243f4780 623
c94c8dfe
RG
624 if (qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
625 /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
626
627 In any negative response, the NSEC or NSEC3 [RFC5155] record type
628 bitmap SHOULD be checked to see that there was no DNAME that could
629 have been applied. If the DNAME bit in the type bitmap is set and
630 the query name is a subdomain of the closest encloser that is
631 asserted, then DNAME substitution should have been done, but the
632 substitution has not been done as specified.
633 */
baaa61a1 634 VLOG(log, qname << ": The DNAME bit is set and the query name is a subdomain of that NSEC"<< endl);
c94c8dfe
RG
635 return dState::NODENIAL;
636 }
637
da204325 638 /* check if the whole NAME is denied existing */
95256c05 639 if (isCoveredByNSEC(qname, owner, nsec->d_next)) {
baaa61a1 640 VLOG(log, qname<< ": Is covered by ("<<owner<<" to "<<nsec->d_next<<") ");
95256c05
RG
641
642 if (nsecProvesENT(qname, owner, nsec->d_next)) {
643 if (wantsNoDataProof) {
644 /* if the name is an ENT and we received a NODATA answer,
645 we are fine with a NSEC proving that the name does not exist. */
baaa61a1 646 VLOG_NO_PREFIX(log, "Denies existence of type "<<qname<<"/"<<QType(qtype)<<" by proving that "<<qname<<" is an ENT"<<endl);
98307d0f 647 return dState::NXQTYPE;
95256c05 648 }
7ada1188
OM
649 /* but for a NXDOMAIN proof, this doesn't make sense! */
650 VLOG_NO_PREFIX(log, "but it tries to deny the existence of "<<qname<<" by proving that "<<qname<<" is an ENT, this does not make sense!"<<endl);
651 return dState::NODENIAL;
00e3fef4
RG
652 }
653
82566a96 654 if (!needWildcardProof) {
baaa61a1 655 VLOG_NO_PREFIX(log, "and we did not need a wildcard proof"<<endl);
98307d0f 656 return dState::NXDOMAIN;
82566a96
RG
657 }
658
baaa61a1 659 VLOG_NO_PREFIX(log, "but we do need a wildcard proof so ");
5de1b16e 660 DNSName closestEncloser = getClosestEncloserFromNSEC(qname, owner, nsec->d_next);
82566a96 661 if (wantsNoDataProof) {
baaa61a1 662 VLOG_NO_PREFIX(log, "looking for NODATA proof"<<endl);
10971f78 663 if (provesNoDataWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
98307d0f 664 return dState::NXQTYPE;
9b061cf5 665 }
9b061cf5 666 }
82566a96 667 else {
baaa61a1 668 VLOG_NO_PREFIX(log, "looking for NO wildcard proof"<<endl);
10971f78 669 if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
98307d0f 670 return dState::NXDOMAIN;
82566a96
RG
671 }
672 }
673
baaa61a1 674 VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
98307d0f 675 return dState::NODENIAL;
da204325 676 }
243f4780 677
7ada1188 678 VLOG(log, qname << ": Did not deny existence of "<<QType(qtype)<<", "<<validset.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
da204325 679 }
7ada1188
OM
680 } else if(validset.first.second==QType::NSEC3) {
681 for (const auto& record : validset.second.records) {
682 VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
683 auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
ce454638 684 if (!nsec3) {
da204325 685 continue;
ce454638
RG
686 }
687
7ada1188 688 if (validset.second.signatures.empty()) {
da204325 689 continue;
ce454638 690 }
da204325 691
7ada1188
OM
692 const DNSName& hashedOwner = validset.first.first;
693 const DNSName signer = getSigner(validset.second.signatures);
f37a904f 694 if (!hashedOwner.isPartOf(signer)) {
baaa61a1 695 VLOG(log, qname << ": Owner "<<hashedOwner<<" is not part of the signer "<<signer<<", ignoring"<<endl);
3143417d 696 continue;
9b061cf5 697 }
15e973d6 698 numberOfLabelsOfParentZone = std::min(numberOfLabelsOfParentZone, static_cast<uint8_t>(signer.countLabels()));
3143417d 699
0a9dcd16 700 if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
baaa61a1 701 VLOG(log, qname << ": A NSEC3 RR from the child zone cannot deny the existence of a DS"<<endl);
0a9dcd16
RG
702 continue;
703 }
704
15e973d6
OM
705 if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
706 VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
707 return dState::NODENIAL;
708 }
709 nsec3sConsidered++;
710
711 string hash = getHashFromNSEC3(qname, *nsec3, context);
7ada1188 712 if (hash.empty()) {
baaa61a1 713 VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
98307d0f 714 return dState::INSECURE;
d377bb54
RG
715 }
716
9b061cf5
RG
717 nsec3Seen = true;
718
7ada1188 719 VLOG(log, qname << ":\tquery hash: "<<toBase32Hex(hash)<<endl);
f37a904f 720 string beginHash = fromBase32Hex(hashedOwner.getRawLabels()[0]);
da204325
PL
721
722 // If the name exists, check if the qtype is denied
7ada1188 723 if (beginHash == hash) {
9b061cf5 724
78cee429 725 /* The NSEC3 is either a delegation one, from the parent zone, and
c67b13a9 726 * must have the NS bit set but not the SOA one, or a regular NSEC3
f37a904f
RG
727 * either at apex (signer == owner) or with the SOA or NS bits clear.
728 */
729 const bool notApex = signer.countLabels() < qname.countLabels();
730 if (notApex && nsec3->isSet(QType::NS) && nsec3->isSet(QType::SOA)) {
baaa61a1 731 VLOG(log, qname << ": However, that NSEC3 is not at the apex and has both the NS and the SOA bits set!"<<endl);
f37a904f
RG
732 continue;
733 }
734
cd4beb37
RG
735 /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
736 Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
737 nonexistence of any RRs below that zone cut, which include all RRs at
738 that (original) owner name other than DS RRs, and all RRs below that
739 owner name regardless of type.
740 */
03e5e4cb 741 if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, qname, *nsec3)) {
cd4beb37 742 /* this is an "ancestor delegation" NSEC3 RR */
baaa61a1 743 VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
98307d0f 744 return dState::NODENIAL;
9b061cf5
RG
745 }
746
03e5e4cb 747 if (!isTypeDenied(*nsec3, QType(qtype))) {
baaa61a1 748 VLOG(log, qname << ": Does _not_ deny existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
399f391d
RG
749 return dState::NODENIAL;
750 }
751
baaa61a1 752 VLOG(log, qname << ": Denies existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
cd4beb37 753
43c04e77
PL
754 /*
755 * RFC 5155 section 8.9:
756 * If there is an NSEC3 RR present in the response that matches the
757 * delegation name, then the validator MUST ensure that the NS bit is
758 * set and that the DS bit is not set in the Type Bit Maps field of the
759 * NSEC3 RR.
760 */
be5d851d
RG
761 if (referralToUnsigned && qtype == QType::DS) {
762 if (!nsec3->isSet(QType::NS)) {
baaa61a1 763 VLOG(log, qname << ": However, no NS record exists at this level!"<<endl);
be5d851d
RG
764 return dState::NODENIAL;
765 }
da204325 766 }
243f4780 767
98307d0f 768 return dState::NXQTYPE;
da204325 769 }
243f4780 770 }
da204325 771 }
243f4780 772 }
812c3a69 773
9b061cf5
RG
774 /* if we have no NSEC3 records, we are done */
775 if (!nsec3Seen) {
98307d0f 776 return dState::NODENIAL;
9b061cf5
RG
777 }
778
779 DNSName closestEncloser(qname);
812c3a69 780 bool found = false;
9b061cf5 781 if (needWildcardProof) {
15e973d6 782 nsec3sConsidered = 0;
9b061cf5
RG
783 /* We now need to look for a NSEC3 covering the closest (provable) encloser
784 RFC 5155 section-7.2.1
c1797419 785 RFC 7129 section-5.5
9b061cf5 786 */
baaa61a1 787 VLOG(log, qname << ": Now looking for the closest encloser for "<<qname<<endl);
812c3a69 788
15e973d6 789 while (!found && closestEncloser.chopOff() && closestEncloser.countLabels() >= numberOfLabelsOfParentZone) {
dabcae2a 790
7ada1188
OM
791 for(const auto& validset : validrrsets) {
792 if(validset.first.second==QType::NSEC3) {
793 for(const auto& record : validset.second.records) {
794 VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
795 auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
c94c8dfe 796 if (!nsec3) {
9b061cf5 797 continue;
c94c8dfe 798 }
d377bb54 799
7ada1188
OM
800 const DNSName signer = getSigner(validset.second.signatures);
801 if (!validset.first.first.isPartOf(signer)) {
802 VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
b580fad2
RG
803 continue;
804 }
805
15e973d6
OM
806 if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
807 VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
808 return dState::NODENIAL;
809 }
810 nsec3sConsidered++;
811
812 string hash = getHashFromNSEC3(closestEncloser, *nsec3, context);
7ada1188 813 if (hash.empty()) {
15e973d6 814 VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
98307d0f 815 return dState::INSECURE;
9b061cf5
RG
816 }
817
7ada1188 818 string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
812c3a69 819
7ada1188
OM
820 VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
821 if (beginHash == hash) {
0a9dcd16 822 /* If the closest encloser is a delegation NS we know nothing about the names in the child zone. */
7ada1188 823 if (isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
baaa61a1 824 VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
b580fad2
RG
825 continue;
826 }
827
baaa61a1 828 VLOG(log, qname << ": Closest encloser for "<<qname<<" is "<<closestEncloser<<endl);
9b061cf5 829 found = true;
c94c8dfe
RG
830
831 if (nsec3->isSet(QType::DNAME)) {
832 /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
833
834 In any negative response, the NSEC or NSEC3 [RFC5155] record type
835 bitmap SHOULD be checked to see that there was no DNAME that could
836 have been applied. If the DNAME bit in the type bitmap is set and
837 the query name is a subdomain of the closest encloser that is
838 asserted, then DNAME substitution should have been done, but the
839 substitution has not been done as specified.
840 */
baaa61a1 841 VLOG(log, qname << ":\tThe closest encloser NSEC3 has the DNAME bit is set"<<endl);
c94c8dfe
RG
842 return dState::NODENIAL;
843 }
844
9b061cf5
RG
845 break;
846 }
812c3a69
RG
847 }
848 }
7ada1188 849 if (found) {
9b061cf5
RG
850 break;
851 }
812c3a69
RG
852 }
853 }
854 }
9b061cf5
RG
855 else {
856 /* RFC 5155 section-7.2.6:
857 "It is not necessary to return an NSEC3 RR that matches the closest encloser,
858 as the existence of this closest encloser is proven by the presence of the
859 expanded wildcard in the response.
860 */
861 found = true;
e4894ce0
RG
862 unsigned int closestEncloserLabelsCount = closestEncloser.countLabels();
863 while (wildcardLabelsCount > 0 && closestEncloserLabelsCount > wildcardLabelsCount) {
864 closestEncloser.chopOff();
865 closestEncloserLabelsCount--;
866 }
9b061cf5
RG
867 }
868
869 bool nextCloserFound = false;
870 bool isOptOut = false;
812c3a69 871
7ada1188 872 if (found) {
9b061cf5 873 /* now that we have found the closest (provable) encloser,
c1797419 874 we can construct the next closer (RFC7129 section-5.5) name
9b061cf5
RG
875 and look for a NSEC3 RR covering it */
876 unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels();
812c3a69 877 if (labelIdx >= 1) {
9b061cf5 878 DNSName nextCloser(closestEncloser);
812c3a69 879 nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
15e973d6 880 nsec3sConsidered = 0;
baaa61a1 881 VLOG(log, qname << ":Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl);
812c3a69 882
15e973d6
OM
883 for (const auto& validset : validrrsets) {
884 if (validset.first.second == QType::NSEC3) {
885 for (const auto& record : validset.second.records) {
7ada1188
OM
886 VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
887 auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
888 if (!nsec3) {
812c3a69 889 continue;
7ada1188 890 }
b7c40613 891
15e973d6
OM
892 if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
893 VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
894 return dState::NODENIAL;
895 }
896 nsec3sConsidered++;
897
898 string hash = getHashFromNSEC3(nextCloser, *nsec3, context);
7ada1188 899 if (hash.empty()) {
15e973d6 900 VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
98307d0f 901 return dState::INSECURE;
d377bb54 902 }
812c3a69 903
7ada1188
OM
904 const DNSName signer = getSigner(validset.second.signatures);
905 if (!validset.first.first.isPartOf(signer)) {
906 VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
cd4beb37
RG
907 continue;
908 }
909
7ada1188 910 string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
812c3a69 911
7ada1188
OM
912 VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
913 if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
baaa61a1 914 VLOG(log, qname << ": Denies existence of name "<<qname<<"/"<<QType(qtype));
9b061cf5
RG
915 nextCloserFound = true;
916
ce454638 917 if (nsec3->isOptOut()) {
baaa61a1 918 VLOG_NO_PREFIX(log, " but is opt-out!");
9b061cf5 919 isOptOut = true;
812c3a69 920 }
ce454638 921
baaa61a1 922 VLOG_NO_PREFIX(log, endl);
9b061cf5 923 break;
812c3a69 924 }
7ada1188 925 VLOG(log, qname << ": Did not cover us ("<<qname<<"), start="<<validset.first.first<<", us="<<toBase32Hex(hash)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
812c3a69
RG
926 }
927 }
9b061cf5
RG
928 if (nextCloserFound) {
929 break;
930 }
931 }
932 }
933 }
934
935 if (nextCloserFound) {
936 bool wildcardExists = false;
937 /* RFC 7129 section-5.6 */
15e973d6 938 if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, log, context)) {
9b061cf5 939 if (!isOptOut) {
baaa61a1 940 VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype)<<endl);
98307d0f 941 return dState::NODENIAL;
9b061cf5
RG
942 }
943 }
944
945 if (isOptOut) {
98307d0f 946 return dState::OPTOUT;
9b061cf5 947 }
7ada1188
OM
948 if (wildcardExists) {
949 return dState::NXQTYPE;
812c3a69 950 }
7ada1188 951 return dState::NXDOMAIN;
812c3a69
RG
952 }
953
da204325 954 // There were no valid NSEC(3) records
98307d0f 955 return dState::NODENIAL;
243f4780 956}
957
03e5e4cb 958bool isRRSIGNotExpired(const time_t now, const RRSIGRecordContent& sig)
dbbef467 959{
57fe2038 960 // Should use https://www.rfc-editor.org/rfc/rfc4034.txt section 3.1.5
03e5e4cb 961 return sig.d_sigexpire >= now;
fecac3ba
RG
962}
963
03e5e4cb 964bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig)
fecac3ba 965{
57fe2038 966 // Should use https://www.rfc-editor.org/rfc/rfc4034.txt section 3.1.5
03e5e4cb 967 return sig.d_siginception - g_signatureInceptionSkew <= now;
dbbef467
RG
968}
969
15e973d6
OM
970namespace {
971[[nodiscard]] bool checkSignatureInceptionAndExpiry(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, vState& ede, const OptLog& log)
972{
973 /* rfc4035:
974 - The validator's notion of the current time MUST be less than or equal to the time listed in the RRSIG RR's Expiration field.
975 - The validator's notion of the current time MUST be greater than or equal to the time listed in the RRSIG RR's Inception field.
976 */
977 if (isRRSIGIncepted(now, sig) && isRRSIGNotExpired(now, sig)) {
978 return true;
979 }
980 ede = ((sig.d_siginception - g_signatureInceptionSkew) > now) ? vState::BogusSignatureNotYetValid : vState::BogusSignatureExpired;
981 VLOG(log, qname << ": Signature is "<<(ede == vState::BogusSignatureNotYetValid ? "not yet valid" : "expired")<<" (inception: "<<sig.d_siginception<<", inception skew: "<<g_signatureInceptionSkew<<", expiration: "<<sig.d_sigexpire<<", now: "<<now<<")"<<endl);
982 return false;
983}
984
985[[nodiscard]] bool checkSignatureWithKey(const DNSName& qname, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
4d2be65d
RG
986{
987 bool result = false;
988 try {
15e973d6
OM
989 auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
990 result = dke->verify(msg, sig.d_signature);
991 VLOG(log, qname << ": Signature by key with tag "<<sig.d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig.d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
992 if (!result) {
993 ede = vState::BogusNoValidRRSIG;
4d2be65d 994 }
4d2be65d 995 }
fecac3ba 996 catch (const std::exception& e) {
baaa61a1 997 VLOG(log, qname << ": Could not make a validator for signature: "<<e.what()<<endl);
eea5263b 998 ede = vState::BogusUnsupportedDNSKEYAlgo;
4d2be65d
RG
999 }
1000 return result;
1001}
1002
15e973d6
OM
1003}
1004
1005vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, pdns::validation::ValidationContext& context, bool validateAllSigs)
4d2be65d 1006{
15e973d6 1007 bool missingKey = false;
4d2be65d 1008 bool isValid = false;
fecac3ba
RG
1009 bool allExpired = true;
1010 bool noneIncepted = true;
15e973d6 1011 uint16_t signaturesConsidered = 0;
4d2be65d 1012
15e973d6 1013 for (const auto& signature : signatures) {
bb07ad8e
RG
1014 unsigned int labelCount = name.countLabels();
1015 if (signature->d_labels > labelCount) {
a8cd5db5 1016 VLOG(log, name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
0a425443 1017 continue;
bb07ad8e
RG
1018 }
1019
15e973d6
OM
1020 vState ede = vState::Indeterminate;
1021 if (!checkSignatureInceptionAndExpiry(name, now, *signature, ede, log)) {
1022 if (isRRSIGIncepted(now, *signature)) {
1023 noneIncepted = false;
1024 }
1025 if (isRRSIGNotExpired(now, *signature)) {
1026 allExpired = false;
1027 }
1028 continue;
1029 }
1030
1031 if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
1032 VLOG(log, name<<": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
1033 // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1034 break;
1035 }
1036 signaturesConsidered++;
1037 context.d_validationsCounter++;
1038
10971f78 1039 auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm, log);
4d2be65d 1040
d2a42d06 1041 if (keysMatchingTag.empty()) {
15e973d6
OM
1042 VLOG(log, name << ": No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
1043 missingKey = true;
4d2be65d
RG
1044 continue;
1045 }
1046
d2a42d06 1047 string msg = getMessageForRRSET(name, *signature, toSign, true);
15e973d6 1048 uint16_t dnskeysConsidered = 0;
fecac3ba 1049 for (const auto& key : keysMatchingTag) {
15e973d6
OM
1050 if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
1051 VLOG(log, name << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(signature->d_tag)<<" and algorithm "<<std::to_string(signature->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
1052 return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
1053 }
1054 dnskeysConsidered++;
1055
1056 bool signIsValid = checkSignatureWithKey(name, *signature, *key, msg, ede, log);
fecac3ba 1057
d2a42d06 1058 if (signIsValid) {
4d2be65d 1059 isValid = true;
baaa61a1 1060 VLOG(log, name<< ": Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
4d2be65d
RG
1061 // cerr<<"valid"<<endl;
1062 // cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
1063 }
1064 else {
baaa61a1 1065 VLOG(log, name << ": signature invalid"<<endl);
03e5e4cb 1066 if (isRRSIGIncepted(now, *signature)) {
fecac3ba
RG
1067 noneIncepted = false;
1068 }
03e5e4cb 1069 if (isRRSIGNotExpired(now, *signature)) {
fecac3ba
RG
1070 allExpired = false;
1071 }
4d2be65d 1072 }
fecac3ba 1073
4d2be65d 1074 if (signIsValid && !validateAllSigs) {
fecac3ba 1075 return vState::Secure;
4d2be65d
RG
1076 }
1077 }
1078 }
1079
fecac3ba
RG
1080 if (isValid) {
1081 return vState::Secure;
1082 }
15e973d6 1083 if (missingKey) {
fecac3ba
RG
1084 return vState::BogusNoValidRRSIG;
1085 }
1086 if (noneIncepted) {
eea5263b 1087 // ede should be vState::BogusSignatureNotYetValid
fecac3ba
RG
1088 return vState::BogusSignatureNotYetValid;
1089 }
1090 if (allExpired) {
eea5263b 1091 // ede should be vState::BogusSignatureExpired);
fecac3ba
RG
1092 return vState::BogusSignatureExpired;
1093 }
1094
1095 return vState::BogusNoValidRRSIG;
4d2be65d
RG
1096}
1097
243f4780 1098// returns vState
1099// should return vState, zone cut and validated keyset
1100// i.e. www.7bits.nl -> insecure/7bits.nl/[]
1101// www.powerdnssec.org -> secure/powerdnssec.org/[keys]
1102// www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
1103
243f4780 1104cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
1105{
1106 cspmap_t cspmap;
1107 for(const auto& rec : recs) {
1108 // cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl;
7ada1188
OM
1109 if (rec.d_type == QType::OPT) {
1110 continue;
1111 }
57fe2038 1112
243f4780 1113 if(rec.d_type == QType::RRSIG) {
1114 auto rrc = getRR<RRSIGRecordContent>(rec);
ba3c54cb
RG
1115 if (rrc) {
1116 cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc);
1117 }
243f4780 1118 }
1119 else {
d06dcda4 1120 cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
243f4780 1121 }
1122 }
1123 return cspmap;
1124}
1125
4d2be65d
RG
1126bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res)
1127{
7ada1188 1128 const auto& iter = anchors.find(zone);
4d2be65d 1129
7ada1188 1130 if (iter == anchors.cend()) {
4d2be65d
RG
1131 return false;
1132 }
1133
7ada1188 1134 res = iter->second;
4d2be65d
RG
1135 return true;
1136}
1137
1138bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason)
1139{
7ada1188 1140 const auto& iter = negAnchors.find(zone);
4d2be65d 1141
7ada1188 1142 if (iter == negAnchors.cend()) {
4d2be65d
RG
1143 return false;
1144 }
1145
7ada1188 1146 reason = iter->second;
4d2be65d
RG
1147 return true;
1148}
1149
15e973d6 1150vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog& log, pdns::validation::ValidationContext& context)
4d2be65d
RG
1151{
1152 /*
1153 * Check all DNSKEY records against all DS records and place all DNSKEY records
1154 * that have DS records (that we support the algo for) in the tentative key storage
1155 */
15e973d6
OM
1156 uint16_t dssConsidered = 0;
1157 for (const auto& dsrc : dsmap) {
1158 if (g_maxDSsToConsider > 0 && dssConsidered > g_maxDSsToConsider) {
1159 VLOG(log, zone << ": We have already considered "<<std::to_string(dssConsidered)<<" DS"<<addS(dssConsidered)<<", not considering the remaining ones"<<endl;);
1160 return vState::BogusNoValidDNSKEY;
1161 }
1162 ++dssConsidered;
1163
1164 uint16_t dnskeysConsidered = 0;
7ada1188 1165 auto record = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
3d5ebf10 1166 // cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<DNSSECKeeper::algorithm2name(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
4d2be65d 1167
15e973d6 1168 for (const auto& drc : record) {
4d2be65d
RG
1169 bool isValid = false;
1170 bool dsCreated = false;
1171 DSRecordContent dsrc2;
15e973d6
OM
1172
1173 if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
1174 VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(dsrc.d_tag)<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<", not considering the remaining ones for this DS"<<endl;);
1175 // we need to break because we can have a partially validated set
1176 // where the KSK signs the ZSK(s), and even if we don't
1177 // we are going to try to get the correct EDE status (revoked, expired, ...)
1178 break;
1179 }
1180 dnskeysConsidered++;
1181
4d2be65d 1182 try {
5780cb46 1183 dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
4d2be65d
RG
1184 dsCreated = true;
1185 isValid = dsrc == dsrc2;
1186 }
fecac3ba 1187 catch (const std::exception &e) {
baaa61a1 1188 VLOG(log, zone << ": Unable to make DS from DNSKey: "<<e.what()<<endl);
4d2be65d
RG
1189 }
1190
fecac3ba 1191 if (isValid) {
baaa61a1 1192 VLOG(log, zone << ": got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<" for "<<zone<<endl);
4d2be65d
RG
1193
1194 validkeys.insert(drc);
4d2be65d
RG
1195 }
1196 else {
1197 if (dsCreated) {
baaa61a1 1198 VLOG(log, zone << ": DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
4d2be65d
RG
1199 }
1200 }
4d2be65d
RG
1201 }
1202 }
1203
eea5263b
O
1204 vState ede = vState::BogusNoValidDNSKEY;
1205
4d2be65d
RG
1206 // cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
1207 // these counts could be off if we somehow ended up with
1208 // duplicate keys. Should switch to a type that prevents that.
15e973d6 1209 if (!tkeys.empty() && validkeys.size() < tkeys.size()) {
4d2be65d
RG
1210 // this should mean that we have one or more DS-validated DNSKEYs
1211 // but not a fully validated DNSKEY set, yet
1212 // one of these valid DNSKEYs should be able to validate the
1213 // whole set
15e973d6
OM
1214 uint16_t signaturesConsidered = 0;
1215 for (const auto& sig : sigs) {
1216 if (!checkSignatureInceptionAndExpiry(zone, now, *sig, ede, log)) {
1217 continue;
1218 }
1219
4d2be65d 1220 // cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
10971f78 1221 auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm, log);
4d2be65d
RG
1222
1223 if (bytag.empty()) {
1224 continue;
1225 }
1226
15e973d6
OM
1227 if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
1228 VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
1229 // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1230 return vState::BogusNoValidDNSKEY;
1231 }
1232
5780cb46 1233 string msg = getMessageForRRSET(zone, *sig, toSign);
15e973d6 1234 uint16_t dnskeysConsidered = 0;
fecac3ba 1235 for (const auto& key : bytag) {
15e973d6
OM
1236 if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
1237 VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(sig->d_tag)<<" and algorithm "<<std::to_string(sig->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
1238 return vState::BogusNoValidDNSKEY;
1239 }
1240 dnskeysConsidered++;
1241
1242 if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
1243 VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
1244 // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1245 return vState::BogusNoValidDNSKEY;
1246 }
4d2be65d 1247 // cerr<<"validating : ";
15e973d6
OM
1248 bool signIsValid = checkSignatureWithKey(zone, *sig, *key, msg, ede, log);
1249 signaturesConsidered++;
1250 context.d_validationsCounter++;
4d2be65d 1251
03e5e4cb 1252 if (signIsValid) {
baaa61a1 1253 VLOG(log, zone << ": Validation succeeded - whole DNSKEY set is valid"<<endl);
5780cb46 1254 validkeys = tkeys;
4d2be65d
RG
1255 break;
1256 }
7ada1188 1257 VLOG(log, zone << ": Validation did not succeed!"<<endl);
4d2be65d 1258 }
15e973d6
OM
1259
1260 if (validkeys.size() == tkeys.size()) {
1261 // we validated the whole DNSKEY set already */
1262 break;
1263 }
4d2be65d
RG
1264 // if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
1265 }
1266 }
fecac3ba
RG
1267
1268 if (validkeys.size() < tkeys.size()) {
1269 /* so we failed to validate the whole set, let's try to find out why exactly */
1270 bool dnskeyAlgoSupported = false;
1271 bool dsDigestSupported = false;
1272
1273 for (const auto& dsrc : dsmap)
1274 {
1275 if (DNSCryptoKeyEngine::isAlgorithmSupported(dsrc.d_algorithm)) {
1276 dnskeyAlgoSupported = true;
1277 if (DNSCryptoKeyEngine::isDigestSupported(dsrc.d_digesttype)) {
1278 dsDigestSupported = true;
1279 }
1280 }
1281 }
1282
1283 if (!dnskeyAlgoSupported) {
1284 return vState::BogusUnsupportedDNSKEYAlgo;
1285 }
1286 if (!dsDigestSupported) {
1287 return vState::BogusUnsupportedDSDigestType;
1288 }
1289
1290 bool zoneKey = false;
1291 bool notRevoked = false;
1292 bool validProtocol = false;
1293
1294 for (const auto& key : tkeys) {
1295 if (!isAZoneKey(*key)) {
1296 continue;
1297 }
1298 zoneKey = true;
1299
1300 if (isRevokedKey(*key)) {
1301 continue;
1302 }
1303 notRevoked = true;
1304
1305 if (key->d_protocol != 3) {
1306 continue;
1307 }
1308 validProtocol = true;
1309 }
1310
1311 if (!zoneKey) {
1312 return vState::BogusNoZoneKeyBitSet;
1313 }
1314 if (!notRevoked) {
1315 return vState::BogusRevokedDNSKEY;
1316 }
1317 if (!validProtocol) {
1318 return vState::BogusInvalidDNSKEYProtocol;
1319 }
1320
eea5263b 1321 return ede;
fecac3ba
RG
1322 }
1323
1324 return vState::Secure;
4d2be65d
RG
1325}
1326
7ada1188 1327bool isSupportedDS(const DSRecordContent& dsrec, const OptLog& log)
8455425c 1328{
7ada1188
OM
1329 if (!DNSCryptoKeyEngine::isAlgorithmSupported(dsrec.d_algorithm)) {
1330 VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support algorithm number "<<std::to_string(dsrec.d_algorithm)<<endl);
8455425c
RG
1331 return false;
1332 }
1333
7ada1188
OM
1334 if (!DNSCryptoKeyEngine::isDigestSupported(dsrec.d_digesttype)) {
1335 VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support digest number "<<std::to_string(dsrec.d_digesttype)<<endl);
8455425c
RG
1336 return false;
1337 }
1338
1339 return true;
1340}
243f4780 1341
d06dcda4 1342DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
895449a5 1343{
7af99dff 1344 for (const auto& sig : signatures) {
9b061cf5
RG
1345 if (sig) {
1346 return sig->d_signer;
1347 }
895449a5 1348 }
243f4780 1349
7ada1188 1350 return {};
895449a5 1351}
9df058c4 1352
98307d0f
RG
1353const std::string& vStateToString(vState state)
1354{
4cca4055 1355 static const std::vector<std::string> vStates = {"Indeterminate", "Insecure", "Secure", "NTA", "TA", "Bogus - No valid DNSKEY", "Bogus - Invalid denial", "Bogus - Unable to get DSs", "Bogus - Unable to get DNSKEYs", "Bogus - Self Signed DS", "Bogus - No RRSIG", "Bogus - No valid RRSIG", "Bogus - Missing negative indication", "Bogus - Signature not yet valid", "Bogus - Signature expired", "Bogus - Unsupported DNSKEY algorithm", "Bogus - Unsupported DS digest type", "Bogus - No zone key bit set", "Bogus - Revoked DNSKEY", "Bogus - Invalid DNSKEY Protocol" };
98307d0f
RG
1356 return vStates.at(static_cast<size_t>(state));
1357}
1358
7ada1188 1359std::ostream& operator<<(std::ostream &ostr, const vState dstate)
9df058c4 1360{
7ada1188
OM
1361 ostr<<vStateToString(dstate);
1362 return ostr;
9df058c4
RG
1363}
1364
7ada1188 1365std::ostream& operator<<(std::ostream &ostr, const dState dstate)
9df058c4 1366{
cd4beb37 1367 static const std::vector<std::string> dStates = {"no denial", "inconclusive", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
7ada1188
OM
1368 ostr<<dStates.at(static_cast<size_t>(dstate));
1369 return ostr;
9df058c4 1370}
dc3f2d38
RG
1371
1372void updateDNSSECValidationState(vState& state, const vState stateUpdate)
1373{
1374 if (stateUpdate == vState::TA) {
1375 state = vState::Secure;
1376 }
1377 else if (stateUpdate == vState::NTA) {
1378 state = vState::Insecure;
1379 }
7ada1188 1380 else if (vStateIsBogus(stateUpdate) || state == vState::Indeterminate) {
dc3f2d38
RG
1381 state = stateUpdate;
1382 }
1383 else if (stateUpdate == vState::Insecure) {
fd870915 1384 if (!vStateIsBogus(state)) {
dc3f2d38
RG
1385 state = vState::Insecure;
1386 }
1387 }
1388}