summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Hartmann <phartmann@rim.com>2012-12-21 14:02:38 +0100
committerPeter Hartmann <phartmann@blackberry.com>2013-05-07 16:42:40 +0200
commit6c49238d189a2e984163f39851351e6fad831eee (patch)
tree83f3e270b441302ead3edbf73452e18d6fb31fa8
parent6bffa3682d9f01335a98928e3e315a6291684ccb (diff)
[BB10-internal] SSL: Implement session sharing and use it from QNetworkAccessManager
This improves performance since a network round trip can be avoided. Change-Id: I1aaff7e48ef9638cb137de0f43942c3a4dd2884a Initial-patch-by: Markus Goetz <markus@woboq.com> Reviewed-by: Richard J. Moore <rich@kde.org> Author: Marcel Krems <m.krems@software-vision.eu> Date: Thu Feb 7 05:24:52 2013 +0100 SSL: Use correct signature for SSL_get1_session This fixes a compilation error with GCC 4.7 which was introduced in b5652df775efbd1c52eecee5f08e40e600e5d70b In function ‘SSL_SESSION* q_SSL_get1_session(const SSL*)’: invalid conversion from ‘const SSL* {aka const ssl_st*}’ to ‘SSL* {aka ssl_st*}’ [-fpermissive] Change-Id: I909f7fb4295b2019283a7af66a038d4711e5f7cb Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Peter Hartmann <phartmann@rim.com> (backport of b5652df775efbd1c52eecee5f08e40e600e5d70b and 9b5ec67c18cba4e7b0b8a4d5c8c52b64da7549a9 squashed together) Signed-off-by: Peter Hartmann <phartmann@rim.com>
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp12
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h9
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp17
-rw-r--r--src/network/ssl/qsslconfiguration.cpp6
-rw-r--r--src/network/ssl/qsslconfiguration.h2
-rw-r--r--src/network/ssl/qsslconfiguration_p.h5
-rw-r--r--src/network/ssl/qsslcontext.cpp310
-rw-r--r--src/network/ssl/qsslcontext_p.h88
-rw-r--r--src/network/ssl/qsslsocket.cpp17
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp213
-rw-r--r--src/network/ssl/qsslsocket_openssl_p.h5
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols.cpp8
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols_p.h4
-rw-r--r--src/network/ssl/qsslsocket_p.h13
-rw-r--r--src/network/ssl/ssl.pri6
-rw-r--r--tests/auto/qnetworkreply/tst_qnetworkreply.cpp76
16 files changed, 594 insertions, 197 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 0027c4cc83..355ce4d483 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -990,6 +990,18 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config
static_cast<QSslSocket *>(d->channels[i].socket)->setSslConfiguration(config);
}
+QSharedPointer<QSslContext> QHttpNetworkConnection::sslContext()
+{
+ Q_D(QHttpNetworkConnection);
+ return d->sslContext;
+}
+
+void QHttpNetworkConnection::setSslContext(QSharedPointer<QSslContext> context)
+{
+ Q_D(QHttpNetworkConnection);
+ d->sslContext = context;
+}
+
void QHttpNetworkConnection::ignoreSslErrors(int channel)
{
Q_D(QHttpNetworkConnection);
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index b0600631f9..c2b4a2a5a2 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -61,6 +61,7 @@
#include <qauthenticator.h>
#include <qnetworkproxy.h>
#include <qbuffer.h>
+#include <qsharedpointer.h>
#include <private/qhttpnetworkheader_p.h>
#include <private/qhttpnetworkrequest_p.h>
@@ -71,6 +72,8 @@
#ifndef QT_NO_HTTP
#ifndef QT_NO_OPENSSL
+# include <private/qsslcontext_p.h>
+# include <private/qsslsocket_p.h>
# include <QtNetwork/qsslsocket.h>
# include <QtNetwork/qsslerror.h>
#else
@@ -122,6 +125,8 @@ public:
void setSslConfiguration(const QSslConfiguration &config);
void ignoreSslErrors(int channel = -1);
void ignoreSslErrors(const QList<QSslError> &errors, int channel = -1);
+ QSharedPointer<QSslContext> sslContext();
+ void setSslContext(QSharedPointer<QSslContext> context);
#endif
private:
@@ -215,6 +220,10 @@ public:
QList<HttpMessagePair> highPriorityQueue;
QList<HttpMessagePair> lowPriorityQueue;
+#ifndef QT_NO_OPENSSL
+ QSharedPointer<QSslContext> sslContext;
+#endif
+
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> networkSession;
#endif
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 129c459314..1ff602d3d5 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -604,6 +604,13 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
if (ssl) {
#ifndef QT_NO_OPENSSL
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+
+ // check whether we can re-use an existing SSL session
+ // (meaning another socket in this connection has already
+ // performed a full handshake)
+ if (!connection->sslContext().isNull())
+ QSslSocketPrivate::checkSettingSslContext(sslSocket, connection->sslContext());
+
sslSocket->connectToHostEncrypted(connectHost, connectPort);
if (ignoreAllSslErrors)
sslSocket->ignoreSslErrors();
@@ -1029,6 +1036,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
// ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
//channels[i].reconnectAttempts = 2;
+
if (!pendingEncrypt) {
state = QHttpNetworkConnectionChannel::IdleState;
if (!reply)
@@ -1162,6 +1170,15 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
{
if (!socket)
return; // ### error
+
+ if (connection->sslContext().isNull()) {
+ // this socket has made the 1st handshake for this connection,
+ // we need to set the SSL context so new sockets can reuse it
+ QSharedPointer<QSslContext> socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(socket));
+ if (!socketSslContext.isNull())
+ connection->setSslContext(socketSslContext);
+ }
+
state = QHttpNetworkConnectionChannel::IdleState;
pendingEncrypt = false;
if (!reply)
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index 4e7bc4d6c6..54e6751664 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -571,4 +571,10 @@ void QSslConfiguration::setDefaultConfiguration(const QSslConfiguration &configu
QSslConfigurationPrivate::setDefaultConfiguration(configuration);
}
+/*! \internal
+*/
+bool QSslConfigurationPrivate::peerSessionWasShared(const QSslConfiguration &configuration) {
+ return configuration.d->peerSessionShared;
+ }
+
QT_END_NAMESPACE
diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h
index 2796f2ab17..e27b99fc0e 100644
--- a/src/network/ssl/qsslconfiguration.h
+++ b/src/network/ssl/qsslconfiguration.h
@@ -128,6 +128,8 @@ public:
private:
friend class QSslSocket;
friend class QSslConfigurationPrivate;
+ friend class QSslSocketBackendPrivate;
+ friend class QSslContext;
QSslConfiguration(QSslConfigurationPrivate *dd);
QSharedDataPointer<QSslConfigurationPrivate> d;
};
diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h
index dff6ba0e61..cf871c8c71 100644
--- a/src/network/ssl/qsslconfiguration_p.h
+++ b/src/network/ssl/qsslconfiguration_p.h
@@ -84,6 +84,7 @@ public:
peerVerifyMode(QSslSocket::AutoVerifyPeer),
peerVerifyDepth(0),
allowRootCertOnDemandLoading(true),
+ peerSessionShared(false),
sslOptions(QSsl::SslOptionDisableEmptyFragments
|QSsl::SslOptionDisableLegacyRenegotiation
|QSsl::SslOptionDisableCompression)
@@ -91,6 +92,7 @@ public:
QSslCertificate peerCertificate;
QList<QSslCertificate> peerCertificateChain;
+
QSslCertificate localCertificate;
QSslKey privateKey;
@@ -102,6 +104,9 @@ public:
QSslSocket::PeerVerifyMode peerVerifyMode;
int peerVerifyDepth;
bool allowRootCertOnDemandLoading;
+ bool peerSessionShared;
+
+ Q_AUTOTEST_EXPORT static bool peerSessionWasShared(const QSslConfiguration &configuration);
QSsl::SslOptions sslOptions;
diff --git a/src/network/ssl/qsslcontext.cpp b/src/network/ssl/qsslcontext.cpp
new file mode 100644
index 0000000000..e0a9cbc12c
--- /dev/null
+++ b/src/network/ssl/qsslcontext.cpp
@@ -0,0 +1,310 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtNetwork/qsslsocket.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qdatetime.h>
+
+#include "private/qsslcontext_p.h"
+#include "private/qsslsocket_p.h"
+#include "private/qsslsocket_openssl_p.h"
+#include "private/qsslsocket_openssl_symbols_p.h"
+
+QT_BEGIN_NAMESPACE
+
+// defined in qsslsocket_openssl.cpp:
+extern int q_X509Callback(int ok, X509_STORE_CTX *ctx);
+extern QString getErrorsFromOpenSsl();
+
+QSslContext::QSslContext()
+ : ctx(0),
+ pkey(0),
+ session(0)
+{
+}
+
+QSslContext::~QSslContext()
+{
+ if (ctx)
+ // This will decrement the reference count by 1 and free the context eventually when possible
+ q_SSL_CTX_free(ctx);
+
+ if (pkey)
+ q_EVP_PKEY_free(pkey);
+
+ if (session)
+ q_SSL_SESSION_free(session);
+}
+
+QSslContext* QSslContext::fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading)
+{
+ QSslContext *sslContext = new QSslContext();
+ sslContext->sslConfiguration = configuration;
+ sslContext->errorCode = QSslError::NoError;
+
+ bool client = (mode == QSslSocket::SslClientMode);
+
+ bool reinitialized = false;
+init_context:
+ switch (sslContext->sslConfiguration.protocol()) {
+ case QSsl::SslV2:
+#ifndef OPENSSL_NO_SSL2
+ sslContext->ctx = q_SSL_CTX_new(client ? q_SSLv2_client_method() : q_SSLv2_server_method());
+#else
+ sslContext->ctx = 0; // SSL 2 not supported by the system, but chosen deliberately -> error
+#endif
+ break;
+ case QSsl::SslV3:
+ sslContext->ctx = q_SSL_CTX_new(client ? q_SSLv3_client_method() : q_SSLv3_server_method());
+ break;
+ case QSsl::SecureProtocols: // SslV2 will be disabled below
+ case QSsl::TlsV1SslV3: // SslV2 will be disabled below
+ case QSsl::AnyProtocol:
+ default:
+ sslContext->ctx = q_SSL_CTX_new(client ? q_SSLv23_client_method() : q_SSLv23_server_method());
+ break;
+ case QSsl::TlsV1:
+ sslContext->ctx = q_SSL_CTX_new(client ? q_TLSv1_client_method() : q_TLSv1_server_method());
+ break;
+ }
+ if (!sslContext->ctx) {
+ // After stopping Flash 10 the SSL library looses its ciphers. Try re-adding them
+ // by re-initializing the library.
+ if (!reinitialized) {
+ reinitialized = true;
+ if (q_SSL_library_init() == 1)
+ goto init_context;
+ }
+
+ sslContext->errorStr = QSslSocket::tr("Error creating SSL context (%1)").arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+ sslContext->errorCode = QSslError::UnspecifiedError;
+ return sslContext;
+ }
+
+ // Enable bug workarounds.
+ long options;
+ QSsl::SslProtocol protocol = sslContext->sslConfiguration.protocol();
+ QSsl::SslOptions configurationOptions = sslContext->sslConfiguration.d->sslOptions;
+ if (protocol == QSsl::TlsV1SslV3 || protocol == QSsl::SecureProtocols)
+ options = SSL_OP_ALL|SSL_OP_NO_SSLv2;
+ else
+ options = SSL_OP_ALL;
+
+ // This option is disabled by default, so we need to be able to clear it
+ if (configurationOptions & QSsl::SslOptionDisableEmptyFragments)
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+ else
+ options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+ // This option is disabled by default, so we need to be able to clear it
+ if (configurationOptions & QSsl::SslOptionDisableLegacyRenegotiation)
+ options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
+ else
+ options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
+#endif
+
+#ifdef SSL_OP_NO_TICKET
+ if (configurationOptions & QSsl::SslOptionDisableSessionTickets)
+ options |= SSL_OP_NO_TICKET;
+#endif
+#ifdef SSL_OP_NO_COMPRESSION
+ if (configurationOptions & QSsl::SslOptionDisableCompression)
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+
+ q_SSL_CTX_set_options(sslContext->ctx, options);
+
+ // Initialize ciphers
+ QByteArray cipherString;
+ int first = true;
+ QList<QSslCipher> ciphers = sslContext->sslConfiguration.ciphers();
+ if (ciphers.isEmpty())
+ ciphers = QSslSocketPrivate::defaultCiphers();
+ foreach (const QSslCipher &cipher, ciphers) {
+ if (first)
+ first = false;
+ else
+ cipherString.append(':');
+ cipherString.append(cipher.name().toLatin1());
+ }
+
+ if (!q_SSL_CTX_set_cipher_list(sslContext->ctx, cipherString.data())) {
+ sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+ sslContext->errorCode = QSslError::UnspecifiedError;
+ return sslContext;
+ }
+
+ // Add all our CAs to this store.
+ QList<QSslCertificate> expiredCerts;
+ foreach (const QSslCertificate &caCertificate, sslContext->sslConfiguration.caCertificates()) {
+ // add expired certs later, so that the
+ // valid ones are used before the expired ones
+ if (! caCertificate.isValid()) {
+ expiredCerts.append(caCertificate);
+ } else {
+ q_X509_STORE_add_cert(sslContext->ctx->cert_store, (X509 *)caCertificate.handle());
+ }
+ }
+
+ bool addExpiredCerts = true;
+#if defined(Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5)
+ //On Leopard SSL does not work if we add the expired certificates.
+ if (QSysInfo::MacintoshVersion == QSysInfo::MV_10_5)
+ addExpiredCerts = false;
+#endif
+ // now add the expired certs
+ if (addExpiredCerts) {
+ foreach (const QSslCertificate &caCertificate, expiredCerts) {
+ q_X509_STORE_add_cert(sslContext->ctx->cert_store, reinterpret_cast<X509 *>(caCertificate.handle()));
+ }
+ }
+
+ if (QSslSocketPrivate::s_loadRootCertsOnDemand && allowRootCertOnDemandLoading) {
+ // tell OpenSSL the directories where to look up the root certs on demand
+ QList<QByteArray> unixDirs = QSslSocketPrivate::unixRootCertDirectories();
+ for (int a = 0; a < unixDirs.count(); ++a)
+ q_SSL_CTX_load_verify_locations(sslContext->ctx, 0, unixDirs.at(a).constData());
+ }
+
+ // Register a custom callback to get all verification errors.
+ X509_STORE_set_verify_cb_func(sslContext->ctx->cert_store, q_X509Callback);
+
+ if (!sslContext->sslConfiguration.localCertificate().isNull()) {
+ // Require a private key as well.
+ if (sslContext->sslConfiguration.privateKey().isNull()) {
+ sslContext->errorStr = QSslSocket::tr("Cannot provide a certificate with no key, %1").arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+ sslContext->errorCode = QSslError::UnspecifiedError;
+ return sslContext;
+ }
+
+ // Load certificate
+ if (!q_SSL_CTX_use_certificate(sslContext->ctx, (X509 *)sslContext->sslConfiguration.localCertificate().handle())) {
+ sslContext->errorStr = QSslSocket::tr("Error loading local certificate, %1").arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+ sslContext->errorCode = QSslError::UnspecifiedError;
+ return sslContext;
+ }
+
+ // Load private key
+ sslContext->pkey = q_EVP_PKEY_new();
+ // before we were using EVP_PKEY_assign_R* functions and did not use EVP_PKEY_free.
+ // this lead to a memory leak. Now we use the *_set1_* functions which do not
+ // take ownership of the RSA/DSA key instance because the QSslKey already has ownership.
+ if (configuration.d->privateKey.algorithm() == QSsl::Rsa)
+ q_EVP_PKEY_set1_RSA(sslContext->pkey, reinterpret_cast<RSA *>(configuration.d->privateKey.handle()));
+ else
+ q_EVP_PKEY_set1_DSA(sslContext->pkey, reinterpret_cast<DSA *>(configuration.d->privateKey.handle()));
+
+ if (!q_SSL_CTX_use_PrivateKey(sslContext->ctx, sslContext->pkey)) {
+ sslContext->errorStr = QSslSocket::tr("Error loading private key, %1").arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+ sslContext->errorCode = QSslError::UnspecifiedError;
+ return sslContext;
+ }
+
+ // Check if the certificate matches the private key.
+ if (!q_SSL_CTX_check_private_key(sslContext->ctx)) {
+ sslContext->errorStr = QSslSocket::tr("Private key does not certify public key, %1").arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+ sslContext->errorCode = QSslError::UnspecifiedError;
+ return sslContext;
+ }
+ }
+
+ // Initialize peer verification.
+ if (sslContext->sslConfiguration.peerVerifyMode() == QSslSocket::VerifyNone) {
+ q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_NONE, 0);
+ } else {
+ q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_PEER, q_X509Callback);
+ }
+
+ // Set verification depth.
+ if (sslContext->sslConfiguration.peerVerifyDepth() != 0)
+ q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth());
+
+ return sslContext;
+}
+
+// Needs to be deleted by caller
+SSL* QSslContext::createSsl()
+{
+ SSL* ssl = q_SSL_new(ctx);
+ q_SSL_clear(ssl);
+
+ if (session) {
+ // Try to resume the last session we cached
+ if (!q_SSL_set_session(ssl, session)) {
+ qWarning("could not set SSL session");
+ q_SSL_SESSION_free(session);
+ session = 0;
+ }
+ }
+ return ssl;
+}
+
+// We cache exactly one session here
+bool QSslContext::cacheSession(SSL* ssl)
+{
+ // dont cache the same session again
+ if (session && session == q_SSL_get_session(ssl))
+ return true;
+
+ // decrease refcount of currently stored session
+ // (this might happen if there are several concurrent handshakes in flight)
+ if (session)
+ q_SSL_SESSION_free(session);
+
+ // cache the session the caller gave us and increase reference count
+ session = q_SSL_get1_session(ssl);
+ return (session != NULL);
+
+}
+
+QSslError::SslError QSslContext::error() const
+{
+ return errorCode;
+}
+
+QString QSslContext::errorString() const
+{
+ return errorStr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/ssl/qsslcontext_p.h b/src/network/ssl/qsslcontext_p.h
new file mode 100644
index 0000000000..7f2fc4bc9c
--- /dev/null
+++ b/src/network/ssl/qsslcontext_p.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QSSLCONTEXT_H
+#define QSSLCONTEXT_H
+
+#include <QtCore/qvariant.h>
+#include <QtNetwork/qsslcertificate.h>
+#include <QtNetwork/qsslconfiguration.h>
+#include <openssl/ssl.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_OPENSSL
+
+class QSslContextPrivate;
+
+class QSslContext
+{
+public:
+
+ ~QSslContext();
+
+ static QSslContext* fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration,
+ bool allowRootCertOnDemandLoading);
+
+ QSslError::SslError error() const;
+ QString errorString() const;
+
+ SSL* createSsl();
+ bool cacheSession(SSL*); // should be called when handshake completed
+
+protected:
+ QSslContext();
+
+private:
+ SSL_CTX* ctx;
+ EVP_PKEY *pkey;
+ SSL_SESSION *session;
+ QSslError::SslError errorCode;
+ QString errorStr;
+ QSslConfiguration sslConfiguration;
+};
+
+#endif // QT_NO_OPENSSL
+
+QT_END_NAMESPACE
+
+#endif // QSSLCONTEXT_H
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 454a52a3a6..757a49392b 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -2328,6 +2328,23 @@ QList<QByteArray> QSslSocketPrivate::unixRootCertDirectories()
<< "/opt/openssl/certs/"; // HP-UX
}
+/*!
+ \internal
+*/
+void QSslSocketPrivate::checkSettingSslContext(QSslSocket* socket, QSharedPointer<QSslContext> sslContext)
+{
+ if (socket->d_func()->sslContextPointer.isNull())
+ socket->d_func()->sslContextPointer = sslContext;
+}
+
+/*!
+ \internal
+*/
+QSharedPointer<QSslContext> QSslSocketPrivate::sslContext(QSslSocket *socket)
+{
+ return (socket) ? socket->d_func()->sslContextPointer : QSharedPointer<QSslContext>();
+}
+
QT_END_NAMESPACE
// For private slots
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 4b0a9ecdfb..25607dad88 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -172,8 +172,6 @@ static unsigned long id_function()
QSslSocketBackendPrivate::QSslSocketBackendPrivate()
: ssl(0),
- ctx(0),
- pkey(0),
readBio(0),
writeBio(0),
session(0)
@@ -233,7 +231,8 @@ struct QSslErrorList
QList<QPair<int, int> > errors;
};
Q_GLOBAL_STATIC(QSslErrorList, _q_sslErrorList)
-static int q_X509Callback(int ok, X509_STORE_CTX *ctx)
+
+int q_X509Callback(int ok, X509_STORE_CTX *ctx)
{
if (!ok) {
// Store the error and at which depth the error was detected.
@@ -249,188 +248,21 @@ bool QSslSocketBackendPrivate::initSslContext()
{
Q_Q(QSslSocket);
- // Create and initialize SSL context. Accept SSLv2, SSLv3 and TLSv1.
- bool client = (mode == QSslSocket::SslClientMode);
+ // If no external context was set (e.g. bei QHttpNetworkConnection) we will create a default context
+ if (!sslContextPointer)
+ sslContextPointer = QSharedPointer<QSslContext>(
+ QSslContext::fromConfiguration(mode, QSslConfiguration(&configuration), allowRootCertOnDemandLoading));
- bool reinitialized = false;
-init_context:
- switch (configuration.protocol) {
- case QSsl::SslV2:
-#ifndef OPENSSL_NO_SSL2
- ctx = q_SSL_CTX_new(client ? q_SSLv2_client_method() : q_SSLv2_server_method());
-#else
- ctx = 0; // SSL 2 not supported by the system, but chosen deliberately -> error
-#endif
- break;
- case QSsl::SslV3:
- ctx = q_SSL_CTX_new(client ? q_SSLv3_client_method() : q_SSLv3_server_method());
- break;
- case QSsl::SecureProtocols: // SslV2 will be disabled below
- case QSsl::TlsV1SslV3: // SslV2 will be disabled below
- case QSsl::AnyProtocol:
- default:
- ctx = q_SSL_CTX_new(client ? q_SSLv23_client_method() : q_SSLv23_server_method());
- break;
- case QSsl::TlsV1:
- ctx = q_SSL_CTX_new(client ? q_TLSv1_client_method() : q_TLSv1_server_method());
- break;
- }
- if (!ctx) {
- // After stopping Flash 10 the SSL library looses its ciphers. Try re-adding them
- // by re-initializing the library.
- if (!reinitialized) {
- reinitialized = true;
- if (q_SSL_library_init() == 1)
- goto init_context;
- }
-
- // ### Bad error code
- q->setErrorString(QSslSocket::tr("Error creating SSL context (%1)").arg(getErrorsFromOpenSsl()));
+ if (sslContextPointer->error() != QSslError::NoError) {
+ q->setErrorString(sslContextPointer->errorString());
q->setSocketError(QAbstractSocket::UnknownSocketError);
emit q->error(QAbstractSocket::UnknownSocketError);
+ sslContextPointer.clear(); // deletes the QSslContext
return false;
}
- // Enable bug workarounds.
- long options;
- if (configuration.protocol == QSsl::TlsV1SslV3 || configuration.protocol == QSsl::SecureProtocols)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2;
- else
- options = SSL_OP_ALL;
-
- // This option is disabled by default, so we need to be able to clear it
- if (configuration.sslOptions & QSsl::SslOptionDisableEmptyFragments)
- options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
- else
- options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
-
-#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
- // This option is disabled by default, so we need to be able to clear it
- if (configuration.sslOptions & QSsl::SslOptionDisableLegacyRenegotiation)
- options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
- else
- options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
-#endif
-
-#ifdef SSL_OP_NO_TICKET
- if (configuration.sslOptions & QSsl::SslOptionDisableSessionTickets)
- options |= SSL_OP_NO_TICKET;
-#endif
-#ifdef SSL_OP_NO_COMPRESSION
- if (configuration.sslOptions & QSsl::SslOptionDisableCompression)
- options |= SSL_OP_NO_COMPRESSION;
-#endif
-
- q_SSL_CTX_set_options(ctx, options);
-
- // Initialize ciphers
- QByteArray cipherString;
- int first = true;
- QList<QSslCipher> ciphers = configuration.ciphers;
- if (ciphers.isEmpty())
- ciphers = defaultCiphers();
- foreach (const QSslCipher &cipher, ciphers) {
- if (first)
- first = false;
- else
- cipherString.append(':');
- cipherString.append(cipher.name().toLatin1());
- }
-
- if (!q_SSL_CTX_set_cipher_list(ctx, cipherString.data())) {
- // ### Bad error code
- q->setErrorString(QSslSocket::tr("Invalid or empty cipher list (%1)").arg(getErrorsFromOpenSsl()));
- q->setSocketError(QAbstractSocket::UnknownSocketError);
- emit q->error(QAbstractSocket::UnknownSocketError);
- return false;
- }
-
- // Add all our CAs to this store.
- QList<QSslCertificate> expiredCerts;
- foreach (const QSslCertificate &caCertificate, q->caCertificates()) {
- // add expired certs later, so that the
- // valid ones are used before the expired ones
- if (! caCertificate.isValid()) {
- expiredCerts.append(caCertificate);
- } else {
- q_X509_STORE_add_cert(ctx->cert_store, (X509 *)caCertificate.handle());
- }
- }
-
- bool addExpiredCerts = true;
-#if defined(Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5)
- //On Leopard SSL does not work if we add the expired certificates.
- if (QSysInfo::MacintoshVersion == QSysInfo::MV_10_5)
- addExpiredCerts = false;
-#endif
- // now add the expired certs
- if (addExpiredCerts) {
- foreach (const QSslCertificate &caCertificate, expiredCerts) {
- q_X509_STORE_add_cert(ctx->cert_store, (X509 *)caCertificate.handle());
- }
- }
-
- if (s_loadRootCertsOnDemand && allowRootCertOnDemandLoading) {
- // tell OpenSSL the directories where to look up the root certs on demand
- QList<QByteArray> unixDirs = unixRootCertDirectories();
- for (int a = 0; a < unixDirs.count(); ++a)
- q_SSL_CTX_load_verify_locations(ctx, 0, unixDirs.at(a).constData());
- }
-
- // Register a custom callback to get all verification errors.
- X509_STORE_set_verify_cb_func(ctx->cert_store, q_X509Callback);
-
- if (!configuration.localCertificate.isNull()) {
- // Require a private key as well.
- if (configuration.privateKey.isNull()) {
- q->setErrorString(QSslSocket::tr("Cannot provide a certificate with no key, %1").arg(getErrorsFromOpenSsl()));
- emit q->error(QAbstractSocket::UnknownSocketError);
- return false;
- }
-
- // Load certificate
- if (!q_SSL_CTX_use_certificate(ctx, (X509 *)configuration.localCertificate.handle())) {
- q->setErrorString(QSslSocket::tr("Error loading local certificate, %1").arg(getErrorsFromOpenSsl()));
- emit q->error(QAbstractSocket::UnknownSocketError);
- return false;
- }
-
- // Load private key
- pkey = q_EVP_PKEY_new();
- // before we were using EVP_PKEY_assign_R* functions and did not use EVP_PKEY_free.
- // this lead to a memory leak. Now we use the *_set1_* functions which do not
- // take ownership of the RSA/DSA key instance because the QSslKey already has ownership.
- if (configuration.privateKey.algorithm() == QSsl::Rsa)
- q_EVP_PKEY_set1_RSA(pkey, (RSA *)configuration.privateKey.handle());
- else
- q_EVP_PKEY_set1_DSA(pkey, (DSA *)configuration.privateKey.handle());
- if (!q_SSL_CTX_use_PrivateKey(ctx, pkey)) {
- q->setErrorString(QSslSocket::tr("Error loading private key, %1").arg(getErrorsFromOpenSsl()));
- emit q->error(QAbstractSocket::UnknownSocketError);
- return false;
- }
-
- // Check if the certificate matches the private key.
- if (!q_SSL_CTX_check_private_key(ctx)) {
- q->setErrorString(QSslSocket::tr("Private key does not certify public key, %1").arg(getErrorsFromOpenSsl()));
- emit q->error(QAbstractSocket::UnknownSocketError);
- return false;
- }
- }
-
- // Initialize peer verification.
- if (configuration.peerVerifyMode == QSslSocket::VerifyNone) {
- q_SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0);
- } else {
- q_SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, q_X509Callback);
- }
-
- // Set verification depth.
- if (configuration.peerVerifyDepth != 0)
- q_SSL_CTX_set_verify_depth(ctx, configuration.peerVerifyDepth);
-
// Create and initialize SSL session
- if (!(ssl = q_SSL_new(ctx))) {
+ if (!(ssl = sslContextPointer->createSsl())) {
// ### Bad error code
q->setErrorString(QSslSocket::tr("Error creating SSL session, %1").arg(getErrorsFromOpenSsl()));
q->setSocketError(QAbstractSocket::UnknownSocketError);
@@ -443,7 +275,7 @@ init_context:
configuration.protocol == QSsl::TlsV1 ||
configuration.protocol == QSsl::SecureProtocols ||
configuration.protocol == QSsl::AnyProtocol) &&
- client && q_SSLeay() >= 0x00090806fL) {
+ mode == QSslSocket::SslClientMode && q_SSLeay() >= 0x00090806fL) {
// Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format.
QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName;
if (tlsHostName.isEmpty())
@@ -460,7 +292,6 @@ init_context:
#endif
// Clear the session.
- q_SSL_clear(ssl);
errorList.clear();
// Initialize memory BIOs for encryption and decryption.
@@ -491,14 +322,7 @@ void QSslSocketBackendPrivate::destroySslContext()
q_SSL_free(ssl);
ssl = 0;
}
- if (ctx) {
- q_SSL_CTX_free(ctx);
- ctx = 0;
- }
- if (pkey) {
- q_EVP_PKEY_free(pkey);
- pkey = 0;
- }
+ sslContextPointer.clear();
}
/*!
@@ -1188,7 +1012,7 @@ void QSslSocketBackendPrivate::transmit()
break;
}
} while (ssl && readBytes > 0);
- } while (ssl && ctx && transmitting);
+ } while (ssl && transmitting);
}
static QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &cert)
@@ -1438,6 +1262,15 @@ bool QSslSocketBackendPrivate::startHandshake()
}
#endif
+ if (q_SSL_ctrl((ssl), SSL_CTRL_GET_SESSION_REUSED, 0, NULL))
+ configuration.peerSessionShared = true;
+
+ // Cache this SSL session inside the QSslContext
+ if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionTickets)) {
+ if (!sslContextPointer->cacheSession(ssl))
+ sslContextPointer.clear(); // we could not cache the session
+ }
+
connectionEncrypted = true;
emit q->encrypted();
if (autoStartHandshake && pendingClose) {
@@ -1469,7 +1302,7 @@ void QSslSocketBackendPrivate::disconnected()
QSslCipher QSslSocketBackendPrivate::sessionCipher() const
{
- if (!ssl || !ctx)
+ if (!ssl)
return QSslCipher();
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
// FIXME This is fairly evil, but needed to keep source level compatibility
diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h
index 8d671b054c..dd50cb7ecc 100644
--- a/src/network/ssl/qsslsocket_openssl_p.h
+++ b/src/network/ssl/qsslsocket_openssl_p.h
@@ -61,7 +61,10 @@
#if defined(OCSP_RESPONSE)
#undef OCSP_RESPONSE
#endif
+#if defined(X509_NAME)
+#undef X509_NAME
#endif
+#endif // Q_OS_WIN
#include <openssl/asn1.h>
#include <openssl/bio.h>
@@ -101,8 +104,6 @@ public:
bool initSslContext();
void destroySslContext();
SSL *ssl;
- SSL_CTX *ctx;
- EVP_PKEY *pkey;
BIO *readBio;
BIO *writeBio;
SSL_SESSION *session;
diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp
index ec8c452060..e7c9077e85 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp
@@ -220,6 +220,10 @@ DEFINEFUNC3(void, SSL_set_bio, SSL *a, a, BIO *b, b, BIO *c, c, return, DUMMYARG
DEFINEFUNC(void, SSL_set_accept_state, SSL *a, a, return, DUMMYARG)
DEFINEFUNC(void, SSL_set_connect_state, SSL *a, a, return, DUMMYARG)
DEFINEFUNC(int, SSL_shutdown, SSL *a, a, return -1, return)
+DEFINEFUNC2(int, SSL_set_session, SSL* to, to, SSL_SESSION *session, session, return -1, return)
+DEFINEFUNC(void, SSL_SESSION_free, SSL_SESSION *ses, ses, return, DUMMYARG)
+DEFINEFUNC(SSL_SESSION*, SSL_get1_session, SSL *ssl, ssl, return 0, return)
+DEFINEFUNC(SSL_SESSION*, SSL_get_session, const SSL *ssl, ssl, return 0, return)
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
#ifndef OPENSSL_NO_SSL2
DEFINEFUNC(const SSL_METHOD *, SSLv2_client_method, DUMMYARG, DUMMYARG, return 0, return)
@@ -811,6 +815,10 @@ bool q_resolveOpenSslSymbols()
RESOLVEFUNC(SSL_set_bio)
RESOLVEFUNC(SSL_set_connect_state)
RESOLVEFUNC(SSL_shutdown)
+ RESOLVEFUNC(SSL_set_session)
+ RESOLVEFUNC(SSL_SESSION_free)
+ RESOLVEFUNC(SSL_get1_session)
+ RESOLVEFUNC(SSL_get_session)
RESOLVEFUNC(SSL_write)
#ifndef OPENSSL_NO_SSL2
RESOLVEFUNC(SSLv2_client_method)
diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h
index 1f9988ce3d..c740fa445c 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h
@@ -325,6 +325,10 @@ void q_SSL_set_bio(SSL *a, BIO *b, BIO *c);
void q_SSL_set_accept_state(SSL *a);
void q_SSL_set_connect_state(SSL *a);
int q_SSL_shutdown(SSL *a);
+int q_SSL_set_session(SSL *to, SSL_SESSION *session);
+void q_SSL_SESSION_free(SSL_SESSION *ses);
+SSL_SESSION *q_SSL_get1_session(SSL *ssl);
+SSL_SESSION *q_SSL_get_session(const SSL *ssl);
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
const SSL_METHOD *q_SSLv2_client_method();
const SSL_METHOD *q_SSLv3_client_method();
diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h
index f8967d00c2..40f6db9e8c 100644
--- a/src/network/ssl/qsslsocket_p.h
+++ b/src/network/ssl/qsslsocket_p.h
@@ -59,6 +59,7 @@
#include <private/qtcpsocket_p.h>
#include "qsslkey.h"
#include "qsslconfiguration_p.h"
+#include <private/qsslcontext_p.h>
#include <QtCore/qstringlist.h>
@@ -115,6 +116,7 @@ public:
QSslConfigurationPrivate configuration;
QList<QSslError> sslErrors;
+ QSharedPointer<QSslContext> sslContextPointer;
// if set, this hostname is used for certificate validation instead of the hostname
// that was used for connecting to.
@@ -122,6 +124,8 @@ public:
bool allowRootCertOnDemandLoading;
+ static bool s_loadRootCertsOnDemand;
+
static bool supportsSsl();
static void ensureInitialized();
static void deinitialize();
@@ -154,6 +158,10 @@ public:
void createPlainSocket(QIODevice::OpenMode openMode);
static void pauseSocketNotifiers(QSslSocket*);
static void resumeSocketNotifiers(QSslSocket*);
+
+ // ### The 2 methods below should be made member methods once the QSslContext class is made public
+ static void checkSettingSslContext(QSslSocket*, QSharedPointer<QSslContext>);
+ static QSharedPointer<QSslContext> sslContext(QSslSocket *socket);
void _q_connectedSlot();
void _q_hostFoundSlot();
void _q_disconnectedSlot();
@@ -164,6 +172,8 @@ public:
void _q_flushWriteBuffer();
void _q_flushReadBuffer();
+ static QList<QByteArray> unixRootCertDirectories(); // used also by QSslContext
+
virtual qint64 peek(char *data, qint64 maxSize);
virtual QByteArray peek(qint64 maxSize);
@@ -183,9 +193,6 @@ private:
static bool s_libraryLoaded;
static bool s_loadedCiphersAndCerts;
-protected:
- static bool s_loadRootCertsOnDemand;
- static QList<QByteArray> unixRootCertDirectories();
};
QT_END_NAMESPACE
diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri
index 0f4fb67235..26e51e77e2 100644
--- a/src/network/ssl/ssl.pri
+++ b/src/network/ssl/ssl.pri
@@ -20,7 +20,8 @@ symbian {
ssl/qsslsocket.h \
ssl/qsslsocket_openssl_p.h \
ssl/qsslsocket_openssl_symbols_p.h \
- ssl/qsslsocket_p.h
+ ssl/qsslsocket_p.h \
+ ssl/qsslcontext_p.h
SOURCES += ssl/qssl.cpp \
ssl/qsslcertificate.cpp \
ssl/qsslconfiguration.cpp \
@@ -29,7 +30,8 @@ symbian {
ssl/qsslkey.cpp \
ssl/qsslsocket.cpp \
ssl/qsslsocket_openssl.cpp \
- ssl/qsslsocket_openssl_symbols.cpp
+ ssl/qsslsocket_openssl_symbols.cpp \
+ ssl/qsslcontext.cpp
# Add optional SSL libs
# Static linking of OpenSSL with msvc:
diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
index e4373eac4b..c0975817ac 100644
--- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
@@ -66,6 +66,9 @@
#ifndef QT_NO_OPENSSL
#include <QtNetwork/qsslerror.h>
#include <QtNetwork/qsslconfiguration.h>
+#ifdef QT_BUILD_INTERNAL
+#include <QtNetwork/private/qsslconfiguration_p.h>
+#endif
#endif
#ifndef QT_NO_BEARERMANAGEMENT
#include <QtNetwork/qnetworkconfigmanager.h>
@@ -174,6 +177,9 @@ public Q_SLOTS:
void sslErrors(QNetworkReply*,const QList<QSslError> &);
void storeSslConfiguration();
void ignoreSslErrorListSlot(QNetworkReply *reply, const QList<QSslError> &);
+#ifdef QT_BUILD_INTERNAL
+ void sslSessionSharingHelperSlot();
+#endif
#endif
protected Q_SLOTS:
@@ -340,6 +346,10 @@ private Q_SLOTS:
void ignoreSslErrorsListWithSlot();
void sslConfiguration_data();
void sslConfiguration();
+#ifdef QT_BUILD_INTERNAL
+ void sslSessionSharing_data();
+ void sslSessionSharing();
+#endif
#endif
void getAndThenDeleteObject_data();
@@ -5667,6 +5677,72 @@ void tst_QNetworkReply::sslConfiguration()
QCOMPARE(reply->error(), expectedError);
}
+#ifdef QT_BUILD_INTERNAL
+
+void tst_QNetworkReply::sslSessionSharing_data()
+{
+ QTest::addColumn<bool>("sessionSharingEnabled");
+ QTest::newRow("enabled") << true;
+ QTest::newRow("disabled") << false;
+}
+
+void tst_QNetworkReply::sslSessionSharing()
+{
+ QString urlString("https://" + QtNetworkSettings::serverName());
+ QList<QNetworkReplyPtr> replies;
+
+ // warm up SSL session cache
+ QNetworkRequest warmupRequest(urlString);
+ QFETCH(bool, sessionSharingEnabled);
+ warmupRequest.setAttribute(QNetworkRequest::User, sessionSharingEnabled); // so we can read it from the slot
+ if (! sessionSharingEnabled) {
+ QSslConfiguration configuration(QSslConfiguration::defaultConfiguration());
+ configuration.setSslOption(QSsl::SslOptionDisableSessionTickets, true);
+ warmupRequest.setSslConfiguration(configuration);
+ }
+ QNetworkReply *reply = manager.get(warmupRequest);
+ reply->ignoreSslErrors();
+ connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
+ QTestEventLoop::instance().enterLoop(20);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+ reply->deleteLater();
+
+ // now send several requests at the same time, so we open more sockets and reuse the SSL session
+ for (int a = 0; a < 6; a++) {
+ QNetworkRequest request(warmupRequest);
+ replies.append(QNetworkReplyPtr(manager.get(request)));
+ connect(replies.at(a), SIGNAL(finished()), this, SLOT(sslSessionSharingHelperSlot()));
+ }
+ QTestEventLoop::instance().enterLoop(20);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+}
+
+void tst_QNetworkReply::sslSessionSharingHelperSlot()
+{
+ static int count = 0;
+
+ // check that SSL session sharing was used in at least one of the replies
+ static bool sslSessionSharingWasUsed = false;
+ QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
+ bool sslSessionSharingWasUsedInReply = QSslConfigurationPrivate::peerSessionWasShared(reply->sslConfiguration());
+ if (sslSessionSharingWasUsedInReply)
+ sslSessionSharingWasUsed = true;
+
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+
+ count++;
+
+ if (count == 6) { // all replies have finished
+ QTestEventLoop::instance().exitLoop();
+ bool sessionSharingWasEnabled = reply->request().attribute(QNetworkRequest::User).toBool();
+ QCOMPARE(sslSessionSharingWasUsed, sessionSharingWasEnabled);
+ count = 0; // reset for next row
+ sslSessionSharingWasUsed = false; // reset for next row
+ }
+}
+
+#endif // QT_BUILD_INTERNAL
#endif // QT_NO_OPENSSL
void tst_QNetworkReply::getAndThenDeleteObject_data()