tsigutils.hh tsigutils.cc \
ueberbackend.cc \
unix_utility.cc \
+ zonemd.hh zonemd.cc \
zoneparser-tng.cc
pdnsutil_LDFLAGS = \
#include "dns_random.hh"
#include "ipcipher.hh"
#include "misc.hh"
+#include "zonemd.hh"
#include <fstream>
#include <utility>
#include <termios.h> //termios, TCSANOW, ECHO, ICANON
#include "bind-dnssec.schema.sqlite3.sql.h"
#endif
-#include <openssl/evp.h>
-
StatBag S;
AuthPacketCache PC;
AuthQueryCache QC;
return EXIT_SUCCESS;
}
+
static int zonemdVerifyFile(const DNSName& zone, const string& fname) {
ZoneParserTNG zpt(fname, zone);
zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
- typedef std::pair<DNSName, QType> rrSetKey_t;
- typedef std::vector<std::shared_ptr<DNSRecordContent>> rrVector_t;
-
- struct CanonrrSetKeyCompare: public std::binary_function<rrSetKey_t, rrSetKey_t, bool>
- {
- 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<rrSetKey_t, rrVector_t, CanonrrSetKeyCompare> RRsetMap_t;
-
- RRsetMap_t RRsets;
- std::map<rrSetKey_t, uint32_t> RRsetTTLs;
-
- DNSResourceRecord rr;
- std::map<std::pair<uint8_t, uint8_t>, std::shared_ptr<ZONEMDRecordContent>> zonemdRecords;
- std::shared_ptr<SOARecordContent> soarc;
-
- class SHADigest
- {
- public:
- SHADigest(int bits)
- {
- mdctx = EVP_MD_CTX_new();
- if (mdctx == nullptr) {
- throw std::runtime_error("VSHADigest: P_MD_CTX_new failed");
- }
- switch (bits) {
- case 256:
- md = EVP_sha256();
- break;
- case 384:
- md = EVP_sha384();
- break;
- case 512:
- md = EVP_sha512();
- break;
- default:
- throw std::runtime_error("SHADigest: unsupported size");
- }
- if (EVP_DigestInit_ex(mdctx, md, NULL) == 0) {
- throw std::runtime_error("SHADigest: init error");
- }
- }
- ~SHADigest()
- {
- // No free of md needed afaik
- if (mdctx != nullptr) {
- EVP_MD_CTX_free(mdctx);
- }
- }
- void process(const string& msg, size_t sz)
- {
- if (EVP_DigestUpdate(mdctx, msg.data(), msg.size()) == 0) {
- throw std::runtime_error("SHADigest: update error");
- }
- }
- std::string digest()
- {
- string md_value;
- md_value.resize(EVP_MD_size(md));
- unsigned int md_len;
- if (EVP_DigestFinal_ex(mdctx, reinterpret_cast<unsigned char*>(md_value.data()), &md_len) == 0) {
- throw std::runtime_error("SHADigest: finalize error");
- }
- if (md_len != md_value.size()) {
- throw std::runtime_error("SHADigest: inconsisten size");
- }
- return md_value;
- }
- private:
- EVP_MD_CTX *mdctx{nullptr};
- const EVP_MD *md;
- };
-
- while (zpt.get(rr)) {
- if(!rr.qname.isPartOf(zone) && rr.qname!=zone) {
- continue;
- cerr<<"File contains record named '"<<rr.qname<<"' which is not part of zone '"<<zone<<"'"<<endl;
- return EXIT_FAILURE;
- }
- if (rr.qtype == QType::SOA) {
- if (soarc)
- continue;
- }
- std::shared_ptr<DNSRecordContent> drc;
- try {
- drc = DNSRecordContent::mastermake(rr.qtype, QClass::IN, rr.content);
- }
- catch (const PDNSException &pe) {
- cerr<<"Bad record content in record for "<<rr.qname<<"|"<<rr.qtype<<": "<<pe.reason<<endl;
- return EXIT_FAILURE;
- }
- catch (const std::exception &e) {
- cerr<<"Bad record content in record for "<<rr.qname<<"|"<<rr.qtype<<": "<<e.what()<<endl;
- return EXIT_FAILURE;
- }
- if (rr.qtype == QType::SOA && rr.qname == zone) {
- soarc = std::dynamic_pointer_cast<SOARecordContent>(drc);
- }
- if (rr.qtype == QType::ZONEMD && rr.qname == zone) {
- auto zonemd = std::dynamic_pointer_cast<ZONEMDRecordContent>(drc);
- auto inserted = zonemdRecords.insert(pair(pair(zonemd->d_scheme, zonemd->d_hashalgo), zonemd)).second;
- if (!inserted) {
- cerr << "Duplicate ZONEMD record!" << endl;
- }
- }
- rrSetKey_t key = std::pair(rr.qname, rr.qtype);
- RRsets[key].push_back(drc);
- RRsetTTLs[key] = rr.ttl;
- }
-
- unique_ptr<SHADigest> sha384digest{nullptr}, sha512digest{nullptr};
+ bool validationDone, validationOK;
- for (const auto& [k, zonemd] : zonemdRecords) {
- 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) {
- sha384digest = make_unique<SHADigest>(384);
- }
- else if (zonemd->d_hashalgo == 2) {
- sha512digest = make_unique<SHADigest>(512);
- }
- else {
- cerr << "Unsupported hashalgo " << std::to_string(zonemd->d_hashalgo) << endl;
- continue;
- }
+ try {
+ pdns::zonemdVerify(zone, zpt, validationDone, validationOK);
}
-
- 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<RRSIGRecordContent>(_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 (sha384digest) {
- sha384digest->process(msg, msg.size());
- }
- if (sha512digest) {
- sha512digest->process(msg, msg.size());
- }
- } else {
- for (const auto& rrsig : sorted) {
- auto rrsigc = std::dynamic_pointer_cast<RRSIGRecordContent>(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 (sha384digest) {
- sha384digest->process(msg, msg.size());
- }
- if (sha512digest) {
- sha512digest->process(msg, msg.size());
- }
- }
- }
+ catch (const PDNSException& ex) {
+ cerr << "zonemd-verify-file: " << ex.reason << endl;
+ return EXIT_FAILURE;
}
-
- bool validationDone = false;
- bool validationOK = false;
-
- for (const auto& [k, zonemd] : zonemdRecords) {
- 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) {
- validationDone = true;
- if (zonemd->d_digest == sha384digest->digest()) {
- validationOK = true;
- break; // Per RFC: a single succeeding validation is enough
- } else {
- continue;
- }
- }
- else if (zonemd->d_hashalgo == 2) {
- validationDone = true;
- if (zonemd->d_digest == sha512digest->digest()) {
- validationOK = true;
- break; // Per RFC: a single succeeding validation is enough
- } else {
- continue;
- }
- }
- else {
- cerr << "Unsupported hashalgo " << std::to_string(zonemd->d_hashalgo) << endl;
- continue;
- }
+ catch (const std::exception& ex) {
+ cerr << "zonemd-verify-file: " << ex.what() << endl;
}
if (validationDone) {
if (validationOK) {
- cerr << "OK!" << endl;
+ cout << "zonemd-verify-file: Validation of ZONEMD record succeeded" << endl;
return EXIT_SUCCESS;
} else {
- cerr << "Validation of ZONEMD record(s) failed" << endl;
+ cerr << "zonemd-verify-file: Validation of ZONEMD record(s) failed" << endl;
}
}
- if (!validationDone) {
- cerr << "No suitable ZONEMD record found to verify against" << endl;
+ else {
+ cerr << "zonemd-verify-file: No suitable ZONEMD record found to verify against" << endl;
}
return EXIT_FAILURE;
}
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
+
#include <string>
-#include <stdint.h>
#include <openssl/sha.h>
+#include <openssl/evp.h>
inline std::string pdns_sha1sum(const std::string& input)
{
SHA512(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
return std::string(result, result + sizeof result);
}
+
+namespace pdns
+{
+ class SHADigest
+ {
+ public:
+ SHADigest(int bits)
+ {
+ mdctx = EVP_MD_CTX_new();
+ if (mdctx == nullptr) {
+ throw std::runtime_error("VSHADigest: P_MD_CTX_new failed");
+ }
+ switch (bits) {
+ case 256:
+ md = EVP_sha256();
+ break;
+ case 384:
+ md = EVP_sha384();
+ break;
+ case 512:
+ md = EVP_sha512();
+ break;
+ default:
+ throw std::runtime_error("SHADigest: unsupported size");
+ }
+ if (EVP_DigestInit_ex(mdctx, md, NULL) == 0) {
+ throw std::runtime_error("SHADigest: init error");
+ }
+ }
+
+ ~SHADigest()
+ {
+ // No free of md needed afaik
+ if (mdctx != nullptr) {
+ EVP_MD_CTX_free(mdctx);
+ }
+ }
+
+ void process(const std::string& msg, size_t sz)
+ {
+ if (EVP_DigestUpdate(mdctx, msg.data(), msg.size()) == 0) {
+ throw std::runtime_error("SHADigest: update error");
+ }
+ }
+
+ std::string digest()
+ {
+ std::string md_value;
+ md_value.resize(EVP_MD_size(md));
+ unsigned int md_len;
+ if (EVP_DigestFinal_ex(mdctx, reinterpret_cast<unsigned char*>(md_value.data()), &md_len) == 0) {
+ throw std::runtime_error("SHADigest: finalize error");
+ }
+ if (md_len != md_value.size()) {
+ throw std::runtime_error("SHADigest: inconsisten size");
+ }
+ return md_value;
+ }
+ private:
+ EVP_MD_CTX *mdctx{nullptr};
+ const EVP_MD *md;
+ };
+}
--- /dev/null
+#include "zonemd.hh"
+
+#include "dnsrecords.hh"
+#include "dnssecinfra.hh"
+#include "sha.hh"
+#include "zoneparser-tng.hh"
+
+typedef std::pair<DNSName, QType> rrSetKey_t;
+typedef std::vector<std::shared_ptr<DNSRecordContent>> rrVector_t;
+
+struct CanonrrSetKeyCompare: public std::binary_function<rrSetKey_t, rrSetKey_t, bool>
+{
+ 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<rrSetKey_t, rrVector_t, CanonrrSetKeyCompare> RRsetMap_t;
+
+RRsetMap_t RRsets;
+std::map<rrSetKey_t, uint32_t> RRsetTTLs;
+
+void pdns::zonemdVerify(const DNSName& zone, ZoneParserTNG &zpt, bool& validationDone, bool& validationOK)
+{
+ validationDone = false;
+ validationOK = false;
+
+ DNSResourceRecord dnsrr;
+ std::map<std::pair<uint8_t, uint8_t>, std::shared_ptr<ZONEMDRecordContent>> zonemdRecords;
+ std::shared_ptr<SOARecordContent> soarc;
+
+ // Get all records and remember RRSets and TTLs
+ while (zpt.get(dnsrr)) {
+ if (!dnsrr.qname.isPartOf(zone) && dnsrr.qname != zone) {
+ continue;
+ }
+ if (dnsrr.qtype == QType::SOA && soarc) {
+ // XXX skip extra SOA?
+ continue;
+ }
+ std::shared_ptr<DNSRecordContent> drc;
+ try {
+ drc = DNSRecordContent::mastermake(dnsrr.qtype, QClass::IN, dnsrr.content);
+ }
+ catch (const PDNSException& pe) {
+ std::string err = "Bad record content in record for '" + dnsrr.qname.toStringNoDot() + "'|" + dnsrr.qtype.toString() + ": "+ pe.reason;
+ throw PDNSException(err);
+ }
+ catch (const std::exception& e) {
+ std::string err = "Bad record content in record for '" + dnsrr.qname.toStringNoDot() + "|" + dnsrr.qtype.toString() + "': " + e.what();
+ throw PDNSException(err);
+ }
+ if (dnsrr.qtype == QType::SOA && dnsrr.qname == zone) {
+ soarc = std::dynamic_pointer_cast<SOARecordContent>(drc);
+ }
+ if (dnsrr.qtype == QType::ZONEMD && dnsrr.qname == zone) {
+ auto zonemd = std::dynamic_pointer_cast<ZONEMDRecordContent>(drc);
+ auto inserted = zonemdRecords.insert(pair(pair(zonemd->d_scheme, zonemd->d_hashalgo), zonemd)).second;
+ if (!inserted) {
+ throw PDNSException("Duplicate ZONEMD record");
+ }
+ }
+ rrSetKey_t key = std::pair(dnsrr.qname, dnsrr.qtype);
+ RRsets[key].push_back(drc);
+ RRsetTTLs[key] = dnsrr.ttl;
+ }
+
+ // Determine which digests to compute based on accepted zonemd records present
+ unique_ptr<pdns::SHADigest> sha384digest{nullptr}, sha512digest{nullptr};
+
+ for (const auto& [k, zonemd] : zonemdRecords) {
+ if (zonemd->d_serial != soarc->d_st.serial) {
+ // The SOA Serial field MUST exactly match the ZONEMD Serial
+ // field. If the fields do not match, digest verification MUST
+ // NOT be considered successful with this ZONEMD RR.
+ continue;
+ }
+ if (zonemd->d_scheme != 1) {
+ // The Scheme field MUST be checked. If the verifier does not
+ // support the given scheme, verification MUST NOT be considered
+ // successful with this ZONEMD RR.
+ continue;
+ }
+ // The Hash Algorithm field MUST be checked. If the verifier does
+ // not support the given hash algorithm, verification MUST NOT be
+ // considered successful with this ZONEMD RR.
+ if (zonemd->d_hashalgo == 1) {
+ sha384digest = make_unique<pdns::SHADigest>(384);
+ }
+ else if (zonemd->d_hashalgo == 2) {
+ sha512digest = make_unique<pdns::SHADigest>(512);
+ }
+ }
+
+ // A little helper
+ auto hash = [&sha384digest, &sha512digest](const std::string& msg) {
+ if (sha384digest) {
+ sha384digest->process(msg, msg.size());
+ }
+ if (sha512digest) {
+ sha512digest->process(msg, msg.size());
+ }
+ };
+
+ // Compute requested digests
+ 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<RRSIGRecordContent>(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);
+ hash(msg);
+ } else {
+ // RRSIG is special, since original TTL depends on qtype covered by RRSIG
+ // which can be different per record
+ for (const auto& rrsig : sorted) {
+ auto rrsigc = std::dynamic_pointer_cast<RRSIGRecordContent>(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);
+ hash(msg);
+ }
+ }
+ }
+
+ // Final verify
+ for (const auto& [k, zonemd] : zonemdRecords) {
+ if (zonemd->d_serial != soarc->d_st.serial) {
+ // The SOA Serial field MUST exactly match the ZONEMD Serial
+ // field. If the fields do not match, digest verification MUST
+ // NOT be considered successful with this ZONEMD RR.
+ continue;
+ }
+ if (zonemd->d_scheme != 1) {
+ // The Scheme field MUST be checked. If the verifier does not
+ // support the given scheme, verification MUST NOT be considered
+ // successful with this ZONEMD RR.
+ continue;
+ }
+ // The Hash Algorithm field MUST be checked. If the verifier does
+ // not support the given hash algorithm, verification MUST NOT be
+ // considered successful with this ZONEMD RR.
+ if (zonemd->d_hashalgo == 1) {
+ validationDone = true;
+ if (constantTimeStringEquals(zonemd->d_digest, sha384digest->digest())) {
+ validationOK = true;
+ break; // Per RFC: a single succeeding validation is enough
+ }
+ }
+ else if (zonemd->d_hashalgo == 2) {
+ validationDone = true;
+ if (constantTimeStringEquals(zonemd->d_digest, sha512digest->digest())) {
+ validationOK = true;
+ break; // Per RFC: a single succeeding validation is enough
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "config.h"
+
+class DNSName;
+class ZoneParserTNG;
+
+namespace pdns
+{
+ void zonemdVerify(const DNSName& zone, ZoneParserTNG &zpt, bool& validationDone, bool& validationOK);
+
+}