]> granicus.if.org Git - pdns/commitdiff
Check TSIG signature on IXFR
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 15 Sep 2016 13:28:45 +0000 (15:28 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 12 Jan 2017 13:46:53 +0000 (14:46 +0100)
(cherry picked from commit 16c7f7823221d5d75282a77b2e9043b3f60e1ad2)

17 files changed:
pdns/Makefile.am
pdns/dnspacket.cc
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnssecinfra.cc
pdns/dnssecinfra.hh
pdns/ixfr.cc
pdns/misc.hh
pdns/recursordist/Makefile.am
pdns/recursordist/tsigverifier.cc [new symlink]
pdns/recursordist/tsigverifier.hh [new symlink]
pdns/resolver.cc
pdns/resolver.hh
pdns/saxfr.cc
pdns/tcpreceiver.cc
pdns/tsigverifier.cc [new file with mode: 0644]
pdns/tsigverifier.hh [new file with mode: 0644]

index 450e12c0354da6a1b1af902c38f12b4b4ecb099e..66cd5676aa76ca28b64191a1fed2a57818c83c19 100644 (file)
@@ -197,6 +197,7 @@ pdns_server_SOURCES = \
        statbag.cc statbag.hh \
        stubresolver.cc stubresolver.hh \
        tcpreceiver.cc tcpreceiver.hh \
+       tsigverifier.cc tsigverifier.hh \
        tkey.cc \
        ueberbackend.cc ueberbackend.hh \
        unix_semaphore.cc \
@@ -583,6 +584,7 @@ ixplore_SOURCES = \
        sillyrecords.cc \
        sstuff.hh \
        statbag.cc \
+       tsigverifier.cc tsigverifier.hh \
        unix_utility.cc zoneparser-tng.cc
 
 ixplore_LDADD = $(LIBCRYPTO_LIBS)
@@ -734,6 +736,7 @@ tsig_tests_SOURCES = \
        sstuff.hh \
        statbag.cc \
        tsig-tests.cc \
+       tsigverifier.cc tsigverifier.hh \
        unix_utility.cc
 
 tsig_tests_LDADD = $(LIBCRYPTO_LIBS)
index 0862f8081f556840af80c46866631d170f6b7025..17e1fd13801b4d0a8cec53769064b78cd8615164 100644 (file)
@@ -479,7 +479,7 @@ bool DNSPacket::getTSIGDetails(TSIGRecordContent* trc, DNSName* keyname, string*
   
   bool gotit=false;
   for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {          
-    if(i->first.d_type == QType::TSIG) {
+    if(i->first.d_type == QType::TSIG && i->first.d_class == QType::ANY) {
       // cast can fail, f.e. if d_content is an UnknownRecordContent.
       shared_ptr<TSIGRecordContent> content = std::dynamic_pointer_cast<TSIGRecordContent>(i->first.d_content);
       if (!content) {
@@ -654,7 +654,10 @@ bool checkForCorrectTSIG(const DNSPacket* q, UeberBackend* B, DNSName* keyname,
 {
   string message;
 
-  q->getTSIGDetails(trc, keyname, &message);
+  if (!q->getTSIGDetails(trc, keyname, &message)) {
+    return false;
+  }
+
   uint64_t delta = std::abs((int64_t)trc->d_time - (int64_t)time(0));
   if(delta > trc->d_fudge) {
     L<<Logger::Error<<"Packet for '"<<q->qdomain<<"' denied: TSIG (key '"<<*keyname<<"') time delta "<< delta <<" > 'fudge' "<<trc->d_fudge<<endl;
index d60bcd847a514fb6074a094b58aa946f56d20ede..11433c8b8654e2c3688bedc9c0e85dfc7e59adb3 100644 (file)
@@ -284,8 +284,12 @@ void MOADNSParser::init(bool query, const char *packet, unsigned int len)
 
       d_answers.push_back(make_pair(dr, pr.d_pos));
 
-      if(dr.d_type == QType::TSIG && dr.d_class == 0xff) 
+      if(dr.d_type == QType::TSIG && dr.d_class == QClass::ANY) {
+        if(dr.d_place != DNSResourceRecord::ADDITIONAL || n != (unsigned int)(d_header.ancount + d_header.nscount + d_header.arcount) - 1) {
+          throw MOADNSException("Packet ("+d_qname.toString()+"|#"+std::to_string(d_qtype)+") has a TSIG record in an invalid position.");
+        }
         d_tsigPos = recordStartPos + sizeof(struct dnsheader);
+      }
     }
 
 #if 0    
index e70064fbbec8dd9c73a330393f49fe61ccb4419f..03eca9fa1897f787cb4084a7def04bbba9baf370 100644 (file)
@@ -365,7 +365,7 @@ public:
     return pr;
   }
 
-  uint16_t getTSIGPos()
+  uint16_t getTSIGPos() const
   {
     return d_tsigPos;
   }
index 31174acd446727cc590a023b0d146c2803cc3ebd..6a94950c0b982d056762da2bc51e72bbcaad5035 100644 (file)
@@ -595,15 +595,36 @@ string calculateHMAC(const std::string& key, const std::string& text, TSIGHashEn
       md_type = EVP_sha512();
       break;
     default:
-      throw new PDNSException("Unknown hash algorithm requested from calculateHMAC()");
+      throw PDNSException("Unknown hash algorithm requested from calculateHMAC()");
   }
 
   unsigned char* out = HMAC(md_type, reinterpret_cast<const unsigned char*>(key.c_str()), key.size(), reinterpret_cast<const unsigned char*>(text.c_str()), text.size(), hash, &outlen);
-  if (out != NULL && outlen > 0) {
-    return string((char*) hash, outlen);
+  if (out == NULL || outlen == 0) {
+    throw PDNSException("HMAC computation failed");
   }
 
-  return "";
+  return string((char*) hash, outlen);
+}
+
+bool constantTimeStringEquals(const std::string& a, const std::string& b)
+{
+  if (a.size() != b.size()) {
+    return false;
+  }
+  const size_t size = a.size();
+#if OPENSSL_VERSION_NUMBER >= 0x0090819fL
+  return CRYPTO_memcmp(a.c_str(), b.c_str(), size) == 0;
+#else
+  const volatile unsigned char *_a = (const volatile unsigned char *) a.c_str();
+  const volatile unsigned char *_b = (const volatile unsigned char *) b.c_str();
+  unsigned char res = 0;
+
+  for (size_t idx = 0; idx < size; idx++) {
+    res |= _a[idx] ^ _b[idx];
+  }
+
+  return res == 0;
+#endif
 }
 
 string makeTSIGMessageFromTSIGPacket(const string& opacket, unsigned int tsigOffset, const DNSName& keyname, const TSIGRecordContent& trc, const string& previous, bool timersonly, unsigned int dnsHeaderOffset)
index 2c0f7ac55e54f92eb857b9fa46f05d74b70ee8b3..149cc3aa5caa769a2c25468967fa9f6128a1a7d4 100644 (file)
@@ -157,6 +157,7 @@ class DNSPacket;
 void addRRSigs(DNSSECKeeper& dk, UeberBackend& db, const std::set<DNSName>& authMap, vector<DNSResourceRecord>& rrs);
 
 string calculateHMAC(const std::string& key, const std::string& text, TSIGHashEnum hash);
+bool constantTimeStringEquals(const std::string& a, const std::string& b);
 
 string makeTSIGMessageFromTSIGPacket(const string& opacket, unsigned int tsigoffset, const DNSName& keyname, const TSIGRecordContent& trc, const string& previous, bool timersonly, unsigned int dnsHeaderOffset=0);
 void addTSIG(DNSPacketWriter& pw, TSIGRecordContent* trc, const DNSName& tsigkeyname, const string& tsigsecret, const string& tsigprevious, bool timersonly);
index b57d40ea34c03a853a013eeca3b7a85103f4f828..a325eaec5fbdc12b8c54731277745249eda82223 100644 (file)
@@ -24,7 +24,7 @@
 #include "dns_random.hh"
 #include "dnsrecords.hh"
 #include "dnssecinfra.hh"
-
+#include "tsigverifier.hh"
 
 // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr, 
@@ -40,10 +40,11 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
   oursr.d_content->toPacket(pw);
 
   pw.commit();
+  TSIGRecordContent trc;
+  TSIGTCPVerifier tsigVerifier(tt, master, trc);
   if(!tt.algo.empty()) {
     TSIGHashEnum the;
     getTSIGHashEnum(tt.algo, the);
-    TSIGRecordContent trc;
     try {
       trc.d_algoName = getTSIGAlgoName(the);
     } catch(PDNSException& pe) {
@@ -77,6 +78,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
   shared_ptr<SOARecordContent> masterSOA;
   vector<DNSRecord> records;
   size_t receivedBytes = 0;
+
   for(;;) {
     if(s.read((char*)&len, 2)!=2)
       break;
@@ -96,6 +98,11 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
       throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
 
     //    cout<<"Got a response, rcode: "<<mdp.d_header.rcode<<", got "<<mdp.d_answers.size()<<" answers"<<endl;
+
+    if(!tt.algo.empty()) { // TSIG verify message
+      tsigVerifier.check(std::string(reply, len), mdp);
+    }
+
     for(auto& r: mdp.d_answers) {
       if(r.first.d_type == QType::TSIG) 
         continue;
index 2b56cd982ff75412df3eba774a048c896c4ac48f..a4b2f41086f4e36ecdd14a058159b8ef9fd8cf8a 100644 (file)
@@ -611,3 +611,4 @@ uid_t strToUID(const string &str);
 gid_t strToGID(const string &str);
 
 unsigned int pdns_stou(const std::string& str, size_t * idx = 0, int base = 10);
+
index efbac9f8f5ad4d78fe082d4d8d8bdd63749206e7..0ee02e5790b76b8eb629bc76f1683afc102f4b1d 100644 (file)
@@ -128,6 +128,7 @@ pdns_recursor_SOURCES = \
        sortlist.cc sortlist.hh \
        sstuff.hh \
        syncres.cc syncres.hh \
+       tsigverifier.cc tsigverifier.hh \
        ueberbackend.hh \
        unix_utility.cc \
        utility.hh \
diff --git a/pdns/recursordist/tsigverifier.cc b/pdns/recursordist/tsigverifier.cc
new file mode 120000 (symlink)
index 0000000..ec13da5
--- /dev/null
@@ -0,0 +1 @@
+../tsigverifier.cc
\ No newline at end of file
diff --git a/pdns/recursordist/tsigverifier.hh b/pdns/recursordist/tsigverifier.hh
new file mode 120000 (symlink)
index 0000000..fde72c7
--- /dev/null
@@ -0,0 +1 @@
+../tsigverifier.hh
\ No newline at end of file
index e8ec3d1c665633461190e3d1f55f2428d5df0451..6c00edd866dfb259bbc374fd7a38cb893662ea53 100644 (file)
@@ -367,7 +367,7 @@ AXFRRetriever::AXFRRetriever(const ComboAddress& remote,
                              const TSIGTriplet& tt, 
                              const ComboAddress* laddr,
                              size_t maxReceivedBytes)
-  : d_tt(tt), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes), d_tsigPos(0), d_nonSignedMessages(0)
+  : d_tsigVerifier(tt, remote, d_trc), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes)
 {
   ComboAddress local;
   if (laddr != NULL) {
@@ -478,76 +478,13 @@ int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records) //
     if (answer.first.d_type == QType::SOA)
       d_soacount++;
  
-  if(!d_tt.name.empty()) { // TSIG verify message
-    // If we have multiple messages, we need to concatenate them together. We also need to make sure we know the location of 
-    // the TSIG record so we can remove it in makeTSIGMessageFromTSIGPacket
-    d_signData.append(d_buf.get(), len);
-    if (mdp.getTSIGPos() == 0)
-      d_tsigPos += len;
-    else 
-      d_tsigPos += mdp.getTSIGPos();
-
-    string theirMac;
-    bool checkTSIG = false;
-    
-    for(const MOADNSParser::answers_t::value_type& answer :  mdp.d_answers) {
-      if (answer.first.d_type == QType::SOA)  // A SOA is either the first or the last record. We need to check TSIG if that's the case.
-        checkTSIG = true;
-      
-      if(answer.first.d_type == QType::TSIG) {
-        shared_ptr<TSIGRecordContent> trc = getRR<TSIGRecordContent>(answer.first);
-        if(trc) {
-          theirMac = trc->d_mac;
-          d_trc.d_time = trc->d_time;
-          checkTSIG = true;
-        }
-      }
-    }
-
-    if( ! checkTSIG && d_nonSignedMessages > 99) { // We're allowed to get 100 digest without a TSIG.
-      throw ResolverException("No TSIG message received in last 100 messages of AXFR transfer.");
-    }
-
-    if (checkTSIG) {
-      if (theirMac.empty())
-        throw ResolverException("No TSIG on AXFR response from "+d_remote.toStringWithPort()+" , should be signed with TSIG key '"+d_tt.name.toString()+"'");
-
-      string message;
-      if (!d_prevMac.empty()) {
-        message = makeTSIGMessageFromTSIGPacket(d_signData, d_tsigPos, d_tt.name, d_trc, d_prevMac, true, d_signData.size()-len);
-      } else {
-        message = makeTSIGMessageFromTSIGPacket(d_signData, d_tsigPos, d_tt.name, d_trc, d_trc.d_mac, false);
-      }
-
-      TSIGHashEnum algo;
-      if (!getTSIGHashEnum(d_trc.d_algoName, algo)) {
-        throw ResolverException("Unsupported TSIG HMAC algorithm " + d_trc.d_algoName.toString());
-      }
-
-      if (algo == TSIG_GSS) {
-        GssContext gssctx(d_tt.name);
-        if (!gss_verify_signature(d_tt.name, message, theirMac)) {
-          throw ResolverException("Signature failed to validate on AXFR response from "+d_remote.toStringWithPort()+" signed with TSIG key '"+d_tt.name.toString()+"'");
-        }
-      } else {
-        string ourMac=calculateHMAC(d_tt.secret, message, algo);
-
-        // ourMac[0]++; // sabotage == for testing :-)
-        if(ourMac != theirMac) {
-          throw ResolverException("Signature failed to validate on AXFR response from "+d_remote.toStringWithPort()+" signed with TSIG key '"+d_tt.name.toString()+"'");
-        }
-      }
-
-      // Reset and store some values for the next chunks. 
-      d_prevMac = theirMac;
-      d_nonSignedMessages = 0;
-      d_signData.clear();
-      d_tsigPos = 0;
-    }
-    else
-      d_nonSignedMessages++;
+  try {
+    d_tsigVerifier.check(std::string(d_buf.get(), len), mdp);
   }
-  
+  catch(const std::runtime_error& re) {
+    throw ResolverException(re.what());
+  }
+
   return true;
 }
 
index 91152db57695f2f84245f05123b048afa02b0066..548551a0a3b5a17b16aa3f99d9b8e83ab14ba94f 100644 (file)
@@ -41,6 +41,7 @@
 #include "namespaces.hh"
 #include "dnsrecords.hh"
 #include "dnssecinfra.hh"
+#include "tsigverifier.hh"
 
 class ResolverException : public PDNSException
 {
@@ -101,14 +102,10 @@ class AXFRRetriever : public boost::noncopyable
     int d_sock;
     int d_soacount;
     ComboAddress d_remote;
-    
-    TSIGTriplet d_tt;
-    string d_prevMac; // RFC2845 4.4
-    string d_signData;
+    TSIGTCPVerifier d_tsigVerifier;
+
     size_t d_receivedBytes;
     size_t d_maxReceivedBytes;
-    uint32_t d_tsigPos;
-    uint d_nonSignedMessages; // RFC2845 4.4
     TSIGRecordContent d_trc;
 };
 
index ef42bd33577143ddc49134d3e3d434e799b96f40..040522c6270925f5b4a44ef0c431e543453dd90d 100644 (file)
@@ -35,7 +35,7 @@ bool validateTSIG(const string& message, const TSIGHashEnum& algo, const DNSName
     }
     return true;
   }
-  return calculateHMAC(secret, message, algo) == trc->d_mac;
+  return constantTimeStringEquals(calculateHMAC(secret, message, algo), trc->d_mac);
 }
 
 
index dfcc45406441120d881a92dc7786d38fb43e5072..a85f3b65b7cd7b4a949a86d3adaba8eee8c04303 100644 (file)
@@ -606,9 +606,9 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
   DNSName tsigkeyname;
   string tsigsecret;
 
-  q->getTSIGDetails(&trc, &tsigkeyname, 0);
+  bool haveTSIGDetails = q->getTSIGDetails(&trc, &tsigkeyname, 0);
 
-  if(!tsigkeyname.empty()) {
+  if(haveTSIGDetails && !tsigkeyname.empty()) {
     string tsig64;
     DNSName algorithm=trc.d_algoName; // FIXME400: check
     if (algorithm == DNSName("hmac-md5.sig-alg.reg.int"))
@@ -634,7 +634,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
     addRRSigs(dk, signatureDB, authSet, outpacket->getRRS());
   }
   
-  if(!tsigkeyname.empty())
+  if(haveTSIGDetails && !tsigkeyname.empty())
     outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac); // first answer is 'normal'
   
   sendPacket(outpacket, outsock);
@@ -890,7 +890,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
       for(;;) {
         outpacket->getRRS() = csp.getChunk();
         if(!outpacket->getRRS().empty()) {
-          if(!tsigkeyname.empty()) 
+          if(haveTSIGDetails && !tsigkeyname.empty())
             outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true);
           sendPacket(outpacket, outsock);
           trc.d_mac=outpacket->d_trc.d_mac;
@@ -941,7 +941,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
             for(;;) {
               outpacket->getRRS() = csp.getChunk();
               if(!outpacket->getRRS().empty()) {
-                if(!tsigkeyname.empty())
+                if(haveTSIGDetails && !tsigkeyname.empty())
                   outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true);
                 sendPacket(outpacket, outsock);
                 trc.d_mac=outpacket->d_trc.d_mac;
@@ -976,7 +976,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
         for(;;) {
           outpacket->getRRS() = csp.getChunk();
           if(!outpacket->getRRS().empty()) {
-            if(!tsigkeyname.empty())
+            if(haveTSIGDetails && !tsigkeyname.empty())
               outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true); 
             sendPacket(outpacket, outsock);
             trc.d_mac=outpacket->d_trc.d_mac;
@@ -997,7 +997,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
   for(;;) { 
     outpacket->getRRS() = csp.getChunk(true); // flush the pipe
     if(!outpacket->getRRS().empty()) {
-      if(!tsigkeyname.empty())
+      if(haveTSIGDetails && !tsigkeyname.empty())
         outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true); // first answer is 'normal'
       sendPacket(outpacket, outsock);
       trc.d_mac=outpacket->d_trc.d_mac;
@@ -1016,7 +1016,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
   outpacket=getFreshAXFRPacket(q);
   outpacket->addRecord(soa);
   editSOA(dk, sd.qname, outpacket.get());
-  if(!tsigkeyname.empty())
+  if(haveTSIGDetails && !tsigkeyname.empty())
     outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true); 
   
   sendPacket(outpacket, outsock);
@@ -1118,9 +1118,9 @@ int TCPNameserver::doIXFR(shared_ptr<DNSPacket> q, int outsock)
     DNSName tsigkeyname;
     string tsigsecret;
 
-    q->getTSIGDetails(&trc, &tsigkeyname, 0);
+    bool haveTSIGDetails = q->getTSIGDetails(&trc, &tsigkeyname, 0);
 
-    if(!tsigkeyname.empty()) {
+    if(haveTSIGDetails && !tsigkeyname.empty()) {
       string tsig64;
       DNSName algorithm=trc.d_algoName; // FIXME400: was toLowerCanonic, compare output
       if (algorithm == DNSName("hmac-md5.sig-alg.reg.int"))
@@ -1143,7 +1143,7 @@ int TCPNameserver::doIXFR(shared_ptr<DNSPacket> q, int outsock)
       addRRSigs(dk, signatureDB, authSet, outpacket->getRRS());
     }
 
-    if(!tsigkeyname.empty())
+    if(haveTSIGDetails && !tsigkeyname.empty())
       outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac); // first answer is 'normal'
 
     sendPacket(outpacket, outsock);
diff --git a/pdns/tsigverifier.cc b/pdns/tsigverifier.cc
new file mode 100644 (file)
index 0000000..3daeef4
--- /dev/null
@@ -0,0 +1,89 @@
+
+#include "tsigverifier.hh"
+#include "dnssecinfra.hh"
+#include "gss_context.hh"
+
+bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
+{
+  if(d_tt.name.empty()) { // TSIG verify message
+    return true;
+  }
+
+  string theirMac;
+  bool checkTSIG = false;
+  // If we have multiple messages, we need to concatenate them together. We also need to make sure we know the location of
+  // the TSIG record so we can remove it in makeTSIGMessageFromTSIGPacket
+  d_signData.append(data);
+  if (mdp.getTSIGPos() == 0) {
+    d_tsigPos += data.size();
+  }
+  else {
+    d_tsigPos += mdp.getTSIGPos();
+  }
+
+  for(const auto& answer :  mdp.d_answers) {
+    if (answer.first.d_type == QType::SOA) {
+      // A SOA is either the first or the last record. We need to check TSIG if that's the case.
+      checkTSIG = true;
+    }
+
+    if(answer.first.d_type == QType::TSIG) {
+      shared_ptr<TSIGRecordContent> trc = getRR<TSIGRecordContent>(answer.first);
+      if(trc) {
+        theirMac = trc->d_mac;
+        d_trc.d_time = trc->d_time;
+        d_trc.d_fudge = trc->d_fudge;
+        checkTSIG = true;
+      }
+    }
+  }
+
+  if(!checkTSIG && d_nonSignedMessages > 99) { // We're allowed to get 100 digest without a TSIG.
+    throw std::runtime_error("No TSIG message received in last 100 messages of AXFR transfer.");
+  }
+
+  if (checkTSIG) {
+    if (theirMac.empty()) {
+      throw std::runtime_error("No TSIG on AXFR response from "+d_remote.toStringWithPort()+" , should be signed with TSIG key '"+d_tt.name.toString()+"'");
+    }
+
+    uint64_t delta = std::abs((int64_t)d_trc.d_time - (int64_t)time(nullptr));
+    if(delta > d_trc.d_fudge) {
+      throw std::runtime_error("Invalid TSIG time delta " + std::to_string(delta) + " >  fudge " + std::to_string(d_trc.d_fudge));
+    }
+    string message;
+    if (!d_prevMac.empty()) {
+      message = makeTSIGMessageFromTSIGPacket(d_signData, d_tsigPos, d_tt.name, d_trc, d_prevMac, true, d_signData.size()-data.size());
+    } else {
+      message = makeTSIGMessageFromTSIGPacket(d_signData, d_tsigPos, d_tt.name, d_trc, d_trc.d_mac, false);
+    }
+
+    TSIGHashEnum algo;
+    if (!getTSIGHashEnum(d_trc.d_algoName, algo)) {
+      throw std::runtime_error("Unsupported TSIG HMAC algorithm " + d_trc.d_algoName.toString());
+    }
+
+    if (algo == TSIG_GSS) {
+      GssContext gssctx(d_tt.name);
+      if (!gss_verify_signature(d_tt.name, message, theirMac)) {
+        throw std::runtime_error("Signature failed to validate on AXFR response from "+d_remote.toStringWithPort()+" signed with TSIG key '"+d_tt.name.toString()+"'");
+      }
+    } else {
+      string ourMac=calculateHMAC(d_tt.secret, message, algo);
+
+      if(!constantTimeStringEquals(ourMac, theirMac)) {
+        throw std::runtime_error("Signature failed to validate on AXFR response from "+d_remote.toStringWithPort()+" signed with TSIG key '"+d_tt.name.toString()+"'");
+      }
+    }
+
+    // Reset and store some values for the next chunks.
+    d_prevMac = theirMac;
+    d_nonSignedMessages = 0;
+    d_signData.clear();
+    d_tsigPos = 0;
+  }
+  else
+    d_nonSignedMessages++;
+
+  return true;
+}
diff --git a/pdns/tsigverifier.hh b/pdns/tsigverifier.hh
new file mode 100644 (file)
index 0000000..84b87eb
--- /dev/null
@@ -0,0 +1,22 @@
+
+#pragma once
+
+#include "dnsrecords.hh"
+#include "iputils.hh"
+
+class TSIGTCPVerifier
+{
+public:
+  TSIGTCPVerifier(const TSIGTriplet& tt, const ComboAddress& remote, TSIGRecordContent& trc): d_tt(tt), d_remote(remote), d_trc(trc)
+  {
+  }
+  bool check(const string& data, const MOADNSParser& mdp);
+private:
+  const TSIGTriplet& d_tt;
+  const ComboAddress& d_remote;
+  TSIGRecordContent& d_trc;
+  string d_prevMac; // RFC2845 4.4
+  string d_signData;
+  size_t d_tsigPos{0};
+  uint8_t d_nonSignedMessages{0}; // RFC2845 4.4
+};