]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/zonemd.cc
dnsdist: Fix DNS over plain HTTP broken by `reloadAllCertificates()`
[thirdparty/pdns.git] / pdns / zonemd.cc
CommitLineData
e21df721
O
1#include "zonemd.hh"
2
3#include "dnsrecords.hh"
4#include "dnssecinfra.hh"
5#include "sha.hh"
6#include "zoneparser-tng.hh"
3cb47b35 7#include "base32.hh"
e21df721 8
efe79e15 9void pdns::ZoneMD::readRecords(ZoneParserTNG& zpt)
e21df721 10{
d2e2e2ed 11 DNSResourceRecord dnsResourceRecord;
eafa87e6 12
d2e2e2ed 13 while (zpt.get(dnsResourceRecord)) {
e21df721
O
14 std::shared_ptr<DNSRecordContent> drc;
15 try {
d525b58b 16 drc = DNSRecordContent::make(dnsResourceRecord.qtype, QClass::IN, dnsResourceRecord.content);
e21df721
O
17 }
18 catch (const PDNSException& pe) {
d2e2e2ed 19 std::string err = "Bad record content in record for '" + dnsResourceRecord.qname.toStringNoDot() + "'|" + dnsResourceRecord.qtype.toString() + ": " + pe.reason;
e21df721
O
20 throw PDNSException(err);
21 }
22 catch (const std::exception& e) {
d2e2e2ed 23 std::string err = "Bad record content in record for '" + dnsResourceRecord.qname.toStringNoDot() + "|" + dnsResourceRecord.qtype.toString() + "': " + e.what();
e21df721
O
24 throw PDNSException(err);
25 }
94223b01
OM
26 DNSRecord rec;
27 rec.d_name = dnsResourceRecord.qname;
d06dcda4 28 rec.setContent(std::move(drc));
94223b01
OM
29 rec.d_type = dnsResourceRecord.qtype;
30 rec.d_class = dnsResourceRecord.qclass;
31 rec.d_ttl = dnsResourceRecord.ttl;
32 rec.d_clen = dnsResourceRecord.content.length(); // XXX is this correct?
33 readRecord(rec);
e21df721 34 }
efe79e15
OM
35}
36
5ecf9d92
OM
37void pdns::ZoneMD::readRecords(const vector<DNSRecord>& records)
38{
7dcdce8c 39 for (const auto& record : records) {
1c3bc297
OM
40 readRecord(record);
41 }
42}
5ecf9d92 43
7dcdce8c 44void pdns::ZoneMD::processRecord(const DNSRecord& record)
1c3bc297 45{
2088c7b8 46 if (record.d_class == QClass::IN && record.d_name == d_zone) {
e5163239 47 switch (record.d_type) {
94223b01 48 case QType::SOA: {
d06dcda4 49 d_soaRecordContent = getRR<SOARecordContent>(record);
94223b01
OM
50 if (d_soaRecordContent == nullptr) {
51 throw PDNSException("Invalid SOA record");
52 }
e5163239 53 break;
94223b01
OM
54 }
55 case QType::DNSKEY: {
d06dcda4 56 auto dnskey = getRR<DNSKEYRecordContent>(record);
94223b01
OM
57 if (dnskey == nullptr) {
58 throw PDNSException("Invalid DNSKEY record");
59 }
60 d_dnskeys.emplace(dnskey);
e5163239 61 break;
94223b01 62 }
e5163239 63 case QType::ZONEMD: {
d06dcda4 64 auto zonemd = getRR<ZONEMDRecordContent>(record);
94223b01
OM
65 if (zonemd == nullptr) {
66 throw PDNSException("Invalid ZONEMD record");
67 }
e5163239
OM
68 auto inserted = d_zonemdRecords.insert({pair(zonemd->d_scheme, zonemd->d_hashalgo), {zonemd, false}});
69 if (!inserted.second) {
70 // Mark as duplicate
71 inserted.first->second.duplicate = true;
72 }
73 break;
74 }
95b66e0d 75 case QType::RRSIG: {
d06dcda4 76 auto rrsig = getRR<RRSIGRecordContent>(record);
94223b01
OM
77 if (rrsig == nullptr) {
78 throw PDNSException("Invalid RRSIG record");
79 }
c7f594e2 80 d_rrsigs[rrsig->d_type].emplace_back(rrsig);
95b66e0d
OM
81 if (rrsig->d_type == QType::NSEC) {
82 d_nsecs.signatures.emplace_back(rrsig);
83 }
2088c7b8 84 // RRSIG on NEC3 handled below
95b66e0d
OM
85 break;
86 }
94223b01 87 case QType::NSEC: {
d06dcda4 88 auto nsec = getRR<NSECRecordContent>(record);
94223b01
OM
89 if (nsec == nullptr) {
90 throw PDNSException("Invalid NSEC record");
91 }
92 d_nsecs.records.emplace(nsec);
95b66e0d 93 break;
94223b01 94 }
3cb47b35
OM
95 case QType::NSEC3:
96 // Handled below
97 break;
94223b01 98 case QType::NSEC3PARAM: {
d06dcda4 99 auto param = getRR<NSEC3PARAMRecordContent>(record);
94223b01
OM
100 if (param == nullptr) {
101 throw PDNSException("Invalid NSEC3PARAM record");
102 }
7dcdce8c 103 if (g_maxNSEC3Iterations > 0 && param->d_iterations > g_maxNSEC3Iterations) {
94223b01
OM
104 return;
105 }
2088c7b8 106 d_nsec3params.emplace_back(param);
3cb47b35 107 d_nsec3label = d_zone;
94223b01 108 d_nsec3label.prependRawLabel(toBase32Hex(hashQNameWithSalt(param->d_salt, param->d_iterations, d_zone)));
2088c7b8 109 // Zap the NSEC3 at labels that we now know are not relevant
7dcdce8c
OM
110 for (auto item = d_nsec3s.begin(); item != d_nsec3s.end();) {
111 if (item->first != d_nsec3label) {
112 item = d_nsec3s.erase(item);
2088c7b8
OM
113 }
114 else {
7dcdce8c 115 ++item;
2088c7b8
OM
116 }
117 }
3cb47b35
OM
118 break;
119 }
94223b01 120 }
3cb47b35 121 }
7dcdce8c
OM
122}
123
124void pdns::ZoneMD::readRecord(const DNSRecord& record)
125{
126 if (!record.d_name.isPartOf(d_zone) && record.d_name != d_zone) {
127 return;
128 }
129 if (record.d_class == QClass::IN && record.d_type == QType::SOA && d_soaRecordContent) {
130 return;
131 }
132
133 processRecord(record);
134
2088c7b8
OM
135 // Until we have seen the NSEC3PARAM record, we save all of them, as we do not know the label for the zone yet
136 if (record.d_class == QClass::IN && (d_nsec3label.empty() || record.d_name == d_nsec3label)) {
3cb47b35 137 switch (record.d_type) {
94223b01 138 case QType::NSEC3: {
d06dcda4 139 auto nsec3 = getRR<NSEC3RecordContent>(record);
94223b01
OM
140 if (nsec3 == nullptr) {
141 throw PDNSException("Invalid NSEC3 record");
142 }
2088c7b8 143 d_nsec3s[record.d_name].records.emplace(nsec3);
e5163239 144 break;
94223b01
OM
145 }
146 case QType::RRSIG: {
d06dcda4 147 auto rrsig = getRR<RRSIGRecordContent>(record);
94223b01
OM
148 if (rrsig == nullptr) {
149 throw PDNSException("Invalid RRSIG record");
150 }
3cb47b35 151 if (rrsig->d_type == QType::NSEC3) {
2088c7b8 152 d_nsec3s[record.d_name].signatures.emplace_back(rrsig);
3cb47b35
OM
153 }
154 break;
5ecf9d92 155 }
94223b01 156 }
5ecf9d92 157 }
1c3bc297 158 RRSetKey_t key = std::pair(record.d_name, record.d_type);
d06dcda4 159 d_resourceRecordSets[key].push_back(record.getContent());
1c3bc297 160 d_resourceRecordSetTTLs[key] = record.d_ttl;
5ecf9d92
OM
161}
162
04eaccfc 163void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
efe79e15
OM
164{
165 validationDone = false;
166 validationOK = false;
167
1731fbab
OM
168 if (!d_soaRecordContent) {
169 return;
170 }
efe79e15 171 // Get all records and remember RRSets and TTLs
e21df721
O
172
173 // Determine which digests to compute based on accepted zonemd records present
7dcdce8c
OM
174 unique_ptr<pdns::SHADigest> sha384digest{nullptr};
175 unique_ptr<pdns::SHADigest> sha512digest{nullptr};
e21df721 176
7dcdce8c 177 for (const auto& item : d_zonemdRecords) {
eafa87e6
O
178 // The SOA Serial field MUST exactly match the ZONEMD Serial
179 // field. If the fields do not match, digest verification MUST
180 // NOT be considered successful with this ZONEMD RR.
181
182 // The Scheme field MUST be checked. If the verifier does not
183 // support the given scheme, verification MUST NOT be considered
184 // successful with this ZONEMD RR.
185
e21df721
O
186 // The Hash Algorithm field MUST be checked. If the verifier does
187 // not support the given hash algorithm, verification MUST NOT be
188 // considered successful with this ZONEMD RR.
7dcdce8c
OM
189 const auto duplicate = item.second.duplicate;
190 const auto& record = item.second.record;
191 if (!duplicate && record->d_serial == d_soaRecordContent->d_st.serial && record->d_scheme == 1 && (record->d_hashalgo == 1 || record->d_hashalgo == 2)) {
eafa87e6 192 // A supported ZONEMD record
7dcdce8c 193 if (record->d_hashalgo == 1) {
eafa87e6
O
194 sha384digest = make_unique<pdns::SHADigest>(384);
195 }
7dcdce8c 196 else if (record->d_hashalgo == 2) {
eafa87e6
O
197 sha512digest = make_unique<pdns::SHADigest>(512);
198 }
e21df721
O
199 }
200 }
201
77d7ab70
OM
202 if (!sha384digest && !sha512digest) {
203 // No supported ZONEMD algo found, mismatch in SOA, mismatch in scheme or duplicate
204 return;
205 }
206
e21df721
O
207 // A little helper
208 auto hash = [&sha384digest, &sha512digest](const std::string& msg) {
209 if (sha384digest) {
01498d87 210 sha384digest->process(msg);
e21df721
O
211 }
212 if (sha512digest) {
01498d87 213 sha512digest->process(msg);
e21df721
O
214 }
215 };
216
217 // Compute requested digests
efe79e15 218 for (auto& rrset : d_resourceRecordSets) {
e21df721
O
219 const auto& qname = rrset.first.first;
220 const auto& qtype = rrset.first.second;
efe79e15 221 if (qtype == QType::ZONEMD && qname == d_zone) {
e21df721
O
222 continue; // the apex ZONEMD is not digested
223 }
224
225 sortedRecords_t sorted;
7dcdce8c 226 for (auto& resourceRecord : rrset.second) {
e21df721 227 if (qtype == QType::RRSIG) {
7dcdce8c 228 const auto rrsig = std::dynamic_pointer_cast<const RRSIGRecordContent>(resourceRecord);
efe79e15 229 if (rrsig->d_type == QType::ZONEMD && qname == d_zone) {
e21df721
O
230 continue;
231 }
232 }
7dcdce8c 233 sorted.insert(resourceRecord);
e21df721
O
234 }
235
e5163239 236 if (sorted.empty()) {
7f9ac81c 237 continue;
e5163239 238 }
b8d0d0db 239
e21df721
O
240 if (qtype != QType::RRSIG) {
241 RRSIGRecordContent rrc;
efe79e15 242 rrc.d_originalttl = d_resourceRecordSetTTLs[rrset.first];
e21df721
O
243 rrc.d_type = qtype;
244 auto msg = getMessageForRRSET(qname, rrc, sorted, false, false);
245 hash(msg);
2eaaa7c1
O
246 }
247 else {
e21df721
O
248 // RRSIG is special, since original TTL depends on qtype covered by RRSIG
249 // which can be different per record
250 for (const auto& rrsig : sorted) {
d06dcda4 251 auto rrsigc = std::dynamic_pointer_cast<const RRSIGRecordContent>(rrsig);
e21df721 252 RRSIGRecordContent rrc;
efe79e15 253 rrc.d_originalttl = d_resourceRecordSetTTLs[pair(rrset.first.first, rrsigc->d_type)];
e21df721 254 rrc.d_type = qtype;
2eaaa7c1 255 auto msg = getMessageForRRSET(qname, rrc, {rrsigc}, false, false);
e21df721
O
256 hash(msg);
257 }
258 }
259 }
260
77d7ab70 261 // Final verify
efe79e15 262 for (const auto& [k, v] : d_zonemdRecords) {
eafa87e6 263 auto [zonemd, duplicate] = v;
e21df721
O
264 if (zonemd->d_hashalgo == 1) {
265 validationDone = true;
eafa87e6
O
266 auto computed = sha384digest->digest();
267 if (constantTimeStringEquals(zonemd->d_digest, computed)) {
e21df721
O
268 validationOK = true;
269 break; // Per RFC: a single succeeding validation is enough
270 }
271 }
272 else if (zonemd->d_hashalgo == 2) {
273 validationDone = true;
eafa87e6
O
274 auto computed = sha512digest->digest();
275 if (constantTimeStringEquals(zonemd->d_digest, computed)) {
e21df721
O
276 validationOK = true;
277 break; // Per RFC: a single succeeding validation is enough
278 }
279 }
280 }
281}