From: Otto Date: Mon, 13 Dec 2021 17:42:09 +0000 (+0100) Subject: Validation in the presense of RRSIGS now works. X-Git-Tag: auth-4.7.0-alpha1~67^2~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e7ea7d4e12ab73d7367ecc5427da2cb3be7b8390;p=thirdparty%2Fpdns.git Validation in the presense of RRSIGS now works. 1. Skip RSIGS for apex ZONEMD 2. Process records individually for RRSIG rrsets , since they might have different original TTLs. --- diff --git a/pdns/dnsrecords.hh b/pdns/dnsrecords.hh index ef312c1dc5..7a2315f517 100644 --- a/pdns/dnsrecords.hh +++ b/pdns/dnsrecords.hh @@ -592,7 +592,6 @@ public: includeboilerplate(ZONEMD) //ZONEMDRecordContent(uint32_t serial, uint8_t scheme, uint8_t hashalgo, string digest); -private: uint32_t d_serial; uint8_t d_scheme; uint8_t d_hashalgo; diff --git a/pdns/dnssecinfra.cc b/pdns/dnssecinfra.cc index df008fc97a..7c31f965e6 100644 --- a/pdns/dnssecinfra.cc +++ b/pdns/dnssecinfra.cc @@ -400,12 +400,21 @@ std::unique_ptr DNSCryptoKeyEngine::makeFromPEMString(DNSKEY * purposes, as the authoritative server correctly * sets qname to the wildcard. */ -string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, const sortedRecords_t& signRecords, bool processRRSIGLabels) +string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, const sortedRecords_t& signRecords, bool processRRSIGLabels, bool includeRRSIG_RDATA) { string toHash; - toHash.append(const_cast(rrc).serialize(g_rootdnsname, true, true)); - toHash.resize(toHash.size() - rrc.d_signature.length()); // chop off the end, don't sign the signature! + // dnssec: signature = sign(RRSIG_RDATA | RR(1) | RR(2)... ) + // From RFC 4034 + // RRSIG_RDATA is the wire format of the RRSIG RDATA fields + // with the Signer's Name field in canonical form and + // the Signature field excluded; + // zonemd: digest = hash( RR(1) | RR(2) | RR(3) | ... ), so skip RRSIG_RDATA + + if (includeRRSIG_RDATA) { + toHash.append(const_cast(rrc).serialize(g_rootdnsname, true, true)); + toHash.resize(toHash.size() - rrc.d_signature.length()); // chop off the end, don't sign the signature! + } string nameToHash(qname.toDNSStringLC()); if (processRRSIGLabels) { diff --git a/pdns/dnssecinfra.hh b/pdns/dnssecinfra.hh index 00f3befc06..627c683499 100644 --- a/pdns/dnssecinfra.hh +++ b/pdns/dnssecinfra.hh @@ -160,7 +160,7 @@ struct sharedDNSSECRecordCompare { typedef std::set, sharedDNSSECRecordCompare> sortedRecords_t; -string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, const sortedRecords_t& signRecords, bool processRRSIGLabels = false); +string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, const sortedRecords_t& signRecords, bool processRRSIGLabels = false, bool includeRRSIG_RDATA = true); DSRecordContent makeDSFromDNSKey(const DNSName& qname, const DNSKEYRecordContent& drc, uint8_t digest); diff --git a/pdns/pdnsutil.cc b/pdns/pdnsutil.cc index 130fc2a4ee..335bdb8758 100644 --- a/pdns/pdnsutil.cc +++ b/pdns/pdnsutil.cc @@ -39,6 +39,8 @@ #include "bind-dnssec.schema.sqlite3.sql.h" #endif +#include + StatBag S; AuthPacketCache PC; AuthQueryCache QC; @@ -1358,6 +1360,153 @@ static int xcryptIP(const std::string& cmd, const std::string& ip, const std::st return EXIT_SUCCESS; } +static int zonemdVerifyFile(const DNSName& zone, const string& fname) { + ZoneParserTNG zpt(fname, zone); + zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps")); + + typedef pair rrSetKey_t; + typedef vector> rrVector_t; + + struct CanonrrSetKeyCompare: public std::binary_function + { + bool operator()(const rrSetKey_t&a, const rrSetKey_t& b) const + { + // FIXME surely we can be smarter here + if(a.first.canonCompare(b.first)) + return true; + if(b.first.canonCompare(a.first)) + return false; + + return a.second < b.second; + } + }; + + typedef std::map RRsetMap_t; + + RRsetMap_t RRsets; + std::map RRsetTTLs; + + DNSResourceRecord rr; + rrVector_t zonemdRecords; + std::shared_ptr soarc; + + while(zpt.get(rr)) { + if(!rr.qname.isPartOf(zone) && rr.qname!=zone) { + continue; + cerr<<"File contains record named '"< drc; + try { + drc = DNSRecordContent::mastermake(rr.qtype, QClass::IN, rr.content); + } + catch (const PDNSException &pe) { + cerr<<"Bad record content in record for "<(drc); + } + if (rr.qtype == QType::ZONEMD && rr.qname == zone) { + zonemdRecords.push_back(drc); + } + rrSetKey_t key = std::pair(rr.qname, rr.qtype); + RRsets[key].push_back(drc); + RRsetTTLs[key] = rr.ttl; + } + + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (mdctx == nullptr) { + } + const EVP_MD *md = EVP_sha384(); + + if (EVP_DigestInit_ex(mdctx, md, NULL) == 0) { + } + + for (auto& rrset: RRsets) { + const auto& qname = rrset.first.first; + const auto& qtype = rrset.first.second; + if (qtype == QType::ZONEMD && qname == zone) { + continue; // the apex ZONEMD is not digested + } + + sortedRecords_t sorted; + for (auto& _rr: rrset.second) { + if (qtype == QType::RRSIG) { + const auto rrsig = std::dynamic_pointer_cast(_rr); + if (rrsig->d_type == QType::ZONEMD && qname == zone) { + continue; + } + } + sorted.insert(_rr); + } + + if (qtype != QType::RRSIG) { + RRSIGRecordContent rrc; + rrc.d_originalttl = RRsetTTLs[rrset.first]; + rrc.d_type = qtype; + auto msg = getMessageForRRSET(qname, rrc, sorted, false, false); + if (EVP_DigestUpdate(mdctx, msg.data(), msg.size()) == 0) { + } + } else { + for (const auto& rrsig : sorted) { + auto rrsigc = std::dynamic_pointer_cast(rrsig); + RRSIGRecordContent rrc; + rrc.d_originalttl = RRsetTTLs[pair(rrset.first.first, rrsigc->d_type)]; + rrc.d_type = qtype; + auto msg = getMessageForRRSET(qname, rrc, { rrsigc }, false, false); + if (EVP_DigestUpdate(mdctx, msg.data(), msg.size()) == 0) { + } + } + } + } + + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + if (EVP_DigestFinal_ex(mdctx, md_value, &md_len) == 0) { + } + EVP_MD_CTX_free(mdctx); + string sha384 = string(reinterpret_cast(md_value), md_len); + for (const auto& z : zonemdRecords) { + const std::shared_ptr zonemd = std::dynamic_pointer_cast(z); + cerr << "Checking against " << zonemd->getZoneRepresentation() << endl; + if (zonemd->d_serial != soarc->d_st.serial) { + cerr << "SOA serial does not match " << endl; + continue; + } + if (zonemd->d_scheme != 1) { + cerr << "Unsupported scheme " << std::to_string(zonemd->d_scheme) << endl; + continue; + } + if (zonemd->d_hashalgo == 1) { + if (zonemd->d_digest != sha384) { + cerr << "sha384 mismatch" << endl; + continue; + } + } + else if (zonemd->d_hashalgo == 2) { + //if (zonemd->d_digest != pdns_sha512sum(toHash.str())) { + cerr << "sha512 unavailable" << endl; + continue; + //} + } + else { + cerr << "Unsupported hashalgo " << std::to_string(zonemd->d_hashalgo) << endl; + continue; + } + cerr << "OK!" << endl; + } + + return EXIT_SUCCESS; +} static int loadZone(const DNSName& zone, const string& fname) { UeberBackend B; @@ -2536,6 +2685,19 @@ try } } + if(cmds[0] == "zonemd-verify-file") { + if(cmds.size() < 3) { + cerr<<"Syntax: pdnsutil zonemd-verify-file ZONE FILENAME"<