]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: add support for RFC 9615 (DNSSEC bootstrapping)
authorPeter Thomassen <peter@desec.io>
Mon, 15 Apr 2024 00:31:17 +0000 (02:31 +0200)
committerPeter Thomassen <peter@desec.io>
Wed, 16 Jul 2025 13:05:43 +0000 (15:05 +0200)
pdns/packethandler.cc
pdns/packethandler.hh

index 466c16de18f03f4cc846978b8b7fb3fe108fcd4c..5940ec682e6c1d7e7994af70b220a43ebd81b740 100644 (file)
@@ -755,7 +755,7 @@ void PacketHandler::emitNSEC(std::unique_ptr<DNSPacket>& r, const DNSName& name,
   r->addRecord(std::move(rr));
 }
 
-void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRecordContent& ns3prc, const DNSName& name, const string& namehash, const string& nexthash, int mode)
+void PacketHandler::emitNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRecordContent& ns3prc, const DNSName& name, const string& namehash, const string& nexthash, int mode)
 {
   NSEC3RecordContent n3rc;
   n3rc.d_algorithm = ns3prc.d_algorithm;
@@ -787,6 +787,13 @@ void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRec
           }
         }
       }
+    } else if(mode == 6) {
+      if (p.qtype.getCode() != QType::CDS) {
+        n3rc.set(QType::CDS);
+      }
+      if (p.qtype.getCode() != QType::CDNSKEY) {
+        n3rc.set(QType::CDNSKEY);
+      }
     }
 
 #ifdef HAVE_LUA_RECORDS
@@ -854,6 +861,7 @@ void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRec
    mode 3 = Wildcard Answer Responses
    mode 4 = Name Error Responses
    mode 5 = Direct NSEC request
+   mode 6 = Authenticated DNSSEC bootstrapping (RFC 9615)
 */
 void PacketHandler::addNSECX(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, int mode)
 {
@@ -955,7 +963,7 @@ void PacketHandler::addNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const
 
     if (!after.empty()) {
       DLOG(g_log<<"Done calling for matching, hashed: '"<<toBase32Hex(hashed)<<"' before='"<<toBase32Hex(before)<<"', after='"<<toBase32Hex(after)<<"'"<<endl);
-      emitNSEC3(r, ns3rc, unhashed, before, after, mode);
+      emitNSEC3(p, r, ns3rc, unhashed, before, after, mode);
     }
   }
 
@@ -972,7 +980,7 @@ void PacketHandler::addNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const
 
     getNSEC3Hashes(narrow, hashed, true, unhashed, before, after);
     DLOG(g_log<<"Done calling for covering, hashed: '"<<toBase32Hex(hashed)<<"' before='"<<toBase32Hex(before)<<"', after='"<<toBase32Hex(after)<<"'"<<endl);
-    emitNSEC3( r, ns3rc, unhashed, before, after, mode);
+    emitNSEC3(p, r, ns3rc, unhashed, before, after, mode);
   }
 
   // wildcard denial
@@ -984,7 +992,7 @@ void PacketHandler::addNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const
 
     getNSEC3Hashes(narrow, hashed, (mode != 2), unhashed, before, after);
     DLOG(g_log<<"Done calling for '*', hashed: '"<<toBase32Hex(hashed)<<"' before='"<<toBase32Hex(before)<<"', after='"<<toBase32Hex(after)<<"'"<<endl);
-    emitNSEC3( r, ns3rc, unhashed, before, after, mode);
+    emitNSEC3(p, r, ns3rc, unhashed, before, after, mode);
   }
 }
 
@@ -1332,6 +1340,59 @@ void PacketHandler::completeANYRecords(DNSPacket& p, std::unique_ptr<DNSPacket>&
   }
 }
 
+bool PacketHandler::tryAuthSignal(DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNSName &target) {
+  DLOG(g_log<<Logger::Warning<<"Let's try authenticated DNSSEC bootstrapping (RFC 9615) ..."<<endl);
+
+  // Check for prefix mismatch
+  if(target.getRawLabel(0) != "_dsboot") {
+    makeNOError(p, r, target, DNSName(), 0); // could be ENT
+    return true;
+  }
+
+  // Check for qtype match
+  if(!(p.qtype.getCode() == QType::CDS || p.qtype.getCode() == QType::CDNSKEY)) {
+    // We don't know at this point whether CDS/CDNSKEY exist, so let's add both to DoE type map
+    makeNOError(p, r, target, DNSName(), 6);
+    return true;
+  }
+
+  // Extract target zone name and fetch zone
+  SOAData zone_sd;
+  ZoneName zone = ZoneName(target.makeRelative(d_sd.zonename));
+  zone.chopOff();
+  // Zone must exist, but need not be secured (could be using front-sign setup)
+  if(!B.getAuth(zone, p.qtype, &zone_sd, p.getRealRemote()) || zone_sd.zonename != zone) {
+    return false; // Bootstrap target zone unknown, NXDOMAIN at _dsboot is OK
+  }
+
+  // Insert synthetic response
+  bool haveOne = false;
+  bool autoPublish = !d_dk.isPresigned(zone);
+  if(p.qtype.getCode() == QType::CDS) {
+    d_dk.getPublishCDS(zone, val);
+    autoPublish &= !val.empty();
+    if(autoPublish)
+      haveOne = addCDS(p, r, zone_sd);
+  } else if(p.qtype.getCode() == QType::CDNSKEY) {
+    d_dk.getPublishCDNSKEY(zone, val);
+    autoPublish &= !val.empty();
+    if(autoPublish)
+      haveOne = addCDNSKEY(p, r, zone_sd);
+  }
+  if(!autoPublish) {
+      DNSZoneRecord rr;
+      B.lookup(p.qtype.getCode(), DNSName(zone), zone_sd.domain_id, &p);
+      while(B.get(rr)) {
+        rr.dr.d_name = p.qdomain;
+        r->addRecord(std::move(rr));
+        haveOne=true;
+      }
+    }
+  if(!haveOne)
+    makeNOError(p, r, target, DNSName(), 6); // other type might exist
+  return true;
+}
+
 bool PacketHandler::tryDNAME(DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNSName &target)
 {
   if(!d_doDNAME)
@@ -1856,6 +1917,9 @@ bool PacketHandler::opcodeQueryInner2(DNSPacket& pkt, queryState &state, bool re
       state.r->setRcode(RCode::YXDomain);
       return true;
     }
+    if(tryAuthSignal(pkt, state.r, state.target)) {
+      return true;
+    }
 
     if (!(((pkt.qtype.getCode() == QType::CNAME) || (pkt.qtype.getCode() == QType::ANY)) && retargeted)) {
       makeNXDomain(pkt, state.r, state.target, wildcard);
index d60e0a09b7313a680c1f783b639e4205bffa97b4..9d6551f06895954c4d3ffd439b5246f118af7cf0 100644 (file)
@@ -89,7 +89,7 @@ private:
   bool getNSEC3Hashes(bool narrow, const std::string& hashed, bool decrement, DNSName& unhashed, std::string& before, std::string& after, int mode=0);
   void addNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName &target, const DNSName &wildcard, const NSEC3PARAMRecordContent& nsec3param, bool narrow, int mode);
   void emitNSEC(std::unique_ptr<DNSPacket>& r, const DNSName& name, const DNSName& next, int mode);
-  void emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRecordContent &ns3rc, const DNSName& unhashed, const string& begin, const string& end, int mode);
+  void emitNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRecordContent &ns3rc, const DNSName& unhashed, const string& begin, const string& end, int mode);
   int processUpdate(DNSPacket& p);
   int forwardPacket(const string &msgPrefix, const DNSPacket& p, const DomainInfo& di);
   uint performUpdate(const string &msgPrefix, const DNSRecord *rr, DomainInfo *di, bool isPresigned, bool* narrow, bool* haveNSEC3, NSEC3PARAMRecordContent *ns3pr, bool *updatedSerial);
@@ -101,6 +101,7 @@ private:
   void makeNOError(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, int mode);
   vector<DNSZoneRecord> getBestReferralNS(DNSPacket& p, const DNSName &target);
   void getBestDNAMESynth(DNSPacket& p, DNSName &target, vector<DNSZoneRecord> &ret);
+  bool tryAuthSignal(DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNSName &target);
   bool tryDNAME(DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNSName &target);
   bool tryReferral(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName &target, bool retargeted);