diff options
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/ssl/qasn1element.cpp | 291 | ||||
-rw-r--r-- | src/network/ssl/qasn1element_p.h | 116 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate.cpp | 150 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate_p.h | 9 | ||||
-rw-r--r-- | src/network/ssl/ssl.pri | 6 |
5 files changed, 570 insertions, 2 deletions
diff --git a/src/network/ssl/qasn1element.cpp b/src/network/ssl/qasn1element.cpp new file mode 100644 index 0000000000..d282a02827 --- /dev/null +++ b/src/network/ssl/qasn1element.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qasn1element_p.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qvector.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +typedef QMap<QByteArray, QByteArray> OidNameMap; +static OidNameMap createOidMap() +{ + OidNameMap oids; + // 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("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")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.4.9"), QByteArrayLiteral("street")); + return oids; +} +Q_GLOBAL_STATIC_WITH_ARGS(OidNameMap, oidNameMap, (createOidMap())) + +QAsn1Element::QAsn1Element(quint8 type, const QByteArray &value) + : mType(type) + , mValue(value) +{ +} + +bool QAsn1Element::read(QDataStream &stream) +{ + // type + quint8 tmpType; + stream >> tmpType; + if (!tmpType) + return false; + + // length + qint64 length = 0; + quint8 first; + stream >> first; + if (first & 0x80) { + // long form + const quint8 bytes = (first & 0x7f); + if (bytes > 7) + return false; + + quint8 b; + for (int i = 0; i < bytes; i++) { + stream >> b; + length = (length << 8) | b; + } + } else { + // short form + length = (first & 0x7f); + } + + // value + QByteArray tmpValue; + tmpValue.resize(length); + int count = stream.readRawData(tmpValue.data(), tmpValue.size()); + if (count != length) + return false; + + mType = tmpType; + mValue.swap(tmpValue); + return true; +} + +bool QAsn1Element::read(const QByteArray &data) +{ + QDataStream stream(data); + return read(stream); +} + +void QAsn1Element::write(QDataStream &stream) const +{ + // type + stream << mType; + + // length + qint64 length = mValue.size(); + if (length >= 128) { + // long form + quint8 encodedLength = 0x80; + QByteArray ba; + while (length) { + ba.prepend(quint8((length & 0xff))); + length >>= 8; + encodedLength += 1; + } + stream << encodedLength; + stream.writeRawData(ba.data(), ba.size()); + } else { + // short form + stream << quint8(length); + } + + // value + stream.writeRawData(mValue.data(), mValue.size()); +} + +QAsn1Element QAsn1Element::fromInteger(unsigned int val) +{ + QAsn1Element elem(QAsn1Element::IntegerType); + while (val > 127) { + elem.mValue.prepend(val & 0xff); + val >>= 8; + } + elem.mValue.prepend(val & 0x7f); + return elem; +} + +QAsn1Element QAsn1Element::fromVector(const QVector<QAsn1Element> &items) +{ + QAsn1Element seq; + seq.mType = SequenceType; + QDataStream stream(&seq.mValue, QIODevice::WriteOnly); + for (QVector<QAsn1Element>::const_iterator it = items.cbegin(), end = items.cend(); it != end; ++it) + it->write(stream); + return seq; +} + +QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id) +{ + QAsn1Element elem; + elem.mType = ObjectIdentifierType; + QList<QByteArray> bits = id.split('.'); + Q_ASSERT(bits.size() > 2); + elem.mValue += quint8((bits[0].toUInt() * 40 + bits[1].toUInt())); + for (int i = 2; i < bits.size(); ++i) { + char buffer[std::numeric_limits<unsigned int>::digits / 7 + 2]; + char *pBuffer = buffer + sizeof(buffer); + *--pBuffer = '\0'; + unsigned int node = bits[i].toUInt(); + *--pBuffer = quint8((node & 0x7f)); + node >>= 7; + while (node) { + *--pBuffer = quint8(((node & 0x7f) | 0x80)); + node >>= 7; + } + elem.mValue += pBuffer; + } + return elem; +} + +QDateTime QAsn1Element::toDateTime() const +{ + if (mValue.endsWith('Z')) { + if (mType == UtcTimeType && mValue.size() == 13) + return QDateTime(QDate(2000 + mValue.mid(0, 2).toInt(), + mValue.mid(2, 2).toInt(), + mValue.mid(4, 2).toInt()), + QTime(mValue.mid(6, 2).toInt(), + mValue.mid(8, 2).toInt(), + mValue.mid(10, 2).toInt()), + Qt::UTC); + else if (mType == GeneralizedTimeType && mValue.size() == 15) + return QDateTime(QDate(mValue.mid(0, 4).toInt(), + mValue.mid(4, 2).toInt(), + mValue.mid(6, 2).toInt()), + QTime(mValue.mid(8, 2).toInt(), + mValue.mid(10, 2).toInt(), + mValue.mid(12, 2).toInt()), + Qt::UTC); + } + return QDateTime(); +} + +QMultiMap<QByteArray, QString> QAsn1Element::toInfo() const +{ + QMultiMap<QByteArray, QString> info; + QAsn1Element elem; + QDataStream issuerStream(mValue); + while (elem.read(issuerStream) && elem.mType == QAsn1Element::SetType) { + QAsn1Element issuerElem; + QDataStream setStream(elem.mValue); + if (issuerElem.read(setStream) && issuerElem.mType == QAsn1Element::SequenceType) { + QVector<QAsn1Element> elems = issuerElem.toVector(); + if (elems.size() == 2) { + const QByteArray key = elems.front().toObjectName(); + if (!key.isEmpty()) + info.insert(key, elems.back().toString()); + } + } + } + return info; +} + +QVector<QAsn1Element> QAsn1Element::toVector() const +{ + QVector<QAsn1Element> items; + if (mType == SequenceType) { + QAsn1Element elem; + QDataStream stream(mValue); + while (elem.read(stream)) + items << elem; + } + return items; +} + +QByteArray QAsn1Element::toObjectId() const +{ + QByteArray key; + if (mType == ObjectIdentifierType && !mValue.isEmpty()) { + quint8 b = mValue[0]; + key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40); + unsigned int val = 0; + for (int i = 1; i < mValue.size(); ++i) { + b = mValue[i]; + val = (val << 7) | (b & 0x7f); + if (!(b & 0x80)) { + key += '.' + QByteArray::number(val); + val = 0; + } + } + } + return key; +} + +QByteArray QAsn1Element::toObjectName() const +{ + QByteArray key = toObjectId(); + return oidNameMap->value(key, key); +} + +QString QAsn1Element::toString() const +{ + if (mType == PrintableStringType || mType == TeletexStringType) + return QString::fromLatin1(mValue, mValue.size()); + if (mType == Utf8StringType) + return QString::fromUtf8(mValue, mValue.size()); + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qasn1element_p.h b/src/network/ssl/qasn1element_p.h new file mode 100644 index 0000000000..6b3179ac35 --- /dev/null +++ b/src/network/ssl/qasn1element_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QASN1ELEMENT_P_H +#define QASN1ELEMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qdatetime.h> +#include <QtCore/qmap.h> + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QAsn1Element +{ +public: + enum ElementType { + // universal + IntegerType = 0x02, + BitStringType = 0x03, + OctetStringType = 0x04, + NullType = 0x05, + ObjectIdentifierType = 0x06, + Utf8StringType = 0x0c, + PrintableStringType = 0x13, + TeletexStringType = 0x14, + UtcTimeType = 0x17, + GeneralizedTimeType = 0x18, + SequenceType = 0x30, + SetType = 0x31, + + // application + Rfc822NameType = 0x81, + DnsNameType = 0x82, + + // context specific + Context0Type = 0xA0, + Context3Type = 0xA3 + }; + + explicit QAsn1Element(quint8 type = 0, const QByteArray &value = QByteArray()); + bool read(QDataStream &data); + bool read(const QByteArray &data); + void write(QDataStream &data) const; + + static QAsn1Element fromInteger(unsigned int val); + static QAsn1Element fromVector(const QVector<QAsn1Element> &items); + static QAsn1Element fromObjectId(const QByteArray &id); + + QDateTime toDateTime() const; + QMultiMap<QByteArray, QString> toInfo() const; + QVector<QAsn1Element> toVector() const; + QByteArray toObjectId() const; + QByteArray toObjectName() const; + QString toString() const; + + quint8 type() const { return mType; } + QByteArray value() const { return mValue; } + +private: + quint8 mType; + QByteArray mValue; +}; +Q_DECLARE_TYPEINFO(QAsn1Element, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp index 47ea3343ea..bae78f4347 100644 --- a/src/network/ssl/qsslcertificate.cpp +++ b/src/network/ssl/qsslcertificate.cpp @@ -122,6 +122,7 @@ #include "qsslcertificate.h" #include "qsslcertificate_p.h" +#include "qasn1element_p.h" #include "qsslkey_p.h" #include <QtCore/qdir.h> @@ -641,6 +642,155 @@ 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 4bee9edcb9..0eeff0db41 100644 --- a/src/network/ssl/qsslcertificate_p.h +++ b/src/network/ssl/qsslcertificate_p.h @@ -99,9 +99,18 @@ public: QDateTime notValidAfter; QDateTime notValidBefore; +#ifdef QT_NO_OPENSSL + bool subjectMatchesIssuer; + QSsl::KeyAlgorithm publicKeyAlgorithm; + QByteArray publicKeyDerData; + QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames; + + QByteArray derData; +#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/ssl.pri b/src/network/ssl/ssl.pri index 0fbeb1d369..f7dceeb579 100644 --- a/src/network/ssl/ssl.pri +++ b/src/network/ssl/ssl.pri @@ -1,6 +1,7 @@ # OpenSSL support; compile in QSslSocket. contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { - HEADERS += ssl/qssl.h \ + HEADERS += ssl/qasn1element_p.h \ + ssl/qssl.h \ ssl/qsslcertificate.h \ ssl/qsslcertificate_p.h \ ssl/qsslconfiguration.h \ @@ -14,7 +15,8 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op ssl/qsslsocket_p.h \ ssl/qsslcertificateextension.h \ ssl/qsslcertificateextension_p.h - SOURCES += ssl/qssl.cpp \ + SOURCES += ssl/qasn1element.cpp \ + ssl/qssl.cpp \ ssl/qsslcertificate.cpp \ ssl/qsslconfiguration.cpp \ ssl/qsslcipher.cpp \ |