Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First part of ZONEMD support #11100

Merged
merged 16 commits into from Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect.txt
Expand Up @@ -1916,6 +1916,7 @@ zilopbg
Zmd
zonecryptokey
zonefile
zonemd
zonemetadata
zonename
zoneparser
Expand Down
2 changes: 2 additions & 0 deletions docs/manpages/pdnsutil.1.rst
Expand Up @@ -251,6 +251,8 @@ unset-presigned *ZONE*
Disables presigned operation for *ZONE*.
raw-lua-from-content *TYPE* *CONTENT*
Display record contents in a form suitable for dnsdist's `SpoofRawAction`.
zonemd-verify-file *ZONE* *FILE*
Validate ZONEMD for *ZONE* read from *FILE*.

DEBUGGING TOOLS
---------------
Expand Down
3 changes: 3 additions & 0 deletions pdns/Makefile.am
Expand Up @@ -378,6 +378,7 @@ pdnsutil_SOURCES = \
tsigutils.hh tsigutils.cc \
ueberbackend.cc \
unix_utility.cc \
zonemd.hh zonemd.cc \
zoneparser-tng.cc

pdnsutil_LDFLAGS = \
Expand Down Expand Up @@ -1405,13 +1406,15 @@ testrunner_SOURCES = \
test-trusted-notification-proxy_cc.cc \
test-tsig.cc \
test-ueberbackend_cc.cc \
test-zonemd_cc.cc \
test-zoneparser_tng_cc.cc \
testrunner.cc \
threadname.hh threadname.cc \
trusted-notification-proxy.cc \
tsigverifier.cc tsigverifier.hh \
ueberbackend.cc ueberbackend.hh \
unix_utility.cc \
zonemd.cc zonemd.hh \
zoneparser-tng.cc zoneparser-tng.hh

testrunner_LDFLAGS = \
Expand Down
8 changes: 8 additions & 0 deletions pdns/dnsrecords.cc
Expand Up @@ -305,6 +305,13 @@ boilerplate_conv(KEY,
conv.xfrBlob(d_certificate);
);

boilerplate_conv(ZONEMD,
conv.xfr32BitInt(d_serial);
conv.xfr8BitInt(d_scheme);
conv.xfr8BitInt(d_hashalgo);
conv.xfrHexBlob(d_digest, true); // keep reading across spaces
);

boilerplate_conv(CERT,
conv.xfr16BitInt(d_type);
if (d_type == 0) throw MOADNSException("CERT type 0 is reserved");
Expand Down Expand Up @@ -963,6 +970,7 @@ void reportOtherTypes()
L32RecordContent::report();
L64RecordContent::report();
LPRecordContent::report();
ZONEMDRecordContent::report();
}

void reportAllTypes()
Expand Down
12 changes: 12 additions & 0 deletions pdns/dnsrecords.hh
Expand Up @@ -586,6 +586,18 @@ public:
struct soatimes d_st;
};

class ZONEMDRecordContent : public DNSRecordContent
{
public:
includeboilerplate(ZONEMD)
//ZONEMDRecordContent(uint32_t serial, uint8_t scheme, uint8_t hashalgo, string digest);

uint32_t d_serial;
uint8_t d_scheme;
uint8_t d_hashalgo;
string d_digest;
};

class NSECBitmap
{
public:
Expand Down
15 changes: 12 additions & 3 deletions pdns/dnssecinfra.cc
Expand Up @@ -400,12 +400,21 @@ std::unique_ptr<DNSCryptoKeyEngine> 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<RRSIGRecordContent&>(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<RRSIGRecordContent&>(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) {
Expand Down
2 changes: 1 addition & 1 deletion pdns/dnssecinfra.hh
Expand Up @@ -160,7 +160,7 @@ struct sharedDNSSECRecordCompare {

typedef std::set<std::shared_ptr<DNSRecordContent>, 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);

Expand Down
46 changes: 46 additions & 0 deletions pdns/pdnsutil.cc
Expand Up @@ -27,6 +27,7 @@
#include "dns_random.hh"
#include "ipcipher.hh"
#include "misc.hh"
#include "zonemd.hh"
#include <fstream>
#include <utility>
#include <termios.h> //termios, TCSANOW, ECHO, ICANON
Expand Down Expand Up @@ -1359,6 +1360,38 @@ static int xcryptIP(const std::string& cmd, const std::string& ip, const std::st
}


static int zonemdVerifyFile(const DNSName& zone, const string& fname) {
ZoneParserTNG zpt(fname, zone);
zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));

bool validationDone, validationOK;

try {
pdns::zonemdVerify(zone, zpt, validationDone, validationOK);
}
catch (const PDNSException& ex) {
cerr << "zonemd-verify-file: " << ex.reason << endl;
return EXIT_FAILURE;
}
catch (const std::exception& ex) {
cerr << "zonemd-verify-file: " << ex.what() << endl;
return EXIT_FAILURE;
}

if (validationDone) {
if (validationOK) {
cout << "zonemd-verify-file: Verification of ZONEMD record succeeded" << endl;
return EXIT_SUCCESS;
} else {
cerr << "zonemd-verify-file: Verification of ZONEMD record(s) failed" << endl;
}
}
else {
cerr << "zonemd-verify-file: No suitable ZONEMD record found to verify against" << endl;
}
return EXIT_FAILURE;
}

static int loadZone(const DNSName& zone, const string& fname) {
UeberBackend B;
DomainInfo di;
Expand Down Expand Up @@ -2413,6 +2446,7 @@ try
cout<<"unset-publish-cds ZONE Disable sending CDS responses for ZONE"<<endl;
cout<<"test-schema ZONE Test DB schema - will create ZONE"<<endl;
cout<<"raw-lua-from-content TYPE CONTENT Display record contents in a form suitable for dnsdist's `SpoofRawAction`"<<endl;
cout<<"zonemd-verify-file ZONE FILE Validate ZONEMD for ZONE"<<endl;
cout<<desc<<endl;
return 0;
}
Expand Down Expand Up @@ -2536,6 +2570,18 @@ try
}
}

if(cmds[0] == "zonemd-verify-file") {
if(cmds.size() < 3) {
cerr<<"Syntax: pdnsutil zonemd-verify-file ZONE FILENAME"<<endl;
return 1;
}
if(cmds[1]==".")
cmds[1].clear();

auto ret = zonemdVerifyFile(DNSName(cmds[1]), cmds[2]);
return ret;
}

DNSSECKeeper dk;

if (cmds.at(0) == "test-schema") {
Expand Down
1 change: 1 addition & 0 deletions pdns/qtype.cc
Expand Up @@ -73,6 +73,7 @@ const map<const string, uint16_t> QType::names = {
{"CDNSKEY", 60},
{"OPENPGPKEY", 61},
{"CSYNC", 62},
{"ZONEMD", 63},
{"SVCB", 64},
{"HTTPS", 65},
{"SPF", 99},
Expand Down
1 change: 1 addition & 0 deletions pdns/qtype.hh
Expand Up @@ -102,6 +102,7 @@ public:
CDNSKEY = 60,
OPENPGPKEY = 61,
CSYNC = 62,
ZONEMD = 63,
SVCB = 64,
HTTPS = 65,
SPF = 99,
Expand Down
64 changes: 63 additions & 1 deletion pdns/sha.hh
Expand Up @@ -20,9 +20,10 @@
* 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)
{
Expand Down Expand Up @@ -51,3 +52,64 @@ inline std::string pdns_sha512sum(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
chbruyand marked this conversation as resolved.
Show resolved Hide resolved
{
public:
SHADigest(unsigned int bits) :
mdctx(std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(EVP_MD_CTX_new(), EVP_MD_CTX_free))
{
if (mdctx == nullptr) {
throw std::runtime_error("SHADigest: EVP_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::invalid_argument("SHADigest: unsupported size");
}
if (EVP_DigestInit_ex(mdctx.get(), md, NULL) == 0) {
throw std::runtime_error("SHADigest: init error");
}
}

~SHADigest()
{
// No free of md needed and mdctx is cleaned up by unique_ptr
}

void process(const std::string& msg)
{
if (EVP_DigestUpdate(mdctx.get(), 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.get(), 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: inconsistent size");
}
return md_value;
}

private:
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> mdctx;
const EVP_MD* md;
};
}
4 changes: 4 additions & 0 deletions pdns/test-dnsrecords_cc.cc
Expand Up @@ -206,6 +206,10 @@ BOOST_AUTO_TEST_CASE(test_record_types) {

(CASE_S(QType::CSYNC, "66 3 A NS AAAA", "\x00\x00\x00\x42\x00\x03\x00\x04\x60\x00\x00\x08"))

// ZONEMD
(CASE_S(QType::ZONEMD, "2018031900 1 1 a3b69bad980a3504e1cffcb0fd6397f93848071c93151f552ae2f6b1711d4bd2d8b39808226d7b9db71e34b72077f8fe", "\x78\x48\xb9\x1c\x01\x01\xa3\xb6\x9b\xad\x98\x0a\x35\x04\xe1\xcf\xfc\xb0\xfd\x63\x97\xf9\x38\x48\x07\x1c\x93\x15\x1f\x55\x2a\xe2\xf6\xb1\x71\x1d\x4b\xd2\xd8\xb3\x98\x08\x22\x6d\x7b\x9d\xb7\x1e\x34\xb7\x20\x77\xf8\xfe"))
(CASE_L(QType::ZONEMD, " 2018031900 1 1 ( 616c6c6f77656420 6275742069676e6f \n 7265642e20616c6c \n6f77656420627574\n 2069676e6f726564 \n2e20616c6c6f7765 \n)", "2018031900 1 1 616c6c6f776564206275742069676e6f7265642e20616c6c6f776564206275742069676e6f7265642e20616c6c6f7765", "\x78\x48\xb9\x1c\x01\x01\x61\x6c\x6c\x6f\x77\x65\x64\x20\x62\x75\x74\x20\x69\x67\x6e\x6f\x72\x65\x64\x2e\x20\x61\x6c\x6c\x6f\x77\x65\x64\x20\x62\x75\x74\x20\x69\x67\x6e\x6f\x72\x65\x64\x2e\x20\x61\x6c\x6c\x6f\x77\x65"))

// Alias mode
(CASE_S(QType::SVCB, "0 foo.powerdns.org.", "\0\0\3foo\x08powerdns\x03org\x00"))
(CASE_L(QType::SVCB, "0 foo.powerdns.org", "0 foo.powerdns.org.", "\0\0\3foo\x08powerdns\x03org\x00"))
Expand Down
103 changes: 103 additions & 0 deletions pdns/test-zonemd_cc.cc
@@ -0,0 +1,103 @@
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>

#include "zonemd.hh"
#include "dnsrecords.hh"
#include "zoneparser-tng.hh"

BOOST_AUTO_TEST_SUITE(test_zonemd_cc)

static void testZoneMD(const std::string& zone, const std::string& file, bool ex, bool done, bool ok)
{
const char* p = std::getenv("SRCDIR");
if (!p) {
p = ".";
}
DNSName z(zone);
std::ostringstream pathbuf;
pathbuf << p << "/../regression-tests/zones/" + file;
ZoneParserTNG zpt(pathbuf.str(), z);

bool validationDone, validationOK;

try {
pdns::zonemdVerify(z, zpt, validationDone, validationOK);
}
catch (const PDNSException& e) {
BOOST_CHECK(ex);
}
catch (const std::exception& e) {
BOOST_CHECK(ex);
}

BOOST_CHECK(validationDone == done);
BOOST_CHECK(validationOK == ok);
}

BOOST_AUTO_TEST_CASE(test_zonemd1)
{
testZoneMD("example", "zonemd1.zone", false, true, true);
}

BOOST_AUTO_TEST_CASE(test_zonemd2)
{
testZoneMD("example", "zonemd2.zone", false, true, true);
}

BOOST_AUTO_TEST_CASE(test_zonemd3)
{
testZoneMD("example", "zonemd3.zone", false, true, true);
}

BOOST_AUTO_TEST_CASE(test_zonemd4)
{
testZoneMD("uri.arpa", "zonemd4.zone", false, true, true);
}

BOOST_AUTO_TEST_CASE(test_zonemd5)
{
testZoneMD("root-servers.net", "zonemd5.zone", false, true, true);
}

BOOST_AUTO_TEST_CASE(test_zonemd6)
{
testZoneMD("example", "zonemd-invalid.zone", false, true, false);
}

BOOST_AUTO_TEST_CASE(test_zonemd7)
{
testZoneMD("example", "zonemd-nozonemd.zone", false, false, false);
}

BOOST_AUTO_TEST_CASE(test_zonemd8)
{
testZoneMD("example", "zonemd-allunsup.zone", false, false, false);
}

BOOST_AUTO_TEST_CASE(test_zonemd9)
{
testZoneMD("example", "zonemd-sha512.zone", false, true, true);
}

BOOST_AUTO_TEST_CASE(test_zonemd10)
{
testZoneMD("example", "zonemd-serialmismatch.zone", false, false, false);
}

BOOST_AUTO_TEST_CASE(test_zonemd11)
{
testZoneMD("example", "zonemd-duplicate.zone", false, false, false);
}

BOOST_AUTO_TEST_CASE(test_zonemd12)
{
testZoneMD("root-servers.net", "zonemd-syntax.zone", true, false, false);
}

BOOST_AUTO_TEST_CASE(test_zonemd13)
{
testZoneMD("xxx", "zonemd1.zone", false, false, false);
}

BOOST_AUTO_TEST_SUITE_END()