• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 18225652081

03 Oct 2025 02:49PM UTC coverage: 65.825%. Remained the same
18225652081

Pull #16211

github

web-flow
Merge a109c2507 into feeb24672
Pull Request #16211: auth 5.0.x: backport "grow auth source tarballs fatter"

42048 of 92452 branches covered (45.48%)

Branch coverage included in aggregate %.

127983 of 165855 relevant lines covered (77.17%)

5370244.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.77
/pdns/validate.cc
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

23
#include "validate.hh"
24
#include "misc.hh"
25
#include "dnssecinfra.hh"
26
#include "dnsseckeeper.hh"
27
#include "rec-lua-conf.hh"
28
#include "base32.hh"
29
#include "logger.hh"
30

31
uint32_t g_signatureInceptionSkew{0};
32
uint16_t g_maxNSEC3Iterations{0};
33
uint16_t g_maxRRSIGsPerRecordToConsider{0};
34
uint16_t g_maxNSEC3sPerRecordToConsider{0};
35
uint16_t g_maxDNSKEYsToConsider{0};
36
uint16_t g_maxDSsToConsider{0};
37

38
static bool isAZoneKey(const DNSKEYRecordContent& key)
39
{
11,093✔
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;
11,093✔
51
}
11,093✔
52

53
static bool isRevokedKey(const DNSKEYRecordContent& key)
54
{
11,088✔
55
  /* rfc5011 Section 3 */
56
  return (key.d_flags & 128) != 0;
11,088✔
57
}
11,088✔
58

59
static vector<shared_ptr<const DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm, const OptLog& log)
60
{
7,562✔
61
  vector<shared_ptr<const DNSKEYRecordContent>> ret;
7,562✔
62

63
  for (const auto& key : keys) {
11,048✔
64
    if (!isAZoneKey(*key)) {
11,046✔
65
      VLOG(log, "Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" is not a zone key, skipping"<<endl;);
2!
66
      continue;
2✔
67
    }
2✔
68

69
    if (isRevokedKey(*key)) {
11,044✔
70
      VLOG(log, "Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" has been revoked, skipping"<<endl;);
2!
71
      continue;
2✔
72
    }
2✔
73

74
    if (key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm) {
11,043✔
75
      ret.push_back(key);
7,132✔
76
    }
7,132✔
77
  }
11,042✔
78

79
  return ret;
7,562✔
80
}
7,562✔
81

82
bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash)
83
{
11,774✔
84
  int order_bh = beginHash.compare(hash);
11,774✔
85
  int order_hn = hash.compare(nextHash);
11,774✔
86
  if (order_bh < 0 && order_hn < 0) { // beginHash < hash && hash < nextHash
11,774✔
87
    return true; // no wrap          BEGINNING --- HASH -- END
4,458✔
88
  }
4,458✔
89
  int order_bn = beginHash.compare(nextHash);
7,316✔
90
  if (order_hn < 0 && order_bn > 0) { // nextHash > hash && beginHash > nextHash
7,316✔
91
    return true; // wrap             HASH --- END --- BEGINNING
16✔
92
  }
16✔
93
  if (order_bn > 0 && order_bh < 0) { // nextHash < beginHash && beginHash < hash
7,300✔
94
    return true; // wrap other case  END --- BEGINNING --- HASH
19✔
95
  }
19✔
96
  if (order_bn == 0 && order_bh != 0) { // beginHash == nextHash && hash != beginHash
7,281!
97
    return true; // "we have only 1 NSEC3 record, LOL!"
×
98
  }
×
99
  return false;
7,281✔
100
}
7,281✔
101

102
// Same logic as above, using DNSName::canonCompare_three_way instead of std::string::compare.
103
bool isCoveredByNSEC3Hash(const DNSName& name, const DNSName& beginHash, const DNSName& nextHash)
104
{
49✔
105
  int order_bh = beginHash.canonCompare_three_way(name);
49✔
106
  int order_hn = name.canonCompare_three_way(nextHash);
49✔
107
  if (order_bh < 0 && order_hn < 0) {
49✔
108
    return true; // no wrap          BEGINNING --- HASH -- END
30✔
109
  }
30✔
110
  int order_bn = beginHash.canonCompare_three_way(nextHash);
19✔
111
  if (order_hn < 0 && order_bn > 0) {
19!
112
    return true; // wrap             HASH --- END --- BEGINNING
×
113
  }
×
114
  if (order_bn > 0 && order_bh < 0) {
19!
115
    return true; // wrap other case  END --- BEGINNING --- HASH
×
116
  }
×
117
  if (order_bn == 0 && order_bh != 0) {
19!
118
    return true; // "we have only 1 NSEC3 record, LOL!"
×
119
  }
×
120
  return false;
19✔
121
}
19✔
122

123
// Exact same logic as above, except that the arguments are not hashes.
124
bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next)
125
{
927✔
126
  int order_bh = begin.canonCompare_three_way(name);
927✔
127
  int order_hn = name.canonCompare_three_way(next);
927✔
128
  if (order_bh < 0 && order_hn < 0) {
927✔
129
    return true; // no wrap          BEGINNING --- NAME -- NEXT
747✔
130
  }
747✔
131
  int order_bn = begin.canonCompare_three_way(next);
180✔
132
  if (order_hn < 0 && order_bn > 0) {
180✔
133
    return true; // wrap             NEXT --- END --- BEGINNING
6✔
134
  }
6✔
135
  if (order_bn > 0 && order_bh < 0) {
174✔
136
    return true; // wrap other case  END --- BEGINNING --- NEXT
11✔
137
  }
11✔
138
  if (order_bn == 0 && order_bh != 0) {
163!
139
    return true; // "we have only 1 NSEC record, LOL!"
4✔
140
  }
4✔
141
  return false;
159✔
142
}
163✔
143

144
static bool nsecProvesENT(const DNSName& name, const DNSName& begin, const DNSName& next)
145
{
713✔
146
  /* if name is an ENT:
147
     - begin < name
148
     - next is a child of name
149
  */
150
  return begin.canonCompare(name) && next != name && next.isPartOf(name);
713!
151
}
713✔
152

153
[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context)
154
{
19,929✔
155
  std::string result;
19,929✔
156

157
  if (g_maxNSEC3Iterations != 0 && iterations > g_maxNSEC3Iterations) {
19,930✔
158
    return result;
6✔
159
  }
6✔
160

161
  auto key = std::tuple(qname, salt, iterations);
19,923✔
162
  auto iter = context.d_nsec3Cache.find(key);
19,923✔
163
  if (iter != context.d_nsec3Cache.end()) {
19,923✔
164
    return iter->second;
15,319✔
165
  }
15,319✔
166

167
  if (context.d_nsec3IterationsRemainingQuota < iterations) {
4,604✔
168
    // we throw here because we cannot take the risk that the result
169
    // be cached, since a different query can try to validate the
170
    // same result with a bigger NSEC3 iterations quota
171
    throw pdns::validation::TooManySEC3IterationsException();
2✔
172
  }
2✔
173

174
  result = hashQNameWithSalt(salt, iterations, qname);
4,602✔
175
  context.d_nsec3IterationsRemainingQuota -= iterations;
4,602✔
176
  context.d_nsec3Cache[key] = result;
4,602✔
177
  return result;
4,602✔
178
}
4,604✔
179

180
[[nodiscard]] static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, pdns::validation::ValidationContext& context)
181
{
19,718✔
182
  return getHashFromNSEC3(qname, nsec3.d_iterations, nsec3.d_salt, context);
19,718✔
183
}
19,718✔
184

185
/* There is no delegation at this exact point if:
186
   - the name exists but the NS type is not set
187
   - the name does not exist
188
   One exception, if the name is covered by an opt-out NSEC3
189
   it doesn't prove that an insecure delegation doesn't exist.
190
*/
191
bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords, pdns::validation::ValidationContext& context)
192
{
2,516✔
193
  uint16_t nsec3sConsidered = 0;
2,516✔
194

195
  for (const auto& record : dsrecords) {
5,009✔
196
    if (record.d_type == QType::NSEC) {
5,009✔
197
      const auto nsec = getRR<NSECRecordContent>(record);
143✔
198
      if (!nsec) {
143!
199
        continue;
×
200
      }
×
201

202
      if (record.d_name == zone) {
143✔
203
        return !nsec->isSet(QType::NS);
141✔
204
      }
141✔
205

206
      if (isCoveredByNSEC(zone, record.d_name, nsec->d_next)) {
2!
207
        return true;
2✔
208
      }
2✔
209
    }
2✔
210
    else if (record.d_type == QType::NSEC3) {
4,866✔
211
      const auto nsec3 = getRR<NSEC3RecordContent>(record);
4,711✔
212
      if (!nsec3) {
4,711!
213
        continue;
×
214
      }
×
215

216
      if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
4,711!
217
        context.d_limitHit = true;
×
218
        return false;
×
219
      }
×
220
      nsec3sConsidered++;
4,711✔
221

222
      const string hash = getHashFromNSEC3(zone, *nsec3, context);
4,711✔
223
      if (hash.empty()) {
4,711!
224
        return false;
×
225
      }
×
226

227
      const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]);
4,711✔
228
      if (beginHash == hash) {
4,711✔
229
        return !nsec3->isSet(QType::NS);
15✔
230
      }
15✔
231

232
      if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
4,696✔
233
        return !(nsec3->isOptOut());
2,348✔
234
      }
2,348✔
235
    }
4,696✔
236
  }
5,009✔
237

238
  return false;
10✔
239
}
2,516✔
240

241
/* RFC 4035 section-5.3.4:
242
   "If the number of labels in an RRset's owner name is greater than the
243
   Labels field of the covering RRSIG RR, then the RRset and its
244
   covering RRSIG RR were created as a result of wildcard expansion."
245
*/
246
bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign)
247
{
5,416✔
248
  return sign.d_labels < labelCount;
5,416✔
249
}
5,416✔
250

251
static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
252
{
208✔
253
  if (signatures.empty()) {
208!
254
    return false;
×
255
  }
×
256

257
  const auto& sign = signatures.at(0);
208✔
258
  unsigned int labelsCount = owner.countLabels();
208✔
259
  return isWildcardExpanded(labelsCount, *sign);
208✔
260
}
208✔
261

262
bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const RRSIGRecordContent& sign)
263
{
1,218✔
264
  /* this is a wildcard alright, but it has not been expanded */
265
  return owner.isWildcard() && (labelCount - 1) == sign.d_labels;
1,218✔
266
}
1,218✔
267

268
static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
269
{
2✔
270
  if (signatures.empty()) {
2!
271
    return false;
×
272
  }
×
273

274
  const auto& sign = signatures.at(0);
2✔
275
  unsigned int labelsCount = owner.countLabels();
2✔
276
  return isWildcardExpandedOntoItself(owner, labelsCount, *sign);
2✔
277
}
2✔
278

279
/* if this is a wildcard NSEC, the owner name has been modified
280
   to match the name. Make sure we use the original '*' form. */
281
DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
282
{
2,200✔
283
  DNSName result = initialOwner;
2,200✔
284

285
  if (signatures.empty()) {
2,200!
286
    return result;
×
287
  }
×
288

289
  const auto& sign = signatures.at(0);
2,200✔
290
  unsigned int labelsCount = initialOwner.countLabels();
2,200✔
291
  if (sign && sign->d_labels < labelsCount) {
2,200!
292
    do {
1,422✔
293
      result.chopOff();
1,422✔
294
      labelsCount--;
1,422✔
295
    }
1,422✔
296
    while (sign->d_labels < labelsCount);
1,422!
297

298
    result = g_wildcarddnsname + result;
1,422✔
299
  }
1,422✔
300

301
  return result;
2,200✔
302
}
2,200✔
303

304
static bool isNSECAncestorDelegation(const DNSName& signer, const DNSName& owner, const NSECRecordContent& nsec)
305
{
364✔
306
  return nsec.isSet(QType::NS) &&
364✔
307
    !nsec.isSet(QType::SOA) &&
364✔
308
    signer.countLabels() < owner.countLabels();
364✔
309
}
364✔
310

311
bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const NSEC3RecordContent& nsec3)
312
{
2,233✔
313
  return nsec3.isSet(QType::NS) &&
2,233✔
314
    !nsec3.isSet(QType::SOA) &&
2,233✔
315
    signer.countLabels() < owner.countLabels();
2,233!
316
}
2,233✔
317

318
static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t& validrrsets, const OptLog& log)
319
{
18✔
320
  const DNSName wildcard = g_wildcarddnsname + closestEncloser;
18✔
321
  VLOG(log, qname << ": Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
18!
322
  for (const auto& validset : validrrsets) {
18✔
323
    VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
18!
324
    if (validset.first.second == QType::NSEC) {
18!
325
      for (const auto& record : validset.second.records) {
18✔
326
        VLOG(log, ":\t"<<record->getZoneRepresentation()<<endl);
18!
327
        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
18✔
328
        if (!nsec) {
18!
329
          continue;
×
330
        }
×
331

332
        DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
18✔
333
        if (owner != wildcard) {
18✔
334
          continue;
4✔
335
        }
4✔
336

337
        VLOG(log, qname << ":\tWildcard matches");
14!
338
        if (qtype == 0 || isTypeDenied(*nsec, QType(qtype))) {
14!
339
          VLOG_NO_PREFIX(log, " and proves that the type did not exist"<<endl);
8!
340
          return true;
8✔
341
        }
8✔
342
        VLOG_NO_PREFIX(log, " BUT the type did exist!"<<endl);
6!
343
        return false;
6✔
344
      }
14✔
345
    }
18✔
346
  }
18✔
347

348
  return false;
4✔
349
}
18✔
350

351
DNSName getClosestEncloserFromNSEC(const DNSName& name, const DNSName& owner, const DNSName& next)
352
{
87✔
353
  DNSName commonWithOwner(name.getCommonLabels(owner));
87✔
354
  DNSName commonWithNext(name.getCommonLabels(next));
87✔
355
  if (commonWithOwner.countLabels() >= commonWithNext.countLabels()) {
87!
356
    return commonWithOwner;
87✔
357
  }
87✔
358
  return commonWithNext;
×
359
}
87✔
360

361
/*
362
  This function checks whether the non-existence of a wildcard covering qname|qtype
363
  is proven by the NSEC records in validrrsets.
364
*/
365
static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t & validrrsets, const OptLog& log)
366
{
55✔
367
  VLOG(log, qname << ": Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
55!
368
  const DNSName wildcard = g_wildcarddnsname + closestEncloser;
55✔
369
  for (const auto& validset : validrrsets) {
55✔
370
    VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
55!
371
    if (validset.first.second == QType::NSEC) {
55!
372
      for (const auto& records : validset.second.records) {
55✔
373
        VLOG(log, qname << ":\t"<<records->getZoneRepresentation()<<endl);
55!
374
        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(records);
55✔
375
        if (!nsec) {
55!
376
          continue;
×
377
        }
×
378

379
        const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
55✔
380
        VLOG(log, qname << ": Comparing owner: "<<owner<<" with target: "<<wildcard<<endl);
55!
381

382
        if (qname != owner && qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
55!
383
          /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
384

385
             In any negative response, the NSEC or NSEC3 [RFC5155] record type
386
             bitmap SHOULD be checked to see that there was no DNAME that could
387
             have been applied.  If the DNAME bit in the type bitmap is set and
388
             the query name is a subdomain of the closest encloser that is
389
             asserted, then DNAME substitution should have been done, but the
390
             substitution has not been done as specified.
391
          */
392
          VLOG(log, qname << ":\tThe qname is a subdomain of the NSEC and the DNAME bit is set"<<endl);
×
393
          return false;
×
394
        }
×
395

396
        if (wildcard != owner && isCoveredByNSEC(wildcard, owner, nsec->d_next)) {
55!
397
          VLOG(log, qname << ":\tWildcard is covered"<<endl);
53!
398
          return true;
53✔
399
        }
53✔
400
      }
55✔
401
    }
55✔
402
  }
55✔
403

404
  return false;
2✔
405
}
55✔
406

407
/*
408
  This function checks whether the non-existence of a wildcard covering qname|qtype
409
  is proven by the NSEC3 records in validrrsets.
410
  If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
411
  for this qname but doesn't have this qtype.
412
*/
413
static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const qtype, const cspmap_t& validrrsets, bool* wildcardExists, const OptLog& log, pdns::validation::ValidationContext& context)
414
{
2,115✔
415
  auto wildcard = g_wildcarddnsname + closestEncloser;
2,115✔
416
  VLOG(log, closestEncloser << ": Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype)<<endl);
2,115!
417

418
  for (const auto& validset : validrrsets) {
4,213✔
419
    VLOG(log, closestEncloser << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
4,213!
420
    if (validset.first.second == QType::NSEC3) {
4,213!
421
      for (const auto& records : validset.second.records) {
4,213✔
422
        VLOG(log, closestEncloser << ":\t"<<records->getZoneRepresentation()<<endl);
4,213!
423
        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(records);
4,213✔
424
        if (!nsec3) {
4,213!
425
          continue;
×
426
        }
×
427

428
        const DNSName signer = getSigner(validset.second.signatures);
4,213✔
429
        if (!validset.first.first.isPartOf(signer) || !closestEncloser.isPartOf(signer)) {
4,213!
430
          continue;
×
431
        }
×
432

433
        string hash = getHashFromNSEC3(wildcard, *nsec3, context);
4,213✔
434
        if (hash.empty()) {
4,213!
435
          VLOG(log, closestEncloser << ": Unsupported hash, ignoring"<<endl);
×
436
          return false;
×
437
        }
×
438
        VLOG(log, closestEncloser << ":\tWildcard hash: "<<toBase32Hex(hash)<<endl);
4,213!
439
        string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
4,213✔
440
        VLOG(log, closestEncloser << ":\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
4,213!
441

442
        if (beginHash == hash) {
4,213✔
443
          VLOG(log, closestEncloser << ":\tWildcard hash matches");
17!
444
          if (wildcardExists != nullptr) {
17!
445
            *wildcardExists = true;
17✔
446
          }
17✔
447

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
          */
454
          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
17!
455
            /* this is an "ancestor delegation" NSEC3 RR */
456
            VLOG_NO_PREFIX(log, " BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
×
457
            return false;
×
458
          }
×
459

460
          if (qtype == 0 || isTypeDenied(*nsec3, QType(qtype))) {
17!
461
            VLOG_NO_PREFIX(log, " and proves that the type did not exist"<<endl);
11!
462
            return true;
11✔
463
          }
11✔
464
          VLOG_NO_PREFIX(log, " BUT the type did exist!"<<endl);
6!
465
          return false;
6✔
466
        }
17✔
467

468
        if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
4,196✔
469
          VLOG(log, closestEncloser << ":\tWildcard hash is covered"<<endl);
21!
470
          return true;
21✔
471
        }
21✔
472
      }
4,196✔
473
    }
4,213✔
474
  }
4,213✔
475

476
  return false;
2,077✔
477
}
2,115✔
478

479
dState 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)
480
{
167✔
481
  const DNSName signer = getSigner(signatures);
167✔
482
  if (!name.isPartOf(signer) || !nsecOwner.isPartOf(signer)) {
167!
483
    return dState::INCONCLUSIVE;
×
484
  }
×
485

486
  const DNSName owner = getNSECOwnerName(nsecOwner, signatures);
167✔
487
  /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
488
     Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
489
     nonexistence of any RRs below that zone cut, which include all RRs at
490
     that (original) owner name other than DS RRs, and all RRs below that
491
     owner name regardless of type.
492
  */
493
  if (name.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, nsec)) {
167✔
494
    /* this is an "ancestor delegation" NSEC RR */
495
    if (qtype != QType::DS || name != owner) {
6✔
496
      VLOG_NO_PREFIX(log, "An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
4!
497
      return dState::NODENIAL;
4✔
498
    }
4✔
499
  }
6✔
500

501
  /* check if the type is denied */
502
  if (name == owner) {
163✔
503
    if (!isTypeDenied(nsec, QType(qtype))) {
35✔
504
      VLOG_NO_PREFIX(log, "does _not_ deny existence of type "<<QType(qtype)<<endl);
15!
505
      return dState::NODENIAL;
15✔
506
    }
15✔
507

508
    if (qtype == QType::DS && signer == name) {
20✔
509
      VLOG_NO_PREFIX(log, "the NSEC comes from the child zone and cannot be used to deny a DS"<<endl);
2!
510
      return dState::NODENIAL;
2✔
511
    }
2✔
512

513
    VLOG_NO_PREFIX(log, "Denies existence of type "<<QType(qtype)<<endl);
18!
514
    return dState::NXQTYPE;
18✔
515
  }
20✔
516

517
  if (name.isPartOf(owner) && nsec.isSet(QType::DNAME)) {
128✔
518
    /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
519

520
       In any negative response, the NSEC or NSEC3 [RFC5155] record type
521
       bitmap SHOULD be checked to see that there was no DNAME that could
522
       have been applied.  If the DNAME bit in the type bitmap is set and
523
       the query name is a subdomain of the closest encloser that is
524
       asserted, then DNAME substitution should have been done, but the
525
       substitution has not been done as specified.
526
    */
527
    VLOG(log, "the DNAME bit is set and the query name is a subdomain of that NSEC");
9!
528
    return dState::NODENIAL;
9✔
529
  }
9✔
530

531
  if (isCoveredByNSEC(name, owner, nsec.d_next)) {
119✔
532
    VLOG_NO_PREFIX(log, name << ": is covered by ("<<owner<<" to "<<nsec.d_next<<")");
36!
533

534
    if (nsecProvesENT(name, owner, nsec.d_next)) {
36✔
535
      VLOG_NO_PREFIX(log, " denies existence of type "<<name<<"/"<<QType(qtype)<<" by proving that "<<name<<" is an ENT"<<endl);
15!
536
      return dState::NXQTYPE;
15✔
537
    }
15✔
538

539
    return dState::NXDOMAIN;
21✔
540
  }
36✔
541

542
  return dState::INCONCLUSIVE;
83✔
543
}
119✔
544

545
[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength)
546
{
68✔
547
  return static_cast<uint64_t>((iterations + 1U + (saltLength > 0 ? 1U : 0U))) * maxLabels;
68✔
548
}
68✔
549

550
/*
551
  This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
552
  in validrrsets.
553
  - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODENIAL
554
  if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9.
555
  - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return
556
  NXQTYPE if the name is proven to be ENT and NXDOMAIN otherwise.
557
  - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is
558
  useful when we have a positive answer synthesized from a wildcard and we only need to prove that the exact
559
  name does not exist.
560
*/
561
dState 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
562
{
3,117✔
563
  bool nsec3Seen = false;
3,117✔
564
  if (!needWildcardProof && wildcardLabelsCount == 0) {
3,117!
565
    throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
×
566
  }
×
567

568
  uint8_t numberOfLabelsOfParentZone{std::numeric_limits<uint8_t>::max()};
3,117✔
569
  uint16_t nsec3sConsidered = 0;
3,117✔
570
  for (const auto& validset : validrrsets) {
5,338✔
571
    VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
5,338!
572

573
    if (validset.first.second==QType::NSEC) {
5,338✔
574
      for (const auto& record : validset.second.records) {
977✔
575
        VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
977!
576

577
        if (validset.second.signatures.empty()) {
977!
578
          continue;
×
579
        }
×
580

581
        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
977✔
582
        if (!nsec) {
977!
583
          continue;
×
584
        }
×
585

586
        const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
977✔
587
        const DNSName signer = getSigner(validset.second.signatures);
977✔
588
        if (!validset.first.first.isPartOf(signer) || !owner.isPartOf(signer) || !qname.isPartOf(signer)) {
977!
589
           continue;
4✔
590
        }
4✔
591

592
        /* The NSEC is either a delegation one, from the parent zone, and
593
         * must have the NS bit set but not the SOA one, or a regular NSEC
594
         * either at apex (signer == owner) or with the SOA or NS bits clear.
595
         */
596
        const bool notApex = signer.countLabels() < owner.countLabels();
973✔
597
        if (notApex && nsec->isSet(QType::NS) && nsec->isSet(QType::SOA)) {
973✔
598
          VLOG(log, qname << ": However, that NSEC is not at the apex and has both the NS and the SOA bits set!"<<endl);
2!
599
          continue;
2✔
600
        }
2✔
601

602
        /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
603
           Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
604
           nonexistence of any RRs below that zone cut, which include all RRs at
605
           that (original) owner name other than DS RRs, and all RRs below that
606
           owner name regardless of type.
607
        */
608
        if (qname.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, *nsec)) {
971✔
609
          /* this is an "ancestor delegation" NSEC RR */
610
          if (qtype != QType::DS || qname != owner) {
113!
611
            VLOG(log, qname << ": An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
4!
612
            return dState::NODENIAL;
4✔
613
          }
4✔
614
        }
113✔
615

616
        if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
967✔
617
          VLOG(log, qname << ": A NSEC RR from the child zone cannot deny the existence of a DS"<<endl);
2!
618
          continue;
2✔
619
        }
2✔
620

621
        /* check if the type is denied */
622
        if (qname == owner) {
965✔
623
          if (!isTypeDenied(*nsec, QType(qtype))) {
214✔
624
            VLOG(log, qname << ": Does _not_ deny existence of type "<<QType(qtype)<<endl);
4!
625
            return dState::NODENIAL;
4✔
626
          }
4✔
627

628
          VLOG(log, qname << ": Denies existence of type "<<QType(qtype)<<endl);
210!
629

630
          /*
631
           * RFC 4035 Section 2.3:
632
           * The bitmap for the NSEC RR at a delegation point requires special
633
           * attention.  Bits corresponding to the delegation NS RRset and any
634
           * RRsets for which the parent zone has authoritative data MUST be set
635
           */
636
          if (referralToUnsigned && qtype == QType::DS) {
210!
637
            if (!nsec->isSet(QType::NS)) {
75✔
638
              VLOG(log, qname << ": However, no NS record exists at this level!"<<endl);
2!
639
              return dState::NODENIAL;
2✔
640
            }
2✔
641
          }
75✔
642

643
          /* we know that the name exists (but this qtype doesn't) so except
644
             if the answer was generated by a wildcard expansion, no wildcard
645
             could have matched (rfc4035 section 5.4 bullet 1) */
646
          if (needWildcardProof && (!isWildcardExpanded(owner, validset.second.signatures) || isWildcardExpandedOntoItself(owner, validset.second.signatures))) {
208!
647
            needWildcardProof = false;
208✔
648
          }
208✔
649

650
          if (!needWildcardProof) {
208!
651
            return dState::NXQTYPE;
208✔
652
          }
208✔
653

654
          DNSName closestEncloser = getClosestEncloserFromNSEC(qname, owner, nsec->d_next);
×
655
          if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
×
656
            return dState::NXQTYPE;
×
657
          }
×
658

659
          VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
×
660
          return dState::NODENIAL;
×
661
        }
×
662

663
        if (qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
751!
664
          /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
665

666
             In any negative response, the NSEC or NSEC3 [RFC5155] record type
667
             bitmap SHOULD be checked to see that there was no DNAME that could
668
             have been applied.  If the DNAME bit in the type bitmap is set and
669
             the query name is a subdomain of the closest encloser that is
670
             asserted, then DNAME substitution should have been done, but the
671
             substitution has not been done as specified.
672
          */
673
          VLOG(log, qname << ": The DNAME bit is set and the query name is a subdomain of that NSEC"<< endl);
×
674
          return dState::NODENIAL;
×
675
        }
×
676

677
        /* check if the whole NAME is denied existing */
678
        if (isCoveredByNSEC(qname, owner, nsec->d_next)) {
751✔
679
          VLOG(log, qname<< ": Is covered by ("<<owner<<" to "<<nsec->d_next<<") ");
677!
680

681
          if (nsecProvesENT(qname, owner, nsec->d_next)) {
677✔
682
            if (wantsNoDataProof) {
9✔
683
              /* if the name is an ENT and we received a NODATA answer,
684
                 we are fine with a NSEC proving that the name does not exist. */
685
              VLOG_NO_PREFIX(log, "Denies existence of type "<<qname<<"/"<<QType(qtype)<<" by proving that "<<qname<<" is an ENT"<<endl);
7!
686
              return dState::NXQTYPE;
7✔
687
            }
7✔
688
            /* but for a NXDOMAIN proof, this doesn't make sense! */
689
            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);
2!
690
            return dState::NODENIAL;
2✔
691
          }
9✔
692

693
          if (!needWildcardProof) {
668✔
694
            VLOG_NO_PREFIX(log, "and we did not need a wildcard proof"<<endl);
595!
695
            return dState::NXDOMAIN;
595✔
696
          }
595✔
697

698
          VLOG_NO_PREFIX(log, "but we do need a wildcard proof so ");
73!
699
          DNSName closestEncloser = getClosestEncloserFromNSEC(qname, owner, nsec->d_next);
73✔
700
          if (wantsNoDataProof) {
73✔
701
            VLOG_NO_PREFIX(log, "looking for NODATA proof"<<endl);
18!
702
            if (provesNoDataWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
18✔
703
              return dState::NXQTYPE;
8✔
704
            }
8✔
705
          }
18✔
706
          else {
55✔
707
            VLOG_NO_PREFIX(log, "looking for NO wildcard proof"<<endl);
55!
708
            if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
55✔
709
              return dState::NXDOMAIN;
53✔
710
            }
53✔
711
          }
55✔
712

713
          VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
12!
714
          return dState::NODENIAL;
12✔
715
        }
73✔
716

717
        VLOG(log, qname << ": Did not deny existence of "<<QType(qtype)<<", "<<validset.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
74!
718
      }
74✔
719
    } else if(validset.first.second==QType::NSEC3) {
5,052!
720
      for (const auto& record : validset.second.records) {
4,364✔
721
        VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
4,364!
722
        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
4,364✔
723
        if (!nsec3) {
4,364!
724
          continue;
×
725
        }
×
726

727
        if (validset.second.signatures.empty()) {
4,364!
728
          continue;
×
729
        }
×
730

731
        const DNSName& hashedOwner = validset.first.first;
4,364✔
732
        const DNSName signer = getSigner(validset.second.signatures);
4,364✔
733
        if (!hashedOwner.isPartOf(signer)) {
4,364✔
734
          VLOG(log, qname << ": Owner "<<hashedOwner<<" is not part of the signer "<<signer<<", ignoring"<<endl);
2!
735
          continue;
2✔
736
        }
2✔
737
        numberOfLabelsOfParentZone = std::min(numberOfLabelsOfParentZone, static_cast<uint8_t>(signer.countLabels()));
4,362✔
738

739
        if (!qname.isPartOf(signer)) {
4,362!
740
          continue;
×
741
        }
×
742

743
        if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
4,362✔
744
          VLOG(log, qname << ": A NSEC3 RR from the child zone cannot deny the existence of a DS"<<endl);
2!
745
          continue;
2✔
746
        }
2✔
747

748
        if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
4,360✔
749
          VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
4!
750
          context.d_limitHit = true;
4✔
751
          return dState::NODENIAL;
4✔
752
        }
4✔
753
        nsec3sConsidered++;
4,356✔
754

755
        string hash = getHashFromNSEC3(qname, *nsec3, context);
4,356✔
756
        if (hash.empty()) {
4,356✔
757
          VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
6!
758
          return dState::INSECURE;
6✔
759
        }
6✔
760

761
        nsec3Seen = true;
4,350✔
762

763
        VLOG(log, qname << ":\tquery hash: "<<toBase32Hex(hash)<<endl);
4,350!
764
        string beginHash = fromBase32Hex(hashedOwner.getRawLabels()[0]);
4,350✔
765

766
        // If the name exists, check if the qtype is denied
767
        if (beginHash == hash) {
4,350✔
768

769
          /* The NSEC3 is either a delegation one, from the parent zone, and
770
           * must have the NS bit set but not the SOA one, or a regular NSEC3
771
           * either at apex (signer == owner) or with the SOA or NS bits clear.
772
           */
773
          const bool notApex = signer.countLabels() < qname.countLabels();
50✔
774
          if (notApex && nsec3->isSet(QType::NS) && nsec3->isSet(QType::SOA)) {
50✔
775
            VLOG(log, qname << ": However, that NSEC3 is not at the apex and has both the NS and the SOA bits set!"<<endl);
2!
776
            continue;
2✔
777
          }
2✔
778

779
          /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
780
             Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
781
             nonexistence of any RRs below that zone cut, which include all RRs at
782
             that (original) owner name other than DS RRs, and all RRs below that
783
             owner name regardless of type.
784
          */
785
          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, qname, *nsec3)) {
48✔
786
            /* this is an "ancestor delegation" NSEC3 RR */
787
            VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
2!
788
            return dState::NODENIAL;
2✔
789
          }
2✔
790

791
          if (!isTypeDenied(*nsec3, QType(qtype))) {
46✔
792
            VLOG(log, qname << ": Does _not_ deny existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
2!
793
            return dState::NODENIAL;
2✔
794
          }
2✔
795

796
          VLOG(log, qname << ": Denies existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
44!
797

798
          /*
799
           * RFC 5155 section 8.9:
800
           * If there is an NSEC3 RR present in the response that matches the
801
           * delegation name, then the validator MUST ensure that the NS bit is
802
           * set and that the DS bit is not set in the Type Bit Maps field of the
803
           * NSEC3 RR.
804
           */
805
          if (referralToUnsigned && qtype == QType::DS) {
44!
806
            if (!nsec3->isSet(QType::NS)) {
15✔
807
              VLOG(log, qname << ": However, no NS record exists at this level!"<<endl);
2!
808
              return dState::NODENIAL;
2✔
809
            }
2✔
810
          }
15✔
811

812
          return dState::NXQTYPE;
42✔
813
        }
44✔
814
      }
4,350✔
815
    }
4,363✔
816
  }
5,338✔
817

818
  /* if we have no NSEC3 records, we are done */
819
  if (!nsec3Seen) {
2,164✔
820
    return dState::NODENIAL;
32✔
821
  }
32✔
822

823
  DNSName closestEncloser(qname);
2,132✔
824
  bool found = false;
2,132✔
825
  if (needWildcardProof) {
2,132✔
826
    /* We now need to look for a NSEC3 covering the closest (provable) encloser
827
       RFC 5155 section-7.2.1
828
       RFC 7129 section-5.5
829
    */
830
    VLOG(log, qname << ": Now looking for the closest encloser for "<<qname<<endl);
2,123!
831

832
    while (!found && closestEncloser.chopOff() && closestEncloser.countLabels() >= numberOfLabelsOfParentZone) {
4,259✔
833
      nsec3sConsidered = 0;
2,136✔
834

835
      for(const auto& validset : validrrsets) {
3,551✔
836
        if(validset.first.second==QType::NSEC3) {
3,551!
837
          for(const auto& record : validset.second.records) {
3,551✔
838
            VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
3,551!
839
            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
3,551✔
840
            if (!nsec3) {
3,551!
841
              continue;
×
842
            }
×
843

844
            const DNSName signer = getSigner(validset.second.signatures);
3,551✔
845
            if (!validset.first.first.isPartOf(signer)) {
3,551!
846
              VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
×
847
              continue;
×
848
            }
×
849

850
            if (!closestEncloser.isPartOf(signer)) {
3,551!
851
              continue;
×
852
            }
×
853

854
            if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
3,552!
855
              VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
×
856
              context.d_limitHit = true;
×
857
              return dState::NODENIAL;
×
858
            }
×
859
            nsec3sConsidered++;
3,551✔
860

861
            string hash = getHashFromNSEC3(closestEncloser, *nsec3, context);
3,551✔
862
            if (hash.empty()) {
3,551!
863
              VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
×
864
              return dState::INSECURE;
×
865
            }
×
866

867
            string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
3,551✔
868

869
            VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
3,551!
870
            if (beginHash == hash) {
3,551✔
871
              /* If the closest encloser is a delegation NS we know nothing about the names in the child zone. */
872
              if (isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
2,121✔
873
                VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
4!
874
                continue;
4✔
875
              }
4✔
876

877
              VLOG(log, qname << ": Closest encloser for "<<qname<<" is "<<closestEncloser<<endl);
2,117!
878
              found = true;
2,117✔
879

880
              if (nsec3->isSet(QType::DNAME)) {
2,117!
881
                /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
882

883
                   In any negative response, the NSEC or NSEC3 [RFC5155] record type
884
                   bitmap SHOULD be checked to see that there was no DNAME that could
885
                   have been applied.  If the DNAME bit in the type bitmap is set and
886
                   the query name is a subdomain of the closest encloser that is
887
                   asserted, then DNAME substitution should have been done, but the
888
                   substitution has not been done as specified.
889
                */
890
                VLOG(log, qname << ":\tThe closest encloser NSEC3 has the DNAME bit is set"<<endl);
×
891
                return dState::NODENIAL;
×
892
              }
×
893

894
              break;
2,117✔
895
            }
2,117✔
896
          }
3,551✔
897
        }
3,551✔
898
        if (found) {
3,551✔
899
          break;
2,117✔
900
        }
2,117✔
901
      }
3,551✔
902
    }
2,136✔
903
  }
2,123✔
904
  else {
9✔
905
    /* RFC 5155 section-7.2.6:
906
       "It is not necessary to return an NSEC3 RR that matches the closest encloser,
907
       as the existence of this closest encloser is proven by the presence of the
908
       expanded wildcard in the response.
909
    */
910
    found = true;
9✔
911
    unsigned int closestEncloserLabelsCount = closestEncloser.countLabels();
9✔
912
    while (wildcardLabelsCount > 0 && closestEncloserLabelsCount > wildcardLabelsCount) {
20!
913
      closestEncloser.chopOff();
11✔
914
      closestEncloserLabelsCount--;
11✔
915
    }
11✔
916
  }
9✔
917

918
  bool nextCloserFound = false;
2,132✔
919
  bool isOptOut = false;
2,132✔
920

921
  if (found) {
2,132✔
922
    /* now that we have found the closest (provable) encloser,
923
       we can construct the next closer (RFC7129 section-5.5) name
924
       and look for a NSEC3 RR covering it */
925
    unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels();
2,126✔
926
    if (labelIdx >= 1) {
2,126!
927
      DNSName nextCloser(closestEncloser);
2,126✔
928
      nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
2,126✔
929
      nsec3sConsidered = 0;
2,126✔
930
      VLOG(log, qname << ":Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl);
2,126!
931

932
      for (const auto& validset : validrrsets) {
2,887✔
933
        if (validset.first.second == QType::NSEC3) {
2,887!
934
          for (const auto& record : validset.second.records) {
2,887✔
935
            VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
2,887!
936
            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
2,887✔
937
            if (!nsec3) {
2,887!
938
              continue;
×
939
            }
×
940

941
            if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
2,887!
942
              VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
×
943
              context.d_limitHit = true;
×
944
              return dState::NODENIAL;
×
945
            }
×
946
            nsec3sConsidered++;
2,887✔
947

948
            string hash = getHashFromNSEC3(nextCloser, *nsec3, context);
2,887✔
949
            if (hash.empty()) {
2,887!
950
              VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
×
951
              return dState::INSECURE;
×
952
            }
×
953

954
            const DNSName signer = getSigner(validset.second.signatures);
2,887✔
955
            if (!validset.first.first.isPartOf(signer)) {
2,887✔
956
              VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
2!
957
              continue;
2✔
958
            }
2✔
959

960
            if (!nextCloser.isPartOf(signer)) {
2,885!
961
              continue;
×
962
            }
×
963

964
            string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
2,885✔
965

966
            VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
2,885!
967
            if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
2,885✔
968
              VLOG(log, qname << ": Denies existence of name "<<qname<<"/"<<QType(qtype));
2,124!
969
              nextCloserFound = true;
2,124✔
970

971
              if (nsec3->isOptOut()) {
2,124✔
972
                VLOG_NO_PREFIX(log, " but is opt-out!");
2,081!
973
                isOptOut = true;
2,081✔
974
              }
2,081✔
975

976
              VLOG_NO_PREFIX(log, endl);
2,124!
977
              break;
2,124✔
978
            }
2,124✔
979
            VLOG(log, qname << ": Did not cover us ("<<qname<<"), start="<<validset.first.first<<", us="<<toBase32Hex(hash)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
761!
980
          }
761✔
981
        }
2,887✔
982
        if (nextCloserFound) {
2,887✔
983
          break;
2,124✔
984
        }
2,124✔
985
      }
2,887✔
986
    }
2,126✔
987
  }
2,126✔
988

989
  if (nextCloserFound) {
2,132✔
990
    bool wildcardExists = false;
2,124✔
991
    /* RFC 7129 section-5.6 */
992
    if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, log, context)) {
2,124✔
993
      if (!isOptOut) {
2,081✔
994
        VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype)<<endl);
8!
995
        return dState::NODENIAL;
8✔
996
      }
8✔
997
    }
2,081✔
998

999
    if (isOptOut) {
2,116✔
1000
      return dState::OPTOUT;
2,081✔
1001
    }
2,081✔
1002
    if (wildcardExists) {
35✔
1003
      return dState::NXQTYPE;
11✔
1004
    }
11✔
1005
    return dState::NXDOMAIN;
24✔
1006
  }
35✔
1007

1008
  // There were no valid NSEC(3) records
1009
  return dState::NODENIAL;
8✔
1010
}
2,132✔
1011

1012
bool isRRSIGNotExpired(const time_t now, const RRSIGRecordContent& sig)
1013
{
23,858✔
1014
  // it's an uint32_t rfc1982 compare, explicitly cast now to uint32_t to avoid Coverity warning
1015
  // implicitly converting a time_t to a smaller int.
1016
  return rfc1982LessThanOrEqual<uint32_t>(static_cast<uint32_t>(now), sig.d_sigexpire);
23,858✔
1017
}
23,858✔
1018

1019
bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig)
1020
{
5,698✔
1021
  // it's an uint32_t rfc1982 compare, explicitly cast now to uint32_t to avoid Coverity warning
1022
  // implicitly converting a time_t to a smaller int.
1023
  return rfc1982LessThanOrEqual<uint32_t>(sig.d_siginception - g_signatureInceptionSkew, static_cast<uint32_t>(now));
5,698✔
1024
}
5,698✔
1025

1026
namespace {
1027
[[nodiscard]] bool checkSignatureInceptionAndExpiry(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, vState& ede, const OptLog& log)
1028
{
5,653✔
1029
  /* rfc4035:
1030
     - 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.
1031
     - 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.
1032
  */
1033
  vState localEDE = vState::Indeterminate;
5,653✔
1034
  if (!isRRSIGIncepted(now, sig)) {
5,653✔
1035
    localEDE = vState::BogusSignatureNotYetValid;
8✔
1036
  }
8✔
1037
  else if (!isRRSIGNotExpired(now, sig)) {
5,645✔
1038
    localEDE = vState::BogusSignatureExpired;
9✔
1039
  }
9✔
1040
  if (localEDE == vState::Indeterminate) {
5,653✔
1041
    return true;
5,636✔
1042
  }
5,636✔
1043
  ede = localEDE;
17✔
1044
  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);
17!
1045
  return false;
17✔
1046
}
5,653✔
1047

1048
[[nodiscard]] bool checkSignatureWithKey(const DNSName& qname, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
1049
{
5,581✔
1050
  bool result = false;
5,581✔
1051
  try {
5,581✔
1052
    auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
5,581✔
1053
    result = dke->verify(msg, sig.d_signature);
5,581✔
1054
    VLOG(log, qname << ": Signature by key with tag "<<sig.d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig.d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
5,581!
1055
    if (!result) {
5,581✔
1056
      ede = vState::BogusNoValidRRSIG;
28✔
1057
    }
28✔
1058
  }
5,581✔
1059
  catch (const std::exception& e) {
5,581✔
1060
    VLOG(log, qname << ": Could not make a validator for signature: "<<e.what()<<endl);
×
1061
    ede = vState::BogusUnsupportedDNSKEYAlgo;
×
1062
  }
×
1063
  return result;
5,581✔
1064
}
5,581✔
1065

1066
}
1067

1068
vState 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)
1069
{
5,319✔
1070
  bool missingKey = false;
5,319✔
1071
  bool isValid = false;
5,319✔
1072
  bool allExpired = true;
5,319✔
1073
  bool noneIncepted = true;
5,319✔
1074
  uint16_t signaturesConsidered = 0;
5,319✔
1075

1076
  for (const auto& signature : signatures) {
5,335✔
1077
    unsigned int labelCount = name.countLabels();
5,335✔
1078
    if (signature->d_labels > labelCount) {
5,335!
1079
      VLOG(log, name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
×
1080
      continue;
×
1081
    }
×
1082

1083
    vState ede = vState::Indeterminate;
5,335✔
1084
    if (!DNSCryptoKeyEngine::isAlgorithmSupported(signature->d_algorithm)) {
5,335!
1085
        continue;
×
1086
    }
×
1087
    if (!checkSignatureInceptionAndExpiry(name, now, *signature, ede, log)) {
5,335✔
1088
      if (isRRSIGIncepted(now, *signature)) {
17✔
1089
        noneIncepted = false;
9✔
1090
      }
9✔
1091
      if (isRRSIGNotExpired(now, *signature)) {
17✔
1092
        allExpired = false;
8✔
1093
      }
8✔
1094
      continue;
17✔
1095
    }
17✔
1096

1097
    if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
5,318✔
1098
      VLOG(log, name<<": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
2!
1099
      // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1100
      context.d_limitHit = true;
2✔
1101
      break;
2✔
1102
    }
2✔
1103
    signaturesConsidered++;
5,316✔
1104
    context.d_validationsCounter++;
5,316✔
1105

1106
    auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm, log);
5,316✔
1107

1108
    if (keysMatchingTag.empty()) {
5,316✔
1109
      VLOG(log, name << ": No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
8!
1110
      missingKey = true;
8✔
1111
      continue;
8✔
1112
    }
8✔
1113

1114
    string msg = getMessageForRRSET(name, *signature, toSign, true);
5,308✔
1115
    uint16_t dnskeysConsidered = 0;
5,308✔
1116
    for (const auto& key : keysMatchingTag) {
5,310✔
1117
      if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
5,310✔
1118
        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;);
2!
1119
        if (!isValid) {
2!
1120
          context.d_limitHit = true;
2✔
1121
        }
2✔
1122
        return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
2!
1123
      }
2✔
1124
      dnskeysConsidered++;
5,308✔
1125

1126
      bool signIsValid = checkSignatureWithKey(name, *signature, *key, msg, ede, log);
5,308✔
1127

1128
      if (signIsValid) {
5,308✔
1129
        isValid = true;
5,279✔
1130
        VLOG(log, name<< ": Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
5,279!
1131
        //          cerr<<"valid"<<endl;
1132
        //          cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
1133
      }
5,279✔
1134
      else {
29✔
1135
        VLOG(log, name << ": signature invalid"<<endl);
29!
1136
        if (isRRSIGIncepted(now, *signature)) {
29✔
1137
          noneIncepted = false;
28✔
1138
        }
28✔
1139
        if (isRRSIGNotExpired(now, *signature)) {
29✔
1140
          allExpired = false;
28✔
1141
        }
28✔
1142
      }
29✔
1143

1144
      if (signIsValid && !validateAllSigs) {
5,308✔
1145
        return vState::Secure;
5,275✔
1146
      }
5,275✔
1147
    }
5,308✔
1148
  }
5,308✔
1149

1150
  if (isValid) {
42✔
1151
    return vState::Secure;
5✔
1152
  }
5✔
1153
  if (missingKey) {
37✔
1154
    return vState::BogusNoValidRRSIG;
8✔
1155
  }
8✔
1156
  if (noneIncepted) {
29✔
1157
    // ede should be vState::BogusSignatureNotYetValid
1158
    return vState::BogusSignatureNotYetValid;
8✔
1159
  }
8✔
1160
  if (allExpired) {
21✔
1161
    // ede should be vState::BogusSignatureExpired);
1162
    return vState::BogusSignatureExpired;
9✔
1163
  }
9✔
1164

1165
  return vState::BogusNoValidRRSIG;
12✔
1166
}
21✔
1167

1168
bool getTrustAnchor(const map<DNSName,dsset_t>& anchors, const DNSName& zone, dsset_t &res)
1169
{
81,087✔
1170
  const auto& iter = anchors.find(zone);
81,087✔
1171

1172
  if (iter == anchors.cend()) {
81,087✔
1173
    return false;
57,863✔
1174
  }
57,863✔
1175

1176
  res = iter->second;
23,224✔
1177
  return true;
23,224✔
1178
}
81,087✔
1179

1180
bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason)
1181
{
81,104✔
1182
  const auto& iter = negAnchors.find(zone);
81,104✔
1183

1184
  if (iter == negAnchors.cend()) {
81,104✔
1185
    return false;
81,088✔
1186
  }
81,088✔
1187

1188
  reason = iter->second;
16✔
1189
  return true;
16✔
1190
}
81,104✔
1191

1192
vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsset_t& dsset, 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) // NOLINT(readability-function-cognitive-complexity)
1193
{
1,516✔
1194
  /*
1195
   * Check all DNSKEY records against all DS records and place all DNSKEY records
1196
   * that have DS records (that we support the algo for) in the tentative key storage
1197
   */
1198
  uint16_t dssConsidered = 0;
1,516✔
1199
  for (const auto& dsrc : dsset) {
1,930✔
1200
    if (g_maxDSsToConsider > 0 && dssConsidered > g_maxDSsToConsider) {
1,930✔
1201
      VLOG(log, zone << ": We have already considered "<<std::to_string(dssConsidered)<<" DS"<<addS(dssConsidered)<<", not considering the remaining ones"<<endl;);
2!
1202
      return vState::BogusNoValidDNSKEY;
2✔
1203
    }
2✔
1204
    ++dssConsidered;
1,928✔
1205

1206
    uint16_t dnskeysConsidered = 0;
1,928✔
1207
    auto record = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
1,928✔
1208
    // 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;
1209

1210
    for (const auto& drc : record) {
1,928✔
1211
      bool isValid = false;
1,549✔
1212
      bool dsCreated = false;
1,549✔
1213
      DSRecordContent dsrc2;
1,549✔
1214

1215
      if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
1,549✔
1216
        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;);
4!
1217
        // we need to break because we can have a partially validated set
1218
        // where the KSK signs the ZSK(s), and even if we don't
1219
        // we are going to try to get the correct EDE status (revoked, expired, ...)
1220
        context.d_limitHit = true;
4✔
1221
        break;
4✔
1222
      }
4✔
1223
      dnskeysConsidered++;
1,545✔
1224

1225
      try {
1,545✔
1226
        dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
1,545✔
1227
        dsCreated = true;
1,545✔
1228
        isValid = dsrc == dsrc2;
1,545✔
1229
      }
1,545✔
1230
      catch (const std::exception &e) {
1,545✔
1231
        VLOG(log, zone << ": Unable to make DS from DNSKey: "<<e.what()<<endl);
×
1232
      }
×
1233

1234
      if (isValid) {
1,545✔
1235
        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);
1,543!
1236

1237
        validkeys.insert(drc);
1,543✔
1238
      }
1,543✔
1239
      else {
2✔
1240
        if (dsCreated) {
2!
1241
          VLOG(log, zone << ": DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
2!
1242
        }
2✔
1243
      }
2✔
1244
    }
1,545✔
1245
  }
1,928✔
1246

1247
  vState ede = vState::BogusNoValidDNSKEY;
1,514✔
1248

1249
  //    cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
1250
  // these counts could be off if we somehow ended up with
1251
  // duplicate keys. Should switch to a type that prevents that.
1252
  if (!tkeys.empty() && validkeys.size() < tkeys.size()) {
1,514!
1253
    // this should mean that we have one or more DS-validated DNSKEYs
1254
    // but not a fully validated DNSKEY set, yet
1255
    // one of these valid DNSKEYs should be able to validate the
1256
    // whole set
1257
    uint16_t signaturesConsidered = 0;
315✔
1258
    for (const auto& sig : sigs) {
318✔
1259
      if (!DNSCryptoKeyEngine::isAlgorithmSupported(sig->d_algorithm)) {
318!
1260
        continue;
×
1261
      }
×
1262
      if (!checkSignatureInceptionAndExpiry(zone, now, *sig, ede, log)) {
318!
1263
        continue;
×
1264
      }
×
1265

1266
      //        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;
1267
      auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm, log);
318✔
1268

1269
      if (bytag.empty()) {
318✔
1270
        continue;
45✔
1271
      }
45✔
1272

1273
      if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
273!
1274
        VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
×
1275
        // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1276
        context.d_limitHit = true;
×
1277
        return vState::BogusNoValidDNSKEY;
×
1278
      }
×
1279

1280
      string msg = getMessageForRRSET(zone, *sig, toSign);
273✔
1281
      uint16_t dnskeysConsidered = 0;
273✔
1282
      for (const auto& key : bytag) {
273!
1283
        if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
273!
1284
          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;);
×
1285
          context.d_limitHit = true;
×
1286
          return vState::BogusNoValidDNSKEY;
×
1287
        }
×
1288
        dnskeysConsidered++;
273✔
1289

1290
        if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
273!
1291
          VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
×
1292
          // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1293
          context.d_limitHit = true;
×
1294
          return vState::BogusNoValidDNSKEY;
×
1295
        }
×
1296
        //          cerr<<"validating : ";
1297
        bool signIsValid = checkSignatureWithKey(zone, *sig, *key, msg, ede, log);
273✔
1298
        signaturesConsidered++;
273✔
1299
        context.d_validationsCounter++;
273✔
1300

1301
        if (signIsValid) {
273!
1302
          VLOG(log, zone << ": Validation succeeded - whole DNSKEY set is valid"<<endl);
273!
1303
          validkeys = tkeys;
273✔
1304
          break;
273✔
1305
        }
273✔
1306
        VLOG(log, zone << ": Validation did not succeed!"<<endl);
×
1307
      }
×
1308

1309
      if (validkeys.size() == tkeys.size()) {
273!
1310
        // we validated the whole DNSKEY set already */
1311
        break;
273✔
1312
      }
273✔
1313
      //        if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
1314
    }
273✔
1315
  }
315✔
1316

1317
  if (validkeys.size() < tkeys.size()) {
1,514✔
1318
    /* so we failed to validate the whole set, let's try to find out why exactly */
1319
    bool dnskeyAlgoSupported = false;
42✔
1320
    bool dsDigestSupported = false;
42✔
1321

1322
    for (const auto& dsrc : dsset)
42✔
1323
    {
44✔
1324
      if (DNSCryptoKeyEngine::isAlgorithmSupported(dsrc.d_algorithm)) {
44!
1325
        dnskeyAlgoSupported = true;
44✔
1326
        if (DNSCryptoKeyEngine::isDigestSupported(dsrc.d_digesttype)) {
44!
1327
          dsDigestSupported = true;
44✔
1328
        }
44✔
1329
      }
44✔
1330
    }
44✔
1331

1332
    if (!dnskeyAlgoSupported) {
42!
1333
      return vState::BogusUnsupportedDNSKEYAlgo;
×
1334
    }
×
1335
    if (!dsDigestSupported) {
42!
1336
      return vState::BogusUnsupportedDSDigestType;
×
1337
    }
×
1338

1339
    bool zoneKey = false;
42✔
1340
    bool notRevoked = false;
42✔
1341
    bool validProtocol = false;
42✔
1342

1343
    for (const auto& key : tkeys) {
47✔
1344
      if (!isAZoneKey(*key)) {
47✔
1345
        continue;
2✔
1346
      }
2✔
1347
      zoneKey = true;
45✔
1348

1349
      if (isRevokedKey(*key)) {
45✔
1350
        continue;
2✔
1351
      }
2✔
1352
      notRevoked = true;
43✔
1353

1354
      if (key->d_protocol != 3) {
43!
1355
        continue;
×
1356
      }
×
1357
      validProtocol = true;
43✔
1358
    }
43✔
1359

1360
    if (!zoneKey) {
42✔
1361
      return vState::BogusNoZoneKeyBitSet;
2✔
1362
    }
2✔
1363
    if (!notRevoked) {
40✔
1364
      return vState::BogusRevokedDNSKEY;
2✔
1365
    }
2✔
1366
    if (!validProtocol) {
38!
1367
      return vState::BogusInvalidDNSKEYProtocol;
×
1368
    }
×
1369

1370
    return ede;
38✔
1371
  }
38✔
1372

1373
  return vState::Secure;
1,472✔
1374
}
1,514✔
1375

1376
bool isSupportedDS(const DSRecordContent& dsrec, const OptLog& log)
1377
{
51,648✔
1378
  if (!DNSCryptoKeyEngine::isAlgorithmSupported(dsrec.d_algorithm)) {
51,648✔
1379
    VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support algorithm number "<<std::to_string(dsrec.d_algorithm)<<endl);
4!
1380
    return false;
4✔
1381
  }
4✔
1382

1383
  if (!DNSCryptoKeyEngine::isDigestSupported(dsrec.d_digesttype)) {
51,644✔
1384
    VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support digest number "<<std::to_string(dsrec.d_digesttype)<<endl);
4!
1385
    return false;
4✔
1386
  }
4✔
1387

1388
  return true;
51,640✔
1389
}
51,644✔
1390

1391
DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
1392
{
30,526✔
1393
  for (const auto& sig : signatures) {
30,526✔
1394
    if (sig) {
30,525✔
1395
      return sig->d_signer;
30,525✔
1396
    }
30,525✔
1397
  }
30,524✔
1398

1399
  return {};
2,147,483,647✔
1400
}
30,526✔
1401

1402
const std::string& vStateToString(vState state)
1403
{
13,742✔
1404
  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" };
13,742✔
1405
  return vStates.at(static_cast<size_t>(state));
13,742✔
1406
}
13,742✔
1407

1408
std::ostream& operator<<(std::ostream &ostr, const vState dstate)
1409
{
13,549✔
1410
  ostr<<vStateToString(dstate);
13,549✔
1411
  return ostr;
13,549✔
1412
}
13,549✔
1413

1414
std::ostream& operator<<(std::ostream &ostr, const dState dstate)
1415
{
×
1416
  static const std::vector<std::string> dStates = {"no denial", "inconclusive", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
×
1417
  ostr<<dStates.at(static_cast<size_t>(dstate));
×
1418
  return ostr;
×
1419
}
×
1420

1421
void updateDNSSECValidationState(vState& state, const vState stateUpdate)
1422
{
17,157✔
1423
  if (stateUpdate == vState::TA) {
17,157!
1424
    state = vState::Secure;
×
1425
  }
×
1426
  else if (stateUpdate == vState::NTA) {
17,157!
1427
    state = vState::Insecure;
×
1428
  }
×
1429
  else if (vStateIsBogus(stateUpdate) || state == vState::Indeterminate) {
17,157✔
1430
    state = stateUpdate;
10,110✔
1431
  }
10,110✔
1432
  else if (stateUpdate == vState::Insecure) {
7,047✔
1433
    if (!vStateIsBogus(state)) {
5,655✔
1434
      state = vState::Insecure;
5,651✔
1435
    }
5,651✔
1436
  }
5,655✔
1437
}
17,157✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc