summaryrefslogtreecommitdiffstats
path: root/src/plugins/tls/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/tls/shared')
-rw-r--r--src/plugins/tls/shared/qasn1element.cpp353
-rw-r--r--src/plugins/tls/shared/qasn1element_p.h152
-rw-r--r--src/plugins/tls/shared/qdtls_base.cpp79
-rw-r--r--src/plugins/tls/shared/qdtls_base_p.h80
-rw-r--r--src/plugins/tls/shared/qsslsocket_mac_shared.cpp168
-rw-r--r--src/plugins/tls/shared/qsslsocket_qt.cpp271
-rw-r--r--src/plugins/tls/shared/qtlskey_base.cpp97
-rw-r--r--src/plugins/tls/shared/qtlskey_base_p.h72
-rw-r--r--src/plugins/tls/shared/qtlskey_generic.cpp849
-rw-r--r--src/plugins/tls/shared/qtlskey_generic_p.h83
-rw-r--r--src/plugins/tls/shared/qwincrypt_p.h55
-rw-r--r--src/plugins/tls/shared/qx509_base.cpp142
-rw-r--r--src/plugins/tls/shared/qx509_base_p.h89
-rw-r--r--src/plugins/tls/shared/qx509_generic.cpp432
-rw-r--r--src/plugins/tls/shared/qx509_generic_p.h65
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 &params)
+{
+ 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