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

PowerDNS / pdns / 30349644487

28 Jul 2026 10:09AM UTC coverage: 66.97% (-4.2%) from 71.21%
30349644487

push

github

web-flow
Merge pull request #17797 from rgacogne/dnsname-too-long

dnsname: Account for the existing content when parsing labels

43612 of 82684 branches covered (52.75%)

Branch coverage included in aggregate %.

9 of 11 new or added lines in 2 files covered. (81.82%)

7403 existing lines in 84 files now uncovered.

127388 of 172656 relevant lines covered (73.78%)

7698493.62 hits per line

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

63.28
/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 "dnssec.hh"
25
#include "base32.hh"
26

27
uint32_t g_signatureInceptionSkew{0};
28
uint16_t g_maxNSEC3Iterations{0};
29
uint16_t g_maxRRSIGsPerRecordToConsider{0};
30
uint16_t g_maxNSEC3sPerRecordToConsider{0};
31
uint16_t g_maxDNSKEYsToConsider{0};
32
uint16_t g_maxDSsToConsider{0};
33

34
static bool isAZoneKey(const DNSKEYRecordContent& key)
35
{
10,575✔
36
  /* rfc4034 Section 2.1.1:
37
     "Bit 7 of the Flags field is the Zone Key flag.  If bit 7 has value 1,
38
     then the DNSKEY record holds a DNS zone key, and the DNSKEY RR's
39
     owner name MUST be the name of a zone.  If bit 7 has value 0, then
40
     the DNSKEY record holds some other type of DNS public key and MUST
41
     NOT be used to verify RRSIGs that cover RRsets."
42

43
     Let's check that this is a ZONE key, even though there is no other
44
     types of DNSKEYs at the moment.
45
  */
46
  return (key.d_flags & 256) != 0;
10,575✔
47
}
10,575✔
48

49
static bool isRevokedKey(const DNSKEYRecordContent& key)
50
{
10,572✔
51
  /* rfc5011 Section 3 */
52
  return (key.d_flags & 128) != 0;
10,572✔
53
}
10,572✔
54

55
static vector<shared_ptr<const DNSKEYRecordContent>> getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm, const OptLog& log)
56
{
5,857✔
57
  vector<shared_ptr<const DNSKEYRecordContent>> ret;
5,857✔
58

59
  for (const auto& key : keys) {
10,558✔
60
    if (!isAZoneKey(*key)) {
10,558✔
61
      VLOG(log, "Key for tag " << std::to_string(tag) << " and algorithm " << std::to_string(algorithm) << " is not a zone key, skipping" << endl;);
2!
62
      continue;
2✔
63
    }
2✔
64

65
    if (isRevokedKey(*key)) {
10,556✔
66
      VLOG(log, "Key for tag " << std::to_string(tag) << " and algorithm " << std::to_string(algorithm) << " has been revoked, skipping" << endl;);
2!
67
      continue;
2✔
68
    }
2✔
69

70
    if (key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm) {
10,556✔
71
      ret.push_back(key);
5,829✔
72
    }
5,829✔
73
  }
10,554✔
74

75
  return ret;
5,857✔
76
}
5,857✔
77

78
bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash)
79
{
10,806✔
80
  int order_bh = beginHash.compare(hash);
10,806✔
81
  int order_hn = hash.compare(nextHash);
10,806✔
82
  if (order_bh < 0 && order_hn < 0) { // beginHash < hash && hash < nextHash
10,806✔
83
    return true; // no wrap          BEGINNING --- HASH -- END
4,063✔
84
  }
4,063✔
85
  int order_bn = beginHash.compare(nextHash);
6,743✔
86
  if (order_hn < 0 && order_bn > 0) { // nextHash > hash && beginHash > nextHash
6,743✔
87
    return true; // wrap             HASH --- END --- BEGINNING
10✔
88
  }
10✔
89
  if (order_bn > 0 && order_bh < 0) { // nextHash < beginHash && beginHash < hash
6,733✔
90
    return true; // wrap other case  END --- BEGINNING --- HASH
16✔
91
  }
16✔
92
  if (order_bn == 0 && order_bh != 0) { // beginHash == nextHash && hash != beginHash
6,717!
93
    return true; // "we have only 1 NSEC3 record, LOL!"
×
94
  }
×
95
  return false;
6,717✔
96
}
6,717✔
97

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

119
// Exact same logic as above, except that the arguments are not hashes.
120
bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next)
121
{
225✔
122
  int order_bh = begin.canonCompare_three_way(name);
225✔
123
  int order_hn = name.canonCompare_three_way(next);
225✔
124
  if (order_bh < 0 && order_hn < 0) {
225✔
125
    return true; // no wrap          BEGINNING --- NAME -- NEXT
135✔
126
  }
135✔
127
  int order_bn = begin.canonCompare_three_way(next);
90✔
128
  if (order_hn < 0 && order_bn > 0) {
90✔
129
    return true; // wrap             NEXT --- END --- BEGINNING
6✔
130
  }
6✔
131
  if (order_bn > 0 && order_bh < 0) {
84✔
132
    return true; // wrap other case  END --- BEGINNING --- NEXT
4✔
133
  }
4✔
134
  if (order_bn == 0 && order_bh != 0) {
80!
135
    return true; // "we have only 1 NSEC record, LOL!"
4✔
136
  }
4✔
137
  return false;
76✔
138
}
80✔
139

140
static bool nsecProvesENT(const DNSName& name, const DNSName& begin, const DNSName& next)
141
{
106✔
142
  /* if name is an ENT:
143
     - begin < name
144
     - next is a child of name
145
  */
146
  return begin.canonCompare(name) && next != name && next.isPartOf(name);
106!
147
}
106✔
148

149
[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context)
150
{
18,563✔
151
  std::string result;
18,563✔
152

153
  if (g_maxNSEC3Iterations != 0 && iterations > g_maxNSEC3Iterations) {
18,563✔
154
    return result;
6✔
155
  }
6✔
156

157
  auto key = std::tuple(qname, salt, iterations);
18,557✔
158
  auto iter = context.d_nsec3Cache.find(key);
18,557✔
159
  if (iter != context.d_nsec3Cache.end()) {
18,557✔
160
    return iter->second;
14,447✔
161
  }
14,447✔
162

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

170
  result = hashQNameWithSalt(salt, iterations, qname);
4,108✔
171
  context.d_nsec3IterationsRemainingQuota -= iterations;
4,108✔
172
  context.d_nsec3Cache[key] = result;
4,108✔
173
  return result;
4,108✔
174
}
4,110✔
175

176
[[nodiscard]] static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, pdns::validation::ValidationContext& context)
177
{
18,442✔
178
  return getHashFromNSEC3(qname, nsec3.d_iterations, nsec3.d_salt, context);
18,442✔
179
}
18,442✔
180

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

191
  for (const auto& record : dsrecords) {
4,433✔
192
    if (record.d_type == QType::NSEC) {
4,433✔
193
      const auto nsec = getRR<NSECRecordContent>(record);
207✔
194
      if (!nsec) {
207!
195
        continue;
×
196
      }
×
197

198
      if (record.d_name == zone) {
207✔
199
        return !nsec->isSet(QType::NS);
205✔
200
      }
205✔
201

202
      if (isCoveredByNSEC(zone, record.d_name, nsec->d_next) && nsec->d_next.isPartOf(zone)) {
2!
203
        return true;
×
204
      }
×
205
    }
2✔
206
    else if (record.d_type == QType::NSEC3) {
4,226✔
207
      const auto nsec3 = getRR<NSEC3RecordContent>(record);
4,094✔
208
      if (!nsec3) {
4,094!
209
        continue;
×
210
      }
×
211

212
      if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
4,094!
213
        context.d_limitHit = true;
×
214
        return false;
×
215
      }
×
216
      nsec3sConsidered++;
4,094✔
217

218
      const string hash = getHashFromNSEC3(zone, *nsec3, context);
4,094✔
219
      if (hash.empty()) {
4,094!
220
        return false;
×
221
      }
×
222

223
      const string beginHash = fromBase32Hex(record.d_name.getRawLabel(0));
4,094✔
224
      if (beginHash == hash) {
4,094✔
225
        return !nsec3->isSet(QType::NS);
6✔
226
      }
6✔
227

228
      if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
4,088✔
229
        return !(nsec3->isOptOut());
2,044✔
230
      }
2,044✔
231
    }
4,088✔
232
  }
4,433✔
233

234
  return false;
10✔
235
}
2,265✔
236

237
static bool isRRSIGLabelCountValid(const RRSIGRecordContent& sign)
238
{
4,650✔
239
  const auto signerLabelCount = sign.d_signer.countLabels();
4,650✔
240
  return sign.d_labels >= signerLabelCount;
4,650✔
241
}
4,650✔
242

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

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

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

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

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

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

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

287
  if (signatures.empty()) {
721!
288
    return result;
×
289
  }
×
290

291
  const auto& sign = signatures.at(0);
721✔
292
  unsigned int labelsCount = initialOwner.countLabels();
721✔
293
  if (sign && sign->d_labels < labelsCount && isRRSIGLabelCountValid(*sign)) {
721!
294
    do {
52✔
295
      result.chopOff();
52✔
296
      labelsCount--;
52✔
297
    } while (sign->d_labels < labelsCount);
52!
298

299
    result = g_wildcarddnsname + result;
52✔
300
  }
52✔
301

302
  return result;
721✔
303
}
721✔
304

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

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

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

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

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

345
  return false;
4✔
346
}
18✔
347

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

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

376
        const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
43✔
377
        VLOG(log, qname << ": Comparing owner: " << owner << " with target: " << wildcard << endl);
43!
378

379
        if (qname != owner && qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
43!
380
          /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
381

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

393
        if (wildcard != owner && isCoveredByNSEC(wildcard, owner, nsec->d_next)) {
43!
394
          VLOG(log, qname << ":\tWildcard is covered" << endl);
41!
395
          return true;
41✔
396
        }
41✔
397
      }
43✔
398
    }
43✔
399
  }
43✔
400

401
  return false;
2✔
402
}
43✔
403

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

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

425
        const DNSName signer = getSigner(validset.second.signatures);
4,044✔
426
        if (!validset.first.first.isPartOf(signer) || !closestEncloser.isPartOf(signer)) {
4,044!
427
          continue;
×
428
        }
×
429

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

439
        if (beginHash == hash) {
4,044✔
440
          VLOG(log, closestEncloser << ":\tWildcard hash matches");
16!
441
          if (wildcardExists != nullptr) {
16!
442
            *wildcardExists = true;
16✔
443
          }
16✔
444

445
          /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
446
             Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
447
             nonexistence of any RRs below that zone cut, which include all RRs at
448
             that (original) owner name other than DS RRs, and all RRs below that
449
             owner name regardless of type.
450
          */
451
          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
16!
452
            /* this is an "ancestor delegation" NSEC3 RR */
453
            VLOG_NO_PREFIX(log, " BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS" << endl);
×
454
            return false;
×
455
          }
×
456

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

465
        if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
4,028✔
466
          VLOG(log, closestEncloser << ":\tWildcard hash is covered" << endl);
12!
467
          return true;
12✔
468
        }
12✔
469
      }
4,028✔
470
    }
4,044✔
471
  }
4,044✔
472

473
  return false;
2,001✔
474
}
2,029✔
475

476
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)
477
{
68✔
478
  const DNSName signer = getSigner(signatures);
68✔
479
  if (!name.isPartOf(signer) || !nsecOwner.isPartOf(signer)) {
68!
480
    return dState::INCONCLUSIVE;
2✔
481
  }
2✔
482

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

498
  /* check if the type is denied */
499
  if (name == owner) {
62✔
500
    if (!isTypeDenied(nsec, QType(qtype))) {
16✔
501
      VLOG_NO_PREFIX(log, "does _not_ deny existence of type " << QType(qtype) << endl);
2!
502
      return dState::NODENIAL;
2✔
503
    }
2✔
504

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

510
    VLOG_NO_PREFIX(log, "Denies existence of type " << QType(qtype) << endl);
12!
511
    return dState::NXQTYPE;
12✔
512
  }
14✔
513

514
  if (name.isPartOf(owner) && nsec.isSet(QType::DNAME)) {
46!
515
    /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
516

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

528
  if (isCoveredByNSEC(name, owner, nsec.d_next)) {
46✔
529
    VLOG_NO_PREFIX(log, name << ": is covered by (" << owner << " to " << nsec.d_next << ")");
31!
530

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

536
    return dState::NXDOMAIN;
16✔
537
  }
31✔
538

539
  return dState::INCONCLUSIVE;
15✔
540
}
46✔
541

542
[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength)
543
{
46✔
544
  return static_cast<uint64_t>((iterations + 1U + (saltLength > 0 ? 1U : 0U))) * maxLabels;
46✔
545
}
46✔
546

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

565
  uint8_t numberOfLabelsOfParentZone{std::numeric_limits<uint8_t>::max()};
2,616✔
566
  uint16_t nsec3sConsidered = 0;
2,616✔
567
  for (const auto& validset : validrrsets) {
4,726✔
568
    VLOG(log, qname << ": Do have: " << validset.first.first << "/" << DNSRecordContent::NumberToType(validset.first.second) << endl);
4,726!
569

570
    if (validset.first.second == QType::NSEC) {
4,726✔
571
      for (const auto& record : validset.second.records) {
584✔
572
        VLOG(log, qname << ":\t" << record->getZoneRepresentation() << endl);
584!
573

574
        if (validset.second.signatures.empty()) {
584!
575
          continue;
×
576
        }
×
577

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

583
        DNSName nameToDeny;
584✔
584
        if (!needWildcardProof) {
584✔
585
          /* we are trying to prove that a wildcard can apply, so in effect we need to prove that the
586
             next closer does not exist: the next closer can be different from the qname,
587
             and can prevent the wildcard from applying.
588
          */
589
          nameToDeny = qname;
14✔
590
          nameToDeny.trimToLabels(wildcardLabelsCount + 1);
14✔
591
        }
14✔
592
        else {
570✔
593
          nameToDeny = qname;
570✔
594
        }
570✔
595
        const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
584✔
596
        const DNSName signer = getSigner(validset.second.signatures);
584✔
597
        if (!validset.first.first.isPartOf(signer) || !owner.isPartOf(signer) || !nameToDeny.isPartOf(signer) || !nsec->d_next.isPartOf(signer)) {
584!
598
          VLOG(log, nameToDeny << ": Initial owner (" << validset.first.first << "), reconstructed owner (" << owner << ") or name to deny (" << nameToDeny << ") are not part of the signer (" << signer << ")!" << endl);
4!
599
          continue;
4✔
600
        }
4✔
601

602
        /* The NSEC is either a delegation one, from the parent zone, and
603
         * must have the NS bit set but not the SOA one, or a regular NSEC
604
         * either at apex (signer == owner) or with the SOA or NS bits clear.
605
         */
606
        const bool notApex = signer.countLabels() < owner.countLabels();
580✔
607
        if (notApex && nsec->isSet(QType::NS) && nsec->isSet(QType::SOA)) {
580✔
608
          VLOG(log, nameToDeny << ": However, that NSEC is not at the apex and has both the NS and the SOA bits set!" << endl);
2!
609
          continue;
2✔
610
        }
2✔
611

612
        /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
613
           Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
614
           nonexistence of any RRs below that zone cut, which include all RRs at
615
           that (original) owner name other than DS RRs, and all RRs below that
616
           owner name regardless of type.
617
        */
618
        if (nameToDeny.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, *nsec)) {
578✔
619
          /* this is an "ancestor delegation" NSEC RR */
620
          if (qtype != QType::DS || nameToDeny != owner) {
129!
621
            VLOG(log, nameToDeny << ": An ancestor delegation NSEC RR can only deny the existence of a DS" << endl);
4!
622
            return dState::NODENIAL;
4✔
623
          }
4✔
624
        }
129✔
625

626
        if (qtype == QType::DS && !nameToDeny.isRoot() && signer == nameToDeny) {
574✔
627
          VLOG(log, nameToDeny << ": A NSEC RR from the child zone cannot deny the existence of a DS" << endl);
2!
628
          continue;
2✔
629
        }
2✔
630

631
        /* check if the type is denied */
632
        if (nameToDeny == owner) {
572✔
633
          if (!isTypeDenied(*nsec, QType(qtype))) {
438✔
634
            VLOG(log, nameToDeny << ": Does _not_ deny existence of type " << QType(qtype) << endl);
4!
635
            return dState::NODENIAL;
4✔
636
          }
4✔
637

638
          VLOG(log, nameToDeny << ": Denies existence of type " << QType(qtype) << endl);
434!
639

640
          /*
641
           * RFC 4035 Section 2.3:
642
           * The bitmap for the NSEC RR at a delegation point requires special
643
           * attention.  Bits corresponding to the delegation NS RRset and any
644
           * RRsets for which the parent zone has authoritative data MUST be set
645
           */
646
          if (referralToUnsigned && qtype == QType::DS) {
434!
647
            if (!nsec->isSet(QType::NS)) {
93✔
648
              VLOG(log, nameToDeny << ": However, no NS record exists at this level!" << endl);
2!
649
              return dState::NODENIAL;
2✔
650
            }
2✔
651
          }
93✔
652

653
          /* we know that the name exists (but this qtype doesn't) so except
654
             if the answer was generated by a wildcard expansion, no wildcard
655
             could have matched (rfc4035 section 5.4 bullet 1) */
656
          if (needWildcardProof && (!isWildcardExpanded(owner, validset.second.signatures) || isWildcardExpandedOntoItself(owner, validset.second.signatures))) {
432!
657
            needWildcardProof = false;
428✔
658
          }
428✔
659

660
          if (!needWildcardProof) {
432!
661
            return dState::NXQTYPE;
432✔
662
          }
432✔
663

664
          DNSName closestEncloser = getClosestEncloserFromNSEC(nameToDeny, owner, nsec->d_next);
×
665
          if (provesNoWildCard(nameToDeny, qtype, closestEncloser, validrrsets, log)) {
×
666
            return dState::NXQTYPE;
×
667
          }
×
668

669
          VLOG(log, nameToDeny << ": But the existence of a wildcard is not denied for " << nameToDeny << "/" << endl);
×
670
          return dState::NODENIAL;
×
671
        }
×
672

673
        if (nameToDeny.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
134!
674
          /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
675

676
             In any negative response, the NSEC or NSEC3 [RFC5155] record type
677
             bitmap SHOULD be checked to see that there was no DNAME that could
678
             have been applied.  If the DNAME bit in the type bitmap is set and
679
             the query name is a subdomain of the closest encloser that is
680
             asserted, then DNAME substitution should have been done, but the
681
             substitution has not been done as specified.
682
          */
683
          VLOG(log, nameToDeny << ": The DNAME bit is set and the query name is a subdomain of that NSEC" << endl);
×
684
          return dState::NODENIAL;
×
685
        }
×
686

687
        /* check if the whole NAME is denied existing */
688
        if (isCoveredByNSEC(nameToDeny, owner, nsec->d_next)) {
134✔
689
          VLOG(log, nameToDeny << ": Is covered by (" << owner << " to " << nsec->d_next << ") ");
75!
690

691
          if (nsecProvesENT(nameToDeny, owner, nsec->d_next)) {
75✔
692
            if (wantsNoDataProof) {
4✔
693
              /* if the name is an ENT and we received a NODATA answer,
694
                 we are fine with a NSEC proving that the name does not exist. */
695
              VLOG_NO_PREFIX(log, "Denies existence of type " << nameToDeny << "/" << QType(qtype) << " by proving that " << nameToDeny << " is an ENT" << endl);
2!
696
              return dState::NXQTYPE;
2✔
697
            }
2✔
698
            /* but for a NXDOMAIN proof, this doesn't make sense! */
699
            VLOG_NO_PREFIX(log, "but it tries to deny the existence of " << nameToDeny << " by proving that " << nameToDeny << " is an ENT, this does not make sense!" << endl);
2!
700
            return dState::NODENIAL;
2✔
701
          }
4✔
702

703
          if (!needWildcardProof) {
71✔
704
            VLOG_NO_PREFIX(log, "and we did not need a wildcard proof" << endl);
10!
705
            return dState::NXDOMAIN;
10✔
706
          }
10✔
707

708
          VLOG_NO_PREFIX(log, "but we do need a wildcard proof so ");
61!
709
          DNSName closestEncloser = getClosestEncloserFromNSEC(nameToDeny, owner, nsec->d_next);
61✔
710
          if (wantsNoDataProof) {
61✔
711
            VLOG_NO_PREFIX(log, "looking for NODATA proof" << endl);
18!
712
            if (provesNoDataWildCard(nameToDeny, qtype, closestEncloser, validrrsets, log)) {
18✔
713
              return dState::NXQTYPE;
8✔
714
            }
8✔
715
          }
18✔
716
          else {
43✔
717
            VLOG_NO_PREFIX(log, "looking for NO wildcard proof" << endl);
43!
718
            if (provesNoWildCard(nameToDeny, qtype, closestEncloser, validrrsets, log)) {
43✔
719
              return dState::NXDOMAIN;
41✔
720
            }
41✔
721
          }
43✔
722

723
          VLOG(log, nameToDeny << ": But the existence of a wildcard is not denied for " << nameToDeny << "/" << endl);
12!
724
          return dState::NODENIAL;
12✔
725
        }
61✔
726

727
        VLOG(log, nameToDeny << ": Did not deny existence of " << QType(qtype) << ", " << validset.first.first << "?=" << nameToDeny << ", " << nsec->isSet(qtype) << ", next: " << nsec->d_next << endl);
59!
728
      }
59✔
729
    }
582✔
730
    else if (validset.first.second == QType::NSEC3) {
4,144!
731
      for (const auto& record : validset.second.records) {
4,145✔
732
        VLOG(log, qname << ":\t" << record->getZoneRepresentation() << endl);
4,145!
733
        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
4,145✔
734
        if (!nsec3) {
4,145!
735
          continue;
×
736
        }
×
737

738
        if (validset.second.signatures.empty()) {
4,145!
739
          continue;
×
740
        }
×
741

742
        const DNSName& hashedOwner = validset.first.first;
4,145✔
743
        const DNSName signer = getSigner(validset.second.signatures);
4,145✔
744
        if (!hashedOwner.isPartOf(signer)) {
4,145✔
745
          VLOG(log, qname << ": Owner " << hashedOwner << " is not part of the signer " << signer << ", ignoring" << endl);
2!
746
          continue;
2✔
747
        }
2✔
748
        numberOfLabelsOfParentZone = std::min(numberOfLabelsOfParentZone, static_cast<uint8_t>(signer.countLabels()));
4,143✔
749

750
        if (!qname.isPartOf(signer)) {
4,143!
751
          VLOG(log, qname << ": is not part of the signer (" << signer << ")!" << endl);
×
752
          continue;
×
753
        }
×
754

755
        if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
4,144✔
756
          VLOG(log, qname << ": A NSEC3 RR from the child zone cannot deny the existence of a DS" << endl);
2!
757
          continue;
2✔
758
        }
2✔
759

760
        if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
4,141✔
761
          VLOG(log, qname << ": Too many NSEC3s for this record" << endl);
4!
762
          context.d_limitHit = true;
4✔
763
          return dState::NODENIAL;
4✔
764
        }
4✔
765
        nsec3sConsidered++;
4,137✔
766

767
        string hash = getHashFromNSEC3(qname, *nsec3, context);
4,137✔
768
        if (hash.empty()) {
4,137✔
769
          VLOG(log, qname << ": Unsupported hash, ignoring" << endl);
6!
770
          return dState::INSECURE;
6✔
771
        }
6✔
772

773
        nsec3Seen = true;
4,131✔
774

775
        VLOG(log, qname << ":\tquery hash: " << toBase32Hex(hash) << endl);
4,131!
776
        string beginHash = fromBase32Hex(hashedOwner.getRawLabel(0));
4,131✔
777

778
        // If the name exists, check if the qtype is denied
779
        if (beginHash == hash) {
4,131✔
780

781
          /* The NSEC3 is either a delegation one, from the parent zone, and
782
           * must have the NS bit set but not the SOA one, or a regular NSEC3
783
           * either at apex (signer == owner) or with the SOA or NS bits clear.
784
           */
785
          const bool notApex = signer.countLabels() < qname.countLabels();
18✔
786
          if (notApex && nsec3->isSet(QType::NS) && nsec3->isSet(QType::SOA)) {
18✔
787
            VLOG(log, qname << ": However, that NSEC3 is not at the apex and has both the NS and the SOA bits set!" << endl);
2!
788
            continue;
2✔
789
          }
2✔
790

791
          /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
792
             Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
793
             nonexistence of any RRs below that zone cut, which include all RRs at
794
             that (original) owner name other than DS RRs, and all RRs below that
795
             owner name regardless of type.
796
          */
797
          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, qname, *nsec3)) {
16✔
798
            /* this is an "ancestor delegation" NSEC3 RR */
799
            VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS" << endl);
2!
800
            return dState::NODENIAL;
2✔
801
          }
2✔
802

803
          if (!isTypeDenied(*nsec3, QType(qtype))) {
14✔
804
            VLOG(log, qname << ": Does _not_ deny existence of type " << QType(qtype) << " for name " << qname << " (not opt-out)." << endl);
2!
805
            return dState::NODENIAL;
2✔
806
          }
2✔
807

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

810
          /*
811
           * RFC 5155 section 8.9:
812
           * If there is an NSEC3 RR present in the response that matches the
813
           * delegation name, then the validator MUST ensure that the NS bit is
814
           * set and that the DS bit is not set in the Type Bit Maps field of the
815
           * NSEC3 RR.
816
           */
817
          if (referralToUnsigned && qtype == QType::DS) {
12!
818
            if (!nsec3->isSet(QType::NS)) {
6✔
819
              VLOG(log, qname << ": However, no NS record exists at this level!" << endl);
2!
820
              return dState::NODENIAL;
2✔
821
            }
2✔
822
          }
6✔
823

824
          return dState::NXQTYPE;
10✔
825
        }
12✔
826
      }
4,131✔
827
    }
4,144✔
828
  }
4,726✔
829

830
  /* if we have no NSEC3 records, we are done */
831
  if (!nsec3Seen) {
2,073✔
832
    return dState::NODENIAL;
32✔
833
  }
32✔
834

835
  DNSName closestEncloser(qname);
2,041✔
836
  bool found = false;
2,041✔
837
  if (needWildcardProof) {
2,041✔
838
    /* We now need to look for a NSEC3 covering the closest (provable) encloser
839
       RFC 5155 section-7.2.1
840
       RFC 7129 section-5.5
841
    */
842
    VLOG(log, qname << ": Now looking for the closest encloser for " << qname << endl);
2,037!
843

844
    while (!found && closestEncloser.chopOff() && closestEncloser.countLabels() >= numberOfLabelsOfParentZone) {
4,087✔
845
      nsec3sConsidered = 0;
2,050✔
846

847
      for (const auto& validset : validrrsets) {
3,471✔
848
        if (validset.first.second == QType::NSEC3) {
3,471!
849
          for (const auto& record : validset.second.records) {
3,471✔
850
            VLOG(log, qname << ":\t" << record->getZoneRepresentation() << endl);
3,471!
851
            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
3,471✔
852
            if (!nsec3) {
3,471!
853
              continue;
×
854
            }
×
855

856
            const DNSName signer = getSigner(validset.second.signatures);
3,471✔
857
            if (!validset.first.first.isPartOf(signer)) {
3,471!
858
              VLOG(log, qname << ": Owner " << validset.first.first << " is not part of the signer " << signer << ", ignoring" << endl);
×
859
              continue;
×
860
            }
×
861

862
            if (!closestEncloser.isPartOf(signer)) {
3,471!
863
              continue;
×
864
            }
×
865

866
            if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
3,472!
867
              VLOG(log, qname << ": Too many NSEC3s for this record" << endl);
×
868
              context.d_limitHit = true;
×
869
              return dState::NODENIAL;
×
870
            }
×
871
            nsec3sConsidered++;
3,471✔
872

873
            string hash = getHashFromNSEC3(closestEncloser, *nsec3, context);
3,471✔
874
            if (hash.empty()) {
3,471!
875
              VLOG(log, qname << ": Unsupported hash, ignoring" << endl);
×
876
              return dState::INSECURE;
×
877
            }
×
878

879
            string beginHash = fromBase32Hex(validset.first.first.getRawLabel(0));
3,471✔
880

881
            VLOG(log, qname << ": Comparing " << toBase32Hex(hash) << " (" << closestEncloser << ") against " << toBase32Hex(beginHash) << endl);
3,471!
882
            if (beginHash == hash) {
3,471✔
883
              /* If the closest encloser is a delegation NS we know nothing about the names in the child zone. */
884
              if (isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
2,035✔
885
                VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS" << endl);
4!
886
                continue;
4✔
887
              }
4✔
888

889
              VLOG(log, qname << ": Closest encloser for " << qname << " is " << closestEncloser << endl);
2,031!
890
              found = true;
2,031✔
891

892
              if (nsec3->isSet(QType::DNAME)) {
2,031!
893
                /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
894

895
                   In any negative response, the NSEC or NSEC3 [RFC5155] record type
896
                   bitmap SHOULD be checked to see that there was no DNAME that could
897
                   have been applied.  If the DNAME bit in the type bitmap is set and
898
                   the query name is a subdomain of the closest encloser that is
899
                   asserted, then DNAME substitution should have been done, but the
900
                   substitution has not been done as specified.
901
                */
902
                VLOG(log, qname << ":\tThe closest encloser NSEC3 has the DNAME bit is set" << endl);
×
903
                return dState::NODENIAL;
×
904
              }
×
905

906
              break;
2,031✔
907
            }
2,031✔
908
          }
3,471✔
909
        }
3,471✔
910
        if (found) {
3,471✔
911
          break;
2,031✔
912
        }
2,031✔
913
      }
3,471✔
914
    }
2,050✔
915
  }
2,037✔
916
  else {
4✔
917
    /* RFC 5155 section-7.2.6:
918
       "It is not necessary to return an NSEC3 RR that matches the closest encloser,
919
       as the existence of this closest encloser is proven by the presence of the
920
       expanded wildcard in the response.
921
    */
922
    found = true;
4✔
923
    unsigned int closestEncloserLabelsCount = closestEncloser.countLabels();
4✔
924
    while (wildcardLabelsCount > 0 && closestEncloserLabelsCount > wildcardLabelsCount) {
10!
925
      closestEncloser.chopOff();
6✔
926
      closestEncloserLabelsCount--;
6✔
927
    }
6✔
928
  }
4✔
929

930
  bool nextCloserFound = false;
2,041✔
931
  bool isOptOut = false;
2,041✔
932

933
  if (found) {
2,041✔
934
    /* now that we have found the closest (provable) encloser,
935
       we can construct the next closer (RFC7129 section-5.5) name
936
       and look for a NSEC3 RR covering it */
937
    unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels();
2,035✔
938
    if (labelIdx >= 1) {
2,035!
939
      DNSName nextCloser(closestEncloser);
2,035✔
940
      nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
2,035✔
941
      nsec3sConsidered = 0;
2,035✔
942
      VLOG(log, qname << ":Looking for a NSEC3 covering the next closer name " << nextCloser << endl);
2,035!
943

944
      for (const auto& validset : validrrsets) {
2,695✔
945
        if (validset.first.second == QType::NSEC3) {
2,695!
946
          for (const auto& record : validset.second.records) {
2,695✔
947
            VLOG(log, qname << ":\t" << record->getZoneRepresentation() << endl);
2,695!
948
            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
2,695✔
949
            if (!nsec3) {
2,695!
950
              continue;
×
951
            }
×
952

953
            if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
2,695!
954
              VLOG(log, qname << ": Too many NSEC3s for this record" << endl);
×
955
              context.d_limitHit = true;
×
956
              return dState::NODENIAL;
×
957
            }
×
958
            nsec3sConsidered++;
2,695✔
959

960
            string hash = getHashFromNSEC3(nextCloser, *nsec3, context);
2,695✔
961
            if (hash.empty()) {
2,695!
962
              VLOG(log, qname << ": Unsupported hash, ignoring" << endl);
×
963
              return dState::INSECURE;
×
964
            }
×
965

966
            const DNSName signer = getSigner(validset.second.signatures);
2,695✔
967
            if (!validset.first.first.isPartOf(signer)) {
2,695✔
968
              VLOG(log, qname << ": Owner " << validset.first.first << " is not part of the signer " << signer << ", ignoring" << endl);
2!
969
              continue;
2✔
970
            }
2✔
971

972
            if (!nextCloser.isPartOf(signer)) {
2,693!
973
              continue;
×
974
            }
×
975

976
            string beginHash = fromBase32Hex(validset.first.first.getRawLabel(0));
2,693✔
977

978
            VLOG(log, qname << ": Comparing " << toBase32Hex(hash) << " against " << toBase32Hex(beginHash) << " -> " << toBase32Hex(nsec3->d_nexthash) << endl);
2,693!
979
            if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
2,693✔
980
              VLOG(log, qname << ": Denies existence of name " << qname << "/" << QType(qtype));
2,033!
981
              nextCloserFound = true;
2,033✔
982

983
              if (nsec3->isOptOut()) {
2,033✔
984
                VLOG_NO_PREFIX(log, " but is opt-out!");
2,003!
985
                isOptOut = true;
2,003✔
986
              }
2,003✔
987

988
              VLOG_NO_PREFIX(log, endl);
2,033!
989
              break;
2,033✔
990
            }
2,033✔
991
            VLOG(log, qname << ": Did not cover us (" << qname << "), start=" << validset.first.first << ", us=" << toBase32Hex(hash) << ", end=" << toBase32Hex(nsec3->d_nexthash) << endl);
660!
992
          }
660✔
993
        }
2,695✔
994
        if (nextCloserFound) {
2,695✔
995
          break;
2,033✔
996
        }
2,033✔
997
      }
2,695✔
998
    }
2,035✔
999
  }
2,035✔
1000

1001
  if (nextCloserFound) {
2,041✔
1002
    bool wildcardExists = false;
2,033✔
1003
    /* RFC 7129 section-5.6 */
1004
    if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, log, context)) {
2,033✔
1005
      if (!isOptOut) {
2,005✔
1006
        VLOG(log, qname << ": But the existence of a wildcard is not denied for " << qname << "/" << QType(qtype) << endl);
8!
1007
        return dState::NODENIAL;
8✔
1008
      }
8✔
1009
    }
2,005✔
1010

1011
    if (isOptOut) {
2,025✔
1012
      return dState::OPTOUT;
2,003✔
1013
    }
2,003✔
1014
    if (wildcardExists) {
22✔
1015
      return dState::NXQTYPE;
10✔
1016
    }
10✔
1017
    return dState::NXDOMAIN;
12✔
1018
  }
22✔
1019

1020
  // There were no valid NSEC(3) records
1021
  return dState::NODENIAL;
8✔
1022
}
2,041✔
1023

1024
bool isRRSIGNotExpired(const time_t now, const RRSIGRecordContent& sig)
1025
{
20,872✔
1026
  // it's an uint32_t rfc1982 compare, explicitly cast now to uint32_t to avoid Coverity warning
1027
  // implicitly converting a time_t to a smaller int.
1028
  return rfc1982LessThanOrEqual<uint32_t>(static_cast<uint32_t>(now), sig.d_sigexpire);
20,872✔
1029
}
20,872✔
1030

1031
bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig)
1032
{
4,965✔
1033
  // it's an uint32_t rfc1982 compare, explicitly cast now to uint32_t to avoid Coverity warning
1034
  // implicitly converting a time_t to a smaller int.
1035
  return rfc1982LessThanOrEqual<uint32_t>(sig.d_siginception - g_signatureInceptionSkew, static_cast<uint32_t>(now));
4,965✔
1036
}
4,965✔
1037

1038
namespace
1039
{
1040
[[nodiscard]] bool checkSignatureInceptionAndExpiry(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, vState& ede, const OptLog& log)
1041
{
4,927✔
1042
  /* rfc4035:
1043
     - 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.
1044
     - 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.
1045
  */
1046
  vState localEDE = vState::Indeterminate;
4,927✔
1047
  if (!isRRSIGIncepted(now, sig)) {
4,927✔
1048
    localEDE = vState::BogusSignatureNotYetValid;
4✔
1049
  }
4✔
1050
  else if (!isRRSIGNotExpired(now, sig)) {
4,923✔
1051
    localEDE = vState::BogusSignatureExpired;
6✔
1052
  }
6✔
1053
  if (localEDE == vState::Indeterminate) {
4,927✔
1054
    return true;
4,917✔
1055
  }
4,917✔
1056
  ede = localEDE;
10✔
1057
  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);
10!
1058
  return false;
10✔
1059
}
4,927✔
1060

1061
[[nodiscard]] bool checkSignatureWithKey(const DNSName& qname, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
1062
{
4,898✔
1063
  bool result = false;
4,898✔
1064
  try {
4,898✔
1065
    auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(g_slog->withName("validate"), key.d_algorithm, key.d_key);
4,898✔
1066
    result = dke->verify(msg, sig.d_signature);
4,898✔
1067
    VLOG(log, qname << ": Signature by key with tag " << sig.d_tag << " and algorithm " << DNSSEC::algorithm2name(sig.d_algorithm) << " was " << (result ? "" : "NOT ") << "valid" << endl);
4,898!
1068
    if (!result) {
4,898✔
1069
      ede = vState::BogusNoValidRRSIG;
28✔
1070
    }
28✔
1071
  }
4,898✔
1072
  catch (const std::exception& e) {
4,898✔
1073
    VLOG(log, qname << ": Could not make a validator for signature: " << e.what() << endl);
×
1074
    ede = vState::BogusUnsupportedDNSKEYAlgo;
×
1075
  }
×
1076
  return result;
4,898✔
1077
}
4,898✔
1078

1079
}
1080

1081
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)
1082
{
4,522✔
1083
  // whether we could not find the corresponding key in keys for at least one signature
1084
  bool missingKey = false;
4,522✔
1085
  // whether we were able to validate at least one signature
1086
  bool isValid = false;
4,522✔
1087
  // whether all signatures were expired (will be set to false if at least one signature is not expired)
1088
  bool allExpired = true;
4,522✔
1089
  // whether we discarded all signatures (will be set to false if we accept, but not necessarily validate, at least one signature)
1090
  bool allDiscarded = true;
4,522✔
1091
  // whether all signatures were not yet incepted (will be set to false if at least one signature is found to be incepted)
1092
  bool noneIncepted = true;
4,522✔
1093
  uint16_t signaturesConsidered = 0;
4,522✔
1094

1095
  for (const auto& signature : signatures) {
4,539✔
1096
    unsigned int labelCount = name.countLabels();
4,539✔
1097
    if (signature->d_labels > labelCount) {
4,539!
1098
      VLOG(log, name << ": Discarding invalid RRSIG whose label count is " << signature->d_labels << " while the RRset owner name has only " << labelCount << endl);
×
1099
      continue;
×
1100
    }
×
1101
    if (!isRRSIGLabelCountValid(*signature)) {
4,539✔
1102
      VLOG(log, name << ": Discarding invalid RRSIG whose label count is " << signature->d_labels << ", not enough to match the signer which has " << signature->d_signer.countLabels() << endl);
4!
1103
      continue;
4✔
1104
    }
4✔
1105
    // we don't have the information in this function at the moment, but it would be nice to validate that the signer is coherent with the zone we are validating
1106

1107
    allDiscarded = false;
4,535✔
1108

1109
    vState ede = vState::Indeterminate;
4,535✔
1110
    if (!DNSCryptoKeyEngine::isAlgorithmSupported(signature->d_algorithm)) {
4,535!
1111
      continue;
×
1112
    }
×
1113
    if (!checkSignatureInceptionAndExpiry(name, now, *signature, ede, log)) {
4,535✔
1114
      if (isRRSIGIncepted(now, *signature)) {
10✔
1115
        noneIncepted = false;
6✔
1116
      }
6✔
1117
      if (isRRSIGNotExpired(now, *signature)) {
10✔
1118
        allExpired = false;
4✔
1119
      }
4✔
1120
      continue;
10✔
1121
    }
10✔
1122

1123
    if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
4,525✔
1124
      VLOG(log, name << ": We have already considered " << std::to_string(signaturesConsidered) << " RRSIG" << addS(signaturesConsidered) << " for this record, stopping now" << endl;);
2!
1125
      // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1126
      context.d_limitHit = true;
2✔
1127
      break;
2✔
1128
    }
2✔
1129
    signaturesConsidered++;
4,523✔
1130
    context.d_validationsCounter++;
4,523✔
1131

1132
    auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm, log);
4,523✔
1133

1134
    if (keysMatchingTag.empty()) {
4,523✔
1135
      VLOG(log, name << ": No key provided for " << signature->d_tag << " and algorithm " << std::to_string(signature->d_algorithm) << endl;);
4!
1136
      missingKey = true;
4✔
1137
      continue;
4✔
1138
    }
4✔
1139

1140
    string msg = getMessageForRRSET(name, *signature, toSign, true);
4,519✔
1141
    uint16_t dnskeysConsidered = 0;
4,519✔
1142
    for (const auto& key : keysMatchingTag) {
4,521✔
1143
      if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
4,521✔
1144
        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!
1145
        if (!isValid) {
2!
1146
          context.d_limitHit = true;
2✔
1147
        }
2✔
1148
        return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
2!
1149
      }
2✔
1150
      dnskeysConsidered++;
4,519✔
1151

1152
      bool signIsValid = checkSignatureWithKey(name, *signature, *key, msg, ede, log);
4,519✔
1153

1154
      if (signIsValid) {
4,519✔
1155
        isValid = true;
4,491✔
1156
        VLOG(log, name << ": Validated " << name << "/" << DNSRecordContent::NumberToType(signature->d_type) << endl);
4,491!
1157
        //          cerr<<"valid"<<endl;
1158
        //          cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
1159
      }
4,491✔
1160
      else {
28✔
1161
        VLOG(log, name << ": signature invalid" << endl);
28!
1162
        if (isRRSIGIncepted(now, *signature)) {
28!
1163
          noneIncepted = false;
28✔
1164
        }
28✔
1165
        if (isRRSIGNotExpired(now, *signature)) {
28!
1166
          allExpired = false;
28✔
1167
        }
28✔
1168
      }
28✔
1169

1170
      if (signIsValid && !validateAllSigs) {
4,519✔
1171
        return vState::Secure;
4,487✔
1172
      }
4,487✔
1173
    }
4,519✔
1174
  }
4,519✔
1175

1176
  if (isValid) {
2,147,483,681✔
1177
    return vState::Secure;
4✔
1178
  }
4✔
1179
  if (missingKey) {
2,147,483,677✔
1180
    return vState::BogusNoValidRRSIG;
4✔
1181
  }
4✔
1182
  if (allDiscarded) {
2,147,483,673✔
1183
    return vState::BogusNoValidRRSIG;
4✔
1184
  }
4✔
1185
  if (noneIncepted) {
2,147,483,669✔
1186
    // ede should be vState::BogusSignatureNotYetValid
1187
    return vState::BogusSignatureNotYetValid;
4✔
1188
  }
4✔
1189
  if (allExpired) {
2,147,483,665✔
1190
    // ede should be vState::BogusSignatureExpired);
1191
    return vState::BogusSignatureExpired;
6✔
1192
  }
6✔
1193

1194
  return vState::BogusNoValidRRSIG;
2,147,483,659✔
1195
}
2,147,483,665✔
1196

1197
bool getTrustAnchor(const map<DNSName, dsset_t>& anchors, const DNSName& zone, dsset_t& res)
1198
{
79,957✔
1199
  const auto& iter = anchors.find(zone);
79,957✔
1200

1201
  if (iter == anchors.cend()) {
79,957✔
1202
    return false;
58,760✔
1203
  }
58,760✔
1204

1205
  res = iter->second;
21,197✔
1206
  return true;
21,197✔
1207
}
79,957✔
1208

1209
bool haveNegativeTrustAnchor(const map<DNSName, std::string>& negAnchors, const DNSName& zone, std::string& reason)
1210
{
79,988✔
1211
  const auto& iter = negAnchors.find(zone);
79,988✔
1212

1213
  if (iter == negAnchors.cend()) {
79,990✔
1214
    return false;
79,958✔
1215
  }
79,958✔
1216

1217
  reason = iter->second;
2,147,483,677✔
1218
  return true;
2,147,483,677✔
1219
}
79,988✔
1220

1221
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)
1222
{
908✔
1223
  /*
1224
   * Check all DNSKEY records against all DS records and place all DNSKEY records
1225
   * that have DS records (that we support the algo for) in the tentative key storage
1226
   */
1227
  uint16_t dssConsidered = 0;
908✔
1228
  for (const auto& dsrc : dsset) {
944✔
1229
    if (g_maxDSsToConsider > 0 && dssConsidered > g_maxDSsToConsider) {
944✔
1230
      VLOG(log, zone << ": We have already considered " << std::to_string(dssConsidered) << " DS" << addS(dssConsidered) << ", not considering the remaining ones" << endl;);
2!
1231
      return vState::BogusNoValidDNSKEY;
2✔
1232
    }
2✔
1233
    ++dssConsidered;
942✔
1234

1235
    uint16_t dnskeysConsidered = 0;
942✔
1236
    auto record = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
942✔
1237
    // cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<DNSSEC::algorithm2name(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
1238

1239
    auto slog = g_slog->withName("validate");
942✔
1240
    for (const auto& drc : record) {
942✔
1241
      bool isValid = false;
930✔
1242
      bool dsCreated = false;
930✔
1243
      DSRecordContent dsrc2;
930✔
1244

1245
      if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
930✔
1246
        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!
1247
        // we need to break because we can have a partially validated set
1248
        // where the KSK signs the ZSK(s), and even if we don't
1249
        // we are going to try to get the correct EDE status (revoked, expired, ...)
1250
        context.d_limitHit = true;
4✔
1251
        break;
4✔
1252
      }
4✔
1253
      dnskeysConsidered++;
926✔
1254

1255
      try {
926✔
1256
        dsrc2 = makeDSFromDNSKey(slog, zone, *drc, dsrc.d_digesttype);
926✔
1257
        dsCreated = true;
926✔
1258
        isValid = dsrc.operator==(dsrc2);
926✔
1259
      }
926✔
1260
      catch (const std::exception& e) {
926✔
1261
        VLOG(log, zone << ": Unable to make DS from DNSKey: " << e.what() << endl);
×
1262
      }
×
1263

1264
      if (isValid) {
926✔
1265
        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);
924!
1266

1267
        validkeys.insert(drc);
924✔
1268
      }
924✔
1269
      else {
2✔
1270
        if (dsCreated) {
2!
1271
          VLOG(log, zone << ": DNSKEY did not match the DS, parent DS: " << dsrc.getZoneRepresentation() << " ! = " << dsrc2.getZoneRepresentation() << endl);
2!
1272
        }
2✔
1273
      }
2✔
1274
    }
926✔
1275
  }
942✔
1276

1277
  vState ede = vState::BogusNoValidDNSKEY;
906✔
1278

1279
  //    cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
1280
  // these counts could be off if we somehow ended up with
1281
  // duplicate keys. Should switch to a type that prevents that.
1282
  if (!tkeys.empty() && validkeys.size() < tkeys.size()) {
906!
1283
    // this should mean that we have one or more DS-validated DNSKEYs
1284
    // but not a fully validated DNSKEY set, yet
1285
    // one of these valid DNSKEYs should be able to validate the
1286
    // whole set
1287
    uint16_t signaturesConsidered = 0;
393✔
1288
    for (const auto& sig : sigs) {
393✔
1289
      if (!DNSCryptoKeyEngine::isAlgorithmSupported(sig->d_algorithm)) {
393!
1290
        continue;
×
1291
      }
×
1292
      if (!checkSignatureInceptionAndExpiry(zone, now, *sig, ede, log)) {
393!
UNCOV
1293
        continue;
×
UNCOV
1294
      }
×
1295

1296
      //        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;
1297
      auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm, log);
393✔
1298

1299
      if (bytag.empty()) {
393✔
1300
        continue;
14✔
1301
      }
14✔
1302

1303
      if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
379!
1304
        VLOG(log, zone << ": We have already considered " << std::to_string(signaturesConsidered) << " RRSIG" << addS(signaturesConsidered) << " for this record, stopping now" << endl;);
×
1305
        // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1306
        context.d_limitHit = true;
×
1307
        return vState::BogusNoValidDNSKEY;
×
1308
      }
×
1309

1310
      string msg = getMessageForRRSET(zone, *sig, toSign);
379✔
1311
      uint16_t dnskeysConsidered = 0;
379✔
1312
      for (const auto& key : bytag) {
379!
1313
        if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
379!
1314
          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;);
×
1315
          context.d_limitHit = true;
×
1316
          return vState::BogusNoValidDNSKEY;
×
1317
        }
×
1318
        dnskeysConsidered++;
379✔
1319

1320
        if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
379!
1321
          VLOG(log, zone << ": We have already considered " << std::to_string(signaturesConsidered) << " RRSIG" << addS(signaturesConsidered) << " for this record, stopping now" << endl;);
×
1322
          // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
1323
          context.d_limitHit = true;
×
1324
          return vState::BogusNoValidDNSKEY;
×
1325
        }
×
1326
        //          cerr<<"validating : ";
1327
        bool signIsValid = checkSignatureWithKey(zone, *sig, *key, msg, ede, log);
379✔
1328
        signaturesConsidered++;
379✔
1329
        context.d_validationsCounter++;
379✔
1330

1331
        if (signIsValid) {
379!
1332
          VLOG(log, zone << ": Validation succeeded - whole DNSKEY set is valid" << endl);
379!
1333
          validkeys = tkeys;
379✔
1334
          break;
379✔
1335
        }
379✔
1336
        VLOG(log, zone << ": Validation did not succeed!" << endl);
×
1337
      }
×
1338

1339
      if (validkeys.size() == tkeys.size()) {
379!
1340
        // we validated the whole DNSKEY set already */
1341
        break;
379✔
1342
      }
379✔
1343
      //        if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
1344
    }
379✔
1345
  }
393✔
1346

1347
  if (validkeys.size() < tkeys.size()) {
906✔
1348
    /* so we failed to validate the whole set, let's try to find out why exactly */
1349
    bool dnskeyAlgoSupported = false;
14✔
1350
    bool dsDigestSupported = false;
14✔
1351

1352
    for (const auto& dsrc : dsset) {
16✔
1353
      if (DNSCryptoKeyEngine::isAlgorithmSupported(dsrc.d_algorithm)) {
16!
1354
        dnskeyAlgoSupported = true;
16✔
1355
        if (DNSCryptoKeyEngine::isDigestSupported(dsrc.d_digesttype)) {
16!
1356
          dsDigestSupported = true;
16✔
1357
        }
16✔
1358
      }
16✔
1359
    }
16✔
1360

1361
    if (!dnskeyAlgoSupported) {
14!
1362
      return vState::BogusUnsupportedDNSKEYAlgo;
×
1363
    }
×
1364
    if (!dsDigestSupported) {
14!
1365
      return vState::BogusUnsupportedDSDigestType;
×
1366
    }
×
1367

1368
    bool zoneKey = false;
14✔
1369
    bool notRevoked = false;
14✔
1370
    bool validProtocol = false;
14✔
1371

1372
    for (const auto& key : tkeys) {
18✔
1373
      if (!isAZoneKey(*key)) {
18✔
1374
        continue;
2✔
1375
      }
2✔
1376
      zoneKey = true;
16✔
1377

1378
      if (isRevokedKey(*key)) {
16✔
1379
        continue;
2✔
1380
      }
2✔
1381
      notRevoked = true;
14✔
1382

1383
      if (key->d_protocol != 3) {
14!
1384
        continue;
×
1385
      }
×
1386
      validProtocol = true;
14✔
1387
    }
14✔
1388

1389
    if (!zoneKey) {
14✔
1390
      return vState::BogusNoZoneKeyBitSet;
2✔
1391
    }
2✔
1392
    if (!notRevoked) {
12✔
1393
      return vState::BogusRevokedDNSKEY;
2✔
1394
    }
2✔
1395
    if (!validProtocol) {
10!
1396
      return vState::BogusInvalidDNSKEYProtocol;
×
1397
    }
×
1398

1399
    return ede;
10✔
1400
  }
10✔
1401

1402
  return vState::Secure;
892✔
1403
}
906✔
1404

1405
bool isSupportedDS(const DSRecordContent& dsrec, const OptLog& log)
1406
{
42,456✔
1407
  if (!DNSCryptoKeyEngine::isAlgorithmSupported(dsrec.d_algorithm)) {
42,456✔
1408
    VLOG(log, "Discarding DS " << dsrec.d_tag << " because we don't support algorithm number " << std::to_string(dsrec.d_algorithm) << endl);
4!
1409
    return false;
4✔
1410
  }
4✔
1411

1412
  if (!DNSCryptoKeyEngine::isDigestSupported(dsrec.d_digesttype)) {
42,452✔
1413
    VLOG(log, "Discarding DS " << dsrec.d_tag << " because we don't support digest number " << std::to_string(dsrec.d_digesttype) << endl);
4!
1414
    return false;
4✔
1415
  }
4✔
1416

1417
  return true;
42,448✔
1418
}
42,452✔
1419

1420
DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures)
1421
{
26,255✔
1422
  for (const auto& sig : signatures) {
26,256✔
1423
    if (sig) {
26,253!
1424
      return sig->d_signer;
26,253✔
1425
    }
26,253✔
1426
  }
26,253✔
1427

1428
  return {};
2,147,483,647✔
1429
}
26,255✔
1430

1431
const std::string& vStateToString(vState state)
1432
{
290✔
1433
  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"};
290✔
1434
  return vStates.at(static_cast<size_t>(state));
290✔
1435
}
290✔
1436

1437
std::ostream& operator<<(std::ostream& ostr, const vState dstate)
1438
{
246✔
1439
  ostr << vStateToString(dstate);
246✔
1440
  return ostr;
246✔
1441
}
246✔
1442

1443
std::ostream& operator<<(std::ostream& ostr, const dState dstate)
1444
{
×
1445
  constexpr std::array dStates = {"no denial", "inconclusive", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
×
1446
  ostr << dStates.at(static_cast<size_t>(dstate));
×
1447
  return ostr;
×
1448
}
×
1449

1450
void updateDNSSECValidationState(vState& state, const vState stateUpdate)
1451
{
17,826✔
1452
  if (stateUpdate == vState::TA) {
17,826!
1453
    state = vState::Secure;
×
1454
  }
×
1455
  else if (stateUpdate == vState::NTA) {
17,826!
1456
    state = vState::Insecure;
×
1457
  }
×
1458
  else if (vStateIsBogus(stateUpdate) || state == vState::Indeterminate) {
17,826✔
1459
    state = stateUpdate;
8,784✔
1460
  }
8,784✔
1461
  else if (stateUpdate == vState::Insecure) {
9,042✔
1462
    if (!vStateIsBogus(state)) {
6,604✔
1463
      state = vState::Insecure;
6,600✔
1464
    }
6,600✔
1465
  }
6,604✔
1466
}
17,826✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc