diff options
Diffstat (limited to 'src/plugins/tls/shared/qx509_generic.cpp')
-rw-r--r-- | src/plugins/tls/shared/qx509_generic.cpp | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/src/plugins/tls/shared/qx509_generic.cpp b/src/plugins/tls/shared/qx509_generic.cpp new file mode 100644 index 0000000000..5006db1a72 --- /dev/null +++ b/src/plugins/tls/shared/qx509_generic.cpp @@ -0,0 +1,432 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtNetwork/private/qsslcertificate_p.h> +#include <QtNetwork/private/qssl_p.h> + +#include "qasn1element_p.h" +#include "qx509_generic_p.h" + +#include <QtNetwork/qhostaddress.h> + +#include <QtCore/qendian.h> +#include <QtCore/qhash.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QTlsPrivate { + +namespace { + +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(':'); +} + +} // Unnamed namespace. + +bool X509CertificateGeneric::isEqual(const X509Certificate &rhs) const +{ + const auto &other = static_cast<const X509CertificateGeneric &>(rhs); + return derData == other.derData; +} + +bool X509CertificateGeneric::isSelfSigned() const +{ + if (null) + return false; + + return subjectMatchesIssuer; +} + +QMultiMap<QSsl::AlternativeNameEntryType, QString> X509CertificateGeneric::subjectAlternativeNames() const +{ + return saNames; +} + +#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" +#define ENDCERTSTRING "-----END CERTIFICATE-----" + +QByteArray X509CertificateGeneric::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 X509CertificateGeneric::toDer() const +{ + return derData; +} + +QString X509CertificateGeneric::toText() const +{ + Q_UNIMPLEMENTED(); + return {}; +} + +Qt::HANDLE X509CertificateGeneric::handle() const +{ + Q_UNIMPLEMENTED(); + return nullptr; +} + +size_t X509CertificateGeneric::hash(size_t seed) const noexcept +{ + return qHash(toDer(), seed); +} + +QList<QSslCertificate> X509CertificateGeneric::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> X509CertificateGeneric::certificatesFromDer(const QByteArray &der, int count) +{ + QList<QSslCertificate> certificates; + + QByteArray data = der; + while (count == -1 || certificates.size() < count) { + QSslCertificate cert; + auto *certBackend = QTlsBackend::backend<X509CertificateGeneric>(cert); + if (!certBackend->parse(data)) + break; + + certificates << cert; + data.remove(0, certBackend->derData.size()); + } + + return certificates; +} + +bool X509CertificateGeneric::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 + || elem.value().isEmpty()) + 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().size(), elem.value().size()); + issuerInfoEntries = 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 (!notValidBefore.isValid()) + return false; + + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidAfter = elem.toDateTime(); + if (!notValidAfter.isValid()) + return false; + + + // subject name + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().size(), elem.value().size()); + subjectInfoEntries = 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) { + X509CertificateExtension extension; + if (!parseExtension(elem.value(), extension)) + return false; + + if (extension.oid == "2.5.29.17"_L1) { + // subjectAltName + + // Note, parseExtension() returns true for this extensions, + // but considers it to be unsupported and assigns a useless + // value. OpenSSL also treats this extension as unsupported, + // but properly creates a map with 'name' and 'value' taken + // from the extension. We only support 'email', 'IP' and 'DNS', + // but this is what our subjectAlternativeNames map can contain + // anyway. + QVariantMap extValue; + 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: + saNames.insert(QSsl::EmailEntry, nameElem.toString()); + extValue[QStringLiteral("email")] = nameElem.toString(); + break; + case QAsn1Element::DnsNameType: + saNames.insert(QSsl::DnsEntry, nameElem.toString()); + extValue[QStringLiteral("DNS")] = nameElem.toString(); + break; + case QAsn1Element::IpAddressType: { + QHostAddress ipAddress; + QByteArray ipAddrValue = nameElem.value(); + switch (ipAddrValue.size()) { + 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()) { + saNames.insert(QSsl::IpAddressEntry, ipAddress.toString()); + extValue[QStringLiteral("IP")] = ipAddress.toString(); + } + break; + } + default: + break; + } + } + extension.value = extValue; + extension.supported = true; + } + } + + extensions << extension; + } + } + } + } + + derData = data.left(dataStream.device()->pos()); + null = false; + return true; +} + +bool X509CertificateGeneric::parseExtension(const QByteArray &data, X509CertificateExtension &extension) +{ + bool ok = false; + 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.toList(); + for (const QAsn1Element &el : elems) { + const auto items = el.toList(); + 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; + const auto items = val.toList(); + 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.toList(); + 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.critical = critical; + extension.supported = supported; + extension.oid = QString::fromLatin1(oid); + extension.name = QString::fromLatin1(oidElem.toObjectName()); + extension.value = value; + + return true; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE |