]>
Commit | Line | Data |
---|---|---|
243f4780 | 1 | #include "validate.hh" |
2 | #include "misc.hh" | |
3 | #include "dnssecinfra.hh" | |
3d5ebf10 | 4 | #include "dnsseckeeper.hh" |
2ac8ae89 | 5 | #include "rec-lua-conf.hh" |
243f4780 | 6 | #include "base32.hh" |
566ab902 | 7 | #include "logger.hh" |
3e9c6c0a | 8 | bool g_dnssecLOG{false}; |
9a3ab3e4 | 9 | time_t g_signatureInceptionSkew{0}; |
d377bb54 | 10 | uint16_t g_maxNSEC3Iterations{0}; |
243f4780 | 11 | |
e6a9dde5 | 12 | #define LOG(x) if(g_dnssecLOG) { g_log <<Logger::Warning << x; } |
243f4780 | 13 | |
ff30823a | 14 | const char *dStates[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"}; |
895449a5 | 15 | const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA", "TA"}; |
243f4780 | 16 | |
4d2be65d | 17 | static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm) |
243f4780 | 18 | { |
4d2be65d | 19 | vector<shared_ptr<DNSKEYRecordContent>> ret; |
243f4780 | 20 | for(const auto& key : keys) |
5780cb46 | 21 | if(key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm) |
243f4780 | 22 | ret.push_back(key); |
23 | return ret; | |
24 | } | |
25 | ||
812c3a69 RG |
26 | static bool isCoveredByNSEC3Hash(const std::string& h, const std::string& beginHash, const std::string& nextHash) |
27 | { | |
28 | return ((beginHash < h && h < nextHash) || // no wrap BEGINNING --- HASH -- END | |
29 | (nextHash > h && beginHash > nextHash) || // wrap HASH --- END --- BEGINNING | |
30 | (nextHash < beginHash && beginHash < h) || // wrap other case END --- BEGINNING --- HASH | |
eb3e3d99 | 31 | (beginHash == nextHash && h != beginHash)); // "we have only 1 NSEC3 record, LOL!" |
812c3a69 RG |
32 | } |
33 | ||
8119e5cb RG |
34 | static bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next) |
35 | { | |
36 | return ((begin.canonCompare(name) && name.canonCompare(next)) || // no wrap BEGINNING --- NAME --- NEXT | |
37 | (name.canonCompare(next) && next.canonCompare(begin)) || // wrap NAME --- NEXT --- BEGINNING | |
38 | (next.canonCompare(begin) && begin.canonCompare(name)) || // wrap other case NEXT --- BEGINNING --- NAME | |
eb3e3d99 | 39 | (begin == next && name != begin)); // "we have only 1 NSEC record, LOL!" |
8119e5cb RG |
40 | } |
41 | ||
00e3fef4 RG |
42 | static bool nsecProvesENT(const DNSName& name, const DNSName& begin, const DNSName& next) |
43 | { | |
44 | /* if name is an ENT: | |
45 | - begin < name | |
46 | - next is a child of name | |
47 | */ | |
00be1ff6 | 48 | return begin.canonCompare(name) && next != name && next.isPartOf(name); |
00e3fef4 RG |
49 | } |
50 | ||
bd9e6242 | 51 | static std::string getHashFromNSEC3(const DNSName& qname, const std::shared_ptr<NSEC3RecordContent>& nsec3) |
5374b03b RG |
52 | { |
53 | std::string result; | |
54 | ||
55 | if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) { | |
56 | return result; | |
57 | } | |
58 | ||
59 | return hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, qname); | |
60 | } | |
61 | ||
9b061cf5 RG |
62 | /* There is no delegation at this exact point if: |
63 | - the name exists but the NS type is not set | |
64 | - the name does not exist | |
65 | One exception, if the name is covered by an opt-out NSEC3 | |
66 | it doesn't prove that an insecure delegation doesn't exist. | |
67 | */ | |
5374b03b RG |
68 | bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords) |
69 | { | |
70 | for (const auto& record : dsrecords) { | |
71 | if (record.d_type == QType::NSEC) { | |
72 | const auto nsec = getRR<NSECRecordContent>(record); | |
73 | if (!nsec) { | |
74 | continue; | |
75 | } | |
76 | ||
77 | if (record.d_name == zone) { | |
27d4a65b | 78 | return !nsec->isSet(QType::NS); |
5374b03b RG |
79 | } |
80 | ||
81 | if (isCoveredByNSEC(zone, record.d_name, nsec->d_next)) { | |
82 | return true; | |
83 | } | |
84 | } | |
85 | else if (record.d_type == QType::NSEC3) { | |
86 | const auto nsec3 = getRR<NSEC3RecordContent>(record); | |
87 | if (!nsec3) { | |
88 | continue; | |
89 | } | |
90 | ||
91 | const string h = getHashFromNSEC3(zone, nsec3); | |
92 | if (h.empty()) { | |
93 | return false; | |
94 | } | |
95 | ||
96 | const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]); | |
97 | if (beginHash == h) { | |
27d4a65b | 98 | return !nsec3->isSet(QType::NS); |
5374b03b RG |
99 | } |
100 | ||
101 | if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) { | |
102 | return !(nsec3->d_flags & 1); | |
103 | } | |
104 | } | |
105 | } | |
106 | ||
107 | return false; | |
108 | } | |
109 | ||
9b061cf5 RG |
110 | /* RFC 4035 section-5.3.4: |
111 | "If the number of labels in an RRset's owner name is greater than the | |
112 | Labels field of the covering RRSIG RR, then the RRset and its | |
113 | covering RRSIG RR were created as a result of wildcard expansion." | |
114 | */ | |
78cdf520 RG |
115 | bool isWildcardExpanded(unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign) |
116 | { | |
117 | if (sign && sign->d_labels < labelCount) { | |
118 | return true; | |
119 | } | |
120 | ||
121 | return false; | |
122 | } | |
123 | ||
9b061cf5 | 124 | static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) |
243f4780 | 125 | { |
9b061cf5 RG |
126 | if (signatures.empty()) { |
127 | return false; | |
128 | } | |
129 | ||
130 | const auto& sign = signatures.at(0); | |
131 | unsigned int labelsCount = owner.countLabels(); | |
78cdf520 RG |
132 | return isWildcardExpanded(labelsCount, sign); |
133 | } | |
134 | ||
135 | bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign) | |
136 | { | |
137 | if (owner.isWildcard() && (labelCount - 1) == sign->d_labels) { | |
138 | /* this is a wildcard alright, but it has not been expanded */ | |
9b061cf5 RG |
139 | return true; |
140 | } | |
9b061cf5 RG |
141 | return false; |
142 | } | |
143 | ||
78cdf520 RG |
144 | static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) |
145 | { | |
146 | if (signatures.empty()) { | |
147 | return false; | |
148 | } | |
149 | ||
150 | const auto& sign = signatures.at(0); | |
151 | unsigned int labelsCount = owner.countLabels(); | |
152 | return isWildcardExpandedOntoItself(owner, labelsCount, sign); | |
153 | } | |
154 | ||
9b061cf5 RG |
155 | /* if this is a wildcard NSEC, the owner name has been modified |
156 | to match the name. Make sure we use the original '*' form. */ | |
157 | static DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) | |
158 | { | |
159 | DNSName result = initialOwner; | |
160 | ||
161 | if (signatures.empty()) { | |
162 | return result; | |
163 | } | |
164 | ||
165 | const auto& sign = signatures.at(0); | |
166 | unsigned int labelsCount = initialOwner.countLabels(); | |
167 | if (sign && sign->d_labels < labelsCount) { | |
168 | do { | |
169 | result.chopOff(); | |
170 | labelsCount--; | |
171 | } | |
172 | while (sign->d_labels < labelsCount); | |
173 | ||
174 | result = g_wildcarddnsname + result; | |
175 | } | |
176 | ||
177 | return result; | |
178 | } | |
179 | ||
bd9e6242 | 180 | static bool isNSECAncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSECRecordContent>& nsec) |
b580fad2 | 181 | { |
27d4a65b RG |
182 | return nsec->isSet(QType::NS) && |
183 | !nsec->isSet(QType::SOA) && | |
b580fad2 RG |
184 | signer.countLabels() < owner.countLabels(); |
185 | } | |
186 | ||
bd9e6242 | 187 | static bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSEC3RecordContent>& nsec3) |
b580fad2 | 188 | { |
27d4a65b RG |
189 | return nsec3->isSet(QType::NS) && |
190 | !nsec3->isSet(QType::SOA) && | |
b580fad2 RG |
191 | signer.countLabels() < owner.countLabels(); |
192 | } | |
193 | ||
82566a96 RG |
194 | static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t& validrrsets) |
195 | { | |
196 | LOG("Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype).getName()<<endl); | |
197 | for (const auto& v : validrrsets) { | |
198 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
199 | if (v.first.second == QType::NSEC) { | |
200 | for (const auto& r : v.second.records) { | |
201 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
202 | auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r); | |
203 | if (!nsec) { | |
204 | continue; | |
205 | } | |
206 | ||
207 | if (!v.first.first.isWildcard()) { | |
208 | continue; | |
209 | } | |
210 | DNSName wildcard = getNSECOwnerName(v.first.first, v.second.signatures); | |
211 | if (qname.countLabels() < wildcard.countLabels()) { | |
212 | continue; | |
213 | } | |
214 | ||
215 | wildcard.chopOff(); | |
216 | ||
217 | if (qname.isPartOf(wildcard)) { | |
218 | LOG("\tWildcard matches"); | |
27d4a65b | 219 | if (qtype == 0 || !nsec->isSet(qtype)) { |
82566a96 RG |
220 | LOG(" and proves that the type did not exist"<<endl); |
221 | return true; | |
222 | } | |
223 | LOG(" BUT the type did exist!"<<endl); | |
224 | return false; | |
225 | } | |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | return false; | |
231 | } | |
232 | ||
9b061cf5 | 233 | /* |
82566a96 | 234 | This function checks whether the non-existence of a wildcard covering qname|qtype |
9b061cf5 | 235 | is proven by the NSEC records in validrrsets. |
9b061cf5 | 236 | */ |
82566a96 | 237 | static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t & validrrsets) |
9b061cf5 RG |
238 | { |
239 | LOG("Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype).getName()<<endl); | |
240 | for (const auto& v : validrrsets) { | |
241 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
242 | if (v.first.second == QType::NSEC) { | |
243 | for (const auto& r : v.second.records) { | |
244 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
245 | auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r); | |
246 | if (!nsec) { | |
247 | continue; | |
248 | } | |
249 | ||
250 | const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures); | |
251 | /* | |
252 | A NSEC can only prove the non-existence of a wildcard with at least the same | |
253 | number of labels than the intersection of its owner name and next name. | |
254 | */ | |
255 | const DNSName commonLabels = owner.getCommonLabels(nsec->d_next); | |
256 | unsigned int commonLabelsCount = commonLabels.countLabels(); | |
257 | ||
258 | DNSName wildcard(qname); | |
259 | unsigned int wildcardLabelsCount = wildcard.countLabels(); | |
260 | while (wildcard.chopOff() && wildcardLabelsCount >= commonLabelsCount) { | |
261 | DNSName target = g_wildcarddnsname + wildcard; | |
262 | ||
82566a96 | 263 | LOG("Comparing owner: "<<owner<<" with target: "<<target<<endl); |
9b061cf5 | 264 | |
82566a96 | 265 | if (isCoveredByNSEC(target, owner, nsec->d_next)) { |
9b061cf5 RG |
266 | LOG("\tWildcard is covered"<<endl); |
267 | return true; | |
268 | } | |
269 | } | |
270 | } | |
271 | } | |
272 | } | |
273 | ||
274 | return false; | |
275 | } | |
276 | ||
277 | /* | |
82566a96 | 278 | This function checks whether the non-existence of a wildcard covering qname|qtype |
9b061cf5 RG |
279 | is proven by the NSEC3 records in validrrsets. |
280 | If `wildcardExists` is not NULL, if will be set to true if a wildcard exists | |
281 | for this qname but doesn't have this qtype. | |
282 | */ | |
283 | static bool provesNSEC3NoWildCard(DNSName wildcard, uint16_t const qtype, const cspmap_t & validrrsets, bool * wildcardExists=nullptr) | |
284 | { | |
285 | wildcard = g_wildcarddnsname + wildcard; | |
286 | LOG("Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype).getName()<<endl); | |
287 | ||
288 | for (const auto& v : validrrsets) { | |
289 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
290 | if (v.first.second == QType::NSEC3) { | |
291 | for (const auto& r : v.second.records) { | |
292 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
293 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
294 | if (!nsec3) { | |
295 | continue; | |
296 | } | |
297 | ||
298 | const DNSName signer = getSigner(v.second.signatures); | |
299 | if (!v.first.first.isPartOf(signer)) | |
300 | continue; | |
301 | ||
302 | string h = getHashFromNSEC3(wildcard, nsec3); | |
303 | if (h.empty()) { | |
304 | return false; | |
305 | } | |
306 | LOG("\tWildcard hash: "<<toBase32Hex(h)<<endl); | |
307 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); | |
308 | LOG("\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl); | |
309 | ||
310 | if (beginHash == h) { | |
311 | LOG("\tWildcard hash matches"); | |
312 | if (wildcardExists) { | |
313 | *wildcardExists = true; | |
314 | } | |
27d4a65b | 315 | if (qtype == 0 || !nsec3->isSet(qtype)) { |
9b061cf5 RG |
316 | LOG(" and proves that the type did not exist"<<endl); |
317 | return true; | |
318 | } | |
319 | LOG(" BUT the type did exist!"<<endl); | |
320 | return false; | |
321 | } | |
322 | ||
323 | if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) { | |
324 | LOG("\tWildcard hash is covered"<<endl); | |
325 | return true; | |
326 | } | |
327 | } | |
328 | } | |
329 | } | |
330 | ||
331 | return false; | |
332 | } | |
333 | ||
334 | /* | |
335 | This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3 | |
336 | in validrrsets. | |
f5a747bb | 337 | - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODATA |
9b061cf5 RG |
338 | if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9. |
339 | - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return | |
340 | NXQTYPE is the name is proven to be ENT and NXDOMAIN otherwise. | |
341 | - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is | |
ef2ea4bf | 342 | useful when we have a positive answer synthesized from a wildcard and we only need to prove that the exact |
9b061cf5 RG |
343 | name does not exist. |
344 | */ | |
e4894ce0 | 345 | dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, bool needWildcardProof, unsigned int wildcardLabelsCount) |
9b061cf5 RG |
346 | { |
347 | bool nsec3Seen = false; | |
e4894ce0 | 348 | if (!needWildcardProof && wildcardLabelsCount == 0) { |
ef2ea4bf | 349 | throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard"); |
e4894ce0 | 350 | } |
9b061cf5 | 351 | |
da204325 PL |
352 | for(const auto& v : validrrsets) { |
353 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
354 | ||
355 | if(v.first.second==QType::NSEC) { | |
356 | for(const auto& r : v.second.records) { | |
357 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
358 | auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r); | |
359 | if(!nsec) | |
360 | continue; | |
361 | ||
3143417d RG |
362 | const DNSName signer = getSigner(v.second.signatures); |
363 | if (!v.first.first.isPartOf(signer)) | |
364 | continue; | |
365 | ||
82566a96 | 366 | const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures); |
95208ae3 RG |
367 | /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs": |
368 | Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume | |
369 | nonexistence of any RRs below that zone cut, which include all RRs at | |
370 | that (original) owner name other than DS RRs, and all RRs below that | |
371 | owner name regardless of type. | |
372 | */ | |
b580fad2 | 373 | if (qtype != QType::DS && (qname == owner || qname.isPartOf(owner)) && isNSECAncestorDelegation(signer, owner, nsec)) { |
27d4a65b | 374 | LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec->isSet(QType::NS))<<", SOA is "<<std::to_string(nsec->isSet(QType::SOA))<<", signer is "<<signer<<", owner name is "<<owner<<endl); |
95208ae3 | 375 | /* this is an "ancestor delegation" NSEC RR */ |
b580fad2 RG |
376 | LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl); |
377 | return NODATA; | |
95208ae3 RG |
378 | } |
379 | ||
da204325 | 380 | /* check if the type is denied */ |
82566a96 | 381 | if(qname == owner) { |
27d4a65b | 382 | if (nsec->isSet(qtype)) { |
eb3e3d99 | 383 | LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<endl); |
b580fad2 | 384 | return NODATA; |
eb3e3d99 RG |
385 | } |
386 | ||
da204325 | 387 | LOG("Denies existence of type "<<QType(qtype).getName()<<endl); |
95823c07 | 388 | |
9b061cf5 | 389 | /* RFC 6840 section 4.3 */ |
27d4a65b | 390 | if (nsec->isSet(QType::CNAME)) { |
9b061cf5 RG |
391 | LOG("However a CNAME exists"<<endl); |
392 | return NODATA; | |
393 | } | |
394 | ||
95823c07 RG |
395 | /* |
396 | * RFC 4035 Section 2.3: | |
397 | * The bitmap for the NSEC RR at a delegation point requires special | |
398 | * attention. Bits corresponding to the delegation NS RRset and any | |
399 | * RRsets for which the parent zone has authoritative data MUST be set | |
400 | */ | |
27d4a65b | 401 | if (referralToUnsigned && qtype == QType::DS && !nsec->isSet(QType::NS)) { |
95823c07 | 402 | LOG("However, no NS record exists at this level!"<<endl); |
b7c40613 | 403 | return NODATA; |
95823c07 RG |
404 | } |
405 | ||
9b061cf5 RG |
406 | /* we know that the name exists (but this qtype doesn't) so except |
407 | if the answer was generated by a wildcard expansion, no wildcard | |
408 | could have matched (rfc4035 section 5.4 bullet 1) */ | |
78cdf520 | 409 | if (!isWildcardExpanded(owner, v.second.signatures) || isWildcardExpandedOntoItself(owner, v.second.signatures)) { |
9b061cf5 RG |
410 | needWildcardProof = false; |
411 | } | |
412 | ||
413 | if (!needWildcardProof || provesNoWildCard(qname, qtype, validrrsets)) { | |
414 | return NXQTYPE; | |
415 | } | |
416 | ||
417 | LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<endl); | |
418 | return NODATA; | |
da204325 | 419 | } |
243f4780 | 420 | |
da204325 | 421 | /* check if the whole NAME is denied existing */ |
82566a96 | 422 | if(isCoveredByNSEC(qname, owner, nsec->d_next)) { |
78cdf520 | 423 | LOG(qname<<" is covered "); |
9b061cf5 RG |
424 | /* if the name is an ENT and we received a NODATA answer, |
425 | we are fine with a NSEC proving that the name does not exist. */ | |
82566a96 | 426 | if (wantsNoDataProof && nsecProvesENT(qname, owner, nsec->d_next)) { |
00e3fef4 RG |
427 | LOG("Denies existence of type "<<qname<<"/"<<QType(qtype).getName()<<" by proving that "<<qname<<" is an ENT"<<endl); |
428 | return NXQTYPE; | |
429 | } | |
430 | ||
82566a96 | 431 | if (!needWildcardProof) { |
78cdf520 | 432 | LOG("and we did not need a wildcard proof"<<endl); |
82566a96 RG |
433 | return NXDOMAIN; |
434 | } | |
435 | ||
78cdf520 | 436 | LOG("but we do need a wildcard proof so "); |
82566a96 | 437 | if (wantsNoDataProof) { |
78cdf520 | 438 | LOG("looking for NODATA proof"<<endl); |
82566a96 | 439 | if (provesNoDataWildCard(qname, qtype, validrrsets)) { |
9b061cf5 RG |
440 | return NXQTYPE; |
441 | } | |
9b061cf5 | 442 | } |
82566a96 | 443 | else { |
78cdf520 | 444 | LOG("looking for NO wildcard proof"<<endl); |
82566a96 RG |
445 | if (provesNoWildCard(qname, qtype, validrrsets)) { |
446 | return NXDOMAIN; | |
447 | } | |
448 | } | |
449 | ||
9b061cf5 RG |
450 | LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl); |
451 | return NODATA; | |
da204325 | 452 | } |
243f4780 | 453 | |
27d4a65b | 454 | LOG("Did not deny existence of "<<QType(qtype).getName()<<", "<<owner<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl); |
da204325 PL |
455 | } |
456 | } else if(v.first.second==QType::NSEC3) { | |
457 | for(const auto& r : v.second.records) { | |
458 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
459 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
460 | if(!nsec3) | |
461 | continue; | |
462 | ||
3143417d | 463 | const DNSName signer = getSigner(v.second.signatures); |
9b061cf5 RG |
464 | if (!v.first.first.isPartOf(signer)) { |
465 | LOG("Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl); | |
3143417d | 466 | continue; |
9b061cf5 | 467 | } |
3143417d | 468 | |
5374b03b RG |
469 | string h = getHashFromNSEC3(qname, nsec3); |
470 | if (h.empty()) { | |
9b061cf5 | 471 | LOG("Unsupported hash, ignoring"<<endl); |
d377bb54 RG |
472 | return INSECURE; |
473 | } | |
474 | ||
9b061cf5 RG |
475 | nsec3Seen = true; |
476 | ||
812c3a69 | 477 | // cerr<<"Salt length: "<<nsec3->d_salt.length()<<", iterations: "<<nsec3->d_iterations<<", hashed: "<<qname<<endl; |
da204325 PL |
478 | LOG("\tquery hash: "<<toBase32Hex(h)<<endl); |
479 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); | |
480 | ||
95823c07 RG |
481 | /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs": |
482 | Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume | |
483 | nonexistence of any RRs below that zone cut, which include all RRs at | |
484 | that (original) owner name other than DS RRs, and all RRs below that | |
485 | owner name regardless of type. | |
486 | */ | |
b580fad2 | 487 | if (qtype != QType::DS && beginHash == h && isNSEC3AncestorDelegation(signer, v.first.first, nsec3)) { |
27d4a65b | 488 | LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec3->isSet(QType::NS))<<", SOA is "<<std::to_string(nsec3->isSet(QType::SOA))<<", signer is "<<signer<<", owner name is "<<v.first.first<<endl); |
95823c07 | 489 | /* this is an "ancestor delegation" NSEC3 RR */ |
b580fad2 RG |
490 | LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl); |
491 | return NODATA; | |
95823c07 RG |
492 | } |
493 | ||
da204325 | 494 | // If the name exists, check if the qtype is denied |
eb3e3d99 | 495 | if(beginHash == h) { |
27d4a65b | 496 | if (nsec3->isSet(qtype)) { |
95823c07 | 497 | LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl); |
b580fad2 | 498 | return NODATA; |
eb3e3d99 RG |
499 | } |
500 | ||
95823c07 | 501 | LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl); |
9b061cf5 RG |
502 | |
503 | /* RFC 6840 section 4.3 */ | |
27d4a65b | 504 | if (nsec3->isSet(QType::CNAME)) { |
9b061cf5 RG |
505 | LOG("However a CNAME exists"<<endl); |
506 | return NODATA; | |
507 | } | |
508 | ||
43c04e77 PL |
509 | /* |
510 | * RFC 5155 section 8.9: | |
511 | * If there is an NSEC3 RR present in the response that matches the | |
512 | * delegation name, then the validator MUST ensure that the NS bit is | |
513 | * set and that the DS bit is not set in the Type Bit Maps field of the | |
514 | * NSEC3 RR. | |
515 | */ | |
27d4a65b | 516 | if (referralToUnsigned && qtype == QType::DS && !nsec3->isSet(QType::NS)) { |
da204325 | 517 | LOG("However, no NS record exists at this level!"<<endl); |
b7c40613 | 518 | return NODATA; |
da204325 | 519 | } |
243f4780 | 520 | |
9b061cf5 | 521 | return NXQTYPE; |
da204325 | 522 | } |
243f4780 | 523 | } |
da204325 | 524 | } |
243f4780 | 525 | } |
812c3a69 | 526 | |
9b061cf5 RG |
527 | /* if we have no NSEC3 records, we are done */ |
528 | if (!nsec3Seen) { | |
529 | return NODATA; | |
530 | } | |
531 | ||
532 | DNSName closestEncloser(qname); | |
812c3a69 RG |
533 | bool found = false; |
534 | ||
9b061cf5 RG |
535 | if (needWildcardProof) { |
536 | /* We now need to look for a NSEC3 covering the closest (provable) encloser | |
537 | RFC 5155 section-7.2.1 | |
c1797419 | 538 | RFC 7129 section-5.5 |
9b061cf5 RG |
539 | */ |
540 | LOG("Now looking for the closest encloser for "<<qname<<endl); | |
812c3a69 | 541 | |
9b061cf5 RG |
542 | while (found == false && closestEncloser.chopOff()) { |
543 | for(const auto& v : validrrsets) { | |
544 | if(v.first.second==QType::NSEC3) { | |
545 | for(const auto& r : v.second.records) { | |
546 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
547 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
548 | if(!nsec3) | |
549 | continue; | |
d377bb54 | 550 | |
b580fad2 RG |
551 | const DNSName signer = getSigner(v.second.signatures); |
552 | if (!v.first.first.isPartOf(signer)) { | |
553 | LOG("Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl); | |
554 | continue; | |
555 | } | |
556 | ||
b7c40613 RG |
557 | string h = getHashFromNSEC3(closestEncloser, nsec3); |
558 | if (h.empty()) { | |
9b061cf5 RG |
559 | return INSECURE; |
560 | } | |
561 | ||
9b061cf5 | 562 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); |
812c3a69 | 563 | |
9b061cf5 RG |
564 | LOG("Comparing "<<toBase32Hex(h)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl); |
565 | if(beginHash == h) { | |
b580fad2 RG |
566 | if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, v.first.first, nsec3)) { |
567 | LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl); | |
568 | continue; | |
569 | } | |
570 | ||
9b061cf5 RG |
571 | LOG("Closest encloser for "<<qname<<" is "<<closestEncloser<<endl); |
572 | found = true; | |
573 | break; | |
574 | } | |
812c3a69 RG |
575 | } |
576 | } | |
9b061cf5 RG |
577 | if (found == true) { |
578 | break; | |
579 | } | |
812c3a69 RG |
580 | } |
581 | } | |
582 | } | |
9b061cf5 RG |
583 | else { |
584 | /* RFC 5155 section-7.2.6: | |
585 | "It is not necessary to return an NSEC3 RR that matches the closest encloser, | |
586 | as the existence of this closest encloser is proven by the presence of the | |
587 | expanded wildcard in the response. | |
588 | */ | |
589 | found = true; | |
e4894ce0 RG |
590 | unsigned int closestEncloserLabelsCount = closestEncloser.countLabels(); |
591 | while (wildcardLabelsCount > 0 && closestEncloserLabelsCount > wildcardLabelsCount) { | |
592 | closestEncloser.chopOff(); | |
593 | closestEncloserLabelsCount--; | |
594 | } | |
9b061cf5 RG |
595 | } |
596 | ||
597 | bool nextCloserFound = false; | |
598 | bool isOptOut = false; | |
812c3a69 RG |
599 | |
600 | if (found == true) { | |
9b061cf5 | 601 | /* now that we have found the closest (provable) encloser, |
c1797419 | 602 | we can construct the next closer (RFC7129 section-5.5) name |
9b061cf5 RG |
603 | and look for a NSEC3 RR covering it */ |
604 | unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels(); | |
812c3a69 | 605 | if (labelIdx >= 1) { |
9b061cf5 | 606 | DNSName nextCloser(closestEncloser); |
812c3a69 RG |
607 | nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1)); |
608 | LOG("Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl); | |
609 | ||
610 | for(const auto& v : validrrsets) { | |
611 | if(v.first.second==QType::NSEC3) { | |
612 | for(const auto& r : v.second.records) { | |
613 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
614 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
615 | if(!nsec3) | |
616 | continue; | |
b7c40613 RG |
617 | |
618 | string h = getHashFromNSEC3(nextCloser, nsec3); | |
619 | if (h.empty()) { | |
d377bb54 RG |
620 | return INSECURE; |
621 | } | |
812c3a69 | 622 | |
812c3a69 RG |
623 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); |
624 | ||
9b061cf5 | 625 | LOG("Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl); |
812c3a69 RG |
626 | if(isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) { |
627 | LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName()); | |
9b061cf5 RG |
628 | nextCloserFound = true; |
629 | ||
18c8faae | 630 | if ((qtype == QType::DS || qtype == 0) && nsec3->d_flags & 1) { |
9b061cf5 RG |
631 | LOG(" but is opt-out!"); |
632 | isOptOut = true; | |
812c3a69 RG |
633 | } |
634 | LOG(endl); | |
9b061cf5 | 635 | break; |
812c3a69 | 636 | } |
9b061cf5 | 637 | LOG("Did not cover us ("<<qname<<"), start="<<v.first.first<<", us="<<toBase32Hex(h)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl); |
812c3a69 RG |
638 | } |
639 | } | |
9b061cf5 RG |
640 | if (nextCloserFound) { |
641 | break; | |
642 | } | |
643 | } | |
644 | } | |
645 | } | |
646 | ||
647 | if (nextCloserFound) { | |
648 | bool wildcardExists = false; | |
649 | /* RFC 7129 section-5.6 */ | |
650 | if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists)) { | |
651 | if (!isOptOut) { | |
652 | LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl); | |
653 | return NODATA; | |
654 | } | |
655 | } | |
656 | ||
657 | if (isOptOut) { | |
658 | return OPTOUT; | |
659 | } | |
660 | else { | |
661 | if (wildcardExists) { | |
662 | return NXQTYPE; | |
812c3a69 | 663 | } |
9b061cf5 | 664 | return NXDOMAIN; |
812c3a69 RG |
665 | } |
666 | } | |
667 | ||
da204325 PL |
668 | // There were no valid NSEC(3) records |
669 | // XXX maybe this should be INSECURE... it depends on the semantics of this function | |
670 | return NODATA; | |
243f4780 | 671 | } |
672 | ||
de838038 PL |
673 | /* |
674 | * Finds all the zone-cuts between begin (longest name) and end (shortest name), | |
675 | * returns them all zone cuts, including end, but (possibly) not begin | |
676 | */ | |
4d2be65d | 677 | static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro) |
de838038 PL |
678 | { |
679 | vector<DNSName> ret; | |
680 | if(!begin.isPartOf(end)) | |
86f1af1c | 681 | throw PDNSException(end.toLogString() + "is not part of " + begin.toLogString()); |
de838038 PL |
682 | |
683 | DNSName qname(end); | |
684 | vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels(); | |
685 | ||
686 | // The shortest name is assumed to a zone cut | |
687 | ret.push_back(qname); | |
688 | while(qname != begin) { | |
e2f91e70 RG |
689 | bool foundCut = false; |
690 | if (labelsToAdd.empty()) | |
691 | break; | |
692 | ||
754914f0 | 693 | qname.prependRawLabel(labelsToAdd.back()); |
de838038 | 694 | labelsToAdd.pop_back(); |
de838038 | 695 | auto records = dro.get(qname, (uint16_t)QType::NS); |
7af99dff | 696 | for (const auto& record : records) { |
4d2be65d | 697 | if(record.d_type != QType::NS || record.d_name != qname) |
de838038 PL |
698 | continue; |
699 | foundCut = true; | |
700 | break; | |
701 | } | |
702 | if (foundCut) | |
703 | ret.push_back(qname); | |
704 | } | |
705 | return ret; | |
706 | } | |
707 | ||
dbbef467 RG |
708 | bool isRRSIGNotExpired(const time_t now, const shared_ptr<RRSIGRecordContent> sig) |
709 | { | |
9a3ab3e4 | 710 | return sig->d_siginception - g_signatureInceptionSkew <= now && sig->d_sigexpire >= now; |
dbbef467 RG |
711 | } |
712 | ||
4d2be65d RG |
713 | static bool checkSignatureWithKey(time_t now, const shared_ptr<RRSIGRecordContent> sig, const shared_ptr<DNSKEYRecordContent> key, const std::string& msg) |
714 | { | |
715 | bool result = false; | |
716 | try { | |
179b340d RG |
717 | /* rfc4035: |
718 | - 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. | |
719 | - 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. | |
720 | */ | |
dbbef467 | 721 | if(isRRSIGNotExpired(now, sig)) { |
4d2be65d RG |
722 | std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(key->d_algorithm, key->d_key)); |
723 | result = dke->verify(msg, sig->d_signature); | |
3d5ebf10 | 724 | LOG("signature by key with tag "<<sig->d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig->d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl); |
4d2be65d RG |
725 | } |
726 | else { | |
9a3ab3e4 | 727 | LOG("Signature is "<<((sig->d_siginception - g_signatureInceptionSkew > now) ? "not yet valid" : "expired")<<" (inception: "<<sig->d_siginception<<", inception skew: "<<g_signatureInceptionSkew<<", expiration: "<<sig->d_sigexpire<<", now: "<<now<<")"<<endl); |
4d2be65d RG |
728 | } |
729 | } | |
5780cb46 | 730 | catch(const std::exception& e) { |
4d2be65d RG |
731 | LOG("Could not make a validator for signature: "<<e.what()<<endl); |
732 | } | |
733 | return result; | |
734 | } | |
735 | ||
c1e7b833 | 736 | bool validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, bool validateAllSigs) |
4d2be65d RG |
737 | { |
738 | bool isValid = false; | |
739 | ||
740 | for(const auto& signature : signatures) { | |
bb07ad8e RG |
741 | unsigned int labelCount = name.countLabels(); |
742 | if (signature->d_labels > labelCount) { | |
743 | LOG(name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl); | |
744 | } | |
745 | ||
4d2be65d RG |
746 | auto r = getByTag(keys, signature->d_tag, signature->d_algorithm); |
747 | ||
748 | if(r.empty()) { | |
3d5ebf10 | 749 | LOG("No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;); |
4d2be65d RG |
750 | continue; |
751 | } | |
752 | ||
753 | string msg=getMessageForRRSET(name, *signature, toSign, true); | |
754 | for(const auto& l : r) { | |
755 | bool signIsValid = checkSignatureWithKey(now, signature, l, msg); | |
756 | if(signIsValid) { | |
757 | isValid = true; | |
758 | LOG("Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl); | |
759 | // cerr<<"valid"<<endl; | |
760 | // cerr<<"! validated "<<i->first.first<<"/"<<)<<endl; | |
761 | } | |
762 | else { | |
763 | LOG("signature invalid"<<endl); | |
764 | } | |
4d2be65d RG |
765 | if (signIsValid && !validateAllSigs) { |
766 | return true; | |
767 | } | |
768 | } | |
769 | } | |
770 | ||
771 | return isValid; | |
772 | } | |
773 | ||
774 | void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys) | |
243f4780 | 775 | { |
776 | validated.clear(); | |
b3f0ed10 | 777 | /* cerr<<"Validating an rrset with following keys: "<<endl; |
243f4780 | 778 | for(auto& key : keys) { |
4d2be65d | 779 | cerr<<"\tTag: "<<key->getTag()<<" -> "<<key->getZoneRepresentation()<<endl; |
243f4780 | 780 | } |
b3f0ed10 | 781 | */ |
4d2be65d | 782 | time_t now = time(nullptr); |
7a4f4632 | 783 | for(auto i=rrsets.cbegin(); i!=rrsets.cend(); i++) { |
a95d1b58 | 784 | LOG("validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs"<<endl); |
4d2be65d RG |
785 | if (validateWithKeySet(now, i->first.first, i->second.records, i->second.signatures, keys, true)) { |
786 | validated[i->first] = i->second; | |
243f4780 | 787 | } |
788 | } | |
789 | } | |
790 | ||
243f4780 | 791 | // returns vState |
792 | // should return vState, zone cut and validated keyset | |
793 | // i.e. www.7bits.nl -> insecure/7bits.nl/[] | |
794 | // www.powerdnssec.org -> secure/powerdnssec.org/[keys] | |
795 | // www.dnssec-failed.org -> bogus/dnssec-failed.org/[] | |
796 | ||
243f4780 | 797 | cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs) |
798 | { | |
799 | cspmap_t cspmap; | |
800 | for(const auto& rec : recs) { | |
801 | // cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl; | |
802 | if(rec.d_type == QType::OPT) continue; | |
803 | ||
804 | if(rec.d_type == QType::RRSIG) { | |
805 | auto rrc = getRR<RRSIGRecordContent>(rec); | |
ba3c54cb RG |
806 | if (rrc) { |
807 | cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc); | |
808 | } | |
243f4780 | 809 | } |
810 | else { | |
c1e7b833 | 811 | cspmap[{rec.d_name, rec.d_type}].records.insert(rec.d_content); |
243f4780 | 812 | } |
813 | } | |
814 | return cspmap; | |
815 | } | |
816 | ||
4d2be65d RG |
817 | bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res) |
818 | { | |
819 | const auto& it = anchors.find(zone); | |
820 | ||
821 | if (it == anchors.cend()) { | |
822 | return false; | |
823 | } | |
824 | ||
825 | res = it->second; | |
826 | return true; | |
827 | } | |
828 | ||
829 | bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason) | |
830 | { | |
831 | const auto& it = negAnchors.find(zone); | |
832 | ||
833 | if (it == negAnchors.cend()) { | |
834 | return false; | |
835 | } | |
836 | ||
837 | reason = it->second; | |
838 | return true; | |
839 | } | |
840 | ||
c1e7b833 | 841 | void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<RRSIGRecordContent> >& sigs, skeyset_t& validkeys) |
4d2be65d RG |
842 | { |
843 | /* | |
844 | * Check all DNSKEY records against all DS records and place all DNSKEY records | |
845 | * that have DS records (that we support the algo for) in the tentative key storage | |
846 | */ | |
847 | for(auto const& dsrc : dsmap) | |
848 | { | |
849 | auto r = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm); | |
3d5ebf10 | 850 | // 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 RG |
851 | |
852 | for(const auto& drc : r) | |
853 | { | |
854 | bool isValid = false; | |
855 | bool dsCreated = false; | |
856 | DSRecordContent dsrc2; | |
857 | try { | |
5780cb46 | 858 | dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype); |
4d2be65d RG |
859 | dsCreated = true; |
860 | isValid = dsrc == dsrc2; | |
861 | } | |
5780cb46 | 862 | catch(const std::exception &e) { |
4d2be65d RG |
863 | LOG("Unable to make DS from DNSKey: "<<e.what()<<endl); |
864 | } | |
865 | ||
866 | if(isValid) { | |
3d5ebf10 | 867 | LOG("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 |
868 | |
869 | validkeys.insert(drc); | |
4d2be65d RG |
870 | } |
871 | else { | |
872 | if (dsCreated) { | |
873 | LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl); | |
874 | } | |
875 | } | |
4d2be65d RG |
876 | } |
877 | } | |
878 | ||
879 | vector<uint16_t> toSignTags; | |
880 | for (const auto& key : tkeys) { | |
881 | toSignTags.push_back(key->getTag()); | |
882 | } | |
883 | ||
884 | // cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl; | |
885 | // these counts could be off if we somehow ended up with | |
886 | // duplicate keys. Should switch to a type that prevents that. | |
887 | if(validkeys.size() < tkeys.size()) | |
888 | { | |
889 | // this should mean that we have one or more DS-validated DNSKEYs | |
890 | // but not a fully validated DNSKEY set, yet | |
891 | // one of these valid DNSKEYs should be able to validate the | |
892 | // whole set | |
893 | for(const auto& sig : sigs) | |
894 | { | |
895 | // 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; | |
896 | auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm); | |
897 | ||
898 | if (bytag.empty()) { | |
899 | continue; | |
900 | } | |
901 | ||
5780cb46 | 902 | string msg = getMessageForRRSET(zone, *sig, toSign); |
4d2be65d RG |
903 | for(const auto& key : bytag) { |
904 | // cerr<<"validating : "; | |
905 | bool signIsValid = checkSignatureWithKey(now, sig, key, msg); | |
906 | ||
4d2be65d RG |
907 | if(signIsValid) |
908 | { | |
909 | LOG("validation succeeded - whole DNSKEY set is valid"<<endl); | |
5780cb46 | 910 | validkeys = tkeys; |
4d2be65d RG |
911 | break; |
912 | } | |
913 | else { | |
914 | LOG("Validation did not succeed!"<<endl); | |
915 | } | |
916 | } | |
917 | // if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl; | |
918 | } | |
919 | } | |
920 | } | |
921 | ||
922 | vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset) | |
243f4780 | 923 | { |
1bd49828 | 924 | auto luaLocal = g_luaconfs.getLocal(); |
4d2be65d | 925 | const auto anchors = luaLocal->dsAnchors; |
e9321921 PL |
926 | if (anchors.empty()) // Nothing to do here |
927 | return Insecure; | |
928 | ||
b2e3fc03 PL |
929 | // Determine the lowest (i.e. with the most labels) Trust Anchor for zone |
930 | DNSName lowestTA("."); | |
931 | for (auto const &anchor : anchors) | |
932 | if (zone.isPartOf(anchor.first) && lowestTA.countLabels() < anchor.first.countLabels()) | |
933 | lowestTA = anchor.first; | |
1bd49828 PL |
934 | |
935 | // Before searching for the keys, see if we have a Negative Trust Anchor. If | |
936 | // so, test if the NTA is valid and return an NTA state | |
4d2be65d | 937 | const auto negAnchors = luaLocal->negAnchors; |
1bd49828 PL |
938 | |
939 | if (!negAnchors.empty()) { | |
b2e3fc03 | 940 | DNSName lowestNTA; |
1bd49828 PL |
941 | |
942 | for (auto const &negAnchor : negAnchors) | |
f418a272 | 943 | if (zone.isPartOf(negAnchor.first) && lowestNTA.countLabels() <= negAnchor.first.countLabels()) |
1bd49828 PL |
944 | lowestNTA = negAnchor.first; |
945 | ||
1bd49828 | 946 | if(!lowestNTA.empty()) { |
317f7521 | 947 | LOG("Found a Negative Trust Anchor for "<<lowestNTA<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', "); |
1bd49828 PL |
948 | |
949 | /* RFC 7646 section 2.1 tells us that we SHOULD still validate if there | |
950 | * is a Trust Anchor below the Negative Trust Anchor for the name we | |
951 | * attempt validation for. However, section 3 tells us this positive | |
952 | * Trust Anchor MUST be *below* the name and not the name itself | |
953 | */ | |
f418a272 | 954 | if(lowestTA.countLabels() <= lowestNTA.countLabels()) { |
1bd49828 PL |
955 | LOG("marking answer Insecure"<<endl); |
956 | return NTA; // Not Insecure, this way validateRecords() can shortcut | |
957 | } | |
317f7521 | 958 | LOG("but a Trust Anchor for "<<lowestTA<<" is configured, continuing validation."<<endl); |
1bd49828 PL |
959 | } |
960 | } | |
961 | ||
4d2be65d | 962 | skeyset_t validkeys; |
7226d53b | 963 | dsmap_t dsmap; |
243f4780 | 964 | |
4d2be65d | 965 | dsmap_t* tmp = (dsmap_t*) rplookup(anchors, lowestTA); |
7226d53b PL |
966 | if (tmp) |
967 | dsmap = *tmp; | |
b2e3fc03 | 968 | |
7226d53b | 969 | auto zoneCuts = getZoneCuts(zone, lowestTA, dro); |
52ad9eea | 970 | |
7226d53b PL |
971 | LOG("Found the following zonecuts:") |
972 | for(const auto& zonecut : zoneCuts) | |
973 | LOG(" => "<<zonecut); | |
974 | LOG(endl); | |
975 | ||
7a4f4632 | 976 | for(auto zoneCutIter = zoneCuts.cbegin(); zoneCutIter != zoneCuts.cend(); ++zoneCutIter) |
7226d53b | 977 | { |
4d2be65d | 978 | vector<shared_ptr<RRSIGRecordContent> > sigs; |
c1e7b833 | 979 | sortedRecords_t toSign; |
243f4780 | 980 | |
4d2be65d | 981 | skeyset_t tkeys; // tentative keys |
243f4780 | 982 | validkeys.clear(); |
52ad9eea | 983 | |
b3f0ed10 | 984 | // cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl; |
7226d53b | 985 | auto records=dro.get(*zoneCutIter, (uint16_t)QType::DNSKEY); |
243f4780 | 986 | // this should use harvest perhaps |
7226d53b PL |
987 | for(const auto& rec : records) { |
988 | if(rec.d_name != *zoneCutIter) | |
243f4780 | 989 | continue; |
990 | ||
991 | if(rec.d_type == QType::RRSIG) | |
992 | { | |
993 | auto rrc=getRR<RRSIGRecordContent> (rec); | |
7226d53b PL |
994 | if(rrc) { |
995 | LOG("Got signature: "<<rrc->getZoneRepresentation()<<" with tag "<<rrc->d_tag<<", for type "<<DNSRecordContent::NumberToType(rrc->d_type)<<endl); | |
996 | if(rrc->d_type != QType::DNSKEY) | |
997 | continue; | |
4d2be65d | 998 | sigs.push_back(rrc); |
7226d53b | 999 | } |
243f4780 | 1000 | } |
1001 | else if(rec.d_type == QType::DNSKEY) | |
1002 | { | |
1003 | auto drc=getRR<DNSKEYRecordContent> (rec); | |
ba3c54cb | 1004 | if(drc) { |
4d2be65d | 1005 | tkeys.insert(drc); |
3d5ebf10 | 1006 | LOG("Inserting key with tag "<<drc->getTag()<<" and algorithm "<<DNSSECKeeper::algorithm2name(drc->d_algorithm)<<": "<<drc->getZoneRepresentation()<<endl); |
243f4780 | 1007 | |
c1e7b833 | 1008 | toSign.insert(rec.d_content); |
ba3c54cb | 1009 | } |
243f4780 | 1010 | } |
1011 | } | |
a95d1b58 | 1012 | LOG("got "<<tkeys.size()<<" keys and "<<sigs.size()<<" sigs from server"<<endl); |
243f4780 | 1013 | |
7226d53b PL |
1014 | /* |
1015 | * Check all DNSKEY records against all DS records and place all DNSKEY records | |
1016 | * that have DS records (that we support the algo for) in the tentative key storage | |
1017 | */ | |
4d2be65d | 1018 | validateDNSKeysAgainstDS(time(nullptr), *zoneCutIter, dsmap, tkeys, toSign, sigs, validkeys); |
243f4780 | 1019 | |
1020 | if(validkeys.empty()) | |
1021 | { | |
164de0a7 | 1022 | LOG("ended up with zero valid DNSKEYs, going Bogus"<<endl); |
7226d53b | 1023 | return Bogus; |
243f4780 | 1024 | } |
7226d53b PL |
1025 | LOG("situation: we have one or more valid DNSKEYs for ["<<*zoneCutIter<<"] (want ["<<zone<<"])"<<endl); |
1026 | ||
7a4f4632 | 1027 | if(zoneCutIter == zoneCuts.cend()-1) { |
164de0a7 | 1028 | LOG("requested keyset found! returning Secure for the keyset"<<endl); |
7a4f4632 | 1029 | keyset.insert(validkeys.cbegin(), validkeys.cend()); |
243f4780 | 1030 | return Secure; |
1031 | } | |
243f4780 | 1032 | |
7226d53b PL |
1033 | // We now have the DNSKEYs, use them to validate the DS records at the next zonecut |
1034 | LOG("next name ["<<*(zoneCutIter+1)<<"], trying to get DS"<<endl); | |
243f4780 | 1035 | |
7226d53b PL |
1036 | dsmap_t tdsmap; // tentative DSes |
1037 | dsmap.clear(); | |
1038 | toSign.clear(); | |
243f4780 | 1039 | |
7226d53b | 1040 | auto recs=dro.get(*(zoneCutIter+1), QType::DS); |
243f4780 | 1041 | |
7226d53b | 1042 | cspmap_t cspmap=harvestCSPFromRecs(recs); |
243f4780 | 1043 | |
7226d53b PL |
1044 | cspmap_t validrrsets; |
1045 | validateWithKeySet(cspmap, validrrsets, validkeys); | |
243f4780 | 1046 | |
7226d53b | 1047 | LOG("got "<<cspmap.count(make_pair(*(zoneCutIter+1),QType::DS))<<" records for DS query of which "<<validrrsets.count(make_pair(*(zoneCutIter+1),QType::DS))<<" valid "<<endl); |
3e9c6c0a | 1048 | |
7226d53b PL |
1049 | auto r = validrrsets.equal_range(make_pair(*(zoneCutIter+1), QType::DS)); |
1050 | if(r.first == r.second) { | |
1051 | LOG("No DS for "<<*(zoneCutIter+1)<<", now look for a secure denial"<<endl); | |
00e3fef4 | 1052 | dState res = getDenial(validrrsets, *(zoneCutIter+1), QType::DS, true, true); |
ff30823a | 1053 | if (res == INSECURE || res == NXDOMAIN) |
da204325 | 1054 | return Bogus; |
ff30823a | 1055 | if (res == NXQTYPE || res == OPTOUT) |
da204325 | 1056 | return Insecure; |
7226d53b | 1057 | } |
243f4780 | 1058 | |
7226d53b PL |
1059 | /* |
1060 | * Collect all DS records and add them to the dsmap for the next iteration | |
1061 | */ | |
1062 | for(auto cspiter =r.first; cspiter!=r.second; cspiter++) { | |
1063 | for(auto j=cspiter->second.records.cbegin(); j!=cspiter->second.records.cend(); j++) | |
1064 | { | |
1065 | const auto dsrc=std::dynamic_pointer_cast<DSRecordContent>(*j); | |
1066 | if(dsrc) { | |
1067 | dsmap.insert(*dsrc); | |
7226d53b PL |
1068 | } |
1069 | } | |
1070 | } | |
243f4780 | 1071 | } |
7226d53b PL |
1072 | // There were no zone cuts (aka, we should never get here) |
1073 | return Bogus; | |
243f4780 | 1074 | } |
1075 | ||
8455425c RG |
1076 | bool isSupportedDS(const DSRecordContent& ds) |
1077 | { | |
1078 | if (!DNSCryptoKeyEngine::isAlgorithmSupported(ds.d_algorithm)) { | |
1079 | LOG("Discarding DS "<<ds.d_tag<<" because we don't support algorithm number "<<std::to_string(ds.d_algorithm)<<endl); | |
1080 | return false; | |
1081 | } | |
1082 | ||
1083 | if (!DNSCryptoKeyEngine::isDigestSupported(ds.d_digesttype)) { | |
1084 | LOG("Discarding DS "<<ds.d_tag<<" because we don't support digest number "<<std::to_string(ds.d_digesttype)<<endl); | |
1085 | return false; | |
1086 | } | |
1087 | ||
1088 | return true; | |
1089 | } | |
243f4780 | 1090 | |
895449a5 RG |
1091 | DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) |
1092 | { | |
7af99dff | 1093 | for (const auto& sig : signatures) { |
9b061cf5 RG |
1094 | if (sig) { |
1095 | return sig->d_signer; | |
1096 | } | |
895449a5 | 1097 | } |
243f4780 | 1098 | |
895449a5 RG |
1099 | return DNSName(); |
1100 | } |