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 <edward.welbourne@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Timur Pocheptsov 2017-11-01 13:19:28 +01:00 committed by Edward Welbourne
parent 094869d4a8
commit 0f0726d36e
1 changed files with 63 additions and 1 deletions

View File

@ -48,6 +48,7 @@
#include <QtCore/qmessageauthenticationcode.h>
#include <QtCore/qcryptographichash.h>
#include <QtCore/qsystemdetection.h>
#include <QtCore/qdatastream.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qvector.h>
@ -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<CFArrayRef> cfArray;
const OSStatus result = SSLCopyALPNProtocols(context, &cfArray);
if (result == errSecSuccess && cfArray && CFArrayGetCount(cfArray)) {
const int size = CFArrayGetCount(cfArray);
QVector<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]) {
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<CFMutableArrayRef> 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);