QDnsLookup: add support for TLSA records
[ChangeLog][QtNetwork][QDnsLookup] Added support for querying records of type TLSA, which are useful in DNS-based Authentication of Named Entities (DANE). Change-Id: I455fe22ef4ad4b2f9b01fffd17c723aa6ab7f278 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
503fd60988
commit
4503dabfbd
@ -124,6 +124,11 @@
|
||||
\title RFC 6724
|
||||
*/
|
||||
|
||||
/*!
|
||||
\externalpage https://datatracker.ietf.org/doc/html/rfc6698
|
||||
\title RFC 6698
|
||||
*/
|
||||
|
||||
/*!
|
||||
\externalpage https://datatracker.ietf.org/doc/html/rfc7049
|
||||
\title RFC 7049
|
||||
|
@ -257,6 +257,8 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records)
|
||||
|
||||
\value SRV service records.
|
||||
|
||||
\value[since 6.8] TLSA TLS association records.
|
||||
|
||||
\value TXT text records.
|
||||
*/
|
||||
|
||||
@ -704,6 +706,21 @@ QList<QDnsTextRecord> QDnsLookup::textRecords() const
|
||||
return d_func()->reply.textRecords;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.8
|
||||
Returns the list of TLS association records associated with this lookup.
|
||||
|
||||
According to the standards relating to DNS-based Authentication of Named
|
||||
Entities (DANE), this field should be ignored and must not be used for
|
||||
verifying the authentity of a given server if the authenticity of the DNS
|
||||
reply cannot itself be confirmed. See isAuthenticData() for more
|
||||
information.
|
||||
*/
|
||||
QList<QDnsTlsAssociationRecord> QDnsLookup::tlsAssociationRecords() const
|
||||
{
|
||||
return d_func()->reply.tlsAssociationRecords;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(ssl)
|
||||
/*!
|
||||
\since 6.8
|
||||
@ -1261,6 +1278,223 @@ QDnsTextRecord &QDnsTextRecord::operator=(const QDnsTextRecord &other)
|
||||
very fast and never fails.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QDnsTlsAssociationRecord
|
||||
\since 6.8
|
||||
\brief The QDnsTlsAssociationRecord class stores information about a DNS TLSA record.
|
||||
|
||||
\inmodule QtNetwork
|
||||
\ingroup network
|
||||
\ingroup shared
|
||||
|
||||
When performing a text lookup, zero or more records will be returned. Each
|
||||
record is represented by a QDnsTlsAssociationRecord instance.
|
||||
|
||||
The meaning of the fields is defined in \l{RFC 6698}.
|
||||
|
||||
\sa QDnsLookup
|
||||
*/
|
||||
|
||||
QT_DEFINE_QSDP_SPECIALIZATION_DTOR(QDnsTlsAssociationRecordPrivate)
|
||||
|
||||
/*!
|
||||
\enum QDnsTlsAssociationRecord::CertificateUsage
|
||||
|
||||
This enumeration contains valid values for the certificate usage field of
|
||||
TLS Association queries. The following list is up-to-date with \l{RFC 6698}
|
||||
section 2.1.1 and RFC 7218 section 2.1. Please refer to those documents for
|
||||
authoritative instructions on interpreting this enumeration.
|
||||
|
||||
\value CertificateAuthorityConstrait
|
||||
Indicates the record includes an association to a specific Certificate
|
||||
Authority that must be found in the TLS server's certificate chain and
|
||||
must pass PKIX validation.
|
||||
|
||||
\value ServiceCertificateConstraint
|
||||
Indicates the record includes an association to a certificate that must
|
||||
match the end entity certificate provided by the TLS server and must
|
||||
pass PKIX validation.
|
||||
|
||||
\value TrustAnchorAssertion
|
||||
Indicates the record includes an association to a certificate that MUST
|
||||
be used as the ultimate trust anchor to validate the TLS server's
|
||||
certificate and must pass PKIX validation.
|
||||
|
||||
\value DomainIssuedCertificate
|
||||
Indicates the record includes an association to a certificate that must
|
||||
match the end entity certificate provided by the TLS server. PKIX
|
||||
validation is not tested.
|
||||
|
||||
\value PrivateUse
|
||||
No standard meaning applied.
|
||||
|
||||
\value PKIX_TA
|
||||
Alias; mnemonic for Public Key Infrastructure Trust Anchor
|
||||
|
||||
\value PKIX_EE
|
||||
Alias; mnemonic for Public Key Infrastructure End Entity
|
||||
|
||||
\value DANE_TA
|
||||
Alias; mnemonic for DNS-based Authentication of Named Entities Trust Anchor
|
||||
|
||||
\value DANE_EE
|
||||
Alias; mnemonic for DNS-based Authentication of Named Entities End Entity
|
||||
|
||||
\value PrivCert
|
||||
Alias
|
||||
|
||||
Other values are currently reserved, but may be unreserved by future
|
||||
standards. This enumeration can be used for those values even if no
|
||||
enumerator is provided.
|
||||
|
||||
\sa certificateUsage()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QDnsTlsAssociationRecord::Selector
|
||||
|
||||
This enumeration contains valid values for the selector field of TLS
|
||||
Association queries. The following list is up-to-date with \l{RFC 6698}
|
||||
section 2.1.2 and RFC 7218 section 2.2. Please refer to those documents for
|
||||
authoritative instructions on interpreting this enumeration.
|
||||
|
||||
\value FullCertificate
|
||||
Indicates this record refers to the full certificate in its binary
|
||||
structure form.
|
||||
|
||||
\value SubjectPublicKeyInfo
|
||||
Indicates the record refers to the certificate's subject and public
|
||||
key information, in DER-encoded binary structure form.
|
||||
|
||||
\value PrivateUse
|
||||
No standard meaning applied.
|
||||
|
||||
\value Cert
|
||||
Alias
|
||||
|
||||
\value SPKI
|
||||
Alias
|
||||
|
||||
\value PrivSel
|
||||
Alias
|
||||
|
||||
Other values are currently reserved, but may be unreserved by future
|
||||
standards. This enumeration can be used for those values even if no
|
||||
enumerator is provided.
|
||||
|
||||
\sa selector()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QDnsTlsAssociationRecord::MatchingType
|
||||
|
||||
This enumeration contains valid values for the matching type field of TLS
|
||||
Association queries. The following list is up-to-date with \l{RFC 6698}
|
||||
section 2.1.3 and RFC 7218 section 2.3. Please refer to those documents for
|
||||
authoritative instructions on interpreting this enumeration.
|
||||
|
||||
\value Exact
|
||||
Indicates this the certificate or SPKI data is stored verbatim in this
|
||||
record.
|
||||
|
||||
\value Sha256
|
||||
Indicates this a SHA-256 checksum of the the certificate or SPKI data
|
||||
present in this record.
|
||||
|
||||
\value Sha512
|
||||
Indicates this a SHA-512 checksum of the the certificate or SPKI data
|
||||
present in this record.
|
||||
|
||||
\value PrivateUse
|
||||
No standard meaning applied.
|
||||
|
||||
\value PrivMatch
|
||||
Alias
|
||||
|
||||
Other values are currently reserved, but may be unreserved by future
|
||||
standards. This enumeration can be used for those values even if no
|
||||
enumerator is provided.
|
||||
|
||||
\sa matchingType()
|
||||
*/
|
||||
|
||||
/*!
|
||||
Constructs an empty TLS Association record.
|
||||
*/
|
||||
QDnsTlsAssociationRecord::QDnsTlsAssociationRecord()
|
||||
: d(new QDnsTlsAssociationRecordPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Constructs a copy of \a other.
|
||||
*/
|
||||
QDnsTlsAssociationRecord::QDnsTlsAssociationRecord(const QDnsTlsAssociationRecord &other) = default;
|
||||
|
||||
/*!
|
||||
Moves the content of \a other into this object.
|
||||
*/
|
||||
QDnsTlsAssociationRecord &
|
||||
QDnsTlsAssociationRecord::operator=(const QDnsTlsAssociationRecord &other) = default;
|
||||
|
||||
/*!
|
||||
Destroys this TLS Association record object.
|
||||
*/
|
||||
QDnsTlsAssociationRecord::~QDnsTlsAssociationRecord() = default;
|
||||
|
||||
/*!
|
||||
Returns the name of this record.
|
||||
*/
|
||||
QString QDnsTlsAssociationRecord::name() const
|
||||
{
|
||||
return d->name;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the duration in seconds for which this record is valid.
|
||||
*/
|
||||
quint32 QDnsTlsAssociationRecord::timeToLive() const
|
||||
{
|
||||
return d->timeToLive;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the certificate usage field for this record.
|
||||
*/
|
||||
QDnsTlsAssociationRecord::CertificateUsage QDnsTlsAssociationRecord::usage() const
|
||||
{
|
||||
return d->usage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selector field for this record.
|
||||
*/
|
||||
QDnsTlsAssociationRecord::Selector QDnsTlsAssociationRecord::selector() const
|
||||
{
|
||||
return d->selector;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the match type field for this record.
|
||||
*/
|
||||
QDnsTlsAssociationRecord::MatchingType QDnsTlsAssociationRecord::matchType() const
|
||||
{
|
||||
return d->matchType;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the binary data field for this record. The interpretation of this
|
||||
binary data depends on the three numeric fields provided by
|
||||
certificateUsage(), selector(), and matchType().
|
||||
|
||||
Do note this is a binary field, even for the checksums, similar to what
|
||||
QCyrptographicHash::result() returns.
|
||||
*/
|
||||
QByteArray QDnsTlsAssociationRecord::value() const
|
||||
{
|
||||
return d->value;
|
||||
}
|
||||
|
||||
static QDnsLookupRunnable::EncodedLabel encodeLabel(const QString &label)
|
||||
{
|
||||
QDnsLookupRunnable::EncodedLabel::value_type rootDomain = u'.';
|
||||
|
@ -22,8 +22,11 @@ class QDnsHostAddressRecordPrivate;
|
||||
class QDnsMailExchangeRecordPrivate;
|
||||
class QDnsServiceRecordPrivate;
|
||||
class QDnsTextRecordPrivate;
|
||||
class QDnsTlsAssociationRecordPrivate;
|
||||
class QSslConfiguration;
|
||||
|
||||
QT_DECLARE_QSDP_SPECIALIZATION_DTOR(QDnsTlsAssociationRecordPrivate)
|
||||
|
||||
class Q_NETWORK_EXPORT QDnsDomainNameRecord
|
||||
{
|
||||
public:
|
||||
@ -138,6 +141,78 @@ private:
|
||||
|
||||
Q_DECLARE_SHARED(QDnsTextRecord)
|
||||
|
||||
class Q_NETWORK_EXPORT QDnsTlsAssociationRecord
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
enum class CertificateUsage : quint8 {
|
||||
// https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#certificate-usages
|
||||
// RFC 6698
|
||||
CertificateAuthorityConstrait = 0,
|
||||
ServiceCertificateConstraint = 1,
|
||||
TrustAnchorAssertion = 2,
|
||||
DomainIssuedCertificate = 3,
|
||||
PrivateUse = 255,
|
||||
|
||||
// Aliases by RFC 7218
|
||||
PKIX_TA = 0,
|
||||
PKIX_EE = 1,
|
||||
DANE_TA = 2,
|
||||
DANE_EE = 3,
|
||||
PrivCert = 255,
|
||||
};
|
||||
Q_ENUM(CertificateUsage)
|
||||
|
||||
enum class Selector : quint8 {
|
||||
// https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#selectors
|
||||
// RFC 6698
|
||||
FullCertificate = 0,
|
||||
SubjectPublicKeyInfo = 1,
|
||||
PrivateUse = 255,
|
||||
|
||||
// Aliases by RFC 7218
|
||||
Cert = FullCertificate,
|
||||
SPKI = SubjectPublicKeyInfo,
|
||||
PrivSel = PrivateUse,
|
||||
};
|
||||
Q_ENUM(Selector)
|
||||
|
||||
enum class MatchingType : quint8 {
|
||||
// https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#matching-types
|
||||
// RFC 6698
|
||||
Exact = 0,
|
||||
Sha256 = 1,
|
||||
Sha512 = 2,
|
||||
PrivateUse = 255,
|
||||
PrivMatch = PrivateUse,
|
||||
};
|
||||
Q_ENUM(MatchingType)
|
||||
|
||||
QDnsTlsAssociationRecord();
|
||||
QDnsTlsAssociationRecord(const QDnsTlsAssociationRecord &other);
|
||||
QDnsTlsAssociationRecord(QDnsTlsAssociationRecord &&other)
|
||||
: d(std::move(other.d))
|
||||
{}
|
||||
QDnsTlsAssociationRecord &operator=(QDnsTlsAssociationRecord &&other) noexcept { swap(other); return *this; }
|
||||
QDnsTlsAssociationRecord &operator=(const QDnsTlsAssociationRecord &other);
|
||||
~QDnsTlsAssociationRecord();
|
||||
|
||||
void swap(QDnsTlsAssociationRecord &other) noexcept { d.swap(other.d); }
|
||||
|
||||
QString name() const;
|
||||
quint32 timeToLive() const;
|
||||
CertificateUsage usage() const;
|
||||
Selector selector() const;
|
||||
MatchingType matchType() const;
|
||||
QByteArray value() const;
|
||||
|
||||
private:
|
||||
QSharedDataPointer<QDnsTlsAssociationRecordPrivate> d;
|
||||
friend class QDnsLookupRunnable;
|
||||
};
|
||||
|
||||
Q_DECLARE_SHARED(QDnsTlsAssociationRecord)
|
||||
|
||||
class Q_NETWORK_EXPORT QDnsLookup : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -178,6 +253,7 @@ public:
|
||||
NS = 2,
|
||||
PTR = 12,
|
||||
SRV = 33,
|
||||
TLSA = 52,
|
||||
TXT = 16
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
@ -230,7 +306,7 @@ public:
|
||||
QList<QDnsDomainNameRecord> pointerRecords() const;
|
||||
QList<QDnsServiceRecord> serviceRecords() const;
|
||||
QList<QDnsTextRecord> textRecords() const;
|
||||
|
||||
QList<QDnsTlsAssociationRecord> tlsAssociationRecords() const;
|
||||
|
||||
#if QT_CONFIG(ssl)
|
||||
void setSslConfiguration(const QSslConfiguration &sslConfiguration);
|
||||
|
@ -57,6 +57,7 @@ public:
|
||||
QList<QDnsDomainNameRecord> nameServerRecords;
|
||||
QList<QDnsDomainNameRecord> pointerRecords;
|
||||
QList<QDnsServiceRecord> serviceRecords;
|
||||
QList<QDnsTlsAssociationRecord> tlsAssociationRecords;
|
||||
QList<QDnsTextRecord> textRecords;
|
||||
|
||||
#if QT_CONFIG(ssl)
|
||||
@ -296,6 +297,15 @@ public:
|
||||
QList<QByteArray> values;
|
||||
};
|
||||
|
||||
class QDnsTlsAssociationRecordPrivate : public QDnsRecordPrivate
|
||||
{
|
||||
public:
|
||||
QDnsTlsAssociationRecord::CertificateUsage usage;
|
||||
QDnsTlsAssociationRecord::Selector selector;
|
||||
QDnsTlsAssociationRecord::MatchingType matchType;
|
||||
QByteArray value;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QDNSLOOKUP_P_H
|
||||
|
@ -406,6 +406,23 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
|
||||
if (status < 0)
|
||||
return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
|
||||
reply->serviceRecords.append(record);
|
||||
} else if (type == QDnsLookup::TLSA) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc6698#section-2.1
|
||||
if (size < 3)
|
||||
return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid TLS association record"));
|
||||
|
||||
const quint8 usage = response[offset];
|
||||
const quint8 selector = response[offset + 1];
|
||||
const quint8 matchType = response[offset + 2];
|
||||
|
||||
QDnsTlsAssociationRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ttl;
|
||||
record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
|
||||
record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
|
||||
record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
|
||||
record.d->value.assign(response + offset + 3, response + offset + size);
|
||||
reply->tlsAssociationRecords.append(std::move(record));
|
||||
} else if (type == QDnsLookup::TXT) {
|
||||
QDnsTextRecord record;
|
||||
record.d->name = name;
|
||||
|
@ -224,6 +224,25 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->weight = ptr->Data.Srv.wWeight;
|
||||
reply->serviceRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::TLSA) {
|
||||
// Note: untested, because the DNS_RECORD reply appears to contain
|
||||
// no records relating to TLSA. Maybe WinDNS filters them out of
|
||||
// zones without DNSSEC.
|
||||
QDnsTlsAssociationRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
|
||||
const auto &tlsa = ptr->Data.Tlsa;
|
||||
const quint8 usage = tlsa.bCertUsage;
|
||||
const quint8 selector = tlsa.bSelector;
|
||||
const quint8 matchType = tlsa.bMatchingType;
|
||||
|
||||
record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
|
||||
record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
|
||||
record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
|
||||
record.d->value.assign(tlsa.bCertificateAssociationData,
|
||||
tlsa.bCertificateAssociationData + tlsa.bCertificateAssociationDataLength);
|
||||
reply->tlsAssociationRecords.append(std::move(record));
|
||||
} else if (ptr->wType == QDnsLookup::TXT) {
|
||||
QDnsTextRecord record;
|
||||
record.d->name = name;
|
||||
|
@ -374,6 +374,14 @@ QStringList tst_QDnsLookup::formatReply(const QDnsLookup *lookup) const
|
||||
result.append(std::move(entry));
|
||||
}
|
||||
|
||||
for (const QDnsTlsAssociationRecord &rr : lookup->tlsAssociationRecords()) {
|
||||
QString entry = u"TLSA %1 %2 %3 %4"_s.arg(int(rr.usage())).arg(int(rr.selector()))
|
||||
.arg(int(rr.matchType())).arg(rr.value().toHex().toUpper());
|
||||
if (rr.name() != domain)
|
||||
entry = "TLSA unexpected label to "_L1 + rr.name();
|
||||
result.append(std::move(entry));
|
||||
}
|
||||
|
||||
result.sort();
|
||||
return result;
|
||||
}
|
||||
@ -504,6 +512,10 @@ void tst_QDnsLookup::lookup_data()
|
||||
"SRV 2 50 7 aaaa-single;"
|
||||
"SRV 3 50 7 a-multi";
|
||||
|
||||
QTest::newRow("tlsa") << QDnsLookup::Type::TLSA << "_25._tcp.multi"
|
||||
<< "TLSA 3 1 1 0123456789ABCDEFFEDCBA9876543210"
|
||||
"0123456789ABCDEFFEDCBA9876543210";
|
||||
|
||||
QTest::newRow("txt-single") << QDnsLookup::TXT << "txt-single"
|
||||
<< "TXT \"Hello\"";
|
||||
QTest::newRow("txt-multi-onerr") << QDnsLookup::TXT << "txt-multi-onerr"
|
||||
@ -522,8 +534,12 @@ void tst_QDnsLookup::lookup()
|
||||
if (!lookup)
|
||||
return;
|
||||
|
||||
QCOMPARE(lookup->error(), QDnsLookup::NoError);
|
||||
#ifdef Q_OS_WIN
|
||||
if (QTest::currentDataTag() == "tlsa"_L1)
|
||||
QSKIP("WinDNS doesn't work properly with TLSA records and we don't know why");
|
||||
#endif
|
||||
QCOMPARE(lookup->errorString(), QString());
|
||||
QCOMPARE(lookup->error(), QDnsLookup::NoError);
|
||||
QCOMPARE(lookup->type(), type);
|
||||
QCOMPARE(lookup->name(), domainName(domain));
|
||||
|
||||
|
@ -37,10 +37,18 @@ static QDnsLookup::Type typeFromString(QString str)
|
||||
return QDnsLookup::Type(value);
|
||||
}
|
||||
|
||||
template <typename Enum> [[maybe_unused]] static const char *enumToKey(Enum e)
|
||||
template <typename Enum> QByteArray enumToString(Enum value)
|
||||
{
|
||||
QMetaEnum me = QMetaEnum::fromType<Enum>();
|
||||
return me.valueToKey(int(e));
|
||||
QByteArray keys = me.valueToKeys(int(value));
|
||||
if (keys.isEmpty())
|
||||
return QByteArrayLiteral("<unknown>");
|
||||
|
||||
// return the last one
|
||||
qsizetype idx = keys.lastIndexOf('|');
|
||||
if (idx > 0)
|
||||
return std::move(keys).sliced(idx + 1);
|
||||
return keys;
|
||||
}
|
||||
|
||||
static int showHelp(const char *argv0, int exitcode)
|
||||
@ -113,6 +121,14 @@ static void printAnswers(const QDnsLookup &lookup)
|
||||
printf("%s ", qPrintable(QDebug::toString(data)));
|
||||
puts("");
|
||||
}
|
||||
|
||||
for (const QDnsTlsAssociationRecord &rr : lookup.tlsAssociationRecords()) {
|
||||
printRecordCommon(rr, "TLSA");
|
||||
printf("( %u %u %u ; %s %s %s\n\t%s )\n", quint8(rr.usage()), quint8(rr.selector()),
|
||||
quint8(rr.matchType()), enumToString(rr.usage()).constData(),
|
||||
enumToString(rr.selector()).constData(), enumToString(rr.matchType()).constData(),
|
||||
rr.value().toHex().toUpper().constData());
|
||||
}
|
||||
}
|
||||
|
||||
static void printResults(const QDnsLookup &lookup, QElapsedTimer::Duration duration)
|
||||
@ -143,7 +159,7 @@ static void printResults(const QDnsLookup &lookup, QElapsedTimer::Duration durat
|
||||
#if QT_CONFIG(ssl)
|
||||
if (lookup.nameserverProtocol() != QDnsLookup::Standard) {
|
||||
if (QSslConfiguration conf = lookup.sslConfiguration(); !conf.isNull()) {
|
||||
printf(" (%s %s)", enumToKey(conf.sessionProtocol()),
|
||||
printf(" (%s %s)", enumToString(conf.sessionProtocol()).constData(),
|
||||
qPrintable(conf.sessionCipher().name()));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user