Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
auth: Implement RFC 7872 and 9018 (COOKIES)
This implements the siphash-based interoperable DNS COOKIES defined in
RFC 9018 for the authoritative server. The EDNSCookieOpt struct has been
expanded to accomodate this and can now has constructors and functions
to check and generate a server cookie.

Cookies will only be sent out if the client sent a cookie and the
edns-cookie-secret setting is configures. The auth will respond with
EDNS+FORMERR when the client cookie is malformed, BADCOOKIE when the
client sent a server cookie we can't decode or is invalid and a normal
response with a cookie (either new or sent by the client) when the
cookie can be validated.
  • Loading branch information
pieterlexis committed Jul 7, 2021
1 parent 5b93259 commit 306bf65
Show file tree
Hide file tree
Showing 19 changed files with 552 additions and 95 deletions.
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect.txt
Expand Up @@ -1509,6 +1509,7 @@ sigs
SIGUSR
singlethreaded
Sipek
siphash
sizeof
Sjoerd
slapd
Expand Down
1 change: 0 additions & 1 deletion .not-formatted
Expand Up @@ -156,7 +156,6 @@
./pdns/dynloader.cc
./pdns/dynmessenger.cc
./pdns/dynmessenger.hh
./pdns/ednscookies.cc
./pdns/ednsoptions.cc
./pdns/ednsoptions.hh
./pdns/ednspadding.cc
Expand Down
15 changes: 15 additions & 0 deletions docs/settings.rst
Expand Up @@ -619,6 +619,21 @@ ADDITIONAL section when sending a referral.
Seconds to cache zone metadata from the database. A value of 0
disables caching.

.. _setting-edns-cookie-secret:

``edns-cookie-secret``
--------------------------

.. versionadded:: 4.6.0

- String
- Default: (empty)

When set, PowerDNS will respond with :rfc:`9018` EDNS Cookies to queries that have the EDNS0 Cookie option.
PowerDNS will also respond with BADCOOKIE to clients that have no or a bad server cookie (section 5.2.3 and 5.2.4 of :rfc:`7873`).

This setting MUST be 32 hexadecimal characters, as the siphash algorithm's key used to create the cookie requires a 128-bit key.

.. _setting-edns-subnet-processing:

``edns-subnet-processing``
Expand Down
9 changes: 9 additions & 0 deletions modules/remotebackend/Makefile.am
Expand Up @@ -4,6 +4,10 @@ AM_CPPFLAGS += \
$(LIBCRYPTO_CFLAGS) \
$(LIBZMQ_CFLAGS)

if LIBSODIUM
AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
endif

AM_LDFLAGS = $(THREADFLAGS)

JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
Expand Down Expand Up @@ -110,6 +114,7 @@ libtestremotebackend_la_SOURCES = \
../../pdns/dnsrecords.cc \
../../pdns/dnssecinfra.cc \
../../pdns/dnswriter.cc \
../../pdns/ednscookies.cc \
../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
../../pdns/ednssubnet.cc \
../../pdns/iputils.cc \
Expand Down Expand Up @@ -146,6 +151,10 @@ libtestremotebackend_la_LDFLAGS = \
$(AM_LDFLAGS) \
$(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS)

if LIBSODIUM
libtestremotebackend_la_LIBADD += $(LIBSODIUM_LIBS)
endif

if REMOTEBACKEND_ZEROMQ
libtestremotebackend_la_LIBADD += $(LIBZMQ_LIBS)
endif
Expand Down
3 changes: 3 additions & 0 deletions pdns/Makefile.am
Expand Up @@ -219,6 +219,7 @@ pdns_server_SOURCES = \
dynhandler.cc dynhandler.hh \
dynlistener.cc dynlistener.hh \
dynmessenger.hh \
ednscookies.cc ednscookies.hh \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
histogram.hh \
Expand Down Expand Up @@ -345,6 +346,7 @@ pdnsutil_SOURCES = \
dnssecsigner.cc \
dnswriter.cc dnswriter.hh \
dynlistener.cc \
ednscookies.cc \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc \
ipcipher.cc ipcipher.hh \
Expand Down Expand Up @@ -1367,6 +1369,7 @@ testrunner_SOURCES = \
test-dnsrecordcontent.cc \
test-dnsrecords_cc.cc \
test-dnswriter_cc.cc \
test-ednscookie_cc.cc \
test-ipcrypt_cc.cc \
test-iputils_hh.cc \
test-ixfr_cc.cc \
Expand Down
21 changes: 21 additions & 0 deletions pdns/common_startup.cc
Expand Up @@ -161,6 +161,8 @@ void declareArguments()
::arg().setSwitch("any-to-tcp","Answer ANY queries with tc=1, shunting to TCP")="yes";
::arg().setSwitch("edns-subnet-processing","If we should act on EDNS Subnet options")="no";

::arg().set("edns-cookie-secret", "When set, set a server cookie in a response to a query with a Client cookie (in hex)")="";

::arg().setSwitch("webserver","Start a webserver for monitoring (api=yes also enables the HTTP listener)")="no";
::arg().setSwitch("webserver-print-arguments","If the webserver should print arguments")="no";
::arg().set("webserver-address","IP Address of webserver/API to listen on")="127.0.0.1";
Expand Down Expand Up @@ -563,6 +565,25 @@ void mainthread()
DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
PacketHandler::s_SVCAutohints = ::arg().mustDo("svc-autohints");

if (::arg()["edns-cookie-secret"].size() != 0) {
// User wants cookie processing
#ifdef HAVE_CRYPTO_SHORTHASH // we can do siphash-based cookies
DNSPacket::s_doEDNSCookieProcessing = true;
try {
if (::arg()["edns-cookie-secret"].size() != 32) {
throw std::range_error("wrong size (" + std::to_string(::arg()["edns-cookie-secret"].size()) + "), must be 32");
}
DNSPacket::s_EDNSCookieKey = makeBytesFromHex(::arg()["edns-cookie-secret"]);
} catch(const std::range_error &e) {
g_log<<Logger::Error<<"edns-cookie-secret invalid: "<<e.what()<<endl;
exit(1);
}
#else
g_log<<Logger::Error<<"Support for EDNS Cookies is not available because of missing cryptographic functions"<<endl;
exit(1);
#endif
}

PC.setTTL(::arg().asNum("cache-ttl"));
PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
QC.setMaxEntries(::arg().asNum("max-cache-entries"));
Expand Down
47 changes: 45 additions & 2 deletions pdns/dnspacket.cc
Expand Up @@ -38,6 +38,7 @@
#include "dns.hh"
#include "dnsbackend.hh"
#include "ednsoptions.hh"
#include "ednscookies.hh"
#include "pdnsexception.hh"
#include "dnspacket.hh"
#include "logger.hh"
Expand All @@ -52,6 +53,8 @@
#include "shuffle.hh"

bool DNSPacket::s_doEDNSSubnetProcessing;
bool DNSPacket::s_doEDNSCookieProcessing;
string DNSPacket::s_EDNSCookieKey;
uint16_t DNSPacket::s_udpTruncationThreshold;

DNSPacket::DNSPacket(bool isQuery): d_isQuery(isQuery)
Expand Down Expand Up @@ -195,7 +198,8 @@ void DNSPacket::setCompress(bool compress)

bool DNSPacket::couldBeCached() const
{
return !d_wantsnsid && qclass==QClass::IN && !d_havetsig;
return !d_wantsnsid && qclass==QClass::IN && !d_havetsig &&
!(d_haveednscookie && s_doEDNSCookieProcessing);
}

unsigned int DNSPacket::getMinTTL()
Expand Down Expand Up @@ -285,6 +289,12 @@ void DNSPacket::wrapup()
optsize += d_eso.source.isIPv4() ? 4 : 16;
}

if (d_haveednscookie) {
if (d_eco.isWellFormed()) {
optsize += 24;
}
}

if (d_trc.d_algoName.countLabels())
{
// TSIG is not OPT, but we count it in optsize anyway
Expand All @@ -294,7 +304,7 @@ void DNSPacket::wrapup()
static_assert(EVP_MAX_MD_SIZE <= 64, "EVP_MAX_MD_SIZE is overly huge on this system, please check");
}

if(!d_rrs.empty() || !opts.empty() || d_haveednssubnet || d_haveednssection) {
if(!d_rrs.empty() || !opts.empty() || d_haveednssubnet || d_haveednssection || d_haveednscookie) {
try {
uint8_t maxScopeMask=0;
for(pos=d_rrs.begin(); pos < d_rrs.end(); ++pos) {
Expand Down Expand Up @@ -325,6 +335,11 @@ void DNSPacket::wrapup()
opts.push_back(make_pair(8, opt)); // 'EDNS SUBNET'
}

if (d_haveednscookie && d_eco.isWellFormed()) {
d_eco.makeServerCookie(s_EDNSCookieKey, getRemote());
opts.push_back(make_pair(EDNSOptionCode::COOKIE, d_eco.makeOptString()));
}

if(!opts.empty() || d_haveednssection || d_dnssecOk)
{
pw.addOpt(s_udpTruncationThreshold, d_ednsrcode, d_dnssecOk ? EDNSOpts::DNSSECOK : 0, opts);
Expand Down Expand Up @@ -386,8 +401,10 @@ std::unique_ptr<DNSPacket> DNSPacket::replyPacket() const
r->d_wantsnsid = d_wantsnsid;
r->d_dnssecOk = d_dnssecOk;
r->d_eso = d_eso;
r->d_eco = d_eco;
r->d_haveednssubnet = d_haveednssubnet;
r->d_haveednssection = d_haveednssection;
r->d_haveednscookie = d_haveednscookie;
r->d_ednsversion = 0;
r->d_ednsrcode = 0;

Expand Down Expand Up @@ -525,6 +542,7 @@ try
d_havetsig = mdp.getTSIGPos();
d_haveednssubnet = false;
d_haveednssection = false;
d_haveednscookie = false;

if(getEDNSOpts(mdp, &edo)) {
d_haveednssection=true;
Expand All @@ -547,6 +565,10 @@ try
d_haveednssubnet=true;
}
}
else if (s_doEDNSCookieProcessing && option.first == EDNSOptionCode::COOKIE) {
d_haveednscookie = true;
d_eco.makeFromString(option.second);
}
else {
// cerr<<"Have an option #"<<iter->first<<": "<<makeHexDump(iter->second)<<endl;
}
Expand Down Expand Up @@ -608,6 +630,27 @@ bool DNSPacket::hasEDNS() const
return d_haveednssection;
}

bool DNSPacket::hasEDNSCookie() const
{
return d_haveednscookie;
}

bool DNSPacket::hasWellFormedEDNSCookie() const
{
if (!d_haveednscookie) {
return false;
}
return d_eco.isWellFormed();
}

bool DNSPacket::hasValidEDNSCookie()
{
if (!hasWellFormedEDNSCookie()) {
return false;
}
return d_eco.isValid(s_EDNSCookieKey, d_remote);
}

Netmask DNSPacket::getRealRemote() const
{
if(d_haveednssubnet)
Expand Down
8 changes: 8 additions & 0 deletions pdns/dnspacket.hh
Expand Up @@ -26,6 +26,7 @@
#include <sys/types.h>
#include "iputils.hh"
#include "ednssubnet.hh"
#include "ednscookies.hh"
#include <unordered_set>
#include <sys/socket.h>
#include <netinet/in.h>
Expand Down Expand Up @@ -121,6 +122,9 @@ public:
bool couldBeCached() const; //!< returns 0 if this query should bypass the packet cache
bool hasEDNSSubnet() const;
bool hasEDNS() const;
bool hasEDNSCookie() const;
bool hasWellFormedEDNSCookie() const;
bool hasValidEDNSCookie(); // Not const, some cookie params might be set
uint8_t getEDNSVersion() const { return d_ednsversion; };
void setEDNSRcode(uint16_t extRCode)
{
Expand Down Expand Up @@ -163,6 +167,8 @@ public:

static uint16_t s_udpTruncationThreshold;
static bool s_doEDNSSubnetProcessing;
static bool s_doEDNSCookieProcessing;
static string s_EDNSCookieKey;

private:
void pasteQ(const char *question, int length); //!< set the question of this packet, useful for crafting replies
Expand All @@ -175,6 +181,7 @@ private:
std::unordered_set<size_t> d_dedup;
string d_rawpacket; // this is where everything lives 8
EDNSSubnetOpts d_eso;
EDNSCookiesOpt d_eco;

int d_maxreplylen{0};
int d_socket{-1}; // 4
Expand All @@ -188,6 +195,7 @@ private:
bool d_tsigtimersonly{false};
bool d_wantsnsid{false};
bool d_haveednssubnet{false};
bool d_haveednscookie{false};
bool d_haveednssection{false};
bool d_isQuery;
};

0 comments on commit 306bf65

Please sign in to comment.