diff options
author | Jeremy Lainé <jeremy.laine@m4x.org> | 2014-09-03 11:41:22 +0200 |
---|---|---|
committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2014-09-05 14:20:44 +0200 |
commit | 070fcf9ce1ef3c2912bd15f1f39db9740cc754c6 (patch) | |
tree | 3447d1878ad60d451f247f2cbb18964dc0000cc9 | |
parent | 2e667c9171f141c6ee0a279aae4479d4414c99f3 (diff) |
ssl: common certificate parser support for extensions
This makes non-OpenSSL backends able to handle to certificate
extensions.
This also converts the Q_OS_WINRT #ifdef's in the unit test to
QT_NO_OPENSSL as the behavior is the same for any non-OpenSSL
backend.
Change-Id: I6a8306dc5c97a659ec96063d5a59cee2ee9a63a9
Reviewed-by: Richard J. Moore <rich@kde.org>
-rw-r--r-- | src/network/ssl/qasn1element.cpp | 8 | ||||
-rw-r--r-- | src/network/ssl/qasn1element_p.h | 7 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate.cpp | 150 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate_p.h | 5 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate_qt.cpp | 256 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_qt.cpp | 4 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp | 12 |
7 files changed, 274 insertions, 168 deletions
diff --git a/src/network/ssl/qasn1element.cpp b/src/network/ssl/qasn1element.cpp index 78fffbf793..f3f280d863 100644 --- a/src/network/ssl/qasn1element.cpp +++ b/src/network/ssl/qasn1element.cpp @@ -56,6 +56,14 @@ static OidNameMap createOidMap() // used by unit tests oids.insert(oids.end(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink")); oids.insert(oids.end(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress")); + oids.insert(oids.end(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess")); + oids.insert(oids.end(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP")); + oids.insert(oids.end(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier")); oids.insert(oids.end(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O")); oids.insert(oids.end(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU")); oids.insert(oids.end(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title")); diff --git a/src/network/ssl/qasn1element_p.h b/src/network/ssl/qasn1element_p.h index b4445dacfd..36a7c90de3 100644 --- a/src/network/ssl/qasn1element_p.h +++ b/src/network/ssl/qasn1element_p.h @@ -59,6 +59,9 @@ QT_BEGIN_NAMESPACE +#define RSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.113549.1.1.1") +#define DSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10040.4.1") + class Q_AUTOTEST_EXPORT QAsn1Element { public: @@ -78,10 +81,6 @@ public: SequenceType = 0x30, SetType = 0x31, - // application - Rfc822NameType = 0x81, - DnsNameType = 0x82, - // context specific Context0Type = 0xA0, Context3Type = 0xA3 diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp index bae78f4347..47ea3343ea 100644 --- a/src/network/ssl/qsslcertificate.cpp +++ b/src/network/ssl/qsslcertificate.cpp @@ -122,7 +122,6 @@ #include "qsslcertificate.h" #include "qsslcertificate_p.h" -#include "qasn1element_p.h" #include "qsslkey_p.h" #include <QtCore/qdir.h> @@ -642,155 +641,6 @@ static const char *certificate_blacklist[] = { 0 }; -bool QSslCertificatePrivate::parse(const QByteArray &data) -{ -#ifndef QT_NO_OPENSSL - Q_UNUSED(data); -#else - QAsn1Element root; - - QDataStream dataStream(data); - if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) - return false; - - QDataStream rootStream(root.value()); - QAsn1Element cert; - if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) - return false; - - // version or serial number - QAsn1Element elem; - QDataStream certStream(cert.value()); - if (!elem.read(certStream)) - return false; - - if (elem.type() == QAsn1Element::Context0Type) { - QDataStream versionStream(elem.value()); - if (!elem.read(versionStream) || elem.type() != QAsn1Element::IntegerType) - return false; - - versionString = QByteArray::number(elem.value()[0] + 1); - if (!elem.read(certStream)) - return false; - } else { - versionString = QByteArray::number(1); - } - - // serial number - if (elem.type() != QAsn1Element::IntegerType) - return false; - - QByteArray hexString; - hexString.reserve(elem.value().size() * 3); - for (int a = 0; a < elem.value().size(); ++a) { - const quint8 b = elem.value().at(a); - if (b || !hexString.isEmpty()) { // skip leading zeros - hexString += QByteArray::number(b, 16).rightJustified(2, '0'); - hexString += ':'; - } - } - hexString.chop(1); - serialNumberString = hexString; - - // algorithm ID - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - //qDebug() << "algorithm ID" << elem.type() << elem.length << elem.value().toHex(); - - // issuer info - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); - issuerInfo = elem.toInfo(); - - // validity period - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QDataStream validityStream(elem.value()); - if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) - return false; - - notValidBefore = elem.toDateTime(); - if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) - return false; - - notValidAfter = elem.toDateTime(); - - // subject name - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); - subjectInfo = elem.toInfo(); - subjectMatchesIssuer = issuerDer == subjectDer; - - // public key - qint64 keyStart = certStream.device()->pos(); - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - publicKeyDerData.resize(certStream.device()->pos() - keyStart); - QDataStream keyStream(elem.value()); - if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - - // key algorithm - if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) - return false; - - const QByteArray oid = elem.toObjectId(); - if (oid == "1.2.840.113549.1.1.1") - publicKeyAlgorithm = QSsl::Rsa; - else if (oid == "1.2.840.10040.4.1") - publicKeyAlgorithm = QSsl::Dsa; - else - publicKeyAlgorithm = QSsl::Opaque; - - certStream.device()->seek(keyStart); - certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); - - // extensions - while (elem.read(certStream)) { - if (elem.type() == QAsn1Element::Context3Type) { - if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { - QDataStream extStream(elem.value()); - while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { - QAsn1Element oidElem, valElem; - QDataStream seqStream(elem.value()); - if (oidElem.read(seqStream) && oidElem.type() == QAsn1Element::ObjectIdentifierType && - valElem.read(seqStream) && valElem.type() == QAsn1Element::OctetStringType) { - // alternative name - if (oidElem.toObjectId() == QByteArray("2.5.29.17")) { - QAsn1Element sanElem; - if (sanElem.read(valElem.value()) && sanElem.type() == QAsn1Element::SequenceType) { - QDataStream nameStream(sanElem.value()); - QAsn1Element nameElem; - while (nameElem.read(nameStream)) { - if (nameElem.type() == QAsn1Element::Rfc822NameType) { - subjectAlternativeNames.insert(QSsl::EmailEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); - } else if (nameElem.type() == QAsn1Element::DnsNameType) { - subjectAlternativeNames.insert(QSsl::DnsEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); - } - } - } - } - } - } - } - } - } - - derData = data.left(dataStream.device()->pos()); - null = false; - -#endif // QT_NO_OPENSSL - return true; -} - bool QSslCertificatePrivate::isBlacklisted(const QSslCertificate &certificate) { for (int a = 0; certificate_blacklist[a] != 0; a++) { diff --git a/src/network/ssl/qsslcertificate_p.h b/src/network/ssl/qsslcertificate_p.h index 472553c30c..b0c99e545d 100644 --- a/src/network/ssl/qsslcertificate_p.h +++ b/src/network/ssl/qsslcertificate_p.h @@ -109,13 +109,16 @@ public: QSsl::KeyAlgorithm publicKeyAlgorithm; QByteArray publicKeyDerData; QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames; + QList<QSslCertificateExtension> extensions; QByteArray derData; + + bool parse(const QByteArray &data); + bool parseExtension(const QByteArray &data, QSslCertificateExtension *extension); #endif X509 *x509; void init(const QByteArray &data, QSsl::EncodingFormat format); - bool parse(const QByteArray &data); static QByteArray asn1ObjectId(ASN1_OBJECT *object); static QByteArray asn1ObjectName(ASN1_OBJECT *object); diff --git a/src/network/ssl/qsslcertificate_qt.cpp b/src/network/ssl/qsslcertificate_qt.cpp index 391ee6f7f9..6ea78a408b 100644 --- a/src/network/ssl/qsslcertificate_qt.cpp +++ b/src/network/ssl/qsslcertificate_qt.cpp @@ -47,9 +47,17 @@ #include "qsslkey_p.h" #include "qsslcertificateextension.h" #include "qsslcertificateextension_p.h" +#include "qasn1element_p.h" QT_BEGIN_NAMESPACE +enum GeneralNameType +{ + Rfc822NameType = 0x81, + DnsNameType = 0x82, + UniformResourceIdentifierType = 0x86 +}; + bool QSslCertificate::operator==(const QSslCertificate &other) const { if (d == other.d) @@ -150,8 +158,7 @@ QSslKey QSslCertificate::publicKey() const QList<QSslCertificateExtension> QSslCertificate::extensions() const { - Q_UNIMPLEMENTED(); - return QList<QSslCertificateExtension>(); + return d->extensions; } #define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" @@ -263,4 +270,249 @@ QList<QSslCertificate> QSslCertificatePrivate::certificatesFromDer(const QByteAr return certificates; } +static QByteArray colonSeparatedHex(const QByteArray &value) +{ + QByteArray hexString; + hexString.reserve(value.size() * 3); + for (int a = 0; a < value.size(); ++a) { + const quint8 b = value.at(a); + if (b || !hexString.isEmpty()) { // skip leading zeros + hexString += QByteArray::number(b, 16).rightJustified(2, '0'); + hexString += ':'; + } + } + hexString.chop(1); + return hexString; +} + +bool QSslCertificatePrivate::parse(const QByteArray &data) +{ + QAsn1Element root; + + QDataStream dataStream(data); + if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) + return false; + + QDataStream rootStream(root.value()); + QAsn1Element cert; + if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) + return false; + + // version or serial number + QAsn1Element elem; + QDataStream certStream(cert.value()); + if (!elem.read(certStream)) + return false; + + if (elem.type() == QAsn1Element::Context0Type) { + QDataStream versionStream(elem.value()); + if (!elem.read(versionStream) || elem.type() != QAsn1Element::IntegerType) + return false; + + versionString = QByteArray::number(elem.value()[0] + 1); + if (!elem.read(certStream)) + return false; + } else { + versionString = QByteArray::number(1); + } + + // serial number + if (elem.type() != QAsn1Element::IntegerType) + return false; + serialNumberString = colonSeparatedHex(elem.value()); + + // algorithm ID + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + // issuer info + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); + issuerInfo = elem.toInfo(); + + // validity period + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QDataStream validityStream(elem.value()); + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidBefore = elem.toDateTime(); + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidAfter = elem.toDateTime(); + + // subject name + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); + subjectInfo = elem.toInfo(); + subjectMatchesIssuer = issuerDer == subjectDer; + + // public key + qint64 keyStart = certStream.device()->pos(); + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + publicKeyDerData.resize(certStream.device()->pos() - keyStart); + QDataStream keyStream(elem.value()); + if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + + // key algorithm + if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) + return false; + + const QByteArray oid = elem.toObjectId(); + if (oid == RSA_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Rsa; + else if (oid == RSA_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Dsa; + else + publicKeyAlgorithm = QSsl::Opaque; + + certStream.device()->seek(keyStart); + certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); + + // extensions + while (elem.read(certStream)) { + if (elem.type() == QAsn1Element::Context3Type) { + if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { + QDataStream extStream(elem.value()); + while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { + QSslCertificateExtension extension; + if (!parseExtension(elem.value(), &extension)) + return false; + extensions << extension; + + if (extension.oid() == QLatin1String("2.5.29.17")) { + // subjectAltName + QAsn1Element sanElem; + if (sanElem.read(extension.value().toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) { + QDataStream nameStream(sanElem.value()); + QAsn1Element nameElem; + while (nameElem.read(nameStream)) { + if (nameElem.type() == Rfc822NameType) { + subjectAlternativeNames.insert(QSsl::EmailEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); + } else if (nameElem.type() == DnsNameType) { + subjectAlternativeNames.insert(QSsl::DnsEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); + } + } + } + } + } + } + } + } + + derData = data.left(dataStream.device()->pos()); + null = false; + return true; +} + +bool QSslCertificatePrivate::parseExtension(const QByteArray &data, QSslCertificateExtension *extension) +{ + bool ok; + bool critical = false; + QAsn1Element oidElem, valElem; + + QDataStream seqStream(data); + + // oid + if (!oidElem.read(seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType) + return false; + const QByteArray oid = oidElem.toObjectId(); + + // critical and value + if (!valElem.read(seqStream)) + return false; + if (valElem.type() == QAsn1Element::BooleanType) { + critical = valElem.toBool(&ok); + if (!ok || !valElem.read(seqStream)) + return false; + } + if (valElem.type() != QAsn1Element::OctetStringType) + return false; + + // interpret value + QAsn1Element val; + bool supported = true; + QVariant value; + if (oid == QByteArrayLiteral("1.3.6.1.5.5.7.1.1")) { + // authorityInfoAccess + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + QVariantMap result; + foreach (const QAsn1Element &el, val.toVector()) { + QVector<QAsn1Element> items = el.toVector(); + if (items.size() != 2) + return false; + const QString key = QString::fromLatin1(items.at(0).toObjectName()); + switch (items.at(1).type()) { + case Rfc822NameType: + case DnsNameType: + case UniformResourceIdentifierType: + result[key] = QString::fromLatin1(items.at(1).value(), items.at(1).value().size()); + break; + } + } + value = result; + } else if (oid == QByteArrayLiteral("2.5.29.14")) { + // subjectKeyIdentifier + if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType) + return false; + value = colonSeparatedHex(val.value()).toUpper(); + } else if (oid == QByteArrayLiteral("2.5.29.19")) { + // basicConstraints + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + + QVariantMap result; + QVector<QAsn1Element> items = val.toVector(); + if (items.size() > 0) { + result[QStringLiteral("ca")] = items.at(0).toBool(&ok); + if (!ok) + return false; + } else { + result[QStringLiteral("ca")] = false; + } + if (items.size() > 1) { + result[QStringLiteral("pathLenConstraint")] = items.at(1).toInteger(&ok); + if (!ok) + return false; + } + value = result; + } else if (oid == QByteArrayLiteral("2.5.29.35")) { + // authorityKeyIdentifier + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + QVariantMap result; + foreach (const QAsn1Element &el, val.toVector()) { + if (el.type() == 0x80) { + result[QStringLiteral("keyid")] = el.value().toHex(); + } else if (el.type() == 0x82) { + result[QStringLiteral("serial")] = colonSeparatedHex(el.value()); + } + } + value = result; + } else { + supported = false; + value = valElem.value(); + } + + extension->d->critical = critical; + extension->d->supported = supported; + extension->d->oid = QString::fromLatin1(oid); + extension->d->name = QString::fromLatin1(oidElem.toObjectName()); + extension->d->value = value; + + return true; +} + QT_END_NAMESPACE diff --git a/src/network/ssl/qsslkey_qt.cpp b/src/network/ssl/qsslkey_qt.cpp index c14cf0250c..bc1ebc3c08 100644 --- a/src/network/ssl/qsslkey_qt.cpp +++ b/src/network/ssl/qsslkey_qt.cpp @@ -133,7 +133,7 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) if (infoItems.size() < 2 || infoItems[0].type() != QAsn1Element::ObjectIdentifierType) return; if (algorithm == QSsl::Rsa) { - if (infoItems[0].toObjectId() != "1.2.840.113549.1.1.1") + if (infoItems[0].toObjectId() != RSA_ENCRYPTION_OID) return; // key data if (!elem.read(keyStream) || elem.type() != QAsn1Element::BitStringType || elem.value().isEmpty()) @@ -144,7 +144,7 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) return; keyLength = numberOfBits(elem.value()); } else if (algorithm == QSsl::Dsa) { - if (infoItems[0].toObjectId() != "1.2.840.10040.4.1") + if (infoItems[0].toObjectId() != DSA_ENCRYPTION_OID) return; if (infoItems[1].type() != QAsn1Element::SequenceType) return; diff --git a/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp b/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp index 56530acb14..229ce4abb5 100644 --- a/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp +++ b/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp @@ -928,7 +928,7 @@ void tst_QSslCertificate::toText() QString txtcert = cert.toText(); -#ifdef Q_OS_WINRT +#ifdef QT_NO_OPENSSL QEXPECT_FAIL("", "QTBUG-40884: QSslCertificate::toText is not implemented on WinRT", Continue); #endif QVERIFY(QString::fromLatin1(txt098) == txtcert || @@ -976,7 +976,7 @@ void tst_QSslCertificate::verify() qPrintable(QString("errors: %1").arg(toString(errors))) \ ) -#ifdef Q_OS_WINRT +#ifdef QT_NO_OPENSSL QEXPECT_FAIL("", "QTBUG-40884: WinRT API does not yet support verifying a chain", Abort); #endif // Empty chain is unspecified error @@ -1060,9 +1060,6 @@ void tst_QSslCertificate::extensions() QSslCertificate cert = certList[0]; QList<QSslCertificateExtension> extensions = cert.extensions(); -#ifdef Q_OS_WINRT - QEXPECT_FAIL("", "QTBUG-40884: WinRT API does not support extensions information", Abort); -#endif QVERIFY(extensions.count() == 9); int unknown_idx = -1; @@ -1161,9 +1158,6 @@ void tst_QSslCertificate::extensionsCritical() QSslCertificate cert = certList[0]; QList<QSslCertificateExtension> extensions = cert.extensions(); -#ifdef Q_OS_WINRT - QEXPECT_FAIL("", "QTBUG-40884: WinRT API does not support extensions information", Abort); -#endif QVERIFY(extensions.count() == 9); int basic_constraints_idx = -1; @@ -1314,7 +1308,7 @@ void tst_QSslCertificate::pkcs12() QSslCertificate cert; QList<QSslCertificate> caCerts; -#ifdef Q_OS_WINRT +#ifdef QT_NO_OPENSSL QEXPECT_FAIL("", "QTBUG-40884: WinRT API does not support pkcs12 imports", Abort); #endif ok = QSslCertificate::importPKCS12(&f, &key, &cert, &caCerts); |