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>
This commit is contained in:
Peter Hartmann 2012-12-21 14:02:38 +01:00 committed by The Qt Project
parent 7765dff1bb
commit b5652df775
16 changed files with 604 additions and 214 deletions

View File

@ -1224,6 +1224,18 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config
d->channels[i].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);

View File

@ -62,6 +62,7 @@
#include <qnetworkproxy.h>
#include <qbuffer.h>
#include <qtimer.h>
#include <qsharedpointer.h>
#include <private/qhttpnetworkheader_p.h>
#include <private/qhttpnetworkrequest_p.h>
@ -72,6 +73,8 @@
#ifndef QT_NO_HTTP
#ifndef QT_NO_SSL
# include <private/qsslcontext_p.h>
# include <private/qsslsocket_p.h>
# include <QtNetwork/qsslsocket.h>
# include <QtNetwork/qsslerror.h>
#else
@ -124,6 +127,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:
@ -234,6 +239,10 @@ public:
QList<HttpMessagePair> highPriorityQueue;
QList<HttpMessagePair> lowPriorityQueue;
#ifndef QT_NO_SSL
QSharedPointer<QSslContext> sslContext;
#endif
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> networkSession;
#endif

View File

@ -622,6 +622,13 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
if (ssl) {
#ifndef QT_NO_SSL
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, QIODevice::ReadWrite, networkLayerPreference);
if (ignoreAllSslErrors)
sslSocket->ignoreSslErrors();
@ -1065,7 +1072,17 @@ 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) {
if (pendingEncrypt) {
#ifndef QT_NO_SSL
if (connection->sslContext().isNull()) {
// this socket is making 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);
}
#endif
} else {
state = QHttpNetworkConnectionChannel::IdleState;
if (!reply)
connection->d_func()->dequeueRequest(socket);

View File

@ -584,4 +584,10 @@ void QSslConfiguration::setDefaultConfiguration(const QSslConfiguration &configu
QSslConfigurationPrivate::setDefaultConfiguration(configuration);
}
/*! \internal
*/
bool QSslConfigurationPrivate::peerSessionWasShared(const QSslConfiguration &configuration) {
return configuration.d->peerSessionShared;
}
QT_END_NAMESPACE

View File

@ -127,6 +127,8 @@ public:
private:
friend class QSslSocket;
friend class QSslConfigurationPrivate;
friend class QSslSocketBackendPrivate;
friend class QSslContext;
QSslConfiguration(QSslConfigurationPrivate *dd);
QSharedDataPointer<QSslConfigurationPrivate> d;
};

View File

@ -84,11 +84,13 @@ public:
peerVerifyMode(QSslSocket::AutoVerifyPeer),
peerVerifyDepth(0),
allowRootCertOnDemandLoading(true),
peerSessionShared(false),
sslOptions(QSslConfigurationPrivate::defaultSslOptions)
{ }
QSslCertificate peerCertificate;
QList<QSslCertificate> peerCertificateChain;
QSslCertificate localCertificate;
QSslKey privateKey;
@ -100,6 +102,9 @@ public:
QSslSocket::PeerVerifyMode peerVerifyMode;
int peerVerifyDepth;
bool allowRootCertOnDemandLoading;
bool peerSessionShared;
Q_AUTOTEST_EXPORT static bool peerSessionWasShared(const QSslConfiguration &configuration);
QSsl::SslOptions sslOptions;

View File

@ -0,0 +1,306 @@
/****************************************************************************
**
** 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 "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_0:
sslContext->ctx = q_SSL_CTX_new(client ? q_TLSv1_client_method() : q_TLSv1_server_method());
break;
case QSsl::TlsV1_1:
#if OPENSSL_VERSION_NUMBER >= 0x10001000L
sslContext->ctx = q_SSL_CTX_new(client ? q_TLSv1_1_client_method() : q_TLSv1_1_server_method());
#else
sslContext->ctx = 0; // TLS 1.1 not supported by the system, but chosen deliberately -> error
#endif
break;
case QSsl::TlsV1_2:
#if OPENSSL_VERSION_NUMBER >= 0x10001000L
sslContext->ctx = q_SSL_CTX_new(client ? q_TLSv1_2_client_method() : q_TLSv1_2_server_method());
#else
sslContext->ctx = 0; // TLS 1.2 not supported by the system, but chosen deliberately -> error
#endif
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 = QSslSocketBackendPrivate::setupOpenSslOptions(configuration.protocol(), configuration.d->sslOptions);
q_SSL_CTX_set_options(sslContext->ctx, options);
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
// Tell OpenSSL to release memory early
// http://www.openssl.org/docs/ssl/SSL_CTX_set_mode.html
if (q_SSLeay() >= 0x10000000L)
q_SSL_CTX_set_mode(sslContext->ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
// 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.expiryDate() < QDateTime::currentDateTime()) {
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;
}
if (configuration.d->privateKey.algorithm() == QSsl::Opaque) {
sslContext->pkey = reinterpret_cast<EVP_PKEY *>(configuration.d->privateKey.handle());
} else {
// 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;
}
if (configuration.d->privateKey.algorithm() == QSsl::Opaque)
sslContext->pkey = 0; // Don't free the private key, it belongs to QSslKey
// 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

View File

@ -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_SSL
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_SSL
QT_END_NAMESPACE
#endif // QSSLCONTEXT_H

View File

@ -2406,6 +2406,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
#include "moc_qsslsocket.cpp"

View File

@ -141,6 +141,19 @@ private:
};
Q_GLOBAL_STATIC(QOpenSslLocks, openssl_locks)
QString QSslSocketBackendPrivate::getErrorsFromOpenSsl()
{
QString errorString;
unsigned long errNum;
while ((errNum = q_ERR_get_error())) {
if (! errorString.isEmpty())
errorString.append(QLatin1String(", "));
const char *error = q_ERR_error_string(errNum, NULL);
errorString.append(QString::fromLatin1(error)); // error is ascii according to man ERR_error_string
}
return errorString;
}
extern "C" {
static void locking_function(int mode, int lockNumber, const char *, int)
{
@ -160,8 +173,6 @@ static unsigned long id_function()
QSslSocketBackendPrivate::QSslSocketBackendPrivate()
: ssl(0),
ctx(0),
pkey(0),
readBio(0),
writeBio(0),
session(0)
@ -225,7 +236,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.
@ -296,191 +308,21 @@ bool QSslSocketBackendPrivate::initSslContext()
{
Q_Q(QSslSocket);
// Create and initialize SSL context. Accept SSLv2, SSLv3 and TLSv1_0.
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_0:
ctx = q_SSL_CTX_new(client ? q_TLSv1_client_method() : q_TLSv1_server_method());
break;
case QSsl::TlsV1_1:
#if OPENSSL_VERSION_NUMBER >= 0x10001000L
ctx = q_SSL_CTX_new(client ? q_TLSv1_1_client_method() : q_TLSv1_1_server_method());
#else
ctx = 0; // TLS 1.1 not supported by the system, but chosen deliberately -> error
#endif
break;
case QSsl::TlsV1_2:
#if OPENSSL_VERSION_NUMBER >= 0x10001000L
ctx = q_SSL_CTX_new(client ? q_TLSv1_2_client_method() : q_TLSv1_2_server_method());
#else
ctx = 0; // TLS 1.2 not supported by the system, but chosen deliberately -> error
#endif
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;
}
q->setErrorString(QSslSocket::tr("Error creating SSL context (%1)").arg(getErrorsFromOpenSsl()));
q->setSocketError(QAbstractSocket::SslInternalError);
emit q->error(QAbstractSocket::SslInternalError);
return false;
}
// Enable bug workarounds.
long options = setupOpenSslOptions(configuration.protocol, configuration.sslOptions);
q_SSL_CTX_set_options(ctx, options);
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
// Tell OpenSSL to release memory early
// http://www.openssl.org/docs/ssl/SSL_CTX_set_mode.html
if (q_SSLeay() >= 0x10000000L)
q_SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
// 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())) {
q->setErrorString(QSslSocket::tr("Invalid or empty cipher list (%1)").arg(getErrorsFromOpenSsl()));
if (sslContextPointer->error() != QSslError::NoError) {
q->setErrorString(sslContextPointer->errorString());
q->setSocketError(QAbstractSocket::SslInvalidUserDataError);
emit q->error(QAbstractSocket::SslInvalidUserDataError);
sslContextPointer.clear(); // deletes the QSslContext
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.expiryDate() < QDateTime::currentDateTime()) {
expiredCerts.append(caCertificate);
} else {
q_X509_STORE_add_cert(ctx->cert_store, reinterpret_cast<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, reinterpret_cast<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()));
q->setSocketError(QAbstractSocket::SslInvalidUserDataError);
emit q->error(QAbstractSocket::SslInvalidUserDataError);
return false;
}
// Load certificate
if (!q_SSL_CTX_use_certificate(ctx, reinterpret_cast<X509 *>(configuration.localCertificate.handle()))) {
q->setErrorString(QSslSocket::tr("Error loading local certificate, %1").arg(getErrorsFromOpenSsl()));
q->setSocketError(QAbstractSocket::SslInternalError);
emit q->error(QAbstractSocket::SslInternalError);
return false;
}
if (configuration.privateKey.algorithm() == QSsl::Opaque) {
pkey = reinterpret_cast<EVP_PKEY *>(configuration.privateKey.handle());
} else {
// 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, reinterpret_cast<RSA *>(configuration.privateKey.handle()));
else
q_EVP_PKEY_set1_DSA(pkey, reinterpret_cast<DSA *>(configuration.privateKey.handle()));
}
if (!q_SSL_CTX_use_PrivateKey(ctx, pkey)) {
q->setErrorString(QSslSocket::tr("Error loading private key, %1").arg(getErrorsFromOpenSsl()));
q->setSocketError(QAbstractSocket::SslInternalError);
emit q->error(QAbstractSocket::SslInternalError);
return false;
}
if (configuration.privateKey.algorithm() == QSsl::Opaque)
pkey = 0; // Don't free the private key, it belongs to QSslKey
// 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()));
q->setSocketError(QAbstractSocket::SslInvalidUserDataError);
emit q->error(QAbstractSocket::SslInvalidUserDataError);
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::SslInternalError);
@ -495,7 +337,7 @@ init_context:
configuration.protocol == QSsl::TlsV1_2 ||
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())
@ -512,7 +354,6 @@ init_context:
#endif
// Clear the session.
q_SSL_clear(ssl);
errorList.clear();
// Initialize memory BIOs for encryption and decryption.
@ -542,14 +383,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();
}
/*!
@ -1087,7 +921,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)
@ -1293,7 +1127,6 @@ bool QSslSocketBackendPrivate::startHandshake()
}
}
#endif
if (!checkSslErrors())
return false;
} else {
@ -1517,7 +1350,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
@ -1537,6 +1370,15 @@ void QSslSocketBackendPrivate::continueHandshake()
if (readBufferMaxSize)
plainSocket->setReadBufferSize(readBufferMaxSize);
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) {
@ -1556,19 +1398,6 @@ QList<QSslCertificate> QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates
return certificates;
}
QString QSslSocketBackendPrivate::getErrorsFromOpenSsl()
{
QString errorString;
unsigned long errNum;
while((errNum = q_ERR_get_error())) {
if (! errorString.isEmpty())
errorString.append(QLatin1String(", "));
const char *error = q_ERR_error_string(errNum, NULL);
errorString.append(QString::fromLatin1(error)); // error is ascii according to man ERR_error_string
}
return errorString;
}
bool QSslSocketBackendPrivate::isMatchingHostname(const QSslCertificate &cert, const QString &peerName)
{
QStringList commonNameList = cert.subjectInfo(QSslCertificate::CommonName);

View File

@ -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;

View File

@ -231,6 +231,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, const 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)
@ -685,6 +689,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)

View File

@ -333,6 +333,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(const 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();

View File

@ -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>
@ -114,6 +115,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.
@ -121,6 +123,8 @@ public:
bool allowRootCertOnDemandLoading;
static bool s_loadRootCertsOnDemand;
static bool supportsSsl();
static long sslLibraryVersionNumber();
static QString sslLibraryVersionString();
@ -155,6 +159,9 @@ 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);
bool isPaused() const;
void _q_connectedSlot();
void _q_hostFoundSlot();
@ -170,6 +177,8 @@ public:
virtual void _q_caRootLoaded(QSslCertificate,QSslCertificate) = 0;
#endif
static QList<QByteArray> unixRootCertDirectories(); // used also by QSslContext
virtual qint64 peek(char *data, qint64 maxSize);
virtual QByteArray peek(qint64 maxSize);
@ -192,8 +201,6 @@ private:
static bool s_loadedCiphersAndCerts;
protected:
bool verifyErrorsHaveBeenIgnored();
static bool s_loadRootCertsOnDemand;
static QList<QByteArray> unixRootCertDirectories();
bool paused;
};

View File

@ -14,7 +14,8 @@ contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) {
ssl/qsslsocket_openssl_symbols_p.h \
ssl/qsslsocket_p.h \
ssl/qsslcertificateextension.h \
ssl/qsslcertificateextension_p.h
ssl/qsslcertificateextension_p.h \
ssl/qsslcontext_p.h
SOURCES += ssl/qssl.cpp \
ssl/qsslcertificate.cpp \
ssl/qsslconfiguration.cpp \
@ -24,7 +25,8 @@ contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) {
ssl/qsslsocket.cpp \
ssl/qsslsocket_openssl.cpp \
ssl/qsslsocket_openssl_symbols.cpp \
ssl/qsslcertificateextension.cpp
ssl/qsslcertificateextension.cpp \
ssl/qsslcontext.cpp
# Add optional SSL libs
# Static linking of OpenSSL with msvc:

View File

@ -71,6 +71,9 @@
#ifndef QT_NO_SSL
#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>
@ -173,6 +176,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:
@ -357,6 +363,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();
@ -5871,6 +5881,73 @@ 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() + "/qtest/mediumfile");
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;
QString urlQueryString = reply->url().query();
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_SSL
void tst_QNetworkReply::getAndThenDeleteObject_data()