| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** 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 The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/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 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qsslcertificate.h" |
| #include "qsslcertificate_p.h" |
| |
| #include "qssl_p.h" |
| #ifndef QT_NO_SSL |
| #include "qsslkey.h" |
| #include "qsslkey_p.h" |
| #endif |
| #include "qsslcertificateextension.h" |
| #include "qsslcertificateextension_p.h" |
| #include "qasn1element_p.h" |
| |
| #include <QtCore/qdatastream.h> |
| #include <QtCore/qendian.h> |
| #include <QtNetwork/qhostaddress.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| bool QSslCertificate::operator==(const QSslCertificate &other) const |
| { |
| if (d == other.d) |
| return true; |
| if (d->null && other.d->null) |
| return true; |
| return d->derData == other.d->derData; |
| } |
| |
| uint qHash(const QSslCertificate &key, uint seed) noexcept |
| { |
| // DER is the native encoding here, so toDer() is just "return d->derData": |
| return qHash(key.toDer(), seed); |
| } |
| |
| bool QSslCertificate::isNull() const |
| { |
| return d->null; |
| } |
| |
| bool QSslCertificate::isSelfSigned() const |
| { |
| if (d->null) |
| return false; |
| |
| qCWarning(lcSsl, |
| "QSslCertificate::isSelfSigned: This function does not check, whether the certificate " |
| "is actually signed. It just checks whether issuer and subject are identical"); |
| return d->subjectMatchesIssuer; |
| } |
| |
| QByteArray QSslCertificate::version() const |
| { |
| return d->versionString; |
| } |
| |
| QByteArray QSslCertificate::serialNumber() const |
| { |
| return d->serialNumberString; |
| } |
| |
| QStringList QSslCertificate::issuerInfo(SubjectInfo info) const |
| { |
| return issuerInfo(QSslCertificatePrivate::subjectInfoToString(info)); |
| } |
| |
| QStringList QSslCertificate::issuerInfo(const QByteArray &attribute) const |
| { |
| return d->issuerInfo.values(attribute); |
| } |
| |
| QStringList QSslCertificate::subjectInfo(SubjectInfo info) const |
| { |
| return subjectInfo(QSslCertificatePrivate::subjectInfoToString(info)); |
| } |
| |
| QStringList QSslCertificate::subjectInfo(const QByteArray &attribute) const |
| { |
| return d->subjectInfo.values(attribute); |
| } |
| |
| QList<QByteArray> QSslCertificate::subjectInfoAttributes() const |
| { |
| return d->subjectInfo.uniqueKeys(); |
| } |
| |
| QList<QByteArray> QSslCertificate::issuerInfoAttributes() const |
| { |
| return d->issuerInfo.uniqueKeys(); |
| } |
| |
| QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlternativeNames() const |
| { |
| return d->subjectAlternativeNames; |
| } |
| |
| QDateTime QSslCertificate::effectiveDate() const |
| { |
| return d->notValidBefore; |
| } |
| |
| QDateTime QSslCertificate::expiryDate() const |
| { |
| return d->notValidAfter; |
| } |
| |
| #if !defined(Q_OS_WINRT) && !QT_CONFIG(schannel) // implemented in qsslcertificate_{winrt,schannel}.cpp |
| Qt::HANDLE QSslCertificate::handle() const |
| { |
| Q_UNIMPLEMENTED(); |
| return nullptr; |
| } |
| #endif |
| |
| #ifndef QT_NO_SSL |
| QSslKey QSslCertificate::publicKey() const |
| { |
| QSslKey key; |
| key.d->type = QSsl::PublicKey; |
| if (d->publicKeyAlgorithm != QSsl::Opaque) { |
| key.d->algorithm = d->publicKeyAlgorithm; |
| key.d->decodeDer(d->publicKeyDerData); |
| } |
| return key; |
| } |
| #endif |
| |
| QList<QSslCertificateExtension> QSslCertificate::extensions() const |
| { |
| return d->extensions; |
| } |
| |
| #define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" |
| #define ENDCERTSTRING "-----END CERTIFICATE-----" |
| |
| QByteArray QSslCertificate::toPem() const |
| { |
| QByteArray array = toDer(); |
| |
| // Convert to Base64 - wrap at 64 characters. |
| array = array.toBase64(); |
| QByteArray tmp; |
| for (int i = 0; i <= array.size() - 64; i += 64) { |
| tmp += QByteArray::fromRawData(array.data() + i, 64); |
| tmp += '\n'; |
| } |
| if (int remainder = array.size() % 64) { |
| tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); |
| tmp += '\n'; |
| } |
| |
| return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; |
| } |
| |
| QByteArray QSslCertificate::toDer() const |
| { |
| return d->derData; |
| } |
| |
| QString QSslCertificate::toText() const |
| { |
| Q_UNIMPLEMENTED(); |
| return QString(); |
| } |
| |
| void QSslCertificatePrivate::init(const QByteArray &data, QSsl::EncodingFormat format) |
| { |
| if (!data.isEmpty()) { |
| const QList<QSslCertificate> certs = (format == QSsl::Pem) |
| ? certificatesFromPem(data, 1) |
| : certificatesFromDer(data, 1); |
| if (!certs.isEmpty()) { |
| *this = *certs.first().d; |
| #if QT_CONFIG(schannel) |
| if (certificateContext) |
| certificateContext = CertDuplicateCertificateContext(certificateContext); |
| #endif |
| } |
| } |
| } |
| |
| static bool matchLineFeed(const QByteArray &pem, int *offset) |
| { |
| char ch = 0; |
| |
| // ignore extra whitespace at the end of the line |
| while (*offset < pem.size() && (ch = pem.at(*offset)) == ' ') |
| ++*offset; |
| |
| if (ch == '\n') { |
| *offset += 1; |
| return true; |
| } |
| if (ch == '\r' && pem.size() > (*offset + 1) && pem.at(*offset + 1) == '\n') { |
| *offset += 2; |
| return true; |
| } |
| return false; |
| } |
| |
| QList<QSslCertificate> QSslCertificatePrivate::certificatesFromPem(const QByteArray &pem, int count) |
| { |
| QList<QSslCertificate> certificates; |
| int offset = 0; |
| while (count == -1 || certificates.size() < count) { |
| int startPos = pem.indexOf(BEGINCERTSTRING, offset); |
| if (startPos == -1) |
| break; |
| startPos += sizeof(BEGINCERTSTRING) - 1; |
| if (!matchLineFeed(pem, &startPos)) |
| break; |
| |
| int endPos = pem.indexOf(ENDCERTSTRING, startPos); |
| if (endPos == -1) |
| break; |
| |
| offset = endPos + sizeof(ENDCERTSTRING) - 1; |
| if (offset < pem.size() && !matchLineFeed(pem, &offset)) |
| break; |
| |
| QByteArray decoded = QByteArray::fromBase64( |
| QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); |
| certificates << certificatesFromDer(decoded, 1);; |
| } |
| |
| return certificates; |
| } |
| |
| QList<QSslCertificate> QSslCertificatePrivate::certificatesFromDer(const QByteArray &der, int count) |
| { |
| QList<QSslCertificate> certificates; |
| |
| QByteArray data = der; |
| while (count == -1 || certificates.size() < count) { |
| QSslCertificate cert; |
| if (!cert.d->parse(data)) |
| break; |
| |
| certificates << cert; |
| data.remove(0, cert.d->derData.size()); |
| } |
| |
| return certificates; |
| } |
| |
| static QByteArray colonSeparatedHex(const QByteArray &value) |
| { |
| const int size = value.size(); |
| int i = 0; |
| while (i < size && !value.at(i)) // skip leading zeros |
| ++i; |
| |
| return value.mid(i).toHex(':'); |
| } |
| |
| 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().at(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 == DSA_ENCRYPTION_OID) |
| publicKeyAlgorithm = QSsl::Dsa; |
| else if (oid == EC_ENCRYPTION_OID) |
| publicKeyAlgorithm = QSsl::Ec; |
| 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)) { |
| switch (nameElem.type()) { |
| case QAsn1Element::Rfc822NameType: |
| subjectAlternativeNames.insert(QSsl::EmailEntry, nameElem.toString()); |
| 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; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| 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 == "1.3.6.1.5.5.7.1.1") { |
| // authorityInfoAccess |
| if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) |
| return false; |
| QVariantMap result; |
| const auto elems = val.toVector(); |
| for (const QAsn1Element &el : elems) { |
| 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 QAsn1Element::Rfc822NameType: |
| case QAsn1Element::DnsNameType: |
| case QAsn1Element::UniformResourceIdentifierType: |
| result[key] = items.at(1).toString(); |
| break; |
| } |
| } |
| value = result; |
| } else if (oid == "2.5.29.14") { |
| // subjectKeyIdentifier |
| if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType) |
| return false; |
| value = colonSeparatedHex(val.value()).toUpper(); |
| } else if (oid == "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 == "2.5.29.35") { |
| // authorityKeyIdentifier |
| if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) |
| return false; |
| QVariantMap result; |
| const auto elems = val.toVector(); |
| for (const QAsn1Element &el : elems) { |
| if (el.type() == 0x80) { |
| const QString key = QStringLiteral("keyid"); |
| result[key] = el.value().toHex(); |
| } else if (el.type() == 0x82) { |
| const QString serial = QStringLiteral("serial"); |
| result[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 |