diff options
Diffstat (limited to 'src/network/ssl/qsslsocket_mac.cpp')
-rw-r--r-- | src/network/ssl/qsslsocket_mac.cpp | 193 |
1 files changed, 114 insertions, 79 deletions
diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp index aa0e1b0dd1..730a9552b5 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -48,6 +48,7 @@ #include <QtCore/qmessageauthenticationcode.h> #include <QtCore/qoperatingsystemversion.h> +#include <QtCore/qscopedvaluerollback.h> #include <QtCore/qcryptographichash.h> #include <QtCore/qsystemdetection.h> #include <QtCore/qdatastream.h> @@ -242,48 +243,70 @@ static const uint8_t dhparam[] = "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; #endif -// No ioErr on iOS/tvOS/watchOS. (defined in MacErrors.h on macOS) -#if defined(QT_PLATFORM_UIKIT) -# define ioErr -36 -#endif - -static OSStatus _q_SSLRead(QTcpSocket *plainSocket, char *data, size_t *dataLength) +OSStatus QSslSocketBackendPrivate::ReadCallback(QSslSocketBackendPrivate *socket, + char *data, size_t *dataLength) { - Q_ASSERT(plainSocket); + Q_ASSERT(socket); Q_ASSERT(data); Q_ASSERT(dataLength); + QTcpSocket *plainSocket = socket->plainSocket; + 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(lcSsl) << plainSocket << "read" << bytes; #endif if (bytes < 0) { *dataLength = 0; - return ioErr; + return errSecIO; } - const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : noErr; + const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; *dataLength = bytes; return err; } -static OSStatus _q_SSLWrite(QTcpSocket *plainSocket, const char *data, size_t *dataLength) +OSStatus QSslSocketBackendPrivate::WriteCallback(QSslSocketBackendPrivate *socket, + const char *data, size_t *dataLength) { - Q_ASSERT(plainSocket); + Q_ASSERT(socket); Q_ASSERT(data); Q_ASSERT(dataLength); + QTcpSocket *plainSocket = socket->plainSocket; + Q_ASSERT(plainSocket); + const qint64 bytes = plainSocket->write(data, *dataLength); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "write" << bytes; #endif if (bytes < 0) { *dataLength = 0; - return ioErr; + return errSecIO; } - const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : noErr; + const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; *dataLength = bytes; return err; @@ -387,12 +410,12 @@ void QSslSocketBackendPrivate::continueHandshake() Q_Q(QSslSocket); connectionEncrypted = true; -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13, __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). - if (__builtin_available(iOS 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 &requestedProtocols = configuration.nextAllowedProtocols; if (const int requestedCount = requestedProtocols.size()) { configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNone; @@ -423,7 +446,9 @@ void QSslSocketBackendPrivate::continueHandshake() } #endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE - emit q->encrypted(); + if (!renegotiating) + emit q->encrypted(); + if (autoStartHandshake && pendingClose) { pendingClose = false; q->disconnectFromHost(); @@ -452,7 +477,7 @@ void QSslSocketBackendPrivate::disconnectFromHost() QSslCipher QSslSocketBackendPrivate::sessionCipher() const { SSLCipherSuite cipher = 0; - if (context && SSLGetNegotiatedCipher(context, &cipher) == noErr) + if (context && SSLGetNegotiatedCipher(context, &cipher) == errSecSuccess) return QSslCipher_from_SSLCipherSuite(cipher); return QSslCipher(); @@ -465,7 +490,7 @@ QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const SSLProtocol protocol = kSSLProtocolUnknown; const OSStatus err = SSLGetNegotiatedProtocolVersion(context, &protocol); - if (err != noErr) { + if (err != errSecSuccess) { qCWarning(lcSsl) << "SSLGetNegotiatedProtocolVersion failed:" << err; return QSsl::UnknownProtocol; } @@ -521,10 +546,10 @@ void QSslSocketBackendPrivate::transmit() if (!context || shutdown) return; - if (!connectionEncrypted) + if (!isHandshakeComplete()) startHandshake(); - if (connectionEncrypted && !writeBuffer.isEmpty()) { + if (isHandshakeComplete() && !writeBuffer.isEmpty()) { qint64 totalBytesWritten = 0; while (writeBuffer.nextDataBlockSize() > 0 && context) { const size_t nextDataBlockSize = writeBuffer.nextDataBlockSize(); @@ -533,7 +558,7 @@ void QSslSocketBackendPrivate::transmit() #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "SSLWrite returned" << err; #endif - if (err != noErr && err != errSSLWouldBlock) { + if (err != errSecSuccess && err != errSSLWouldBlock) { setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLWrite failed: %1").arg(err)); break; @@ -559,7 +584,7 @@ void QSslSocketBackendPrivate::transmit() } } - if (connectionEncrypted) { + if (isHandshakeComplete()) { QVarLengthArray<char, 4096> data; while (context && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { size_t readBytes = 0; @@ -573,12 +598,17 @@ void QSslSocketBackendPrivate::transmit() setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, QSslSocket::tr("The TLS/SSL connection has been closed")); break; - } else if (err != noErr && err != errSSLWouldBlock) { + } else if (err != errSecSuccess && err != errSSLWouldBlock) { setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLRead failed: %1").arg(err)); break; } + if (err == errSSLWouldBlock && renegotiating) { + startHandshake(); + break; + } + if (readBytes) { buffer.append(data.constData(), readBytes); if (readyReadEmittedPointer) @@ -861,16 +891,17 @@ bool QSslSocketBackendPrivate::initSslContext() return false; } - const OSStatus err = SSLSetIOFuncs(context, reinterpret_cast<SSLReadFunc>(&_q_SSLRead), - reinterpret_cast<SSLWriteFunc>(&_q_SSLWrite)); - if (err != noErr) { + const OSStatus err = SSLSetIOFuncs(context, + reinterpret_cast<SSLReadFunc>(&QSslSocketBackendPrivate::ReadCallback), + reinterpret_cast<SSLWriteFunc>(&QSslSocketBackendPrivate::WriteCallback)); + if (err != errSecSuccess) { destroySslContext(); setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); return false; } - SSLSetConnection(context, plainSocket); + SSLSetConnection(context, this); if (mode == QSslSocket::SslServerMode && !configuration.localCertificateChain.isEmpty()) { @@ -889,8 +920,8 @@ bool QSslSocketBackendPrivate::initSslContext() return false; } -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) - if (__builtin_available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13, __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.nextAllowedProtocols; QCFType<CFMutableArrayRef> cfNames(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)); if (cfNames) { @@ -922,10 +953,10 @@ bool QSslSocketBackendPrivate::initSslContext() SSLSetPeerDomainName(context, ace.data(), ace.size()); // tell SecureTransport we handle peer verification ourselves OSStatus err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnCertRequested, true); - if (err != noErr) { + if (err != errSecSuccess) { destroySslContext(); setErrorAndEmit(QSslSocket::SslInternalError, QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); @@ -936,13 +967,13 @@ bool QSslSocketBackendPrivate::initSslContext() if (configuration.peerVerifyMode != QSslSocket::VerifyNone) { // kAlwaysAuthenticate - always fails even if we set break on client auth. OSStatus err = SSLSetClientSideAuthenticate(context, kTryAuthenticate); - if (err == noErr) { + 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 != noErr) { + if (err != errSecSuccess) { destroySslContext(); setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); @@ -1005,7 +1036,7 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, nullptr, nullptr); QCFType<CFArrayRef> items; OSStatus err = SecPKCS12Import(pkcs12, options, &items); - if (err != noErr) { + if (err != errSecSuccess) { #ifdef QSSLSOCKET_DEBUG qCWarning(lcSsl) << plainSocket << QStringLiteral("SecPKCS12Import failed: %1").arg(err); @@ -1051,7 +1082,7 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, } err = SSLSetCertificate(context, certs); - if (err != noErr) { + if (err != errSecSuccess) { #ifdef QSSLSOCKET_DEBUG qCWarning(lcSsl) << plainSocket << QStringLiteral("Cannot set certificate and key: %1").arg(err); @@ -1079,35 +1110,35 @@ bool QSslSocketBackendPrivate::setSessionProtocol() return false; } - OSStatus err = noErr; + OSStatus err = errSecSuccess; if (configuration.protocol == QSsl::SslV3) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : SSLv3"; #endif err = SSLSetProtocolVersionMin(context, kSSLProtocol3); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kSSLProtocol3); } else if (configuration.protocol == QSsl::TlsV1_0) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.0"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol1); } else if (configuration.protocol == QSsl::TlsV1_1) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol11); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol11); } else if (configuration.protocol == QSsl::TlsV1_2) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol12); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::AnyProtocol) { #ifdef QSSLSOCKET_DEBUG @@ -1115,42 +1146,42 @@ bool QSslSocketBackendPrivate::setSessionProtocol() #endif // kSSLProtocol3, since kSSLProtocol2 is disabled: err = SSLSetProtocolVersionMin(context, kSSLProtocol3); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1SslV3) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : SSLv3 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kSSLProtocol3); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::SecureProtocols) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1_0OrLater) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1_1OrLater) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol11); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1_2OrLater) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol12); - if (err == noErr) + if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else { #ifdef QSSLSOCKET_DEBUG @@ -1159,7 +1190,7 @@ bool QSslSocketBackendPrivate::setSessionProtocol() return false; } - return err == noErr; + return err == errSecSuccess; } bool QSslSocketBackendPrivate::canIgnoreTrustVerificationFailure() const @@ -1204,8 +1235,8 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() QCFType<SecTrustRef> trust; OSStatus err = SSLCopyPeerTrust(context, &trust); - // !trust - SSLCopyPeerTrust can return noErr but null trust. - if (err != noErr || !trust) { + // !trust - SSLCopyPeerTrust can return errSecSuccess but null trust. + if (err != errSecSuccess || !trust) { if (!canIgnoreVerify) { setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Failed to obtain peer trust: %1").arg(err)); @@ -1217,40 +1248,38 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() } QList<QSslError> errors; - // store certificates - const int certCount = SecTrustGetCertificateCount(trust); - // TODO: why this test depends on configuration.peerCertificateChain not being empty???? - if (configuration.peerCertificateChain.isEmpty()) { - // 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 != noErr) { - // We can not ignore this, it's not even about trust verification - // probably ... - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); - plainSocket->disconnectFromHost(); - return false; - } - for (int i = 0; i < certCount; ++i) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); - QCFType<CFDataRef> derData = SecCertificateCopyData(cert); - configuration.peerCertificateChain << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); - } + // 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(QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; } - if (certCount > 0) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, 0); + configuration.peerCertificate.clear(); + configuration.peerCertificateChain.clear(); + + const CFIndex certCount = SecTrustGetCertificateCount(trust); + for (CFIndex i = 0; i < certCount; ++i) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); QCFType<CFDataRef> derData = SecCertificateCopyData(cert); - configuration.peerCertificate = QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + configuration.peerCertificateChain << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); } - // check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) + if (configuration.peerCertificateChain.size()) + configuration.peerCertificate = configuration.peerCertificateChain.at(0); + + // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer): for (const QSslCertificate &cert : qAsConst(configuration.peerCertificateChain)) { if (QSslCertificatePrivate::isBlacklisted(cert) && !canIgnoreVerify) { const QSslError error(QSslError::CertificateBlacklisted, cert); @@ -1294,10 +1323,10 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() } // verify certificate chain - QCFType<CFMutableArrayRef> certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + QCFType<CFMutableArrayRef> certArray = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); for (const QSslCertificate &cert : qAsConst(configuration.caCertificates)) { QCFType<CFDataRef> certData = cert.d->derData.toCFData(); - if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(NULL, certData)) + if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, certData)) CFArrayAppendValue(certArray, secRef); else qCWarning(lcSsl, "Failed to create SecCertificate from QSslCertificate"); @@ -1422,6 +1451,7 @@ bool QSslSocketBackendPrivate::startHandshake() // Failure means a real error (invalid certificate, no private key, etc). if (!setSessionCertificate(errorDescription, errorCode)) { setErrorAndEmit(errorCode, errorDescription); + renegotiating = false; return false; } else { // We try to resume a handshake, even if have no @@ -1436,6 +1466,7 @@ bool QSslSocketBackendPrivate::startHandshake() return startHandshake(); } + renegotiating = false; setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("SSLHandshake failed: %1").arg(err)); plainSocket->disconnectFromHost(); @@ -1445,6 +1476,7 @@ bool QSslSocketBackendPrivate::startHandshake() // Connection aborted during handshake phase. if (q->state() != QAbstractSocket::ConnectedState) { qCDebug(lcSsl) << "connection aborted"; + renegotiating = false; return false; } @@ -1453,13 +1485,16 @@ bool QSslSocketBackendPrivate::startHandshake() if (!verifySessionProtocol()) { setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); plainSocket->disconnectFromHost(); + renegotiating = false; return false; } if (verifyPeerTrust()) { continueHandshake(); + renegotiating = false; return true; } else { + renegotiating = false; return false; } } |