3 #include "dnsrecords.hh"
4 #include "dnssecinfra.hh"
6 #include "zoneparser-tng.hh"
9 void pdns::ZoneMD::readRecords(ZoneParserTNG
& zpt
)
11 DNSResourceRecord dnsResourceRecord
;
13 while (zpt
.get(dnsResourceRecord
)) {
14 std::shared_ptr
<DNSRecordContent
> drc
;
16 drc
= DNSRecordContent::make(dnsResourceRecord
.qtype
, QClass::IN
, dnsResourceRecord
.content
);
18 catch (const PDNSException
& pe
) {
19 std::string err
= "Bad record content in record for '" + dnsResourceRecord
.qname
.toStringNoDot() + "'|" + dnsResourceRecord
.qtype
.toString() + ": " + pe
.reason
;
20 throw PDNSException(err
);
22 catch (const std::exception
& e
) {
23 std::string err
= "Bad record content in record for '" + dnsResourceRecord
.qname
.toStringNoDot() + "|" + dnsResourceRecord
.qtype
.toString() + "': " + e
.what();
24 throw PDNSException(err
);
27 rec
.d_name
= dnsResourceRecord
.qname
;
28 rec
.setContent(std::move(drc
));
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?
37 void pdns::ZoneMD::readRecords(const vector
<DNSRecord
>& records
)
39 for (const auto& record
: records
) {
44 void pdns::ZoneMD::processRecord(const DNSRecord
& record
)
46 if (record
.d_class
== QClass::IN
&& record
.d_name
== d_zone
) {
47 switch (record
.d_type
) {
49 d_soaRecordContent
= getRR
<SOARecordContent
>(record
);
50 if (d_soaRecordContent
== nullptr) {
51 throw PDNSException("Invalid SOA record");
56 auto dnskey
= getRR
<DNSKEYRecordContent
>(record
);
57 if (dnskey
== nullptr) {
58 throw PDNSException("Invalid DNSKEY record");
60 d_dnskeys
.emplace(dnskey
);
64 auto zonemd
= getRR
<ZONEMDRecordContent
>(record
);
65 if (zonemd
== nullptr) {
66 throw PDNSException("Invalid ZONEMD record");
68 auto inserted
= d_zonemdRecords
.insert({pair(zonemd
->d_scheme
, zonemd
->d_hashalgo
), {zonemd
, false}});
69 if (!inserted
.second
) {
71 inserted
.first
->second
.duplicate
= true;
76 auto rrsig
= getRR
<RRSIGRecordContent
>(record
);
77 if (rrsig
== nullptr) {
78 throw PDNSException("Invalid RRSIG record");
80 d_rrsigs
[rrsig
->d_type
].emplace_back(rrsig
);
81 if (rrsig
->d_type
== QType::NSEC
) {
82 d_nsecs
.signatures
.emplace_back(rrsig
);
84 // RRSIG on NEC3 handled below
88 auto nsec
= getRR
<NSECRecordContent
>(record
);
89 if (nsec
== nullptr) {
90 throw PDNSException("Invalid NSEC record");
92 d_nsecs
.records
.emplace(nsec
);
98 case QType::NSEC3PARAM
: {
99 auto param
= getRR
<NSEC3PARAMRecordContent
>(record
);
100 if (param
== nullptr) {
101 throw PDNSException("Invalid NSEC3PARAM record");
103 if (g_maxNSEC3Iterations
> 0 && param
->d_iterations
> g_maxNSEC3Iterations
) {
106 d_nsec3params
.emplace_back(param
);
107 d_nsec3label
= d_zone
;
108 d_nsec3label
.prependRawLabel(toBase32Hex(hashQNameWithSalt(param
->d_salt
, param
->d_iterations
, d_zone
)));
109 // Zap the NSEC3 at labels that we now know are not relevant
110 for (auto item
= d_nsec3s
.begin(); item
!= d_nsec3s
.end();) {
111 if (item
->first
!= d_nsec3label
) {
112 item
= d_nsec3s
.erase(item
);
124 void pdns::ZoneMD::readRecord(const DNSRecord
& record
)
126 if (!record
.d_name
.isPartOf(d_zone
) && record
.d_name
!= d_zone
) {
129 if (record
.d_class
== QClass::IN
&& record
.d_type
== QType::SOA
&& d_soaRecordContent
) {
133 processRecord(record
);
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
)) {
137 switch (record
.d_type
) {
139 auto nsec3
= getRR
<NSEC3RecordContent
>(record
);
140 if (nsec3
== nullptr) {
141 throw PDNSException("Invalid NSEC3 record");
143 d_nsec3s
[record
.d_name
].records
.emplace(nsec3
);
147 auto rrsig
= getRR
<RRSIGRecordContent
>(record
);
148 if (rrsig
== nullptr) {
149 throw PDNSException("Invalid RRSIG record");
151 if (rrsig
->d_type
== QType::NSEC3
) {
152 d_nsec3s
[record
.d_name
].signatures
.emplace_back(rrsig
);
158 RRSetKey_t key
= std::pair(record
.d_name
, record
.d_type
);
159 d_resourceRecordSets
[key
].push_back(record
.getContent());
160 d_resourceRecordSetTTLs
[key
] = record
.d_ttl
;
163 void pdns::ZoneMD::verify(bool& validationDone
, bool& validationOK
)
165 validationDone
= false;
166 validationOK
= false;
168 if (!d_soaRecordContent
) {
171 // Get all records and remember RRSets and TTLs
173 // Determine which digests to compute based on accepted zonemd records present
174 unique_ptr
<pdns::SHADigest
> sha384digest
{nullptr};
175 unique_ptr
<pdns::SHADigest
> sha512digest
{nullptr};
177 for (const auto& item
: d_zonemdRecords
) {
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.
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.
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.
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)) {
192 // A supported ZONEMD record
193 if (record
->d_hashalgo
== 1) {
194 sha384digest
= make_unique
<pdns::SHADigest
>(384);
196 else if (record
->d_hashalgo
== 2) {
197 sha512digest
= make_unique
<pdns::SHADigest
>(512);
202 if (!sha384digest
&& !sha512digest
) {
203 // No supported ZONEMD algo found, mismatch in SOA, mismatch in scheme or duplicate
208 auto hash
= [&sha384digest
, &sha512digest
](const std::string
& msg
) {
210 sha384digest
->process(msg
);
213 sha512digest
->process(msg
);
217 // Compute requested digests
218 for (auto& rrset
: d_resourceRecordSets
) {
219 const auto& qname
= rrset
.first
.first
;
220 const auto& qtype
= rrset
.first
.second
;
221 if (qtype
== QType::ZONEMD
&& qname
== d_zone
) {
222 continue; // the apex ZONEMD is not digested
225 sortedRecords_t sorted
;
226 for (auto& resourceRecord
: rrset
.second
) {
227 if (qtype
== QType::RRSIG
) {
228 const auto rrsig
= std::dynamic_pointer_cast
<const RRSIGRecordContent
>(resourceRecord
);
229 if (rrsig
->d_type
== QType::ZONEMD
&& qname
== d_zone
) {
233 sorted
.insert(resourceRecord
);
236 if (sorted
.empty()) {
240 if (qtype
!= QType::RRSIG
) {
241 RRSIGRecordContent rrc
;
242 rrc
.d_originalttl
= d_resourceRecordSetTTLs
[rrset
.first
];
244 auto msg
= getMessageForRRSET(qname
, rrc
, sorted
, false, false);
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
) {
251 auto rrsigc
= std::dynamic_pointer_cast
<const RRSIGRecordContent
>(rrsig
);
252 RRSIGRecordContent rrc
;
253 rrc
.d_originalttl
= d_resourceRecordSetTTLs
[pair(rrset
.first
.first
, rrsigc
->d_type
)];
255 auto msg
= getMessageForRRSET(qname
, rrc
, {rrsigc
}, false, false);
262 for (const auto& [k
, v
] : d_zonemdRecords
) {
263 auto [zonemd
, duplicate
] = v
;
264 if (zonemd
->d_hashalgo
== 1) {
265 validationDone
= true;
266 auto computed
= sha384digest
->digest();
267 if (constantTimeStringEquals(zonemd
->d_digest
, computed
)) {
269 break; // Per RFC: a single succeeding validation is enough
272 else if (zonemd
->d_hashalgo
== 2) {
273 validationDone
= true;
274 auto computed
= sha512digest
->digest();
275 if (constantTimeStringEquals(zonemd
->d_digest
, computed
)) {
277 break; // Per RFC: a single succeeding validation is enough