/****************************************************************************
**
** 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(lcSecureTransport) << "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(lcSecureTransport) << "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(lcSecureTransport) << "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(lcSecureTransport) << "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(lcSecureTransport) << "SecKeychainSettings: failed to disable lock on sleep";
    }

#ifdef QSSLSOCKET_DEBUG
    if (keychain) {
        qCDebug(lcSecureTransport) << "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(lcSecureTransport) << "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, &currentState);
        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(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << 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());
    d->setEncrypted(false);
    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(lcSecureTransport) << "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(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << "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(lcSecureTransport) << "SSLSetALPNProtocols failed - too long protocol names?";
            }
        } else {
            qCWarning(lcSecureTransport) << "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(lcSecureTransport) << "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(lcSecureTransport) << "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(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << plainSocket << "SecureTransport does not support TLS 1.3";
        return false;
    default:;
    }

    OSStatus err = errSecSuccess;

    if (configuration.protocol() == QSsl::TlsV1_0) {
    #ifdef QSSLSOCKET_DEBUG
        qCDebug(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << 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(lcSecureTransport) << plainSocket << "requesting : any";
    #endif
        err = SSLSetProtocolVersionMin(context, kTLSProtocol1);
    } else if (configuration.protocol() == QSsl::SecureProtocols) {
    #ifdef QSSLSOCKET_DEBUG
        qCDebug(lcSecureTransport) << plainSocket << "requesting : TLSv1 - TLSv1.2";
    #endif
        err = SSLSetProtocolVersionMin(context, kTLSProtocol1);
    } else if (configuration.protocol() == QSsl::TlsV1_0OrLater) {
    #ifdef QSSLSOCKET_DEBUG
        qCDebug(lcSecureTransport) << plainSocket << "requesting : TLSv1 - TLSv1.2";
    #endif
        err = SSLSetProtocolVersionMin(context, kTLSProtocol1);
    } else if (configuration.protocol() == QSsl::TlsV1_1OrLater) {
    #ifdef QSSLSOCKET_DEBUG
        qCDebug(lcSecureTransport) << plainSocket << "requesting : TLSv1.1 - TLSv1.2";
    #endif
        err = SSLSetProtocolVersionMin(context, kTLSProtocol11);
    } else if (configuration.protocol() == QSsl::TlsV1_2OrLater) {
    #ifdef QSSLSOCKET_DEBUG
        qCDebug(lcSecureTransport) << plainSocket << "requesting : TLSv1.2";
    #endif
        err = SSLSetProtocolVersionMin(context, kTLSProtocol12);
    } else {
    #ifdef QSSLSOCKET_DEBUG
        qCDebug(lcSecureTransport) << 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(lcSecureTransport, "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(lcSecureTransport) << 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(lcSecureTransport) << "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
