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:
parent
7765dff1bb
commit
b5652df775
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -127,6 +127,8 @@ public:
|
||||
private:
|
||||
friend class QSslSocket;
|
||||
friend class QSslConfigurationPrivate;
|
||||
friend class QSslSocketBackendPrivate;
|
||||
friend class QSslContext;
|
||||
QSslConfiguration(QSslConfigurationPrivate *dd);
|
||||
QSharedDataPointer<QSslConfigurationPrivate> d;
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
306
src/network/ssl/qsslcontext.cpp
Normal file
306
src/network/ssl/qsslcontext.cpp
Normal 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
|
88
src/network/ssl/qsslcontext_p.h
Normal file
88
src/network/ssl/qsslcontext_p.h
Normal 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
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user