From 0f0726d36e36815fb2bbd1702fe35ae33d27b0bf Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 1 Nov 2017 13:19:28 +0100 Subject: QSslSocketPrivate (SecureTransport) - add ALPN support Starting from iOS 11.0 (SDK) Apple has exposed two new functions: SSLSetALPNProtocols and SSLCopyALPNProtocols. This allows us to negotiate http/2 (and any other application layer protocol) via TLS on iOS. Unlike OpenSSL, SecureTransport's version is very limited - we have to compare protocols manually after the SSL handshake has finished. Still, this is better than nothing. These two functions are also declared in macOS SDK starting from 10.13, but unfortunately the symbols are missing and for now this feature is only enabled on iOS. Change-Id: I3ed2f287bfa864f8aca0c231171e804f7d6b8016 Reviewed-by: Edward Welbourne Reviewed-by: Timur Pocheptsov --- src/network/ssl/qsslsocket_mac.cpp | 64 +++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp index e29ebbe028..5c2bd55198 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -48,6 +48,7 @@ #include #include +#include #include #include #include @@ -160,7 +161,8 @@ EphemeralSecKeychain::~EphemeralSecKeychain() } #endif // Q_OS_MACOS -} + +} // unnamed namespace static SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) { @@ -372,6 +374,43 @@ void QSslSocketBackendPrivate::continueHandshake() #endif Q_Q(QSslSocket); connectionEncrypted = true; + +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, __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, *)) { + const auto &requestedProtocols = configuration.nextAllowedProtocols; + if (const int requestedCount = requestedProtocols.size()) { + configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNone; + configuration.nextNegotiatedProtocol.clear(); + + QCFType cfArray; + const OSStatus result = SSLCopyALPNProtocols(context, &cfArray); + if (result == errSecSuccess && cfArray && CFArrayGetCount(cfArray)) { + const int size = CFArrayGetCount(cfArray); + QVector 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]) { + configuration.nextNegotiatedProtocol = requestedName.toLatin1(); + configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated; + break; + } + } + if (configuration.nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNegotiated) + break; + } + } + } + } +#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE + emit q->encrypted(); if (autoStartHandshake && pendingClose) { pendingClose = false; @@ -838,6 +877,29 @@ 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, *)) { + const auto protocolNames = configuration.nextAllowedProtocols; + QCFType cfNames(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)); + if (cfNames) { + for (const QByteArray &name : protocolNames) { + 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(lcSsl) << "SSLSetALPNProtocols failed - too long protocol names?"; + } + } else { + qCWarning(lcSsl) << "failed to allocate ALPN names array"; + } + } +#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE + if (mode == QSslSocket::SslClientMode) { // enable Server Name Indication (SNI) QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); -- cgit v1.2.3