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:
Mårten Nordheim 2019-01-07 18:01:36 +01:00
parent 589a01ff6b
commit 58c9c4b609
8 changed files with 109 additions and 10 deletions

View File

@ -138,6 +138,7 @@ public:
Rfc822NameType = 0x81,
DnsNameType = 0x82,
UniformResourceIdentifierType = 0x86,
IpAddressType = 0x87,
// context specific
Context0Type = 0xA0,

View File

@ -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.

View File

@ -68,7 +68,8 @@ namespace QSsl {
enum AlternativeNameEntryType {
EmailEntry,
DnsEntry
DnsEntry,
IpAddressEntry
};
#if QT_DEPRECATED_SINCE(5,0)

View File

@ -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));

View File

@ -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;
}
}
}

View File

@ -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);

View 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-----

View File

@ -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()