/**************************************************************************** ** ** 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 #include #include 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 QSslCertificate::subjectInfoAttributes() const { return d->subjectInfo.uniqueKeys(); } QList QSslCertificate::issuerInfoAttributes() const { return d->issuerInfo.uniqueKeys(); } QMultiMap 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 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 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 QSslCertificatePrivate::certificatesFromPem(const QByteArray &pem, int count) { QList 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 QSslCertificatePrivate::certificatesFromDer(const QByteArray &der, int count) { QList 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(ipAddrValue.data()))); break; case 16: // IPv6 ipAddress = QHostAddress(reinterpret_cast(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 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 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