diff options
Diffstat (limited to 'src/plugins/tls/shared')
-rw-r--r-- | src/plugins/tls/shared/qasn1element.cpp | 353 | ||||
-rw-r--r-- | src/plugins/tls/shared/qasn1element_p.h | 152 | ||||
-rw-r--r-- | src/plugins/tls/shared/qdtls_base.cpp | 79 | ||||
-rw-r--r-- | src/plugins/tls/shared/qdtls_base_p.h | 80 | ||||
-rw-r--r-- | src/plugins/tls/shared/qsslsocket_mac_shared.cpp | 168 | ||||
-rw-r--r-- | src/plugins/tls/shared/qsslsocket_qt.cpp | 271 | ||||
-rw-r--r-- | src/plugins/tls/shared/qtlskey_base.cpp | 97 | ||||
-rw-r--r-- | src/plugins/tls/shared/qtlskey_base_p.h | 72 | ||||
-rw-r--r-- | src/plugins/tls/shared/qtlskey_generic.cpp | 849 | ||||
-rw-r--r-- | src/plugins/tls/shared/qtlskey_generic_p.h | 83 | ||||
-rw-r--r-- | src/plugins/tls/shared/qwincrypt_p.h | 55 | ||||
-rw-r--r-- | src/plugins/tls/shared/qx509_base.cpp | 142 | ||||
-rw-r--r-- | src/plugins/tls/shared/qx509_base_p.h | 89 | ||||
-rw-r--r-- | src/plugins/tls/shared/qx509_generic.cpp | 432 | ||||
-rw-r--r-- | src/plugins/tls/shared/qx509_generic_p.h | 65 |
15 files changed, 2987 insertions, 0 deletions
diff --git a/src/plugins/tls/shared/qasn1element.cpp b/src/plugins/tls/shared/qasn1element.cpp new file mode 100644 index 0000000000..97be46866d --- /dev/null +++ b/src/plugins/tls/shared/qasn1element.cpp @@ -0,0 +1,353 @@ +// Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#include "qasn1element_p.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qtimezone.h> +#include <QtCore/qlist.h> +#include <QDebug> +#include <private/qtools_p.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +using namespace QtMiscUtils; + +typedef QMap<QByteArray, QByteArray> OidNameMap; +static OidNameMap createOidMap() +{ + OidNameMap oids; + // used by unit tests + oids.insert(oids.cend(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink")); + oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress")); + oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess")); + oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP")); + oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST")); + oids.insert(oids.cend(), 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 + quint64 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); + } + + if (length > quint64(std::numeric_limits<int>::max())) + return false; + + // read value in blocks to avoid being fooled by incorrect length + const int BUFFERSIZE = 4 * 1024; + QByteArray tmpValue; + int remainingLength = length; + while (remainingLength) { + char readBuffer[BUFFERSIZE]; + const int bytesToRead = qMin(remainingLength, BUFFERSIZE); + const int count = stream.readRawData(readBuffer, bytesToRead); + if (count != int(bytesToRead)) + return false; + tmpValue.append(readBuffer, bytesToRead); + remainingLength -= bytesToRead; + } + + 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::fromBool(bool val) +{ + return QAsn1Element(QAsn1Element::BooleanType, + QByteArray(1, val ? 0xff : 0x00)); +} + +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 QList<QAsn1Element> &items) +{ + QAsn1Element seq; + seq.mType = SequenceType; + QDataStream stream(&seq.mValue, QDataStream::WriteOnly); + for (auto 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; + const 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; +} + +bool QAsn1Element::toBool(bool *ok) const +{ + if (*this == fromBool(true)) { + if (ok) + *ok = true; + return true; + } else if (*this == fromBool(false)) { + if (ok) + *ok = true; + return false; + } else { + if (ok) + *ok = false; + return false; + } +} + +QDateTime QAsn1Element::toDateTime() const +{ + QDateTime result; + + if (mValue.size() != 13 && mValue.size() != 15) + return result; + + // QDateTime::fromString is lenient and accepts +- signs in front + // of the year; but ASN.1 doesn't allow them. + if (!isAsciiDigit(mValue[0])) + return result; + + // Timezone must be present, and UTC + if (mValue.back() != 'Z') + return result; + + if (mType == UtcTimeType && mValue.size() == 13) { + // RFC 2459: + // Where YY is greater than or equal to 50, the year shall be + // interpreted as 19YY; and + // + // Where YY is less than 50, the year shall be interpreted as 20YY. + // + // so use 1950 as base year. + constexpr int rfc2459CenturyStart = 1950; + const QLatin1StringView inputView(mValue); + QDate date = QDate::fromString(inputView.first(6), u"yyMMdd", rfc2459CenturyStart); + if (!date.isValid()) + return result; + + Q_ASSERT(date.year() >= rfc2459CenturyStart); + Q_ASSERT(date.year() < 100 + rfc2459CenturyStart); + + QTime time = QTime::fromString(inputView.sliced(6, 6), u"HHmmss"); + if (!time.isValid()) + return result; + result = QDateTime(date, time, QTimeZone::UTC); + } else if (mType == GeneralizedTimeType && mValue.size() == 15) { + result = QDateTime::fromString(QString::fromLatin1(mValue), u"yyyyMMddHHmmsst"); + } + + return result; +} + +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) { + const auto elems = issuerElem.toList(); + if (elems.size() == 2) { + const QByteArray key = elems.front().toObjectName(); + if (!key.isEmpty()) + info.insert(key, elems.back().toString()); + } + } + } + return info; +} + +qint64 QAsn1Element::toInteger(bool *ok) const +{ + if (mType != QAsn1Element::IntegerType || mValue.isEmpty()) { + if (ok) + *ok = false; + return 0; + } + + // NOTE: - negative numbers are not handled + // - greater sizes would overflow + if (mValue.at(0) & 0x80 || mValue.size() > 8) { + if (ok) + *ok = false; + return 0; + } + + qint64 value = mValue.at(0) & 0x7f; + for (int i = 1; i < mValue.size(); ++i) + value = (value << 8) | quint8(mValue.at(i)); + + if (ok) + *ok = true; + return value; +} + +QList<QAsn1Element> QAsn1Element::toList() const +{ + QList<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.at(0); + key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40); + unsigned int val = 0; + for (int i = 1; i < mValue.size(); ++i) { + b = mValue.at(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 +{ + // Detect embedded NULs and reject + if (qstrlen(mValue) < uint(mValue.size())) + return QString(); + + if (mType == PrintableStringType || mType == TeletexStringType + || mType == Rfc822NameType || mType == DnsNameType + || mType == UniformResourceIdentifierType) + return QString::fromLatin1(mValue, mValue.size()); + if (mType == Utf8StringType) + return QString::fromUtf8(mValue, mValue.size()); + + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qasn1element_p.h b/src/plugins/tls/shared/qasn1element_p.h new file mode 100644 index 0000000000..0de46be009 --- /dev/null +++ b/src/plugins/tls/shared/qasn1element_p.h @@ -0,0 +1,152 @@ +// Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#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 purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qmap.h> + +QT_BEGIN_NAMESPACE + +// General +#define RSADSI_OID "1.2.840.113549." + +#define RSA_ENCRYPTION_OID QByteArrayLiteral(RSADSI_OID "1.1.1") +#define DSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10040.4.1") +#define EC_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10045.2.1") +#define DH_ENCRYPTION_OID QByteArrayLiteral(RSADSI_OID "1.3.1") + +// These are mostly from the RFC for PKCS#5 +// PKCS#5: https://tools.ietf.org/html/rfc8018#appendix-B +#define PKCS5_OID RSADSI_OID "1.5." +// PKCS#12: https://tools.ietf.org/html/rfc7292#appendix-D) +#define PKCS12_OID RSADSI_OID "1.12." + +// -PBES1 +#define PKCS5_MD2_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "1") // Not (yet) implemented +#define PKCS5_MD2_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "4") // Not (yet) implemented +#define PKCS5_MD5_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "3") +#define PKCS5_MD5_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "6") +#define PKCS5_SHA1_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "10") +#define PKCS5_SHA1_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "11") +#define PKCS12_SHA1_RC4_128_OID QByteArrayLiteral(PKCS12_OID "1.1") // Not (yet) implemented +#define PKCS12_SHA1_RC4_40_OID QByteArrayLiteral(PKCS12_OID "1.2") // Not (yet) implemented +#define PKCS12_SHA1_3KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.3") +#define PKCS12_SHA1_2KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.4") +#define PKCS12_SHA1_RC2_128_CBC_OID QByteArrayLiteral(PKCS12_OID "1.5") +#define PKCS12_SHA1_RC2_40_CBC_OID QByteArrayLiteral(PKCS12_OID "1.6") + +// -PBKDF2 +#define PKCS5_PBKDF2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "12") + +// -PBES2 +#define PKCS5_PBES2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "13") + +// Digest +#define DIGEST_ALGORITHM_OID RSADSI_OID "2." +// -HMAC-SHA-1 +#define HMAC_WITH_SHA1 QByteArrayLiteral(DIGEST_ALGORITHM_OID "7") +// -HMAC-SHA-2 +#define HMAC_WITH_SHA224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "8") +#define HMAC_WITH_SHA256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "9") +#define HMAC_WITH_SHA384 QByteArrayLiteral(DIGEST_ALGORITHM_OID "10") +#define HMAC_WITH_SHA512 QByteArrayLiteral(DIGEST_ALGORITHM_OID "11") +#define HMAC_WITH_SHA512_224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "12") +#define HMAC_WITH_SHA512_256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "13") + +// Encryption algorithms +#define ENCRYPTION_ALGORITHM_OID RSADSI_OID "3." +#define DES_CBC_ENCRYPTION_OID QByteArrayLiteral("1.3.14.3.2.7") +#define DES_EDE3_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "7") +#define RC2_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "2") +#define RC5_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "9") // Not (yet) implemented +#define AES_OID "2.16.840.1.101.3.4.1." +#define AES128_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "2") +#define AES192_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "22") // Not (yet) implemented +#define AES256_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "42") // Not (yet) implemented + +class QAsn1Element +{ +public: + enum ElementType { + // universal + BooleanType = 0x01, + IntegerType = 0x02, + BitStringType = 0x03, + OctetStringType = 0x04, + NullType = 0x05, + ObjectIdentifierType = 0x06, + Utf8StringType = 0x0c, + PrintableStringType = 0x13, + TeletexStringType = 0x14, + UtcTimeType = 0x17, + GeneralizedTimeType = 0x18, + SequenceType = 0x30, + SetType = 0x31, + + // GeneralNameTypes + Rfc822NameType = 0x81, + DnsNameType = 0x82, + UniformResourceIdentifierType = 0x86, + IpAddressType = 0x87, + + // context specific + Context0Type = 0xA0, + Context1Type = 0xA1, + 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 fromBool(bool val); + static QAsn1Element fromInteger(unsigned int val); + static QAsn1Element fromVector(const QList<QAsn1Element> &items); + static QAsn1Element fromObjectId(const QByteArray &id); + + bool toBool(bool *ok = nullptr) const; + QDateTime toDateTime() const; + QMultiMap<QByteArray, QString> toInfo() const; + qint64 toInteger(bool *ok = nullptr) const; + QList<QAsn1Element> toList() const; + QByteArray toObjectId() const; + QByteArray toObjectName() const; + QString toString() const; + + quint8 type() const { return mType; } + QByteArray value() const { return mValue; } + + friend inline bool operator==(const QAsn1Element &, const QAsn1Element &); + friend inline bool operator!=(const QAsn1Element &, const QAsn1Element &); + +private: + quint8 mType; + QByteArray mValue; +}; +Q_DECLARE_TYPEINFO(QAsn1Element, Q_RELOCATABLE_TYPE); + +inline bool operator==(const QAsn1Element &e1, const QAsn1Element &e2) +{ return e1.mType == e2.mType && e1.mValue == e2.mValue; } + +inline bool operator!=(const QAsn1Element &e1, const QAsn1Element &e2) +{ return e1.mType != e2.mType || e1.mValue != e2.mValue; } + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tls/shared/qdtls_base.cpp b/src/plugins/tls/shared/qdtls_base.cpp new file mode 100644 index 0000000000..19131e5497 --- /dev/null +++ b/src/plugins/tls/shared/qdtls_base.cpp @@ -0,0 +1,79 @@ +// 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 "qdtls_base_p.h" + +QT_BEGIN_NAMESPACE + +void QDtlsBasePrivate::setDtlsError(QDtlsError code, const QString &description) +{ + errorCode = code; + errorDescription = description; +} + +QDtlsError QDtlsBasePrivate::error() const +{ + return errorCode; +} + +QString QDtlsBasePrivate::errorString() const +{ + return errorDescription; +} + +void QDtlsBasePrivate::clearDtlsError() +{ + errorCode = QDtlsError::NoError; + errorDescription.clear(); +} + +QSslConfiguration QDtlsBasePrivate::configuration() const +{ + return dtlsConfiguration; +} + +void QDtlsBasePrivate::setConfiguration(const QSslConfiguration &configuration) +{ + dtlsConfiguration = configuration; + clearDtlsError(); +} + +bool QDtlsBasePrivate::setCookieGeneratorParameters(const GenParams ¶ms) +{ + if (!params.secret.size()) { + setDtlsError(QDtlsError::InvalidInputParameters, + QDtls::tr("Invalid (empty) secret")); + return false; + } + + clearDtlsError(); + + hashAlgorithm = params.hash; + secret = params.secret; + + return true; +} + +QDtlsClientVerifier::GeneratorParameters +QDtlsBasePrivate::cookieGeneratorParameters() const +{ + return {hashAlgorithm, secret}; +} + +bool QDtlsBasePrivate::isDtlsProtocol(QSsl::SslProtocol protocol) +{ + switch (protocol) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case QSsl::DtlsV1_0: + case QSsl::DtlsV1_0OrLater: +QT_WARNING_POP + case QSsl::DtlsV1_2: + case QSsl::DtlsV1_2OrLater: + return true; + default: + return false; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qdtls_base_p.h b/src/plugins/tls/shared/qdtls_base_p.h new file mode 100644 index 0000000000..a8faad6a26 --- /dev/null +++ b/src/plugins/tls/shared/qdtls_base_p.h @@ -0,0 +1,80 @@ +// 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 + +#ifndef QDTLS_BASE_P_H +#define QDTLS_BASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +QT_REQUIRE_CONFIG(dtls); + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtNetwork/qsslconfiguration.h> +#include <QtNetwork/qsslcipher.h> +#include <QtNetwork/qsslsocket.h> +#include <QtNetwork/qssl.h> + +#include <QtNetwork/qhostaddress.h> + +#include <QtCore/qcryptographichash.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE + +// This class exists to re-implement the shared error/cookie handling +// for both QDtls and QDtlsClientVerifier classes. Use it if/when +// you need it. Backend neutral. +class QDtlsBasePrivate : virtual public QTlsPrivate::DtlsBase +{ +public: + QDtlsBasePrivate(QSslSocket::SslMode m, const QByteArray &s) : mode(m), secret(s) {} + void setDtlsError(QDtlsError code, const QString &description) override; + QDtlsError error() const override; + QString errorString() const override; + void clearDtlsError() override; + + void setConfiguration(const QSslConfiguration &configuration) override; + QSslConfiguration configuration() const override; + + bool setCookieGeneratorParameters(const GenParams &) override; + GenParams cookieGeneratorParameters() const override; + + static bool isDtlsProtocol(QSsl::SslProtocol protocol); + + QHostAddress remoteAddress; + quint16 remotePort = 0; + quint16 mtuHint = 0; + + QDtlsError errorCode = QDtlsError::NoError; + QString errorDescription; + QSslConfiguration dtlsConfiguration; + QSslSocket::SslMode mode = QSslSocket::SslClientMode; + QSslCipher sessionCipher; + QSsl::SslProtocol sessionProtocol = QSsl::UnknownProtocol; + QString peerVfyName; + QByteArray secret; + +#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; +#else + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; +#endif +}; + +QT_END_NAMESPACE + +#endif // QDTLS_BASE_P_H diff --git a/src/plugins/tls/shared/qsslsocket_mac_shared.cpp b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp new file mode 100644 index 0000000000..1257240ee2 --- /dev/null +++ b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2015 ownCloud Inc +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtNetwork/qsslcertificate.h> + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qglobal.h> +#include <QtCore/qdebug.h> + + +#ifdef Q_OS_MACOS + +#include <QtCore/private/qcore_mac_p.h> + +#include <CoreFoundation/CFArray.h> +#include <Security/Security.h> + +#endif // Q_OS_MACOS + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcX509, "qt.mac.shared.x509"); + +#ifdef Q_OS_MACOS +namespace { + +bool hasTrustedSslServerPolicy(SecPolicyRef policy, CFDictionaryRef props) { + QCFType<CFDictionaryRef> policyProps = SecPolicyCopyProperties(policy); + // only accept certificates with policies for SSL server validation for now + if (CFEqual(CFDictionaryGetValue(policyProps, kSecPolicyOid), kSecPolicyAppleSSL)) { + CFBooleanRef policyClient; + if (CFDictionaryGetValueIfPresent(policyProps, kSecPolicyClient, reinterpret_cast<const void**>(&policyClient)) && + CFEqual(policyClient, kCFBooleanTrue)) { + return false; // no client certs + } + if (!CFDictionaryContainsKey(props, kSecTrustSettingsResult)) { + // as per the docs, no trust settings result implies full trust + return true; + } + CFNumberRef number = static_cast<CFNumberRef>(CFDictionaryGetValue(props, kSecTrustSettingsResult)); + SecTrustSettingsResult settingsResult; + CFNumberGetValue(number, kCFNumberSInt32Type, &settingsResult); + switch (settingsResult) { + case kSecTrustSettingsResultTrustRoot: + case kSecTrustSettingsResultTrustAsRoot: + return true; + default: + return false; + } + } + return false; +} + +bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain) +{ + QCFType<CFArrayRef> cfTrustSettings; + OSStatus status = SecTrustSettingsCopyTrustSettings(cfCert, SecTrustSettingsDomain(domain), &cfTrustSettings); + if (status == noErr) { + CFIndex size = CFArrayGetCount(cfTrustSettings); + // if empty, trust for everything (as per the Security Framework documentation) + if (size == 0) { + return true; + } else { + for (CFIndex i = 0; i < size; ++i) { + CFDictionaryRef props = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(cfTrustSettings, i)); + if (CFDictionaryContainsKey(props, kSecTrustSettingsPolicy)) { + if (hasTrustedSslServerPolicy((SecPolicyRef)CFDictionaryGetValue(props, kSecTrustSettingsPolicy), props)) + return true; + } + } + } + } + + return false; +} + +bool canDERBeParsed(CFDataRef derData, const QSslCertificate &qtCert) +{ + // We are observing certificates, that while accepted when we copy them + // from the keychain(s), later give us 'Failed to create SslCertificate + // from QSslCertificate'. It's interesting to know at what step the failure + // occurred. Let's check it and skip it below if it's not valid. + + auto checkDer = [](CFDataRef derData, const char *source) + { + Q_ASSERT(source); + Q_ASSERT(derData); + + const auto cfLength = CFDataGetLength(derData); + if (cfLength <= 0) { + qCWarning(lcX509) << source << "returned faulty DER data with invalid length."; + return false; + } + + QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, derData); + if (!secRef) { + qCWarning(lcX509) << source << "returned faulty DER data which cannot be parsed back."; + return false; + } + return true; + }; + + if (!checkDer(derData, "SecCertificateCopyData")) { + qCDebug(lcX509) << "Faulty QSslCertificate is:" << qtCert;// Just in case we managed to parse something. + return false; + } + + // Generic parser failed? + if (qtCert.isNull()) { + qCWarning(lcX509, "QSslCertificate failed to parse DER"); + return false; + } + + const QCFType<CFDataRef> qtDerData = qtCert.toDer().toCFData(); + if (!checkDer(qtDerData, "QSslCertificate")) { + qCWarning(lcX509) << "Faulty QSslCertificate is:" << qtCert; + return false; + } + + return true; +} + +} // unnamed namespace +#endif // Q_OS_MACOS + +namespace QTlsPrivate { +QList<QSslCertificate> systemCaCertificates() +{ + QList<QSslCertificate> systemCerts; + // SecTrustSettingsCopyCertificates is not defined on iOS. +#ifdef Q_OS_MACOS + // iterate through all enum members, order: + // kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainSystem + for (int dom = kSecTrustSettingsDomainUser; dom <= int(kSecTrustSettingsDomainSystem); dom++) { + QCFType<CFArrayRef> cfCerts; + OSStatus status = SecTrustSettingsCopyCertificates(SecTrustSettingsDomain(dom), &cfCerts); + if (status == noErr) { + const CFIndex size = CFArrayGetCount(cfCerts); + for (CFIndex i = 0; i < size; ++i) { + SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i); + QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert); + if (isCaCertificateTrusted(cfCert, dom)) { + if (derData) { + const auto newCert = QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + if (!canDERBeParsed(derData, newCert)) { + // Last attempt to get some information about the certificate: + CFShow(cfCert); + continue; + } + systemCerts << newCert; + } else { + // "Returns NULL if the data passed in the certificate parameter + // is not a valid certificate object." + qCWarning(lcX509, "SecCertificateCopyData returned invalid DER data (nullptr)."); + } + } + } + } + } +#endif + return systemCerts; +} +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qsslsocket_qt.cpp b/src/plugins/tls/shared/qsslsocket_qt.cpp new file mode 100644 index 0000000000..f55b3e3ded --- /dev/null +++ b/src/plugins/tls/shared/qsslsocket_qt.cpp @@ -0,0 +1,271 @@ +// Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qasn1element_p.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qdatastream.h> +#include <QtCore/qmessageauthenticationcode.h> +#include <QtCore/qrandom.h> + +#include <QtNetwork/private/qsslsocket_p.h> +#include <QtNetwork/private/qsslkey_p.h> + +QT_BEGIN_NAMESPACE + +/* + PKCS12 helpers. +*/ + +static QAsn1Element wrap(quint8 type, const QAsn1Element &child) +{ + QByteArray value; + QDataStream stream(&value, QIODevice::WriteOnly); + child.write(stream); + return QAsn1Element(type, value); +} + +static QAsn1Element _q_PKCS7_data(const QByteArray &data) +{ + QList<QAsn1Element> items; + items << QAsn1Element::fromObjectId("1.2.840.113549.1.7.1"); + items << wrap(QAsn1Element::Context0Type, + QAsn1Element(QAsn1Element::OctetStringType, data)); + return QAsn1Element::fromVector(items); +} + +/*! + PKCS #12 key derivation. + + Some test vectors: + http://www.drh-consultancy.demon.co.uk/test.txt + \internal +*/ +static QByteArray _q_PKCS12_keygen(char id, const QByteArray &salt, const QString &passPhrase, int n, int r) +{ + const int u = 20; + const int v = 64; + + // password formatting + QByteArray passUnicode(passPhrase.size() * 2 + 2, '\0'); + char *p = passUnicode.data(); + for (int i = 0; i < passPhrase.size(); ++i) { + quint16 ch = passPhrase[i].unicode(); + *(p++) = (ch & 0xff00) >> 8; + *(p++) = (ch & 0xff); + } + + // prepare I + QByteArray D(64, id); + QByteArray S, P; + const int sSize = v * ((salt.size() + v - 1) / v); + S.resize(sSize); + for (int i = 0; i < sSize; ++i) + S[i] = salt[i % salt.size()]; + const int pSize = v * ((passUnicode.size() + v - 1) / v); + P.resize(pSize); + for (int i = 0; i < pSize; ++i) + P[i] = passUnicode[i % passUnicode.size()]; + QByteArray I = S + P; + + // apply hashing + const int c = (n + u - 1) / u; + QByteArray A; + QByteArray B; + B.resize(v); + for (int i = 0; i < c; ++i) { + // hash r iterations + QByteArray Ai = D + I; + for (int j = 0; j < r; ++j) + Ai = QCryptographicHash::hash(Ai, QCryptographicHash::Sha1); + + for (int j = 0; j < v; ++j) + B[j] = Ai[j % u]; + + // modify I as Ij = (Ij + B + 1) modulo 2^v + for (int p = 0; p < I.size(); p += v) { + quint8 carry = 1; + for (int j = v - 1; j >= 0; --j) { + quint16 v = quint8(I[p + j]) + quint8(B[j]) + carry; + I[p + j] = v & 0xff; + carry = (v & 0xff00) >> 8; + } + } + A += Ai; + } + return A.left(n); +} + +static QByteArray _q_PKCS12_salt() +{ + QByteArray salt; + salt.resize(8); + for (int i = 0; i < salt.size(); ++i) + salt[i] = (QRandomGenerator::global()->generate() & 0xff); + return salt; +} + +static QByteArray _q_PKCS12_certBag(const QSslCertificate &cert) +{ + QList<QAsn1Element> items; + items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.3"); + + // certificate + QList<QAsn1Element> certItems; + certItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.22.1"); + certItems << wrap(QAsn1Element::Context0Type, + QAsn1Element(QAsn1Element::OctetStringType, cert.toDer())); + items << wrap(QAsn1Element::Context0Type, + QAsn1Element::fromVector(certItems)); + + // local key id + const QByteArray localKeyId = cert.digest(QCryptographicHash::Sha1); + QList<QAsn1Element> idItems; + idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); + idItems << wrap(QAsn1Element::SetType, + QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); + items << wrap(QAsn1Element::SetType, QAsn1Element::fromVector(idItems)); + + // dump + QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +static QAsn1Element _q_PKCS12_key(const QSslKey &key) +{ + Q_ASSERT(key.algorithm() == QSsl::Rsa || key.algorithm() == QSsl::Dsa); + + QList<QAsn1Element> keyItems; + keyItems << QAsn1Element::fromInteger(0); + QList<QAsn1Element> algoItems; + if (key.algorithm() == QSsl::Rsa) + algoItems << QAsn1Element::fromObjectId(RSA_ENCRYPTION_OID); + else if (key.algorithm() == QSsl::Dsa) + algoItems << QAsn1Element::fromObjectId(DSA_ENCRYPTION_OID); + algoItems << QAsn1Element(QAsn1Element::NullType); + keyItems << QAsn1Element::fromVector(algoItems); + keyItems << QAsn1Element(QAsn1Element::OctetStringType, key.toDer()); + return QAsn1Element::fromVector(keyItems); +} + +static QByteArray _q_PKCS12_shroudedKeyBag(const QSslKey &key, const QString &passPhrase, const QByteArray &localKeyId) +{ + const int iterations = 2048; + QByteArray salt = _q_PKCS12_salt(); + QByteArray cKey = _q_PKCS12_keygen(1, salt, passPhrase, 24, iterations); + QByteArray cIv = _q_PKCS12_keygen(2, salt, passPhrase, 8, iterations); + + // prepare and encrypt data + QByteArray plain; + QDataStream plainStream(&plain, QIODevice::WriteOnly); + _q_PKCS12_key(key).write(plainStream); + QByteArray crypted = QSslKeyPrivate::encrypt(QTlsPrivate::Cipher::DesEde3Cbc, + plain, cKey, cIv); + + QList<QAsn1Element> items; + items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.2"); + + // key + QList<QAsn1Element> keyItems; + QList<QAsn1Element> algoItems; + algoItems << QAsn1Element::fromObjectId("1.2.840.113549.1.12.1.3"); + QList<QAsn1Element> paramItems; + paramItems << QAsn1Element(QAsn1Element::OctetStringType, salt); + paramItems << QAsn1Element::fromInteger(iterations); + algoItems << QAsn1Element::fromVector(paramItems); + keyItems << QAsn1Element::fromVector(algoItems); + keyItems << QAsn1Element(QAsn1Element::OctetStringType, crypted); + items << wrap(QAsn1Element::Context0Type, + QAsn1Element::fromVector(keyItems)); + + // local key id + QList<QAsn1Element> idItems; + idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); + idItems << wrap(QAsn1Element::SetType, + QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); + items << wrap(QAsn1Element::SetType, + QAsn1Element::fromVector(idItems)); + + // dump + QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +static QByteArray _q_PKCS12_bag(const QList<QSslCertificate> &certs, const QSslKey &key, const QString &passPhrase) +{ + QList<QAsn1Element> items; + + // certs + for (int i = 0; i < certs.size(); ++i) + items << _q_PKCS7_data(_q_PKCS12_certBag(certs[i])); + + // key + if (!key.isNull()) { + const QByteArray localKeyId = certs.first().digest(QCryptographicHash::Sha1); + items << _q_PKCS7_data(_q_PKCS12_shroudedKeyBag(key, passPhrase, localKeyId)); + } + + // dump + QAsn1Element root = QAsn1Element::fromVector(items); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +static QAsn1Element _q_PKCS12_mac(const QByteArray &data, const QString &passPhrase) +{ + const int iterations = 2048; + + // salt generation + QByteArray macSalt = _q_PKCS12_salt(); + QByteArray key = _q_PKCS12_keygen(3, macSalt, passPhrase, 20, iterations); + + // HMAC calculation + QMessageAuthenticationCode hmac(QCryptographicHash::Sha1, key); + hmac.addData(data); + + QList<QAsn1Element> algoItems; + algoItems << QAsn1Element::fromObjectId("1.3.14.3.2.26"); + algoItems << QAsn1Element(QAsn1Element::NullType); + + QList<QAsn1Element> digestItems; + digestItems << QAsn1Element::fromVector(algoItems); + digestItems << QAsn1Element(QAsn1Element::OctetStringType, hmac.result()); + + QList<QAsn1Element> macItems; + macItems << QAsn1Element::fromVector(digestItems); + macItems << QAsn1Element(QAsn1Element::OctetStringType, macSalt); + macItems << QAsn1Element::fromInteger(iterations); + return QAsn1Element::fromVector(macItems); +} + +QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, const QString &passPhrase) +{ + QList<QAsn1Element> items; + + // version + items << QAsn1Element::fromInteger(3); + + // auth safe + const QByteArray data = _q_PKCS12_bag(certs, key, passPhrase); + items << _q_PKCS7_data(data); + + // HMAC + items << _q_PKCS12_mac(data, passPhrase); + + // dump + QAsn1Element root = QAsn1Element::fromVector(items); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qtlskey_base.cpp b/src/plugins/tls/shared/qtlskey_base.cpp new file mode 100644 index 0000000000..ff541165fe --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_base.cpp @@ -0,0 +1,97 @@ +// 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 "qtlskey_base_p.h" +#include "qasn1element_p.h" + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +QByteArray TlsKeyBase::pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const +{ + QByteArray pem(der.toBase64()); + + const int lineWidth = 64; // RFC 1421 + const int newLines = pem.size() / lineWidth; + const bool rem = pem.size() % lineWidth; + + for (int i = 0; i < newLines; ++i) + pem.insert((i + 1) * lineWidth + i, '\n'); + if (rem) + pem.append('\n'); + + QByteArray extra; + if (!headers.isEmpty()) { + QMap<QByteArray, QByteArray>::const_iterator it = headers.constEnd(); + do { + --it; + extra += it.key() + ": " + it.value() + '\n'; + } while (it != headers.constBegin()); + extra += '\n'; + } + + if (isEncryptedPkcs8(der)) { + pem.prepend(pkcs8Header(true) + '\n' + extra); + pem.append(pkcs8Footer(true) + '\n'); + } else if (isPkcs8()) { + pem.prepend(pkcs8Header(false) + '\n' + extra); + pem.append(pkcs8Footer(false) + '\n'); + } else { + pem.prepend(pemHeader() + '\n' + extra); + pem.append(pemFooter() + '\n'); + } + + return pem; +} + +QByteArray TlsKeyBase::pkcs8Header(bool encrypted) +{ + return encrypted + ? QByteArrayLiteral("-----BEGIN ENCRYPTED PRIVATE KEY-----") + : QByteArrayLiteral("-----BEGIN PRIVATE KEY-----"); +} + +QByteArray TlsKeyBase::pkcs8Footer(bool encrypted) +{ + return encrypted + ? QByteArrayLiteral("-----END ENCRYPTED PRIVATE KEY-----") + : QByteArrayLiteral("-----END PRIVATE KEY-----"); +} + +bool TlsKeyBase::isEncryptedPkcs8(const QByteArray &der) +{ + static const QList<QByteArray> pbes1OIds { + // PKCS5 + { PKCS5_MD2_DES_CBC_OID }, { PKCS5_MD2_RC2_CBC_OID }, { PKCS5_MD5_DES_CBC_OID }, + { PKCS5_MD5_RC2_CBC_OID }, { PKCS5_SHA1_DES_CBC_OID }, { PKCS5_SHA1_RC2_CBC_OID }, + }; + QAsn1Element elem; + if (!elem.read(der) || elem.type() != QAsn1Element::SequenceType) + return false; + + const auto items = elem.toList(); + if (items.size() != 2 + || items[0].type() != QAsn1Element::SequenceType + || items[1].type() != QAsn1Element::OctetStringType) { + return false; + } + + const auto encryptionSchemeContainer = items[0].toList(); + if (encryptionSchemeContainer.size() != 2 + || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType + || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { + return false; + } + + const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); + return encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID + || pbes1OIds.contains(encryptionScheme) + || encryptionScheme.startsWith(PKCS12_OID); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + + diff --git a/src/plugins/tls/shared/qtlskey_base_p.h b/src/plugins/tls/shared/qtlskey_base_p.h new file mode 100644 index 0000000000..ebfa15a2f9 --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_base_p.h @@ -0,0 +1,72 @@ +// 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 + +#ifndef QTLSKEY_BASE_P_H +#define QTLSKEY_BASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtNetwork/qssl.h> + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsKeyBase : public TlsKey +{ +public: + TlsKeyBase(KeyType type = QSsl::PublicKey, KeyAlgorithm algorithm = QSsl::Opaque) + : keyType(type), + keyAlgorithm(algorithm) + { + } + + bool isNull() const override + { + return keyIsNull; + } + KeyType type() const override + { + return keyType; + } + KeyAlgorithm algorithm() const override + { + return keyAlgorithm; + } + bool isPkcs8 () const override + { + return false; + } + + QByteArray pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const override; + +protected: + static QByteArray pkcs8Header(bool encrypted); + static QByteArray pkcs8Footer(bool encrypted); + static bool isEncryptedPkcs8(const QByteArray &der); + + bool keyIsNull = true; + KeyType keyType = QSsl::PublicKey; + KeyAlgorithm keyAlgorithm = QSsl::Opaque; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_BASE_P_H diff --git a/src/plugins/tls/shared/qtlskey_generic.cpp b/src/plugins/tls/shared/qtlskey_generic.cpp new file mode 100644 index 0000000000..4645ef4703 --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_generic.cpp @@ -0,0 +1,849 @@ +// Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +// 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 "qtlskey_generic_p.h" +#include "qasn1element_p.h" + +#include <QtNetwork/private/qsslkey_p.h> + +#include <QtNetwork/qpassworddigestor.h> + +#include <QtCore/QMessageAuthenticationCode> +#include <QtCore/qcryptographichash.h> +#include <QtCore/qrandom.h> + +#include <QtCore/qdatastream.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qdebug.h> +#include <QtCore/qmap.h> + +#include <cstring> + +QT_BEGIN_NAMESPACE + +// The code here is essentially what we had in qsslkey_qt.cpp before, with +// minimal changes/restructure. + +namespace QTlsPrivate { + +// OIDs of named curves allowed in TLS as per RFCs 4492 and 7027, +// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 +namespace { + +const quint8 bits_table[256] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +using OidLengthMap = QMap<QByteArray, int>; + +OidLengthMap createOidMap() +{ + OidLengthMap oids; + oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.10045.3.1.1"), 192); // secp192r1 a.k.a prime192v1 + oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.10045.3.1.7"), 256); // secp256r1 a.k.a prime256v1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.1"), 193); // sect193r2 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.10"), 256); // secp256k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.16"), 283); // sect283k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.17"), 283); // sect283r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.26"), 233); // sect233k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.27"), 233); // sect233r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.3"), 239); // sect239k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.30"), 160); // secp160r2 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.31"), 192); // secp192k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.32"), 224); // secp224k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.33"), 224); // secp224r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.34"), 384); // secp384r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.35"), 521); // secp521r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.36"), 409); // sect409k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.37"), 409); // sect409r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.38"), 571); // sect571k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.39"), 571); // sect571r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.8"), 160); // secp160r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.9"), 160); // secp160k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.11"), 384); // brainpoolP384r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.13"), 512); // brainpoolP512r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.7"), 256); // brainpoolP256r1 + return oids; +} + +} // Unnamed namespace. + +Q_GLOBAL_STATIC_WITH_ARGS(OidLengthMap, oidLengthMap, (createOidMap())) + +namespace { + +// Maps OIDs to the encryption cipher they specify +const QMap<QByteArray, QSslKeyPrivate::Cipher> oidCipherMap { + {DES_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesCbc}, + {DES_EDE3_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesEde3Cbc}, + // {PKCS5_MD2_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, // No MD2 + {PKCS5_MD5_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, + {PKCS5_SHA1_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, + // {PKCS5_MD2_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, // No MD2 + {PKCS5_MD5_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, + {PKCS5_SHA1_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, + {RC2_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc2Cbc} + // {RC5_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc5Cbc}, // No RC5 + // {AES128_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes128}, // no AES + // {AES192_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes192}, + // {AES256_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes256} +}; + +struct EncryptionData +{ + EncryptionData() = default; + + EncryptionData(QSslKeyPrivate::Cipher cipher, QByteArray key, QByteArray iv) + : initialized(true), cipher(cipher), key(key), iv(iv) + { + } + bool initialized = false; + QSslKeyPrivate::Cipher cipher; + QByteArray key; + QByteArray iv; +}; + +EncryptionData readPbes2(const QList<QAsn1Element> &element, const QByteArray &passPhrase) +{ + // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.2 + /*** Scheme: *** + * Sequence (scheme-specific info..) + * Sequence (key derivation info) + * Object Identifier (Key derivation algorithm (e.g. PBKDF2)) + * Sequence (salt) + * CHOICE (this entry can be either of the types it contains) + * Octet string (actual salt) + * Object identifier (Anything using this is deferred to a later version of PKCS #5) + * Integer (iteration count) + * Sequence (encryption algorithm info) + * Object identifier (identifier for the algorithm) + * Algorithm dependent, is covered in the switch further down + */ + + static const QMap<QByteArray, QCryptographicHash::Algorithm> pbes2OidHashFunctionMap { + // PBES2/PBKDF2 + {HMAC_WITH_SHA1, QCryptographicHash::Sha1}, + {HMAC_WITH_SHA224, QCryptographicHash::Sha224}, + {HMAC_WITH_SHA256, QCryptographicHash::Sha256}, + {HMAC_WITH_SHA512, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA512_224, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA512_256, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA384, QCryptographicHash::Sha384} + }; + + // Values from their respective sections here: https://tools.ietf.org/html/rfc8018#appendix-B.2 + static const QMap<QSslKeyPrivate::Cipher, int> cipherKeyLengthMap { + {QSslKeyPrivate::Cipher::DesCbc, 8}, + {QSslKeyPrivate::Cipher::DesEde3Cbc, 24}, + // @note: variable key-length (https://tools.ietf.org/html/rfc8018#appendix-B.2.3) + {QSslKeyPrivate::Cipher::Rc2Cbc, 4} + // @todo: AES(, rc5?) + }; + + const QList<QAsn1Element> keyDerivationContainer = element[0].toList(); + if (keyDerivationContainer.size() != 2 + || keyDerivationContainer[0].type() != QAsn1Element::ObjectIdentifierType + || keyDerivationContainer[1].type() != QAsn1Element::SequenceType) { + return {}; + } + + const QByteArray keyDerivationAlgorithm = keyDerivationContainer[0].toObjectId(); + const auto keyDerivationParams = keyDerivationContainer[1].toList(); + + const auto encryptionAlgorithmContainer = element[1].toList(); + if (encryptionAlgorithmContainer.size() != 2 + || encryptionAlgorithmContainer[0].type() != QAsn1Element::ObjectIdentifierType) { + return {}; + } + + auto iterator = oidCipherMap.constFind(encryptionAlgorithmContainer[0].toObjectId()); + if (iterator == oidCipherMap.cend()) { + qWarning() + << "QSslKey: Unsupported encryption cipher OID:" << encryptionAlgorithmContainer[0].toObjectId() + << "\nFile a bug report to Qt (include the line above)."; + return {}; + } + + QSslKeyPrivate::Cipher cipher = *iterator; + QByteArray key; + QByteArray iv; + switch (cipher) { + case QSslKeyPrivate::Cipher::DesCbc: + case QSslKeyPrivate::Cipher::DesEde3Cbc: + // https://tools.ietf.org/html/rfc8018#appendix-B.2.1 (DES-CBC-PAD) + // https://tools.ietf.org/html/rfc8018#appendix-B.2.2 (DES-EDE3-CBC-PAD) + // @todo https://tools.ietf.org/html/rfc8018#appendix-B.2.5 (AES-CBC-PAD) + /*** Scheme: *** + * Octet string (IV) + */ + if (encryptionAlgorithmContainer[1].type() != QAsn1Element::OctetStringType) + return {}; + + // @note: All AES identifiers should be able to use this branch!! + iv = encryptionAlgorithmContainer[1].value(); + + if (iv.size() != 8) // @note: AES needs 16 bytes + return {}; + break; + case QSslKeyPrivate::Cipher::Rc2Cbc: { + // https://tools.ietf.org/html/rfc8018#appendix-B.2.3 + /*** Scheme: *** + * Sequence (rc2 parameters) + * Integer (rc2 parameter version) + * Octet string (IV) + */ + if (encryptionAlgorithmContainer[1].type() != QAsn1Element::SequenceType) + return {}; + const auto rc2ParametersContainer = encryptionAlgorithmContainer[1].toList(); + if ((rc2ParametersContainer.size() != 1 && rc2ParametersContainer.size() != 2) + || rc2ParametersContainer.back().type() != QAsn1Element::OctetStringType) { + return {}; + } + iv = rc2ParametersContainer.back().value(); + if (iv.size() != 8) + return {}; + break; + } // @todo(?): case (RC5 , AES) + case QSslKeyPrivate::Cipher::Aes128Cbc: + case QSslKeyPrivate::Cipher::Aes192Cbc: + case QSslKeyPrivate::Cipher::Aes256Cbc: + Q_UNREACHABLE(); + } + + if (Q_LIKELY(keyDerivationAlgorithm == PKCS5_PBKDF2_ENCRYPTION_OID)) { + // Definition: https://tools.ietf.org/html/rfc8018#appendix-A.2 + QByteArray salt; + if (keyDerivationParams[0].type() == QAsn1Element::OctetStringType) { + salt = keyDerivationParams[0].value(); + } else if (keyDerivationParams[0].type() == QAsn1Element::ObjectIdentifierType) { + Q_UNIMPLEMENTED(); + /* See paragraph from https://tools.ietf.org/html/rfc8018#appendix-A.2 + which ends with: "such facilities are deferred to a future version of PKCS #5" + */ + return {}; + } else { + return {}; + } + + // Iterations needed to derive the key + int iterationCount = keyDerivationParams[1].toInteger(); + // Optional integer + int keyLength = -1; + int vectorPos = 2; + if (keyDerivationParams.size() > vectorPos + && keyDerivationParams[vectorPos].type() == QAsn1Element::IntegerType) { + keyLength = keyDerivationParams[vectorPos].toInteger(nullptr); + ++vectorPos; + } else { + keyLength = cipherKeyLengthMap[cipher]; + } + + // Optional algorithm identifier (default: HMAC-SHA-1) + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; + if (keyDerivationParams.size() > vectorPos + && keyDerivationParams[vectorPos].type() == QAsn1Element::SequenceType) { + const auto hashAlgorithmContainer = keyDerivationParams[vectorPos].toList(); + hashAlgorithm = pbes2OidHashFunctionMap[hashAlgorithmContainer.front().toObjectId()]; + Q_ASSERT(hashAlgorithmContainer[1].type() == QAsn1Element::NullType); + ++vectorPos; + } + Q_ASSERT(keyDerivationParams.size() == vectorPos); + + key = QPasswordDigestor::deriveKeyPbkdf2(hashAlgorithm, passPhrase, salt, iterationCount, keyLength); + } else { + qWarning() + << "QSslKey: Unsupported key derivation algorithm OID:" << keyDerivationAlgorithm + << "\nFile a bugreport to Qt (include the line above)."; + return {}; + } + return {cipher, key, iv}; +} + +// Maps OIDs to the hash function it specifies +const QMap<QByteArray, QCryptographicHash::Algorithm> pbes1OidHashFunctionMap { +#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + // PKCS5 + //{PKCS5_MD2_DES_CBC_OID, QCryptographicHash::Md2}, No MD2 + //{PKCS5_MD2_RC2_CBC_OID, QCryptographicHash::Md2}, + {PKCS5_MD5_DES_CBC_OID, QCryptographicHash::Md5}, + {PKCS5_MD5_RC2_CBC_OID, QCryptographicHash::Md5}, +#endif + {PKCS5_SHA1_DES_CBC_OID, QCryptographicHash::Sha1}, + {PKCS5_SHA1_RC2_CBC_OID, QCryptographicHash::Sha1}, + // PKCS12 (unimplemented) + // {PKCS12_SHA1_RC4_128_OID, QCryptographicHash::Sha1}, // No RC4 + // {PKCS12_SHA1_RC4_40_OID, QCryptographicHash::Sha1}, + // @todo: lacking support. @note: there might be code to do this inside qsslsocket_mac... + // further note that more work may be required for the 3DES variations listed to be available. + // {PKCS12_SHA1_3KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_2KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_RC2_128_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_RC2_40_CBC_OID, QCryptographicHash::Sha1} +}; + +EncryptionData readPbes1(const QList<QAsn1Element> &element, const QByteArray &encryptionScheme, + const QByteArray &passPhrase) +{ + // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.1 + // Steps refer to this section: https://tools.ietf.org/html/rfc8018#section-6.1.2 + /*** Scheme: *** + * Sequence (PBE Parameter) + * Octet string (salt) + * Integer (iteration counter) + */ + // Step 1 + if (element.size() != 2 + || element[0].type() != QAsn1Element::ElementType::OctetStringType + || element[1].type() != QAsn1Element::ElementType::IntegerType) { + return {}; + } + QByteArray salt = element[0].value(); + if (salt.size() != 8) + return {}; + + int iterationCount = element[1].toInteger(); + if (iterationCount < 0) + return {}; + + // Step 2 + auto iterator = pbes1OidHashFunctionMap.constFind(encryptionScheme); + if (iterator == pbes1OidHashFunctionMap.cend()) { + // Qt was compiled with ONLY_SHA1 (or it's MD2) + return {}; + } + QCryptographicHash::Algorithm hashAlgorithm = *iterator; + QByteArray key = QPasswordDigestor::deriveKeyPbkdf1(hashAlgorithm, passPhrase, salt, iterationCount, 16); + if (key.size() != 16) + return {}; + + // Step 3 + QByteArray iv = key.right(8); // last 8 bytes are used as IV + key.truncate(8); // first 8 bytes are used for the key + + QSslKeyPrivate::Cipher cipher = oidCipherMap[encryptionScheme]; + // Steps 4-6 are done after returning + return {cipher, key, iv}; +} + +int curveBits(const QByteArray &oid) +{ + const int length = oidLengthMap->value(oid); + return length ? length : -1; +} + +int numberOfBits(const QByteArray &modulus) +{ + int bits = modulus.size() * 8; + for (int i = 0; i < modulus.size(); ++i) { + quint8 b = modulus[i]; + bits -= 8; + if (b != 0) { + bits += bits_table[b]; + break; + } + } + return bits; +} + +QByteArray deriveAesKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, + const QByteArray &iv) +{ + // This is somewhat simplified and shortened version of what OpenSSL does. + // See, for example, EVP_BytesToKey for the "algorithm" itself and elsewhere + // in their code for what they pass as arguments to EVP_BytesToKey when + // deriving encryption keys (when reading/writing pems files with encrypted + // keys). + + Q_ASSERT(iv.size() >= 8); + + QCryptographicHash hash(QCryptographicHash::Md5); + + QByteArray data(passPhrase); + data.append(iv.data(), 8); // AKA PKCS5_SALT_LEN in OpenSSL. + + hash.addData(data); + + if (cipher == Cipher::Aes128Cbc) + return hash.result(); + + QByteArray key(hash.result()); + hash.reset(); + hash.addData(key); + hash.addData(data); + + if (cipher == Cipher::Aes192Cbc) + return key.append(hash.resultView().first(8)); + + return key.append(hash.resultView()); +} + +QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, + const QByteArray &iv) +{ + QByteArray key; + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(passPhrase); + hash.addData(iv); + switch (cipher) { + case Cipher::DesCbc: + key = hash.result().left(8); + break; + case Cipher::DesEde3Cbc: + key = hash.result(); + hash.reset(); + hash.addData(key); + hash.addData(passPhrase); + hash.addData(iv); + key += hash.result().left(8); + break; + case Cipher::Rc2Cbc: + key = hash.result(); + break; + case Cipher::Aes128Cbc: + case Cipher::Aes192Cbc: + case Cipher::Aes256Cbc: + return deriveAesKey(cipher, passPhrase, iv); + } + return key; +} + +int extractPkcs8KeyLength(const QList<QAsn1Element> &items, TlsKey *that) +{ + Q_ASSERT(items.size() == 3); + Q_ASSERT(that); + + int keyLength = -1; + + auto getName = [](QSsl::KeyAlgorithm algorithm) { + switch (algorithm){ + case QSsl::Rsa: return "RSA"; + case QSsl::Dsa: return "DSA"; + case QSsl::Dh: return "DH"; + case QSsl::Ec: return "EC"; + case QSsl::Opaque: return "Opaque"; + } + Q_UNREACHABLE(); + }; + + const auto pkcs8Info = items[1].toList(); + if (pkcs8Info.size() != 2 || pkcs8Info[0].type() != QAsn1Element::ObjectIdentifierType) + return -1; + const QByteArray value = pkcs8Info[0].toObjectId(); + if (value == RSA_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Rsa)) { + // We could change the 'algorithm' of QSslKey here and continue loading, but + // this is not supported in the openssl back-end, so we'll fail here and give + // the user some feedback. + qWarning() << "QSslKey: Found RSA key when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // Luckily it contains the 'normal' RSA-key format inside, so we can just recurse + // and read the key's info. + that->decodeDer(that->type(), that->algorithm(), items[2].value(), {}, true); + // The real info has been filled out in the call above, so return as if it was invalid + // to avoid overwriting the data. + return -1; + } else if (value == EC_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Ec)) { + // As above for RSA. + qWarning() << "QSslKey: Found EC key when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // I don't know where this is documented, but the elliptic-curve identifier has been + // moved into the "pkcs#8 wrapper", which is what we're interested in. + if (pkcs8Info[1].type() != QAsn1Element::ObjectIdentifierType) + return -1; + keyLength = curveBits(pkcs8Info[1].toObjectId()); + } else if (value == DSA_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Dsa)) { + // As above for RSA. + qWarning() << "QSslKey: Found DSA when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // DSA's structure is documented here: + // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. + if (pkcs8Info[1].type() != QAsn1Element::SequenceType) + return -1; + const auto dsaInfo = pkcs8Info[1].toList(); + if (dsaInfo.size() != 3 || dsaInfo[0].type() != QAsn1Element::IntegerType) + return -1; + keyLength = numberOfBits(dsaInfo[0].value()); + } else if (value == DH_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Dh)) { + // As above for RSA. + qWarning() << "QSslKey: Found DH when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // DH's structure is documented here: + // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. + if (pkcs8Info[1].type() != QAsn1Element::SequenceType) + return -1; + const auto dhInfo = pkcs8Info[1].toList(); + if (dhInfo.size() < 2 || dhInfo.size() > 3 || dhInfo[0].type() != QAsn1Element::IntegerType) + return -1; + keyLength = numberOfBits(dhInfo[0].value()); + } else { + // in case of unexpected formats: + qWarning() << "QSslKey: Unsupported PKCS#8 key algorithm:" << value + << "\nFile a bugreport to Qt (include the line above)."; + return -1; + } + + return keyLength; +} + +} // Unnamed namespace + +void TlsKeyGeneric::decodeDer(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) +{ + keyType = type; + keyAlgorithm = algorithm; + + clear(deepClear); + + if (der.isEmpty()) + return; + // decryptPkcs8 decrypts if necessary or returns 'der' unaltered + QByteArray decryptedDer = decryptPkcs8(der, passPhrase); + + QAsn1Element elem; + if (!elem.read(decryptedDer) || elem.type() != QAsn1Element::SequenceType) + return; + + if (type == QSsl::PublicKey) { + // key info + QDataStream keyStream(elem.value()); + if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) + return; + const auto infoItems = elem.toList(); + if (infoItems.size() < 2 || infoItems[0].type() != QAsn1Element::ObjectIdentifierType) + return; + if (algorithm == QSsl::Rsa) { + if (infoItems[0].toObjectId() != RSA_ENCRYPTION_OID) + return; + // key data + if (!elem.read(keyStream) || elem.type() != QAsn1Element::BitStringType || elem.value().isEmpty()) + return; + if (!elem.read(elem.value().mid(1)) || elem.type() != QAsn1Element::SequenceType) + return; + if (!elem.read(elem.value()) || elem.type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(elem.value()); + } else if (algorithm == QSsl::Dsa) { + if (infoItems[0].toObjectId() != DSA_ENCRYPTION_OID) + return; + if (infoItems[1].type() != QAsn1Element::SequenceType) + return; + // key params + const auto params = infoItems[1].toList(); + if (params.isEmpty() || params[0].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(params[0].value()); + } else if (algorithm == QSsl::Dh) { + if (infoItems[0].toObjectId() != DH_ENCRYPTION_OID) + return; + if (infoItems[1].type() != QAsn1Element::SequenceType) + return; + // key params + const auto params = infoItems[1].toList(); + if (params.isEmpty() || params[0].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(params[0].value()); + } else if (algorithm == QSsl::Ec) { + if (infoItems[0].toObjectId() != EC_ENCRYPTION_OID) + return; + if (infoItems[1].type() != QAsn1Element::ObjectIdentifierType) + return; + keyLength = curveBits(infoItems[1].toObjectId()); + } + + } else { + const auto items = elem.toList(); + if (items.isEmpty()) + return; + + // version + if (items[0].type() != QAsn1Element::IntegerType) + return; + const QByteArray versionHex = items[0].value().toHex(); + + if (items.size() == 3 && items[1].type() == QAsn1Element::SequenceType + && items[2].type() == QAsn1Element::OctetStringType) { + if (versionHex != "00" && versionHex != "01") + return; + int pkcs8KeyLength = extractPkcs8KeyLength(items, this); + if (pkcs8KeyLength == -1) + return; + pkcs8 = true; + keyLength = pkcs8KeyLength; + } else if (algorithm == QSsl::Rsa) { + if (versionHex != "00") + return; + if (items.size() != 9 || items[1].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(items[1].value()); + } else if (algorithm == QSsl::Dsa) { + if (versionHex != "00") + return; + if (items.size() != 6 || items[1].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(items[1].value()); + } else if (algorithm == QSsl::Dh) { + if (versionHex != "00") + return; + if (items.size() < 5 || items.size() > 6 || items[1].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(items[1].value()); + } else if (algorithm == QSsl::Ec) { + if (versionHex != "01") + return; + if (items.size() != 4 + || items[1].type() != QAsn1Element::OctetStringType + || items[2].type() != QAsn1Element::Context0Type + || items[3].type() != QAsn1Element::Context1Type) + return; + QAsn1Element oidElem; + if (!oidElem.read(items[2].value()) + || oidElem.type() != QAsn1Element::ObjectIdentifierType) + return; + keyLength = curveBits(oidElem.toObjectId()); + } + } + + derData = decryptedDer; + keyIsNull = false; +} + +void TlsKeyGeneric::decodePem(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) +{ + keyType = type; + keyAlgorithm = algorithm; + + QMap<QByteArray, QByteArray> headers; + QByteArray data = derFromPem(pem, &headers); + + if (headers.value("Proc-Type") == "4,ENCRYPTED") { + const QList<QByteArray> dekInfo = headers.value("DEK-Info").split(','); + if (dekInfo.size() != 2) { + clear(deepClear); + return; + } + + QSslKeyPrivate::Cipher cipher; + if (dekInfo.first() == "DES-CBC") { + cipher = Cipher::DesCbc; + } else if (dekInfo.first() == "DES-EDE3-CBC") { + cipher = Cipher::DesEde3Cbc; + } else if (dekInfo.first() == "RC2-CBC") { + cipher = Cipher::Rc2Cbc; + } else if (dekInfo.first() == "AES-128-CBC") { + cipher = Cipher::Aes128Cbc; + } else if (dekInfo.first() == "AES-192-CBC") { + cipher = Cipher::Aes192Cbc; + } else if (dekInfo.first() == "AES-256-CBC") { + cipher = Cipher::Aes256Cbc; + } else { + clear(deepClear); + return; + } + + const QByteArray iv = QByteArray::fromHex(dekInfo.last()); + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = decrypt(cipher, data, key, iv); + } + + decodeDer(keyType, keyAlgorithm, data, passPhrase, deepClear); +} + +QByteArray TlsKeyGeneric::toPem(const QByteArray &passPhrase) const +{ + QByteArray data; + QMap<QByteArray, QByteArray> headers; + + if (type() == QSsl::PrivateKey && !passPhrase.isEmpty()) { + // ### use a cryptographically secure random number generator + quint64 random = QRandomGenerator::system()->generate64(); + QByteArray iv = QByteArray::fromRawData(reinterpret_cast<const char *>(&random), sizeof(random)); + + auto cipher = Cipher::DesEde3Cbc; + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = encrypt(cipher, derData, key, iv); + + headers.insert("Proc-Type", "4,ENCRYPTED"); + headers.insert("DEK-Info", "DES-EDE3-CBC," + iv.toHex()); + } else { + data = derData; + } + + return pemFromDer(data, headers); +} + +QByteArray TlsKeyGeneric::derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const +{ + if (derData.size()) + return derData; + + QByteArray header = pemHeader(); + QByteArray footer = pemFooter(); + + QByteArray der(pem); + + int headerIndex = der.indexOf(header); + int footerIndex = der.indexOf(footer, headerIndex + header.length()); + if (type() != QSsl::PublicKey) { + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(true); + footer = pkcs8Footer(true); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.length()); + } + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(false); + footer = pkcs8Footer(false); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.length()); + } + } + if (headerIndex == -1 || footerIndex == -1) + return QByteArray(); + + der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + + if (der.contains("Proc-Type:")) { + // taken from QHttpNetworkReplyPrivate::parseHeader + int i = 0; + while (i < der.length()) { + int j = der.indexOf(':', i); // field-name + if (j == -1) + break; + const QByteArray field = der.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = der.indexOf('\n', j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && der[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += der.mid(j, length).trimmed(); + j = ++i; + } while (i < der.length() && (der.at(i) == ' ' || der.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + headers->insert(field, value); + } + der = der.mid(i); + } + + return QByteArray::fromBase64(der); // ignores newlines +} + +void TlsKeyGeneric::fromHandle(Qt::HANDLE handle, KeyType expectedType) +{ + opaque = handle; + keyType = expectedType; +} + +void TlsKeyGeneric::clear(bool deep) +{ + keyIsNull = true; + if (deep) + std::memset(derData.data(), 0, derData.size()); + derData.clear(); + keyLength = -1; +} + +QByteArray TlsKeyGeneric::decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase) +{ + // RFC 5958: https://tools.ietf.org/html/rfc5958 + /*** Scheme: *** + * Sequence + * Sequence + * Object Identifier (encryption scheme (currently PBES2, PBES1, @todo PKCS12)) + * Sequence (scheme parameters) + * Octet String (the encrypted data) + */ + QAsn1Element elem; + if (!elem.read(encrypted) || elem.type() != QAsn1Element::SequenceType) + return encrypted; + + const auto items = elem.toList(); + if (items.size() != 2 + || items[0].type() != QAsn1Element::SequenceType + || items[1].type() != QAsn1Element::OctetStringType) { + return encrypted; + } + + const auto encryptionSchemeContainer = items[0].toList(); + + if (encryptionSchemeContainer.size() != 2 + || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType + || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { + return encrypted; + } + + const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); + const auto schemeParameterContainer = encryptionSchemeContainer[1].toList(); + + if (schemeParameterContainer.size() != 2 + && schemeParameterContainer[0].type() != QAsn1Element::SequenceType + && schemeParameterContainer[1].type() != QAsn1Element::SequenceType) { + return encrypted; + } + + EncryptionData data; + if (encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID) { + data = readPbes2(schemeParameterContainer, passPhrase); + } else if (pbes1OidHashFunctionMap.contains(encryptionScheme)) { + data = readPbes1(schemeParameterContainer, encryptionScheme, passPhrase); + } else if (encryptionScheme.startsWith(PKCS12_OID)) { + Q_UNIMPLEMENTED(); // this isn't some 'unknown', I know these aren't implemented + return encrypted; + } else { + qWarning() + << "QSslKey: Unsupported encryption scheme OID:" << encryptionScheme + << "\nFile a bugreport to Qt (include the line above)."; + return encrypted; + } + + if (!data.initialized) { + // something went wrong, return + return encrypted; + } + + QByteArray decryptedKey = decrypt(data.cipher, items[1].value(), data.key, data.iv); + // The data is still wrapped in a octet string, so let's unwrap it + QAsn1Element decryptedKeyElement(QAsn1Element::ElementType::OctetStringType, decryptedKey); + return decryptedKeyElement.value(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qtlskey_generic_p.h b/src/plugins/tls/shared/qtlskey_generic_p.h new file mode 100644 index 0000000000..6344633cc7 --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_generic_p.h @@ -0,0 +1,83 @@ +// 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 + +#ifndef QTLSKEY_GENERIC_P_H +#define QTLSKEY_GENERIC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include "qtlskey_base_p.h" + + +#include <QtCore/qnamespace.h> +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +// This class is what previously was known as qsslkey_qt: +// it implements most of functionality needed by QSslKey +// not relying on any TLS implementation. It's used by +// our SecureTransport and Schannel backends. +class TlsKeyGeneric : public TlsKeyBase +{ +public: + using TlsKeyBase::TlsKeyBase; + + void decodeDer(KeyType type, KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) override; + void decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) override; + + QByteArray toPem(const QByteArray &passPhrase) const override; + + QByteArray derFromPem(const QByteArray &pem, QMap<QByteArray, + QByteArray> *headers) const override; + + void fromHandle(Qt::HANDLE opaque, KeyType expectedType) override; + + void clear(bool deep) override; + + Qt::HANDLE handle() const override + { + return Qt::HANDLE(opaque); + } + + int length() const override + { + return keyLength; + } + + bool isPkcs8() const override + { + return pkcs8; + } + +private: + QByteArray decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase); + + bool pkcs8 = false; + Qt::HANDLE opaque = nullptr; + QByteArray derData; + int keyLength = -1; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_GENERIC_P_H diff --git a/src/plugins/tls/shared/qwincrypt_p.h b/src/plugins/tls/shared/qwincrypt_p.h new file mode 100644 index 0000000000..48ca4247fa --- /dev/null +++ b/src/plugins/tls/shared/qwincrypt_p.h @@ -0,0 +1,55 @@ +// 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 + +#ifndef QWINCRYPT_P_H +#define QWINCRYPT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtCore/qt_windows.h> + +#include <QtCore/qglobal.h> + +#include <wincrypt.h> +#ifndef HCRYPTPROV_LEGACY +#define HCRYPTPROV_LEGACY HCRYPTPROV +#endif // !HCRYPTPROV_LEGACY + +#include <memory> + +QT_BEGIN_NAMESPACE + +struct QHCertStoreDeleter { + void operator()(HCERTSTORE store) + { + CertCloseStore(store, 0); + } +}; + +// A simple RAII type used by Schannel code and Window CA fetcher class: +using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>; + +struct QPCCertContextDeleter { + void operator()(PCCERT_CONTEXT context) const + { + CertFreeCertificateContext(context); + } +}; + +// A simple RAII type used by Schannel code +using QPCCertContextPointer = std::unique_ptr<const CERT_CONTEXT, QPCCertContextDeleter>; + +QT_END_NAMESPACE + +#endif // QWINCRYPT_P_H diff --git a/src/plugins/tls/shared/qx509_base.cpp b/src/plugins/tls/shared/qx509_base.cpp new file mode 100644 index 0000000000..dfa6569fa6 --- /dev/null +++ b/src/plugins/tls/shared/qx509_base.cpp @@ -0,0 +1,142 @@ +// 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 "qx509_base_p.h" + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +QByteArray X509CertificateBase::subjectInfoToString(QSslCertificate::SubjectInfo info) +{ + QByteArray str; + switch (info) { + case QSslCertificate::Organization: str = QByteArray("O"); break; + case QSslCertificate::CommonName: str = QByteArray("CN"); break; + case QSslCertificate::LocalityName: str = QByteArray("L"); break; + case QSslCertificate::OrganizationalUnitName: str = QByteArray("OU"); break; + case QSslCertificate::CountryName: str = QByteArray("C"); break; + case QSslCertificate::StateOrProvinceName: str = QByteArray("ST"); break; + case QSslCertificate::DistinguishedNameQualifier: str = QByteArray("dnQualifier"); break; + case QSslCertificate::SerialNumber: str = QByteArray("serialNumber"); break; + case QSslCertificate::EmailAddress: str = QByteArray("emailAddress"); break; + } + + return str; +} + +bool X509CertificateBase::matchLineFeed(const QByteArray &pem, int *offset) +{ + Q_ASSERT(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; +} + +bool X509CertificateBase::isNull() const +{ + return null; +} + +QByteArray X509CertificateBase::version() const +{ + return versionString; +} + +QByteArray X509CertificateBase::serialNumber() const +{ + return serialNumberString; +} + +QStringList X509CertificateBase::issuerInfo(QSslCertificate::SubjectInfo info) const +{ + return issuerInfo(subjectInfoToString(info)); +} + +QStringList X509CertificateBase::issuerInfo(const QByteArray &attribute) const +{ + return issuerInfoEntries.values(attribute); +} + +QStringList X509CertificateBase::subjectInfo(QSslCertificate::SubjectInfo info) const +{ + return subjectInfo(subjectInfoToString(info)); +} + +QStringList X509CertificateBase::subjectInfo(const QByteArray &attribute) const +{ + return subjectInfoEntries.values(attribute); +} + +QList<QByteArray> X509CertificateBase::subjectInfoAttributes() const +{ + return subjectInfoEntries.uniqueKeys(); +} + +QList<QByteArray> X509CertificateBase::issuerInfoAttributes() const +{ + return issuerInfoEntries.uniqueKeys(); +} + +QDateTime X509CertificateBase::effectiveDate() const +{ + return notValidBefore; +} + +QDateTime X509CertificateBase::expiryDate() const +{ + return notValidAfter; +} + +qsizetype X509CertificateBase::numberOfExtensions() const +{ + return extensions.size(); +} + +QString X509CertificateBase::oidForExtension(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].oid; +} + +QString X509CertificateBase::nameForExtension(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].name; +} + +QVariant X509CertificateBase::valueForExtension(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].value; +} + +bool X509CertificateBase::isExtensionCritical(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].critical; +} + +bool X509CertificateBase::isExtensionSupported(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].supported; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qx509_base_p.h b/src/plugins/tls/shared/qx509_base_p.h new file mode 100644 index 0000000000..0f268880af --- /dev/null +++ b/src/plugins/tls/shared/qx509_base_p.h @@ -0,0 +1,89 @@ +// 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 + +#ifndef QX509CERTIFICATE_BASE_P_H +#define QX509CERTIFICATE_BASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtNetwork/qssl.h> + +#include <QtCore/qbytearray.h> +#include <QtCore/qstring.h> +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateBase : public X509Certificate +{ +public: + bool isNull() const override; + QByteArray version() const override; + QByteArray serialNumber() const override; + QStringList issuerInfo(QSslCertificate::SubjectInfo info) const override; + QStringList issuerInfo(const QByteArray &attribute) const override; + QStringList subjectInfo(QSslCertificate::SubjectInfo info) const override; + QStringList subjectInfo(const QByteArray &attribute) const override; + QList<QByteArray> subjectInfoAttributes() const override; + QList<QByteArray> issuerInfoAttributes() const override; + QDateTime effectiveDate() const override; + QDateTime expiryDate() const override; + + qsizetype numberOfExtensions() const override; + QString oidForExtension(qsizetype index) const override; + QString nameForExtension(qsizetype index) const override; + QVariant valueForExtension(qsizetype index) const override; + bool isExtensionCritical(qsizetype index) const override; + bool isExtensionSupported(qsizetype index) const override; + + static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info); + static bool matchLineFeed(const QByteArray &pem, int *offset); + +protected: + bool validIndex(qsizetype index) const + { + return index >= 0 && index < extensions.size(); + } + + bool null = true; + QByteArray versionString; + QByteArray serialNumberString; + + QMultiMap<QByteArray, QString> issuerInfoEntries; + QMultiMap<QByteArray, QString> subjectInfoEntries; + QDateTime notValidAfter; + QDateTime notValidBefore; + + struct X509CertificateExtension + { + QString oid; + QString name; + QVariant value; + bool critical = false; + bool supported = false; + }; + + QList<X509CertificateExtension> extensions; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509CERTIFICATE_BASE_P_H 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 diff --git a/src/plugins/tls/shared/qx509_generic_p.h b/src/plugins/tls/shared/qx509_generic_p.h new file mode 100644 index 0000000000..94a4bae7cf --- /dev/null +++ b/src/plugins/tls/shared/qx509_generic_p.h @@ -0,0 +1,65 @@ +// 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 +#ifndef QX509_GENERIC_P_H +#define QX509_GENERIC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include "qx509_base_p.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +// A part of SecureTransport and Schannel plugin. +class X509CertificateGeneric : public X509CertificateBase +{ +public: + bool isEqual(const X509Certificate &rhs) const override; + bool isSelfSigned() const override; + + QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames() const override; + QByteArray toPem() const override; + QByteArray toDer() const override; + QString toText() const override; + Qt::HANDLE handle() const override; + + size_t hash(size_t seed) const noexcept override; + + static QList<QSslCertificate> certificatesFromPem(const QByteArray &pem, int count); + static QList<QSslCertificate> certificatesFromDer(const QByteArray &der, int count); + +protected: + + bool subjectMatchesIssuer = false; + QSsl::KeyAlgorithm publicKeyAlgorithm = QSsl::Rsa; + QByteArray publicKeyDerData; + + QMultiMap<QSsl::AlternativeNameEntryType, QString> saNames; + QByteArray derData; + + bool parse(const QByteArray &data); + bool parseExtension(const QByteArray &data, X509CertificateExtension &extension); +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_GENERIC_P_H |