Ssl: Add support for IP-address in alternate subject name
While it's not common it still occurs, perhaps especially with 127.0.0.1 Can be tested by attempting to connect to https://1.1.1.1/ using Qt. Change-Id: Idad56476597ab570b8347236ff700fa66ab5b1f4 Fixes: QTBUG-71828 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
589a01ff6b
commit
58c9c4b609
@ -138,6 +138,7 @@ public:
|
||||
Rfc822NameType = 0x81,
|
||||
DnsNameType = 0x82,
|
||||
UniformResourceIdentifierType = 0x86,
|
||||
IpAddressType = 0x87,
|
||||
|
||||
// context specific
|
||||
Context0Type = 0xA0,
|
||||
|
@ -99,6 +99,9 @@ Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
|
||||
\value DnsEntry A DNS host name entry; the entry contains a host name
|
||||
entry that the certificate is valid for. The entry may contain wildcards.
|
||||
|
||||
\value IpAddressEntry An IP address entry; the entry contains an IP address
|
||||
entry that the certificate is valid for, introduced in Qt 5.13.
|
||||
|
||||
\note In Qt 4, this enum was called \c {AlternateNameEntryType}. That name
|
||||
is deprecated in Qt 5.
|
||||
|
||||
|
@ -68,7 +68,8 @@ namespace QSsl {
|
||||
|
||||
enum AlternativeNameEntryType {
|
||||
EmailEntry,
|
||||
DnsEntry
|
||||
DnsEntry,
|
||||
IpAddressEntry
|
||||
};
|
||||
|
||||
#if QT_DEPRECATED_SINCE(5,0)
|
||||
|
@ -44,6 +44,8 @@
|
||||
#include "qsslkey_p.h"
|
||||
#include "qsslcertificateextension_p.h"
|
||||
|
||||
#include <QtCore/qendian.h>
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
#include <QtCore/private/qmutexpool_p.h>
|
||||
#endif
|
||||
@ -207,10 +209,14 @@ QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlter
|
||||
STACK_OF(GENERAL_NAME) *altNames = (STACK_OF(GENERAL_NAME) *)q_X509_get_ext_d2i(
|
||||
d->x509, NID_subject_alt_name, nullptr, nullptr);
|
||||
|
||||
auto altName = [](ASN1_IA5STRING *ia5, int len) {
|
||||
const char *altNameStr = reinterpret_cast<const char *>(q_ASN1_STRING_get0_data(ia5));
|
||||
return QString::fromLatin1(altNameStr, len);
|
||||
};
|
||||
if (altNames) {
|
||||
for (int i = 0; i < q_sk_GENERAL_NAME_num(altNames); ++i) {
|
||||
const GENERAL_NAME *genName = q_sk_GENERAL_NAME_value(altNames, i);
|
||||
if (genName->type != GEN_DNS && genName->type != GEN_EMAIL)
|
||||
if (genName->type != GEN_DNS && genName->type != GEN_EMAIL && genName->type != GEN_IPADD)
|
||||
continue;
|
||||
|
||||
int len = q_ASN1_STRING_length(genName->d.ia5);
|
||||
@ -219,12 +225,32 @@ QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlter
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *altNameStr = reinterpret_cast<const char *>(q_ASN1_STRING_get0_data(genName->d.ia5));
|
||||
const QString altName = QString::fromLatin1(altNameStr, len);
|
||||
if (genName->type == GEN_DNS)
|
||||
result.insert(QSsl::DnsEntry, altName);
|
||||
else if (genName->type == GEN_EMAIL)
|
||||
result.insert(QSsl::EmailEntry, altName);
|
||||
switch (genName->type) {
|
||||
case GEN_DNS:
|
||||
result.insert(QSsl::DnsEntry, altName(genName->d.ia5, len));
|
||||
break;
|
||||
case GEN_EMAIL:
|
||||
result.insert(QSsl::EmailEntry, altName(genName->d.ia5, len));
|
||||
break;
|
||||
case GEN_IPADD: {
|
||||
QHostAddress ipAddress;
|
||||
switch (len) {
|
||||
case 4: // IPv4
|
||||
ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast<quint32 *>(genName->d.iPAddress->data)));
|
||||
break;
|
||||
case 16: // IPv6
|
||||
ipAddress = QHostAddress(reinterpret_cast<quint8 *>(genName->d.iPAddress->data));
|
||||
break;
|
||||
default: // Unknown IP address format
|
||||
break;
|
||||
}
|
||||
if (!ipAddress.isNull())
|
||||
result.insert(QSsl::IpAddressEntry, ipAddress.toString());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
q_OPENSSL_sk_pop_free((OPENSSL_STACK*)altNames, reinterpret_cast<void(*)(void*)>(q_GENERAL_NAME_free));
|
||||
|
@ -50,6 +50,8 @@
|
||||
#include "qasn1element_p.h"
|
||||
|
||||
#include <QtCore/qdatastream.h>
|
||||
#include <QtCore/qendian.h>
|
||||
#include <QtNetwork/qhostaddress.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -403,10 +405,32 @@ bool QSslCertificatePrivate::parse(const QByteArray &data)
|
||||
QDataStream nameStream(sanElem.value());
|
||||
QAsn1Element nameElem;
|
||||
while (nameElem.read(nameStream)) {
|
||||
if (nameElem.type() == QAsn1Element::Rfc822NameType) {
|
||||
switch (nameElem.type()) {
|
||||
case QAsn1Element::Rfc822NameType:
|
||||
subjectAlternativeNames.insert(QSsl::EmailEntry, nameElem.toString());
|
||||
} else if (nameElem.type() == QAsn1Element::DnsNameType) {
|
||||
break;
|
||||
case QAsn1Element::DnsNameType:
|
||||
subjectAlternativeNames.insert(QSsl::DnsEntry, nameElem.toString());
|
||||
break;
|
||||
case QAsn1Element::IpAddressType: {
|
||||
QHostAddress ipAddress;
|
||||
QByteArray ipAddrValue = nameElem.value();
|
||||
switch (ipAddrValue.length()) {
|
||||
case 4: // IPv4
|
||||
ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast<quint32 *>(ipAddrValue.data())));
|
||||
break;
|
||||
case 16: // IPv6
|
||||
ipAddress = QHostAddress(reinterpret_cast<quint8 *>(ipAddrValue.data()));
|
||||
break;
|
||||
default: // Unknown IP address format
|
||||
break;
|
||||
}
|
||||
if (!ipAddress.isNull())
|
||||
subjectAlternativeNames.insert(QSsl::IpAddressEntry, ipAddress.toString());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2882,6 +2882,19 @@ QSharedPointer<QSslContext> QSslSocketPrivate::sslContext(QSslSocket *socket)
|
||||
|
||||
bool QSslSocketPrivate::isMatchingHostname(const QSslCertificate &cert, const QString &peerName)
|
||||
{
|
||||
QHostAddress hostAddress(peerName);
|
||||
if (!hostAddress.isNull()) {
|
||||
const auto subjectAlternativeNames = cert.subjectAlternativeNames();
|
||||
const auto ipAddresses = subjectAlternativeNames.equal_range(QSsl::AlternativeNameEntryType::IpAddressEntry);
|
||||
|
||||
for (auto it = ipAddresses.first; it != ipAddresses.second; it++) {
|
||||
if (QHostAddress(*it).isEqual(hostAddress, QHostAddress::StrictConversion))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString lowerPeerName = QString::fromLatin1(QUrl::toAce(peerName));
|
||||
const QStringList commonNames = cert.subjectInfo(QSslCertificate::CommonName);
|
||||
|
||||
|
20
tests/auto/network/ssl/qsslsocket/certs/subjectAltNameIP.crt
Normal file
20
tests/auto/network/ssl/qsslsocket/certs/subjectAltNameIP.crt
Normal file
@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMDCCAhigAwIBAgIURWaTvdnvU+Y+gPSONs61cMCH8JUwDQYJKoZIhvcNAQEL
|
||||
BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5vcmcwHhcNMTkwMTA4MTExMDMxWhcNMTkw
|
||||
MjA3MTExMDMxWjAWMRQwEgYDVQQDDAtleGFtcGxlLm9yZzCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBALf0qv9vl8RqvDHpEWfjum7DMrY8qrQnD77C/9f/
|
||||
Jl0Jo4UZiSBr1OYYVbiWJyodw8LpQQsKE+fQCo2STb5X9BldJpwpQvvVi6ygxdzN
|
||||
erJnB15G7xhUkGzDI2xhIJw3e6NGqf1PMB4CTNna6eN2cKYAxPfsWo5Pyh1YtU4s
|
||||
5h+B3+43ol32ccBiRo4YXagbYMELjspEf0AvObvMWSxZQoBHcJ5JGEApxcgvFu8i
|
||||
FBSALVy1IrYE3gXAv8TB0AK7IpuNIL48v5JXCA6JOGYbXFljj6aLFTzfrV3lzhQ0
|
||||
kqBVnQNqVfOUQNUhNT93bnEWVf911j/af5zuFtmr1kbMzucCAwEAAaN2MHQwHQYD
|
||||
VR0OBBYEFHZOtGQHV3roaj3nlQ1XRU0O+05TMB8GA1UdIwQYMBaAFHZOtGQHV3ro
|
||||
aj3nlQ1XRU0O+05TMA8GA1UdEwEB/wQFMAMBAf8wIQYDVR0RBBowGIcEwAUIEIcQ
|
||||
/oAAAAAAAAA8KS+h3UQHZTANBgkqhkiG9w0BAQsFAAOCAQEAcvqvtUSJ2JM3rrWj
|
||||
XjCOhosKY/cow4oDAVdn8AvI/Z4FJfcQZ1vA+ZM533/TaJStG4ThfjyX9t1Ej08M
|
||||
UzP4ZUyXJTv8o6C6j5e9ggEwo/cFp1iWP+xr2SXLJ2cabnu8db5FN5J75HjNsuVs
|
||||
PM95LYY9VlTm9W7JxMwkPEIG+wH5zu6Hj45UAAamwwjOKT1hJYumxdmLAp1oyG1p
|
||||
u86b8iVUjiHG+K6qr4hAKXhuSXE1s/pYqcn1feyk2SbkKvGFR6ad+gmdT4ZaiNYT
|
||||
nL8+t2wim/fRkV0CNdWrrJpWtLzjPq1al7g2eIopdLufSlqanouVpnzwuKGN5QC/
|
||||
MuDohA==
|
||||
-----END CERTIFICATE-----
|
@ -1710,6 +1710,17 @@ void tst_QSslSocket::isMatchingHostname()
|
||||
QCOMPARE(QSslSocketPrivate::isMatchingHostname(cert, QString::fromUtf8("foo.foo.xn--schufele-2za.de")), false);
|
||||
QCOMPARE(QSslSocketPrivate::isMatchingHostname(cert, QString::fromUtf8("www.schaufele.de")), false);
|
||||
QCOMPARE(QSslSocketPrivate::isMatchingHostname(cert, QString::fromUtf8("www.schufele.de")), false);
|
||||
|
||||
/* Generated with the following command (only valid with openssl >= 1.1.1 due to "-addext"):
|
||||
openssl req -x509 -nodes -subj "/CN=example.org" \
|
||||
-addext "subjectAltName = IP:192.5.8.16, IP:fe80::3c29:2fa1:dd44:765" \
|
||||
-newkey rsa:2048 -keyout /dev/null -out subjectAltNameIP.crt
|
||||
*/
|
||||
certs = QSslCertificate::fromPath(testDataDir + "certs/subjectAltNameIP.crt");
|
||||
QVERIFY(!certs.isEmpty());
|
||||
cert = certs.first();
|
||||
QCOMPARE(QSslSocketPrivate::isMatchingHostname(cert, QString::fromUtf8("192.5.8.16")), true);
|
||||
QCOMPARE(QSslSocketPrivate::isMatchingHostname(cert, QString::fromUtf8("fe80::3c29:2fa1:dd44:765")), true);
|
||||
}
|
||||
|
||||
void tst_QSslSocket::wildcard()
|
||||
|
Loading…
Reference in New Issue
Block a user