diff options
author | Thiago Macieira <[email protected]> | 2024-04-19 09:41:40 -0700 |
---|---|---|
committer | Thiago Macieira <[email protected]> | 2024-05-08 21:13:45 -0700 |
commit | 503fd609881fb220ac5abe7da2fe367efd90ed4b (patch) | |
tree | 0a4efedaf0508a308166d861d689572c6f9b569c | |
parent | f2f00b2a4632aaeb58e6cdae7faef9e0bafaff49 (diff) |
QDnsLookup: add the ability to tell if the reply was authenticated
This is implemented for DNS-over-TLS and for the native Unix resolver,
because I can find no way to get the state of the reply on Windows with
the WinDNS.h API.
Change-Id: I455fe22ef4ad4b2f9b01fffd17c7bc022ded2363
Reviewed-by: MÃ¥rten Nordheim <[email protected]>
-rw-r--r-- | src/network/kernel/qdnslookup.cpp | 55 | ||||
-rw-r--r-- | src/network/kernel/qdnslookup.h | 2 | ||||
-rw-r--r-- | src/network/kernel/qdnslookup_p.h | 1 | ||||
-rw-r--r-- | src/network/kernel/qdnslookup_unix.cpp | 41 | ||||
-rw-r--r-- | tests/manual/qdnslookup/main.cpp | 7 |
5 files changed, 89 insertions, 17 deletions
diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp index 9a35bd3365a..a3ebbe04db8 100644 --- a/src/network/kernel/qdnslookup.cpp +++ b/src/network/kernel/qdnslookup.cpp @@ -168,7 +168,7 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records) name, or the host name associated with an IP address you should use QHostInfo instead. - \section1 DNS-over-TLS + \section1 DNS-over-TLS and Authentic Data QDnsLookup supports DNS-over-TLS (DoT, as specified by \l{RFC 7858}) on some platforms. That currently includes all Unix platforms where regular @@ -182,6 +182,27 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records) server being connected to. Clients may use setSslConfiguration() to impose additional restrictions and sslConfiguration() to obtain information after the query is complete. + + QDnsLookup will request DNS servers queried over TLS to perform + authentication on the data they return. If they confirm the data is valid, + the \l authenticData property will be set to true. QDnsLookup does not + verify the integrity of the data by itself, so applications should only + trust this property on servers they have confirmed through other means to + be trustworthy. + + \section2 Authentic Data without TLS + + QDnsLookup request Authentic Data for any server set with setNameserver(), + even if TLS encryption is not required. This is useful when querying a + caching nameserver on the same host as the application or on a trusted + network. Though similar to the TLS case, the application is responsible for + determining if the server it chose to use is trustworthy, and if the + unencrypted connection cannot be tampered with. + + QDnsLookup obeys the system configuration to request Authentic Data on the + default nameserver (that is, if setNameserver() is not called). This is + currently only supported on Linux systems using glibc 2.31 or later. On any + other systems, QDnsLookup will ignore the AD bit in the query header. */ /*! @@ -419,6 +440,28 @@ QDnsLookup::~QDnsLookup() } /*! + \since 6.8 + \property QDnsLookup::authenticData + \brief whether the reply was authenticated by the resolver. + + QDnsLookup does not perform the authentication itself. Instead, it trusts + the name server that was queried to perform the authentication and report + it. The application is responsible for determining if any servers it + configured with setNameserver() are trustworthy; if no server was set, + QDnsLookup obeys system configuration on whether responses should be + trusted. + + This property may be set even if error() indicates a resolver error + occurred. + + \sa setNameserver(), nameserverProtocol() +*/ +bool QDnsLookup::isAuthenticData() const +{ + return d_func()->reply.authenticData; +} + +/*! \property QDnsLookup::error \brief the type of error that occurred if the DNS lookup failed, or NoError. */ @@ -1308,6 +1351,7 @@ inline QDebug operator<<(QDebug &d, QDnsLookupRunnable *r) #if QT_CONFIG(ssl) static constexpr std::chrono::milliseconds DnsOverTlsConnectTimeout(15'000); static constexpr std::chrono::milliseconds DnsOverTlsTimeout(120'000); +static constexpr quint8 DnsAuthenticDataBit = 0x20; static int makeReplyErrorFromSocket(QDnsLookupReply *reply, const QAbstractSocket *socket) { @@ -1334,6 +1378,9 @@ bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned c socket.setProtocolTag("domain-s"_L1); # endif + // Request the name server attempt to authenticate the reply. + query[3] |= DnsAuthenticDataBit; + do { quint16 size = qToBigEndian<quint16>(query.size()); QDeadlineTimer timeout(DnsOverTlsTimeout); @@ -1363,8 +1410,12 @@ bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned c // the maximum allocation is small. size = qFromBigEndian(size); response.resize(size); - if (waitForBytes(response.data(), size)) + if (waitForBytes(response.data(), size)) { + // check if the AD bit is set; we'll trust it over TLS requests + if (size >= 4) + reply->authenticData = response[3] & DnsAuthenticDataBit; return true; + } } while (false); // handle errors diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h index 2a56dc16e8c..ad98f8ae5c2 100644 --- a/src/network/kernel/qdnslookup.h +++ b/src/network/kernel/qdnslookup.h @@ -142,6 +142,7 @@ class Q_NETWORK_EXPORT QDnsLookup : public QObject { Q_OBJECT Q_PROPERTY(Error error READ error NOTIFY finished) + Q_PROPERTY(bool authenticData READ isAuthenticData NOTIFY finished) Q_PROPERTY(QString errorString READ errorString NOTIFY finished) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged BINDABLE bindableName) Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged BINDABLE bindableType) @@ -196,6 +197,7 @@ public: quint16 port = 0, QObject *parent = nullptr); ~QDnsLookup(); + bool isAuthenticData() const; Error error() const; QString errorString() const; bool isFinished() const; diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h index 574b279fe6b..259dca0b2e4 100644 --- a/src/network/kernel/qdnslookup_p.h +++ b/src/network/kernel/qdnslookup_p.h @@ -48,6 +48,7 @@ class QDnsLookupReply { public: QDnsLookup::Error error = QDnsLookup::NoError; + bool authenticData = false; QString errorString; QList<QDnsDomainNameRecord> canonicalNameRecords; diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp index 7a283d4f7fd..38a1dce5d9b 100644 --- a/src/network/kernel/qdnslookup_unix.cpp +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -67,11 +67,9 @@ using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill // https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port) { - if (!nameserver.isNull()) { - union res_sockaddr_union u; - setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port); - res_setservers(state, &u, 1); - } + union res_sockaddr_union u; + setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port); + res_setservers(state, &u, 1); return true; } #else @@ -122,9 +120,6 @@ template <typename State> bool setIpv6NameServer(State *, const void *, quint16) static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port) { - if (nameserver.isNull()) - return true; - state->nscount = 1; state->nsaddr_list[0].sin_family = AF_UNSPEC; if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) @@ -155,11 +150,22 @@ prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_r static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer, ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port) { - //Check if a nameserver was set. If so, use it - if (!applyNameServer(state, nameserver, port)) { - reply->setError(QDnsLookup::ResolverError, - QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS")); - return -1; + // Check if a nameserver was set. If so, use it. + if (!nameserver.isNull()) { + if (!applyNameServer(state, nameserver, port)) { + reply->setError(QDnsLookup::ResolverError, + QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS")); + return -1; + } + + // Request the name server attempt to authenticate the reply. + reinterpret_cast<HEADER *>(buffer.data())->ad = true; + +#ifdef RES_TRUSTAD + // Need to set this option even though we set the AD bit, otherwise + // glibc turns it off. + state->options |= RES_TRUSTAD; +#endif } auto attemptToSend = [&]() { @@ -210,6 +216,15 @@ static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsign } } + // We only trust the AD bit in the reply if we're querying a custom name + // server or if we can tell the system administrator configured the resolver + // to trust replies. +#ifndef RES_TRUSTAD + if (nameserver.isNull()) + header->ad = false; +#endif + reply->authenticData = header->ad; + return responseLength; } diff --git a/tests/manual/qdnslookup/main.cpp b/tests/manual/qdnslookup/main.cpp index 129c79bb442..59ce2ab284d 100644 --- a/tests/manual/qdnslookup/main.cpp +++ b/tests/manual/qdnslookup/main.cpp @@ -118,10 +118,13 @@ static void printAnswers(const QDnsLookup &lookup) static void printResults(const QDnsLookup &lookup, QElapsedTimer::Duration duration) { if (QDnsLookup::Error error = lookup.error()) - printf(";; status: %s (%s)\n", QMetaEnum::fromType<QDnsLookup::Error>().valueToKey(error), + printf(";; status: %s (%s)", QMetaEnum::fromType<QDnsLookup::Error>().valueToKey(error), qPrintable(lookup.errorString())); else - printf(";; status: NoError\n"); + printf(";; status: NoError"); + if (lookup.isAuthenticData()) + printf("; AuthenticData"); + puts(""); QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>(); printf(";; QUESTION:\n"); |