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