diff options
Diffstat (limited to 'src/plugins/tls/securetransport')
-rw-r--r-- | src/plugins/tls/securetransport/CMakeLists.txt | 33 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qtls_st.cpp | 1322 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qtls_st_p.h | 141 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qtlsbackend_st.cpp | 341 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qtlsbackend_st_p.h | 98 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qtlskey_st.cpp | 111 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qtlskey_st_p.h | 83 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qx509_st.cpp | 61 | ||||
-rw-r--r-- | src/plugins/tls/securetransport/qx509_st_p.h | 74 |
9 files changed, 2264 insertions, 0 deletions
diff --git a/src/plugins/tls/securetransport/CMakeLists.txt b/src/plugins/tls/securetransport/CMakeLists.txt new file mode 100644 index 0000000000..db9101f43c --- /dev/null +++ b/src/plugins/tls/securetransport/CMakeLists.txt @@ -0,0 +1,33 @@ +qt_internal_add_plugin(QSecureTransportBackend + OUTPUT_NAME securetransportbackend + CLASS_NAME QSecureTransportBackend + TYPE tls + DEFAULT_IF APPLE + SOURCES + ../shared/qsslsocket_mac_shared.cpp + ../shared/qtlskey_generic_p.h + ../shared/qtlskey_generic.cpp + ../shared/qx509_base_p.h + ../shared/qx509_base.cpp + ../shared/qx509_generic_p.h + ../shared/qx509_generic.cpp + ../shared/qtlskey_base_p.h + ../shared/qtlskey_base.cpp + ../shared/qsslsocket_qt.cpp + ../shared/qasn1element_p.h + ../shared/qasn1element.cpp + qtlsbackend_st.cpp + qtlsbackend_st_p.h + qx509_st.cpp + qtlskey_st.cpp + qtlskey_st_p.h + qx509_st_p.h + qtls_st.cpp + qtls_st_p.h + PUBLIC_LIBRARIES + Qt::NetworkPrivate + Qt::CorePrivate + LIBRARIES + ${FWCoreFoundation} + ${FWSecurity} +) diff --git a/src/plugins/tls/securetransport/qtls_st.cpp b/src/plugins/tls/securetransport/qtls_st.cpp new file mode 100644 index 0000000000..306f184f25 --- /dev/null +++ b/src/plugins/tls/securetransport/qtls_st.cpp @@ -0,0 +1,1322 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtls_st_p.h" +#include "qtlsbackend_st_p.h" +#include "qtlskey_st_p.h" + +#include <QtNetwork/private/qssl_p.h> + +#include <QtNetwork/private/qsslcertificate_p.h> +#include <QtNetwork/private/qsslcipher_p.h> +#include <QtNetwork/private/qsslkey_p.h> + +#include <QtNetwork/qsslsocket.h> + +#include <QtCore/qmessageauthenticationcode.h> +#include <QtCore/qoperatingsystemversion.h> +#include <QtCore/qscopedvaluerollback.h> +#include <QtCore/qcryptographichash.h> +#include <QtCore/qsystemdetection.h> +#include <QtCore/qdatastream.h> +#include <QtCore/qsysinfo.h> +#include <QtCore/qlist.h> +#include <QtCore/qmutex.h> +#include <QtCore/qdebug.h> +#include <QtCore/quuid.h> +#include <QtCore/qdir.h> + +#include <algorithm> +#include <cstddef> +#include <limits> +#include <vector> + +#include <QtCore/private/qcore_mac_p.h> + +#ifdef Q_OS_MACOS +#include <CoreServices/CoreServices.h> +#endif + +QT_BEGIN_NAMESPACE + +// Defined in qsslsocket_qt.cpp. +QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, + const QString &passPhrase); + +namespace QTlsPrivate { + +// Defined in qtlsbackend_st.cpp +QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher); + +namespace { + +#ifdef Q_OS_MACOS +/* + +Our own temporarykeychain is needed only on macOS where SecPKCS12Import changes +the default keychain and where we see annoying pop-ups asking about accessing a +private key. + +*/ + +struct EphemeralSecKeychain +{ + EphemeralSecKeychain(); + ~EphemeralSecKeychain(); + + SecKeychainRef keychain = nullptr; + Q_DISABLE_COPY_MOVE(EphemeralSecKeychain) +}; + +EphemeralSecKeychain::EphemeralSecKeychain() +{ + const auto uuid = QUuid::createUuid(); + if (uuid.isNull()) { + qCWarning(lcTlsBackend) << "Failed to create a unique keychain name"; + return; + } + + const QByteArray uuidAsByteArray = uuid.toByteArray(); + Q_ASSERT(uuidAsByteArray.size() > 2); + Q_ASSERT(uuidAsByteArray.startsWith('{')); + Q_ASSERT(uuidAsByteArray.endsWith('}')); + const auto uuidAsString = QLatin1String(uuidAsByteArray.data(), uuidAsByteArray.size()).mid(1, uuidAsByteArray.size() - 2); + + const QString keychainName + = QDir::tempPath() + QDir::separator() + uuidAsString + QLatin1String(".keychain"); + // SecKeychainCreate, pathName parameter: + // + // "A constant character string representing the POSIX path indicating where + // to store the keychain." + // + // Internally they seem to use std::string, but this does not really help. + // Fortunately, CFString has a convenient API. + QCFType<CFStringRef> cfName = keychainName.toCFString(); + std::vector<char> posixPath; + // "Extracts the contents of a string as a NULL-terminated 8-bit string + // appropriate for passing to POSIX APIs." + posixPath.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfName)); + const auto ok = CFStringGetFileSystemRepresentation(cfName, &posixPath[0], + CFIndex(posixPath.size())); + if (!ok) { + qCWarning(lcTlsBackend) << "Failed to create a unique keychain name from" + << "QDir::tempPath()"; + return; + } + + std::vector<uint8_t> passUtf8(256); + if (SecRandomCopyBytes(kSecRandomDefault, passUtf8.size(), &passUtf8[0])) { + qCWarning(lcTlsBackend) << "SecRandomCopyBytes: failed to create a key"; + return; + } + + const OSStatus status = SecKeychainCreate(&posixPath[0], passUtf8.size(), + &passUtf8[0], FALSE, nullptr, + &keychain); + if (status != errSecSuccess || !keychain) { + qCWarning(lcTlsBackend) << "SecKeychainCreate: failed to create a custom keychain"; + if (keychain) { + SecKeychainDelete(keychain); + CFRelease(keychain); + keychain = nullptr; + } + } + + if (keychain) { + SecKeychainSettings settings = {}; + settings.version = SEC_KEYCHAIN_SETTINGS_VERS1; + // Strange, huh? But that's what their docs say to do! With lockOnSleep + // == false, set interval to INT_MAX to never lock ... + settings.lockInterval = INT_MAX; + if (SecKeychainSetSettings(keychain, &settings) != errSecSuccess) + qCWarning(lcTlsBackend) << "SecKeychainSettings: failed to disable lock on sleep"; + } + +#ifdef QSSLSOCKET_DEBUG + if (keychain) { + qCDebug(lcTlsBackend) << "Custom keychain with name" << keychainName << "was created" + << "successfully"; + } +#endif +} + +EphemeralSecKeychain::~EphemeralSecKeychain() +{ + if (keychain) { + // clear file off disk + SecKeychainDelete(keychain); + CFRelease(keychain); + } +} + +#endif // Q_OS_MACOS + +void qt_releaseSecureTransportContext(SSLContextRef context) +{ + if (context) + CFRelease(context); +} + +} // unnamed namespace + +// To be also used by qtlsbackend_st.cpp (thus not in unnamed namespace). +SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) +{ + const bool isServer = mode == QSslSocket::SslServerMode; + const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; + // We never use kSSLDatagramType, so it's kSSLStreamType unconditionally. + SSLContextRef context = SSLCreateContext(nullptr, side, kSSLStreamType); + if (!context) + qCWarning(lcTlsBackend) << "SSLCreateContext failed"; + return context; +} + +QSecureTransportContext::QSecureTransportContext(SSLContextRef c) + : context(c) +{ +} + +QSecureTransportContext::~QSecureTransportContext() +{ + qt_releaseSecureTransportContext(context); +} + +QSecureTransportContext::operator SSLContextRef()const +{ + return context; +} + +void QSecureTransportContext::reset(SSLContextRef newContext) +{ + qt_releaseSecureTransportContext(context); + context = newContext; +} + +#if !defined(QT_PLATFORM_UIKIT) // dhparam is only used on macOS. (see the SSLSetDiffieHellmanParams call below) +static const uint8_t dhparam[] = + "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80" + "\x9c\x74\x56\x98\xd8\x56\x97\x2b\x20\x6c\x77\xe2\x82\xbb\xc8\x84\xbe\xe7" + "\x63\xaf\xcc\x30\xd0\x67\x97\x7d\x1b\xab\x59\x30\xa9\x13\x67\x21\xd7\xd4" + "\x0e\x46\xcf\xe5\x80\xdf\xc9\xb9\xba\x54\x9b\x46\x2f\x3b\x45\xfc\x2f\xaf" + "\xad\xc0\x17\x56\xdd\x52\x42\x57\x45\x70\x14\xe5\xbe\x67\xaa\xde\x69\x75" + "\x30\x0d\xf9\xa2\xc4\x63\x4d\x7a\x39\xef\x14\x62\x18\x33\x44\xa1\xf9\xc1" + "\x52\xd1\xb6\x72\x21\x98\xf8\xab\x16\x1b\x7b\x37\x65\xe3\xc5\x11\x00\xf6" + "\x36\x1f\xd8\x5f\xd8\x9f\x43\xa8\xce\x9d\xbf\x5e\xd6\x2d\xfa\x0a\xc2\x01" + "\x54\xc2\xd9\x81\x54\x55\xb5\x26\xf8\x88\x37\xf5\xfe\xe0\xef\x4a\x34\x81" + "\xdc\x5a\xb3\x71\x46\x27\xe3\xcd\x24\xf6\x1b\xf1\xe2\x0f\xc2\xa1\x39\x53" + "\x5b\xc5\x38\x46\x8e\x67\x4c\xd9\xdd\xe4\x37\x06\x03\x16\xf1\x1d\x7a\xba" + "\x2d\xc1\xe4\x03\x1a\x58\xe5\x29\x5a\x29\x06\x69\x61\x7a\xd8\xa9\x05\x9f" + "\xc1\xa2\x45\x9c\x17\xad\x52\x69\x33\xdc\x18\x8d\x15\xa6\x5e\xcd\x94\xf4" + "\x45\xbb\x9f\xc2\x7b\x85\x00\x61\xb0\x1a\xdc\x3c\x86\xaa\x9f\x5c\x04\xb3" + "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; +#endif + +OSStatus TlsCryptographSecureTransport::ReadCallback(TlsCryptographSecureTransport *socket, + char *data, size_t *dataLength) +{ + Q_ASSERT(socket); + Q_ASSERT(data); + Q_ASSERT(dataLength); + + Q_ASSERT(socket->d); + QTcpSocket *plainSocket = socket->d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + if (socket->isHandshakeComplete()) { + // Check if it's a renegotiation attempt, when the handshake is complete, the + // session state is 'kSSLConnected': + SSLSessionState currentState = kSSLConnected; + const OSStatus result = SSLGetSessionState(socket->context, ¤tState); + if (result != noErr) { + *dataLength = 0; + return result; + } + + if (currentState == kSSLHandshake) { + // Renegotiation detected, don't allow read more yet - 'transmit' + // will notice this and will call 'startHandshake': + *dataLength = 0; + socket->renegotiating = true; + return errSSLWouldBlock; + } + } + + const qint64 bytes = plainSocket->read(data, *dataLength); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "read" << bytes; +#endif + if (bytes < 0) { + *dataLength = 0; + return errSecIO; + } + + const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; + *dataLength = bytes; + + return err; +} + +OSStatus TlsCryptographSecureTransport::WriteCallback(TlsCryptographSecureTransport *socket, + const char *data, size_t *dataLength) +{ + Q_ASSERT(socket); + Q_ASSERT(data); + Q_ASSERT(dataLength); + + Q_ASSERT(socket->d); + QTcpSocket *plainSocket = socket->d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const qint64 bytes = plainSocket->write(data, *dataLength); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "write" << bytes; +#endif + if (bytes < 0) { + *dataLength = 0; + return errSecIO; + } + + const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; + *dataLength = bytes; + + return err; +} + +TlsCryptographSecureTransport::TlsCryptographSecureTransport() + : context(nullptr) +{ +} + +TlsCryptographSecureTransport::~TlsCryptographSecureTransport() +{ + destroySslContext(); +} + +void TlsCryptographSecureTransport::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + q = qObj; + d = dObj; + + renegotiating = false; + shutdown = false; +} + +void TlsCryptographSecureTransport::continueHandshake() +{ + Q_ASSERT(q); + Q_ASSERT(d); + d->setEncrypted(true); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "connection encrypted"; +#endif + +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) + // Unlike OpenSSL, Secure Transport does not allow to negotiate protocols via + // a callback during handshake. We can only set our list of preferred protocols + // (and send it during handshake) and then receive what our peer has sent to us. + // And here we can finally try to find a match (if any). + const auto &configuration = q->sslConfiguration(); + if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { + const auto &requestedProtocols = configuration.allowedNextProtocols(); + if (const int requestedCount = requestedProtocols.size()) { + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); + QTlsBackend::setNegotiatedProtocol(d, {}); + + QCFType<CFArrayRef> cfArray; + const OSStatus result = SSLCopyALPNProtocols(context, &cfArray); + if (result == errSecSuccess && cfArray && CFArrayGetCount(cfArray)) { + const int size = CFArrayGetCount(cfArray); + QList<QString> peerProtocols(size); + for (int i = 0; i < size; ++i) + peerProtocols[i] = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(cfArray, i)); + + for (int i = 0; i < requestedCount; ++i) { + const auto requestedName = QString::fromLatin1(requestedProtocols[i]); + for (int j = 0; j < size; ++j) { + if (requestedName == peerProtocols[j]) { + QTlsBackend::setNegotiatedProtocol(d, requestedName.toLatin1()); + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); + break; + } + } + if (configuration.nextProtocolNegotiationStatus() == QSslConfiguration::NextProtocolNegotiationNegotiated) + break; + } + } + } + } +#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE + + if (!renegotiating) + emit q->encrypted(); + + if (d->isAutoStartingHandshake() && d->isPendingClose()) { + d->setPendingClose(false); + q->disconnectFromHost(); + } +} + +void TlsCryptographSecureTransport::disconnected() +{ + Q_ASSERT(d && d->plainTcpSocket()); + if (d->plainTcpSocket()->bytesAvailable() <= 0) + destroySslContext(); + // If there is still buffered data in the plain socket, don't destroy the ssl context yet. + // It will be destroyed when the socket is deleted. +} + +void TlsCryptographSecureTransport::disconnectFromHost() +{ + Q_ASSERT(d && d->plainTcpSocket()); + if (context) { + if (!shutdown) { + SSLClose(context); + shutdown = true; + } + } + d->plainTcpSocket()->disconnectFromHost(); +} + +QSslCipher TlsCryptographSecureTransport::sessionCipher() const +{ + SSLCipherSuite cipher = 0; + if (context && SSLGetNegotiatedCipher(context, &cipher) == errSecSuccess) + return QSslCipher_from_SSLCipherSuite(cipher); + + return QSslCipher(); +} + +QSsl::SslProtocol TlsCryptographSecureTransport::sessionProtocol() const +{ + if (!context) + return QSsl::UnknownProtocol; + + SSLProtocol protocol = kSSLProtocolUnknown; + const OSStatus err = SSLGetNegotiatedProtocolVersion(context, &protocol); + if (err != errSecSuccess) { + qCWarning(lcTlsBackend) << "SSLGetNegotiatedProtocolVersion failed:" << err; + return QSsl::UnknownProtocol; + } + + switch (protocol) { + case kTLSProtocol1: + return QSsl::TlsV1_0; + case kTLSProtocol11: + return QSsl::TlsV1_1; + case kTLSProtocol12: + return QSsl::TlsV1_2; + case kTLSProtocol13: + return QSsl::TlsV1_3; + default: + return QSsl::UnknownProtocol; + } +} + +void TlsCryptographSecureTransport::startClientEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + // Error description/code were set, 'error' emitted + // by initSslContext, but OpenSSL socket also sets error, + // emits a signal twice, so ... + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); + return; + } + + startHandshake(); +} + +void TlsCryptographSecureTransport::startServerEncryption() +{ + if (!initSslContext()) { + // Error description/code were set, 'error' emitted + // by initSslContext, but OpenSSL socket also sets error + // emits a signal twice, so ... + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); + return; + } + + startHandshake(); +} + +void TlsCryptographSecureTransport::transmit() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + // If we don't have any SSL context, don't bother transmitting. + // Edit: if SSL session closed, don't bother either. + if (!context || shutdown) + return; + + if (!isHandshakeComplete()) + startHandshake(); + + auto &writeBuffer = d->tlsWriteBuffer(); + if (isHandshakeComplete() && !writeBuffer.isEmpty()) { + qint64 totalBytesWritten = 0; + while (writeBuffer.nextDataBlockSize() > 0 && context) { + const size_t nextDataBlockSize = writeBuffer.nextDataBlockSize(); + size_t writtenBytes = 0; + const OSStatus err = SSLWrite(context, writeBuffer.readPointer(), nextDataBlockSize, &writtenBytes); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLWrite returned" << err; +#endif + if (err != errSecSuccess && err != errSSLWouldBlock) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("SSLWrite failed: %1").arg(err)); + break; + } + + if (writtenBytes) { + writeBuffer.free(writtenBytes); + totalBytesWritten += writtenBytes; + } + + if (writtenBytes < nextDataBlockSize) + break; + } + + if (totalBytesWritten > 0) { + // Don't emit bytesWritten() recursively. + auto &emittedBytesWritten = d->tlsEmittedBytesWritten(); + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(totalBytesWritten); + emittedBytesWritten = false; + } + emit q->channelBytesWritten(0, totalBytesWritten); + } + } + + auto &buffer = d->tlsBuffer(); + const auto readBufferMaxSize = d->maxReadBufferSize(); + if (isHandshakeComplete()) { + QVarLengthArray<char, 4096> data; + while (context && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { + size_t readBytes = 0; + data.resize(4096); + const OSStatus err = SSLRead(context, data.data(), data.size(), &readBytes); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLRead returned" << err; +#endif + if (err == errSSLClosedGraceful) { + shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves + setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + break; + } else if (err != errSecSuccess && err != errSSLWouldBlock) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("SSLRead failed: %1").arg(err)); + break; + } + + if (err == errSSLWouldBlock && renegotiating) { + startHandshake(); + break; + } + + if (readBytes) { + buffer.append(data.constData(), readBytes); + if (bool *readyReadEmittedPointer = d->readyReadPointer()) + *readyReadEmittedPointer = true; + emit q->readyRead(); + emit q->channelReadyRead(0); + } + + if (err == errSSLWouldBlock) + break; + } + } +} + +SSLCipherSuite TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph) +{ + if (ciph.name() == QLatin1String("AES128-SHA")) + return TLS_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA")) + return TLS_DHE_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("AES256-SHA")) + return TLS_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA")) + return TLS_DHE_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-NULL-SHA")) + return TLS_ECDH_ECDSA_WITH_NULL_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) + return TLS_ECDH_ECDSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) + return TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) + return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) + return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) + return TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) + return TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) + return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) + return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-NULL-SHA")) + return TLS_ECDH_RSA_WITH_NULL_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) + return TLS_ECDH_RSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) + return TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) + return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) + return TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) + return TLS_ECDHE_RSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) + return TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) + return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) + return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("DES-CBC3-SHA")) + return TLS_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("AES128-SHA256")) + return TLS_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("AES256-SHA256")) + return TLS_RSA_WITH_AES_256_CBC_SHA256; + if (ciph.name() == QLatin1String("DHE-RSA-DES-CBC3-SHA")) + return TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA256")) + return TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA256")) + return TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; + if (ciph.name() == QLatin1String("AES256-GCM-SHA384")) + return TLS_RSA_WITH_AES_256_GCM_SHA384; + if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES128-SHA256")) + return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES256-SHA384")) + return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA256")) + return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA384")) + return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES128-SHA256")) + return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) + return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) + return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-GCM-SHA384")) + return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; + return 0; +} + +bool TlsCryptographSecureTransport::initSslContext() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + Q_ASSERT_X(!context, Q_FUNC_INFO, "invalid socket state, context is not null"); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const auto mode = d->tlsMode(); + + context.reset(qt_createSecureTransportContext(mode)); + if (!context) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("SSLCreateContext failed")); + return false; + } + + const OSStatus err = SSLSetIOFuncs(context, + reinterpret_cast<SSLReadFunc>(&TlsCryptographSecureTransport::ReadCallback), + reinterpret_cast<SSLWriteFunc>(&TlsCryptographSecureTransport::WriteCallback)); + if (err != errSecSuccess) { + destroySslContext(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); + return false; + } + + SSLSetConnection(context, this); + + const auto &configuration = q->sslConfiguration(); + if (mode == QSslSocket::SslServerMode + && !configuration.localCertificateChain().isEmpty()) { + QString errorDescription; + QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; + if (!setSessionCertificate(errorDescription, errorCode)) { + destroySslContext(); + setErrorAndEmit(d, errorCode, errorDescription); + return false; + } + } + + if (!setSessionProtocol()) { + destroySslContext(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("Failed to set protocol version")); + return false; + } + +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) + if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { + const auto protocolNames = configuration.allowedNextProtocols(); + QCFType<CFMutableArrayRef> cfNames(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)); + if (cfNames) { + for (const QByteArray &name : protocolNames) { + if (name.size() > 255) { + qCWarning(lcTlsBackend) << "TLS ALPN extension" << name + << "is too long and will be ignored."; + continue; + } else if (name.isEmpty()) { + continue; + } + QCFString cfName(QString::fromLatin1(name).toCFString()); + CFArrayAppendValue(cfNames, cfName); + } + + if (CFArrayGetCount(cfNames)) { + // Up to the application layer to check that negotiation + // failed, and handle this non-TLS error, we do not handle + // the result of this call as an error: + if (SSLSetALPNProtocols(context, cfNames) != errSecSuccess) + qCWarning(lcTlsBackend) << "SSLSetALPNProtocols failed - too long protocol names?"; + } + } else { + qCWarning(lcTlsBackend) << "failed to allocate ALPN names array"; + } + } +#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE + + if (mode == QSslSocket::SslClientMode) { + // enable Server Name Indication (SNI) + const auto verificationPeerName = d->verificationName(); + QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); + if (tlsHostName.isEmpty()) + tlsHostName = d->tlsHostName(); + + const QByteArray ace(QUrl::toAce(tlsHostName)); + SSLSetPeerDomainName(context, ace.data(), ace.size()); + // tell SecureTransport we handle peer verification ourselves + OSStatus err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); + if (err == errSecSuccess) + err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnCertRequested, true); + + if (err != errSecSuccess) { + destroySslContext(); + setErrorAndEmit(d, QSslSocket::SslInternalError, + QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); + return false; + } + // + } else { + if (configuration.peerVerifyMode() != QSslSocket::VerifyNone) { + // kAlwaysAuthenticate - always fails even if we set break on client auth. + OSStatus err = SSLSetClientSideAuthenticate(context, kTryAuthenticate); + if (err == errSecSuccess) { + // We'd like to verify peer ourselves, otherwise handshake will + // most probably fail before we can do anything. + err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnClientAuth, true); + } + + if (err != errSecSuccess) { + destroySslContext(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); + return false; + } + } +#if !defined(QT_PLATFORM_UIKIT) + // No SSLSetDiffieHellmanParams on iOS; calling it is optional according to docs. + SSLSetDiffieHellmanParams(context, dhparam, sizeof(dhparam)); +#endif + } + if (configuration.ciphers().size() > 0) { + QVector<SSLCipherSuite> cfCiphers; + for (const QSslCipher &cipher : configuration.ciphers()) { + if (auto sslCipher = TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(cipher)) + cfCiphers << sslCipher; + } + if (cfCiphers.size() == 0) { + qCWarning(lcTlsBackend) << "failed to add any of the requested ciphers from the configuration"; + return false; + } + OSStatus err = SSLSetEnabledCiphers(context, cfCiphers.data(), cfCiphers.size()); + if (err != errSecSuccess) { + qCWarning(lcTlsBackend) << "failed to set the ciphers from the configuration"; + return false; + } + } + return true; +} + +void TlsCryptographSecureTransport::destroySslContext() +{ + context.reset(nullptr); +} + +bool TlsCryptographSecureTransport::setSessionCertificate(QString &errorDescription, QAbstractSocket::SocketError &errorCode) +{ + Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + + Q_ASSERT(d); + const auto &configuration = q->sslConfiguration(); + +#ifdef QSSLSOCKET_DEBUG + auto *plainSocket = d->plainTcpSocket(); +#endif + + QSslCertificate localCertificate; + + if (!configuration.localCertificateChain().isEmpty()) + localCertificate = configuration.localCertificateChain().at(0); + + if (!localCertificate.isNull()) { + // Require a private key as well. + if (configuration.privateKey().isNull()) { + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("Cannot provide a certificate with no key"); + return false; + } + + // import certificates and key + const QString passPhrase(QString::fromLatin1("foobar")); + QCFType<CFDataRef> pkcs12 = _q_makePkcs12(configuration.localCertificateChain(), + configuration.privateKey(), passPhrase).toCFData(); + QCFType<CFStringRef> password = passPhrase.toCFString(); + const void *keys[2] = { kSecImportExportPassphrase }; + const void *values[2] = { password }; + CFIndex nKeys = 1; +#ifdef Q_OS_MACOS + bool envOk = false; + const int env = qEnvironmentVariableIntValue("QT_SSL_USE_TEMPORARY_KEYCHAIN", &envOk); + if (envOk && env) { + static const EphemeralSecKeychain temporaryKeychain; + if (temporaryKeychain.keychain) { + nKeys = 2; + keys[1] = kSecImportExportKeychain; + values[1] = temporaryKeychain.keychain; + } + } +#endif + QCFType<CFDictionaryRef> options = CFDictionaryCreate(nullptr, keys, values, nKeys, + nullptr, nullptr); + QCFType<CFArrayRef> items; + OSStatus err = SecPKCS12Import(pkcs12, options, &items); + if (err != errSecSuccess) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) << plainSocket + << QStringLiteral("SecPKCS12Import failed: %1").arg(err); +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import failed: %1").arg(err); + return false; + } + + if (!CFArrayGetCount(items)) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no items"; +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import returned no items"); + return false; + } + + CFDictionaryRef import = (CFDictionaryRef)CFArrayGetValueAtIndex(items, 0); + SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(import, kSecImportItemIdentity); + if (!identity) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no identity"; +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import returned no identity"); + return false; + } + + QCFType<CFMutableArrayRef> certs = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); + if (!certs) { + errorCode = QAbstractSocket::SslInternalError; + errorDescription = QStringLiteral("Failed to allocate certificates array"); + return false; + } + + CFArrayAppendValue(certs, identity); + + CFArrayRef chain = (CFArrayRef)CFDictionaryGetValue(import, kSecImportItemCertChain); + if (chain) { + for (CFIndex i = 1, e = CFArrayGetCount(chain); i < e; ++i) + CFArrayAppendValue(certs, CFArrayGetValueAtIndex(chain, i)); + } + + err = SSLSetCertificate(context, certs); + if (err != errSecSuccess) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) + << plainSocket << QStringLiteral("Cannot set certificate and key: %1").arg(err); +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("Cannot set certificate and key: %1").arg(err); + return false; + } + } + + return true; +} + +bool TlsCryptographSecureTransport::setSessionProtocol() +{ + Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + Q_ASSERT(q); + Q_ASSERT(d); + // SecureTransport has kTLSProtocol13 constant and also, kTLSProtocolMaxSupported. + // Calling SSLSetProtocolVersionMax/Min with any of these two constants results + // in errInvalidParam and a failure to set the protocol version. This means + // no TLS 1.3 on macOS and iOS. + const auto &configuration = q->sslConfiguration(); + auto *plainSocket = d->plainTcpSocket(); + switch (configuration.protocol()) { + case QSsl::TlsV1_3: + case QSsl::TlsV1_3OrLater: + qCWarning(lcTlsBackend) << plainSocket << "SecureTransport does not support TLS 1.3"; + return false; + default:; + } + + OSStatus err = errSecSuccess; + + if (configuration.protocol() == QSsl::TlsV1_0) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.0"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + if (err == errSecSuccess) + err = SSLSetProtocolVersionMax(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::TlsV1_1) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol11); + if (err == errSecSuccess) + err = SSLSetProtocolVersionMax(context, kTLSProtocol11); + } else if (configuration.protocol() == QSsl::TlsV1_2) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol12); + if (err == errSecSuccess) + err = SSLSetProtocolVersionMax(context, kTLSProtocol12); + } else if (configuration.protocol() == QSsl::AnyProtocol) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : any"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::SecureProtocols) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::TlsV1_0OrLater) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::TlsV1_1OrLater) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol11); + } else if (configuration.protocol() == QSsl::TlsV1_2OrLater) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol12); + } else { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "no protocol version found in the configuration"; + #endif + return false; + } + + return err == errSecSuccess; +} + +bool TlsCryptographSecureTransport::canIgnoreTrustVerificationFailure() const +{ + Q_ASSERT(q); + Q_ASSERT(d); + const auto &configuration = q->sslConfiguration(); + const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode(); + return d->tlsMode() == QSslSocket::SslServerMode + && (verifyMode == QSslSocket::QueryPeer + || verifyMode == QSslSocket::AutoVerifyPeer + || verifyMode == QSslSocket::VerifyNone); +} + +bool TlsCryptographSecureTransport::verifySessionProtocol() const +{ + Q_ASSERT(q); + + const auto &configuration = q->sslConfiguration(); + bool protocolOk = false; + if (configuration.protocol() == QSsl::AnyProtocol) + protocolOk = true; + else if (configuration.protocol() == QSsl::SecureProtocols) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); + else if (configuration.protocol() == QSsl::TlsV1_0OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); + else if (configuration.protocol() == QSsl::TlsV1_1OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_1); + else if (configuration.protocol() == QSsl::TlsV1_2OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_2); + else if (configuration.protocol() == QSsl::TlsV1_3OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_3OrLater); + else + protocolOk = (sessionProtocol() == configuration.protocol()); + + return protocolOk; +} + +bool TlsCryptographSecureTransport::verifyPeerTrust() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + const auto mode = d->tlsMode(); + const QSslSocket::PeerVerifyMode verifyMode = q->peerVerifyMode(); + const bool canIgnoreVerify = canIgnoreTrustVerificationFailure(); + + Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + QCFType<SecTrustRef> trust; + OSStatus err = SSLCopyPeerTrust(context, &trust); + // !trust - SSLCopyPeerTrust can return errSecSuccess but null trust. + if (err != errSecSuccess || !trust) { + if (!canIgnoreVerify) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("Failed to obtain peer trust: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; + } else { + return true; + } + } + + QList<QSslError> errors; + + // Store certificates. + // Apple's docs say SetTrustEvaluate must be called before + // SecTrustGetCertificateAtIndex, but this results + // in 'kSecTrustResultRecoverableTrustFailure', so + // here we just ignore 'res' (later we'll use SetAnchor etc. + // and evaluate again). + SecTrustResultType res = kSecTrustResultInvalid; + err = SecTrustEvaluate(trust, &res); + if (err != errSecSuccess) { + // We can not ignore this, it's not even about trust verification + // probably ... + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; + } + + QTlsBackend::clearPeerCertificates(d); + + QList<QSslCertificate> peerCertificateChain; + const CFIndex certCount = SecTrustGetCertificateCount(trust); + for (CFIndex i = 0; i < certCount; ++i) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); + QCFType<CFDataRef> derData = SecCertificateCopyData(cert); + peerCertificateChain << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + } + QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); + + if (peerCertificateChain.size()) + QTlsBackend::storePeerCertificate(d, peerCertificateChain.at(0)); + + // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer): + for (const QSslCertificate &cert : qAsConst(peerCertificateChain)) { + if (QSslCertificatePrivate::isBlacklisted(cert) && !canIgnoreVerify) { + const QSslError error(QSslError::CertificateBlacklisted, cert); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + const bool doVerifyPeer = verifyMode == QSslSocket::VerifyPeer + || (verifyMode == QSslSocket::AutoVerifyPeer + && d->tlsMode() == QSslSocket::SslClientMode); + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + const auto &peerCertificate = q->peerCertificate(); + if (!peerCertificate.isNull()) { + // but only if we're a client connecting to a server + // if we're the server, don't check CN + const QString verificationPeerName = d->verificationName(); + if (mode == QSslSocket::SslClientMode) { + const QString peerName(verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); + if (!isMatchingHostname(peerCertificate, peerName) && !canIgnoreVerify) { + // No matches in common names or alternate names. + const QSslError error(QSslError::HostNameMismatch, peerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } else { + // No peer certificate presented. Report as error if the socket + // expected one. + if (doVerifyPeer && !canIgnoreVerify) { + const QSslError error(QSslError::NoPeerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + // verify certificate chain + QCFType<CFMutableArrayRef> certArray = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); + const auto &caCertificates = q->sslConfiguration().caCertificates(); + for (const QSslCertificate &cert : caCertificates) { + QCFType<CFDataRef> certData = cert.toDer().toCFData(); + if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, certData)) + CFArrayAppendValue(certArray, secRef); + else + qCWarning(lcTlsBackend, "Failed to create SecCertificate from QSslCertificate"); + } + + SecTrustSetAnchorCertificates(trust, certArray); + + // By default SecTrustEvaluate uses both CA certificates provided in + // QSslConfiguration and the ones from the system database. This behavior can + // be unexpected if a user's code tries to limit the trusted CAs to those + // explicitly set in QSslConfiguration. + // Since on macOS we initialize the default QSslConfiguration copying the + // system CA certificates (using SecTrustSettingsCopyCertificates) we can + // call SecTrustSetAnchorCertificatesOnly(trust, true) to force SecTrustEvaluate + // to use anchors only from our QSslConfiguration. + // Unfortunately, SecTrustSettingsCopyCertificates is not available on iOS + // and the default QSslConfiguration always has an empty list of system CA + // certificates. This leaves no way to provide client code with access to the + // actual system CA certificate list (which most use-cases need) other than + // by letting SecTrustEvaluate fall through to the system list; so, in this case + // (even though the client code may have provided its own certs), we retain + // the default behavior. Note, with macOS SDK below 10.12 using 'trust my + // anchors only' may result in some valid chains rejected, apparently the + // ones containing intermediated certificates; so we use this functionality + // on more recent versions only. + + bool anchorsFromConfigurationOnly = false; + +#ifdef Q_OS_MACOS + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) + anchorsFromConfigurationOnly = true; +#endif // Q_OS_MACOS + + SecTrustSetAnchorCertificatesOnly(trust, anchorsFromConfigurationOnly); + + SecTrustResultType trustResult = kSecTrustResultInvalid; + SecTrustEvaluate(trust, &trustResult); + switch (trustResult) { + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + break; + default: + if (!canIgnoreVerify) { + const QSslError error(QSslError::CertificateUntrusted, peerCertificate); + errors << error; + emit q->peerVerifyError(error); + } + } + + // report errors + if (!errors.isEmpty() && !canIgnoreVerify) { + sslErrors = errors; + // checkSslErrors unconditionally emits sslErrors: + // a user's slot can abort/close/disconnect on this + // signal, so we also test the socket's state: + if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) + return false; + } else { + sslErrors.clear(); + } + + return true; +} + +/* + Copied verbatim from qsslsocket_openssl.cpp +*/ +bool TlsCryptographSecureTransport::checkSslErrors() +{ + if (sslErrors.isEmpty()) + return true; + + Q_ASSERT(q); + Q_ASSERT(d); + + emit q->sslErrors(sslErrors); + const auto mode = d->tlsMode(); + const auto &configuration = q->sslConfiguration(); + const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); + // check whether we need to emit an SSL handshake error + if (doVerifyPeer && doEmitSslError) { + if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); + } else { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + sslErrors.constFirst().errorString()); + Q_ASSERT(d->plainTcpSocket()); + d->plainTcpSocket()->disconnectFromHost(); + } + return false; + } + + return true; +} + +bool TlsCryptographSecureTransport::startHandshake() +{ + Q_ASSERT(context); + Q_ASSERT(q); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + const auto mode = d->tlsMode(); + + OSStatus err = SSLHandshake(context); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "SSLHandhake returned" << err; +#endif + + if (err == errSSLWouldBlock) { + // startHandshake has to be called again ... later. + return false; + } else if (err == errSSLServerAuthCompleted) { + // errSSLServerAuthCompleted is a define for errSSLPeerAuthCompleted, + // it works for both server/client modes. + // In future we'll evaluate peer's trust at this point, + // for now we just continue. + // if (!verifyPeerTrust()) + // ... + return startHandshake(); + } else if (err == errSSLClientCertRequested) { + Q_ASSERT(mode == QSslSocket::SslClientMode); + QString errorDescription; + QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; + // setSessionCertificate does not fail if we have no certificate. + // Failure means a real error (invalid certificate, no private key, etc). + if (!setSessionCertificate(errorDescription, errorCode)) { + setErrorAndEmit(d, errorCode, errorDescription); + renegotiating = false; + return false; + } else { + // We try to resume a handshake, even if have no + // local certificates ... (up to server to deal with our failure). + return startHandshake(); + } + } else if (err != errSecSuccess) { + if (err == errSSLBadCert && canIgnoreTrustVerificationFailure()) { + // We're on the server side and client did not provide any + // certificate. This is the new 'nice' error returned by + // Security Framework after it was recently updated. + return startHandshake(); + } + + renegotiating = false; + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SSLHandshake failed: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; + } + + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) { + qCDebug(lcTlsBackend) << "connection aborted"; + renegotiating = false; + return false; + } + + // check protocol version ourselves, as Secure Transport does not enforce + // the requested min / max versions. + if (!verifySessionProtocol()) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); + plainSocket->disconnectFromHost(); + renegotiating = false; + return false; + } + + if (verifyPeerTrust()) { + continueHandshake(); + renegotiating = false; + return true; + } else { + renegotiating = false; + return false; + } +} + +bool TlsCryptographSecureTransport::isHandshakeComplete() const +{ + Q_ASSERT(q); + return q->isEncrypted() && !renegotiating; +} + +QList<QSslError> TlsCryptographSecureTransport::tlsErrors() const +{ + return sslErrors; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/securetransport/qtls_st_p.h b/src/plugins/tls/securetransport/qtls_st_p.h new file mode 100644 index 0000000000..3dad24f348 --- /dev/null +++ b/src/plugins/tls/securetransport/qtls_st_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTLS_ST_P_H +#define QTLS_ST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QtNetwork library. 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 "qtlsbackend_st_p.h" + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/private/qsslsocket_p.h> + +#include <Security/Security.h> +#include <Security/SecureTransport.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class QSecureTransportContext +{ +public: + explicit QSecureTransportContext(SSLContextRef context); + ~QSecureTransportContext(); + + operator SSLContextRef () const; + void reset(SSLContextRef newContext); +private: + SSLContextRef context; + + Q_DISABLE_COPY_MOVE(QSecureTransportContext) +}; + +class TlsCryptographSecureTransport : public TlsCryptograph +{ +public: + TlsCryptographSecureTransport(); + ~TlsCryptographSecureTransport() override; + + void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; + void continueHandshake() override; + void disconnected() override; + void disconnectFromHost() override; + QSslCipher sessionCipher() const override; + QSsl::SslProtocol sessionProtocol() const override; + void startClientEncryption() override; + void startServerEncryption() override; + void transmit() override; + QList<QSslError> tlsErrors() const override; + + SSLCipherSuite SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph); + +private: + // SSL context management/properties: + bool initSslContext(); + void destroySslContext(); + bool setSessionCertificate(QString &errorDescription, + QAbstractSocket::SocketError &errorCode); + bool setSessionProtocol(); + // Aux. functions to do a verification during handshake phase: + bool canIgnoreTrustVerificationFailure() const; + bool verifySessionProtocol() const; + bool verifyPeerTrust(); + + bool checkSslErrors(); + bool startHandshake(); + + bool isHandshakeComplete() const; + + // IO callbacks: + static OSStatus ReadCallback(TlsCryptographSecureTransport *socket, char *data, size_t *dataLength); + static OSStatus WriteCallback(TlsCryptographSecureTransport *plainSocket, const char *data, size_t *dataLength); + + QSecureTransportContext context; + bool renegotiating = false; + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; + bool shutdown = false; + QList<QSslError> sslErrors; + + Q_DISABLE_COPY_MOVE(TlsCryptographSecureTransport) +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLS_ST_P_H diff --git a/src/plugins/tls/securetransport/qtlsbackend_st.cpp b/src/plugins/tls/securetransport/qtlsbackend_st.cpp new file mode 100644 index 0000000000..7fc7692350 --- /dev/null +++ b/src/plugins/tls/securetransport/qtlsbackend_st.cpp @@ -0,0 +1,341 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtlsbackend_st_p.h" +#include "qtlskey_st_p.h" +#include "qx509_st_p.h" +#include "qtls_st_p.h" + +#include <QtCore/qsysinfo.h> +#include <QtCore/qmutex.h> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QRecursiveMutex, qt_securetransport_mutex) + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.securetransport"); + +namespace QTlsPrivate { + +QList<QSslCertificate> systemCaCertificates(); // defined in qsslsocket_mac_shared.cpp + +SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode); + +QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher) +{ + QString name; + switch (cipher) { + // Sorted as in CipherSuite.h (and groupped by their RFC) + // TLS addenda using AES, per RFC 3268 + case TLS_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("AES128-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("DHE-RSA-AES128-SHA"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("AES256-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("DHE-RSA-AES256-SHA"); + break; + + // ECDSA addenda, RFC 4492 + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + name = QLatin1String("ECDH-ECDSA-NULL-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDH-ECDSA-RC4-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-DES-CBC3-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-AES128-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-AES256-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + name = QLatin1String("ECDHE-ECDSA-NULL-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDHE-ECDSA-RC4-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-DES-CBC3-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-AES128-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); + break; + case TLS_ECDH_RSA_WITH_NULL_SHA: + name = QLatin1String("ECDH-RSA-NULL-SHA"); + break; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDH-RSA-RC4-SHA"); + break; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDH-RSA-DES-CBC3-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDH-RSA-AES128-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDH-RSA-AES256-SHA"); + break; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + name = QLatin1String("ECDHE-RSA-NULL-SHA"); + break; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDHE-RSA-RC4-SHA"); + break; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDHE-RSA-DES-CBC3-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDHE-RSA-AES128-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDHE-RSA-AES256-SHA"); + break; + + // TLS 1.2 addenda, RFC 5246 + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("DES-CBC3-SHA"); + break; + case TLS_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("AES128-SHA256"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + name = QLatin1String("AES256-SHA256"); + break; + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("DHE-RSA-DES-CBC3-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("DHE-RSA-AES128-SHA256"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + name = QLatin1String("DHE-RSA-AES256-SHA256"); + break; + + // Addendum from RFC 4279, TLS PSK + // all missing atm. + + // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption + // all missing atm. + + // Addenda from rfc 5288 AES Galois Counter Mode (CGM) Cipher Suites for TLS + case TLS_RSA_WITH_AES_256_GCM_SHA384: + name = QLatin1String("AES256-GCM-SHA384"); + break; + + // RFC 5487 - PSK with SHA-256/384 and AES GCM + // all missing atm. + + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384 + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); + break; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); + break; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDHE-RSA-AES128-SHA256"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDHE-RSA-AES256-SHA384"); + break; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDH-RSA-AES128-SHA256"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDH-RSA-AES256-SHA384"); + break; + + // Addenda from rfc 5289 Elliptic Curve Cipher Suites + // with SHA-256/384 and AES Galois Counter Mode (GCM) + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + name = QLatin1String("ECDHE-RSA-AES256-GCM-SHA384"); + break; + + default: + return {}; + } + + return QTlsBackend::createCiphersuite(name, QSsl::TlsV1_2, QLatin1String("TLSv1.2")); +} + +} // namespace QTlsPrivate + +bool QSecureTransportBackend::s_loadedCiphersAndCerts = false; + +QString QSecureTransportBackend::tlsLibraryVersionString() const +{ + return QLatin1String("Secure Transport, ") + QSysInfo::prettyProductName(); +} + +QString QSecureTransportBackend::tlsLibraryBuildVersionString() const +{ + return tlsLibraryVersionString(); +} + +void QSecureTransportBackend::ensureInitialized() const +{ + const QMutexLocker locker(qt_securetransport_mutex()); + if (s_loadedCiphersAndCerts) + return; + + // We have to set it before setDefaultSupportedCiphers, + // since this function can trigger static (global)'s initialization + // and as a result - recursive ensureInitialized call + // from QSslCertificatePrivate's ctor. + s_loadedCiphersAndCerts = true; + + const QTlsPrivate::QSecureTransportContext context(QTlsPrivate::qt_createSecureTransportContext(QSslSocket::SslClientMode)); + if (context) { + QList<QSslCipher> ciphers; + QList<QSslCipher> defaultCiphers; + + size_t numCiphers = 0; + // Fails only if any of parameters is null. + SSLGetNumberSupportedCiphers(context, &numCiphers); + QList<SSLCipherSuite> cfCiphers(numCiphers); + // Fails only if any of parameter is null or number of ciphers is wrong. + SSLGetSupportedCiphers(context, cfCiphers.data(), &numCiphers); + + for (size_t i = 0; i < size_t(cfCiphers.size()); ++i) { + const QSslCipher ciph(QTlsPrivate::QSslCipher_from_SSLCipherSuite(cfCiphers.at(i))); + if (!ciph.isNull()) { + ciphers << ciph; + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(defaultCiphers); + + if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + setDefaultCaCertificates(systemCaCertificates()); + } else { + s_loadedCiphersAndCerts = false; + } +} + +QString QSecureTransportBackend::backendName() const +{ + return builtinBackendNames[nameIndexSecureTransport]; +} + +QTlsPrivate::TlsKey *QSecureTransportBackend::createKey() const +{ + return new QTlsPrivate::TlsKeySecureTransport; +} + +QTlsPrivate::X509Certificate *QSecureTransportBackend::createCertificate() const +{ + return new QTlsPrivate::X509CertificateSecureTransport; +} + +QList<QSslCertificate> QSecureTransportBackend::systemCaCertificates() const +{ + return QTlsPrivate::systemCaCertificates(); +} + +QList<QSsl::SslProtocol> QSecureTransportBackend::supportedProtocols() const +{ + QList<QSsl::SslProtocol> protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + + return protocols; +} + +QList<QSsl::SupportedFeature> QSecureTransportBackend::supportedFeatures() const +{ + QList<QSsl::SupportedFeature> features; + features << QSsl::SupportedFeature::ClientSideAlpn; + + return features; +} + +QList<QSsl::ImplementedClass> QSecureTransportBackend::implementedClasses() const +{ + QList<QSsl::ImplementedClass> classes; + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; +} + +QTlsPrivate::X509PemReaderPtr QSecureTransportBackend::X509PemReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; +} + +QTlsPrivate::X509DerReaderPtr QSecureTransportBackend::X509DerReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; +} + +QTlsPrivate::TlsCryptograph *QSecureTransportBackend::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographSecureTransport; +} + +QT_END_NAMESPACE + diff --git a/src/plugins/tls/securetransport/qtlsbackend_st_p.h b/src/plugins/tls/securetransport/qtlsbackend_st_p.h new file mode 100644 index 0000000000..ebce859db3 --- /dev/null +++ b/src/plugins/tls/securetransport/qtlsbackend_st_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTLSBACKEND_ST_P_H +#define QTLSBACKEND_ST_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 <QtCore/qglobal.h> + + +QT_BEGIN_NAMESPACE + +class QSecureTransportBackend : public QTlsBackend +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QTlsBackend_iid) + Q_INTERFACES(QTlsBackend) + +private: + + QString tlsLibraryVersionString() const override; + virtual QString tlsLibraryBuildVersionString() const override; + virtual void ensureInitialized() const override; + + QString backendName() const override; + + QList<QSsl::SslProtocol> supportedProtocols() const override; + QList<QSsl::SupportedFeature> supportedFeatures() const override; + QList<QSsl::ImplementedClass> implementedClasses() const override; + + QTlsPrivate::TlsKey *createKey() const override; + QTlsPrivate::X509Certificate *createCertificate() const override; + + QList<QSslCertificate> systemCaCertificates() const override; + + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; + QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + + QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; + + static bool s_loadedCiphersAndCerts; +}; + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_ST_P_H + + diff --git a/src/plugins/tls/securetransport/qtlskey_st.cpp b/src/plugins/tls/securetransport/qtlskey_st.cpp new file mode 100644 index 0000000000..85f86c7bf8 --- /dev/null +++ b/src/plugins/tls/securetransport/qtlskey_st.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtlskey_st_p.h" + +#include <QtNetwork/private/qsslkey_p.h> + +#include <QtCore/qbytearray.h> + +#include <CommonCrypto/CommonCrypto.h> + +#include <cstddef> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { +namespace { + +// Before this code was located in qsslkey_mac.cpp. +QByteArray wrapCCCrypt(CCOperation ccOp, QSslKeyPrivate::Cipher cipher, + const QByteArray &data, const QByteArray &key, + const QByteArray &iv) +{ + int blockSize = {}; + CCAlgorithm ccAlgorithm = {}; + switch (cipher) { + case Cipher::DesCbc: + blockSize = kCCBlockSizeDES; + ccAlgorithm = kCCAlgorithmDES; + break; + case Cipher::DesEde3Cbc: + blockSize = kCCBlockSize3DES; + ccAlgorithm = kCCAlgorithm3DES; + break; + case Cipher::Rc2Cbc: + blockSize = kCCBlockSizeRC2; + ccAlgorithm = kCCAlgorithmRC2; + break; + case Cipher::Aes128Cbc: + case Cipher::Aes192Cbc: + case Cipher::Aes256Cbc: + blockSize = kCCBlockSizeAES128; + ccAlgorithm = kCCAlgorithmAES; + break; + } + std::size_t plainLength = 0; + QByteArray plain(data.size() + blockSize, 0); + CCCryptorStatus status = CCCrypt(ccOp, ccAlgorithm, kCCOptionPKCS7Padding, + key.constData(), std::size_t(key.size()), + iv.constData(), data.constData(), std::size_t(data.size()), + plain.data(), std::size_t(plain.size()), &plainLength); + if (status == kCCSuccess) + return plain.left(int(plainLength)); + + return {}; +} + +} // Unnamed namespace. + +QByteArray TlsKeySecureTransport::decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return wrapCCCrypt(kCCDecrypt, cipher, data, key, iv); +} + +QByteArray TlsKeySecureTransport::encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return wrapCCCrypt(kCCEncrypt, cipher, data, key, iv); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/securetransport/qtlskey_st_p.h b/src/plugins/tls/securetransport/qtlskey_st_p.h new file mode 100644 index 0000000000..c9dcc4e3ec --- /dev/null +++ b/src/plugins/tls/securetransport/qtlskey_st_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTLSKEY_ST_P_H +#define QTLSKEY_ST_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 "../shared/qtlskey_generic_p.h" + +#include <QtCore/qglobal.h> + +QT_REQUIRE_CONFIG(ssl); + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsKeySecureTransport final : public TlsKeyGeneric +{ +public: + using TlsKeyGeneric::TlsKeyGeneric; + + QByteArray decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + QByteArray encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + + Q_DISABLE_COPY_MOVE(TlsKeySecureTransport) +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_ST_P_H diff --git a/src/plugins/tls/securetransport/qx509_st.cpp b/src/plugins/tls/securetransport/qx509_st.cpp new file mode 100644 index 0000000000..737b15cef8 --- /dev/null +++ b/src/plugins/tls/securetransport/qx509_st.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtlskey_st_p.h" +#include "qx509_st_p.h" + +#include <memory> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +TlsKey *X509CertificateSecureTransport::publicKey() const +{ + auto key = std::make_unique<TlsKeySecureTransport>(QSsl::PublicKey); + if (publicKeyAlgorithm != QSsl::Opaque) + key->decodeDer(QSsl::PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); + + return key.release(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + diff --git a/src/plugins/tls/securetransport/qx509_st_p.h b/src/plugins/tls/securetransport/qx509_st_p.h new file mode 100644 index 0000000000..5e5b42e791 --- /dev/null +++ b/src/plugins/tls/securetransport/qx509_st_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QX509_ST_P_H +#define QX509_ST_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 "../shared/qx509_generic_p.h" + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateSecureTransport final : public X509CertificateGeneric +{ +public: + TlsKey *publicKey() const override; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_ST_P_H |