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 \
sillyrecords.cc \
sstuff.hh \
statbag.cc \
+ tsigverifier.cc tsigverifier.hh \
unix_utility.cc zoneparser-tng.cc
ixplore_LDADD = $(LIBCRYPTO_LIBS)
sstuff.hh \
statbag.cc \
tsig-tests.cc \
+ tsigverifier.cc tsigverifier.hh \
unix_utility.cc
tsig_tests_LDADD = $(LIBCRYPTO_LIBS)
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) {
{
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;
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
return pr;
}
- uint16_t getTSIGPos()
+ uint16_t getTSIGPos() const
{
return d_tsigPos;
}
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)
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);
#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,
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) {
shared_ptr<SOARecordContent> masterSOA;
vector<DNSRecord> records;
size_t receivedBytes = 0;
+
for(;;) {
if(s.read((char*)&len, 2)!=2)
break;
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;
gid_t strToGID(const string &str);
unsigned int pdns_stou(const std::string& str, size_t * idx = 0, int base = 10);
+
sortlist.cc sortlist.hh \
sstuff.hh \
syncres.cc syncres.hh \
+ tsigverifier.cc tsigverifier.hh \
ueberbackend.hh \
unix_utility.cc \
utility.hh \
--- /dev/null
+../tsigverifier.cc
\ No newline at end of file
--- /dev/null
+../tsigverifier.hh
\ No newline at end of file
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) {
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;
}
#include "namespaces.hh"
#include "dnsrecords.hh"
#include "dnssecinfra.hh"
+#include "tsigverifier.hh"
class ResolverException : public PDNSException
{
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;
};
}
return true;
}
- return calculateHMAC(secret, message, algo) == trc->d_mac;
+ return constantTimeStringEquals(calculateHMAC(secret, message, algo), trc->d_mac);
}
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"))
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);
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;
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;
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;
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;
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);
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"))
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);
--- /dev/null
+
+#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;
+}
--- /dev/null
+
+#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
+};