QSslConfiguration: add API to persist and resume SSL sessions
Session tickets can be cached on the client side for hours (e.g. graph.facebook.com: ~ 24 hours, api.twitter.com: 4 hours), because the server does not need to maintain state. We need public API for it so an application can cache the session (e.g. to disk) and resume a session already with the 1st handshake, saving one network round trip. Task-number: QTBUG-20668 Change-Id: I10255932dcd528ee1231538cb72b52b97f9f4a3c Reviewed-by: Richard J. Moore <rich@kde.org>
This commit is contained in:
parent
2116f9904a
commit
3be197881f
@ -163,12 +163,18 @@ QT_BEGIN_NAMESPACE
|
|||||||
possibility that an attacker could inject plaintext into the SSL session.
|
possibility that an attacker could inject plaintext into the SSL session.
|
||||||
\value SslOptionDisableSessionSharing Disables SSL session sharing via
|
\value SslOptionDisableSessionSharing Disables SSL session sharing via
|
||||||
the session ID handshake attribute.
|
the session ID handshake attribute.
|
||||||
|
\value SslOptionDisableSessionPersistence Disables storing the SSL session
|
||||||
|
in ASN.1 format as returned by QSslConfiguration::session(). Enabling
|
||||||
|
this feature adds memory overhead of approximately 1K per used session
|
||||||
|
ticket.
|
||||||
|
|
||||||
By default, SslOptionDisableEmptyFragments is turned on since this causes
|
By default, SslOptionDisableEmptyFragments is turned on since this causes
|
||||||
problems with a large number of servers. SslOptionDisableLegacyRenegotiation
|
problems with a large number of servers. SslOptionDisableLegacyRenegotiation
|
||||||
is also turned on, since it introduces a security risk.
|
is also turned on, since it introduces a security risk.
|
||||||
SslOptionDisableCompression is turned on to prevent the attack publicised by
|
SslOptionDisableCompression is turned on to prevent the attack publicised by
|
||||||
CRIME. The other options are turned off.
|
CRIME.
|
||||||
|
SslOptionDisableSessionPersistence is turned on to optimize memory usage.
|
||||||
|
The other options are turned off.
|
||||||
|
|
||||||
Note: Availability of above options depends on the version of the SSL
|
Note: Availability of above options depends on the version of the SSL
|
||||||
backend in use.
|
backend in use.
|
||||||
|
@ -96,7 +96,8 @@ namespace QSsl {
|
|||||||
SslOptionDisableCompression = 0x04,
|
SslOptionDisableCompression = 0x04,
|
||||||
SslOptionDisableServerNameIndication = 0x08,
|
SslOptionDisableServerNameIndication = 0x08,
|
||||||
SslOptionDisableLegacyRenegotiation = 0x10,
|
SslOptionDisableLegacyRenegotiation = 0x10,
|
||||||
SslOptionDisableSessionSharing = 0x20
|
SslOptionDisableSessionSharing = 0x20,
|
||||||
|
SslOptionDisableSessionPersistence = 0x40
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(SslOptions, SslOption)
|
Q_DECLARE_FLAGS(SslOptions, SslOption)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,8 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOptionDisableEmptyFragments
|
const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOptionDisableEmptyFragments
|
||||||
|QSsl::SslOptionDisableLegacyRenegotiation
|
|QSsl::SslOptionDisableLegacyRenegotiation
|
||||||
|QSsl::SslOptionDisableCompression;
|
|QSsl::SslOptionDisableCompression
|
||||||
|
|QSsl::SslOptionDisableSessionPersistence;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\class QSslConfiguration
|
\class QSslConfiguration
|
||||||
@ -182,7 +183,9 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
|
|||||||
d->peerVerifyMode == other.d->peerVerifyMode &&
|
d->peerVerifyMode == other.d->peerVerifyMode &&
|
||||||
d->peerVerifyDepth == other.d->peerVerifyDepth &&
|
d->peerVerifyDepth == other.d->peerVerifyDepth &&
|
||||||
d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading &&
|
d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading &&
|
||||||
d->sslOptions == other.d->sslOptions;
|
d->sslOptions == other.d->sslOptions &&
|
||||||
|
d->sslSession == other.d->sslSession &&
|
||||||
|
d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -216,7 +219,9 @@ bool QSslConfiguration::isNull() const
|
|||||||
d->privateKey.isNull() &&
|
d->privateKey.isNull() &&
|
||||||
d->peerCertificate.isNull() &&
|
d->peerCertificate.isNull() &&
|
||||||
d->peerCertificateChain.count() == 0 &&
|
d->peerCertificateChain.count() == 0 &&
|
||||||
d->sslOptions == QSslConfigurationPrivate::defaultSslOptions);
|
d->sslOptions == QSslConfigurationPrivate::defaultSslOptions &&
|
||||||
|
d->sslSession.isNull() &&
|
||||||
|
d->sslSessionTicketLifeTimeHint == -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -593,6 +598,60 @@ bool QSslConfiguration::testSslOption(QSsl::SslOption option) const
|
|||||||
return d->sslOptions & option;
|
return d->sslOptions & option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 5.2
|
||||||
|
|
||||||
|
If QSsl::SslOptionDisableSessionPersistence was turned off, this
|
||||||
|
function returns the session used in the SSL handshake in ASN.1
|
||||||
|
format, suitable to e.g. be persisted to disk. If no session was
|
||||||
|
used or QSsl::SslOptionDisableSessionPersistence was not turned off,
|
||||||
|
this function returns an empty QByteArray.
|
||||||
|
|
||||||
|
\b{Note:} When persisting the session to disk or similar, be
|
||||||
|
careful not to expose the session to a potential attacker, as
|
||||||
|
knowledge of the session allows for eavesdropping on data
|
||||||
|
encrypted with the session parameters.
|
||||||
|
|
||||||
|
\sa setSession(), QSsl::SslOptionDisableSessionPersistence, setSslOption()
|
||||||
|
*/
|
||||||
|
QByteArray QSslConfiguration::session() const
|
||||||
|
{
|
||||||
|
return d->sslSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 5.2
|
||||||
|
|
||||||
|
Sets the session to be used in an SSL handshake.
|
||||||
|
QSsl::SslOptionDisableSessionPersistence must be turned off
|
||||||
|
for this to work, and \a session must be in ASN.1 format
|
||||||
|
as returned by session().
|
||||||
|
|
||||||
|
\sa session(), QSsl::SslOptionDisableSessionPersistence, setSslOption()
|
||||||
|
*/
|
||||||
|
void QSslConfiguration::setSession(const QByteArray &session)
|
||||||
|
{
|
||||||
|
d->sslSession = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 5.2
|
||||||
|
|
||||||
|
If QSsl::SslOptionDisableSessionPersistence was turned off, this
|
||||||
|
function returns the session ticket life time hint sent by the
|
||||||
|
server (which might be 0).
|
||||||
|
If the server did not send a session ticket (e.g. when
|
||||||
|
resuming a session or when the server does not support it) or
|
||||||
|
QSsl::SslOptionDisableSessionPersistence was not turned off,
|
||||||
|
this function returns -1.
|
||||||
|
|
||||||
|
\sa session(), QSsl::SslOptionDisableSessionPersistence, setSslOption()
|
||||||
|
*/
|
||||||
|
int QSslConfiguration::sessionTicketLifeTimeHint() const
|
||||||
|
{
|
||||||
|
return d->sslSessionTicketLifeTimeHint;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the default SSL configuration to be used in new SSL
|
Returns the default SSL configuration to be used in new SSL
|
||||||
connections.
|
connections.
|
||||||
|
@ -124,6 +124,10 @@ public:
|
|||||||
void setSslOption(QSsl::SslOption option, bool on);
|
void setSslOption(QSsl::SslOption option, bool on);
|
||||||
bool testSslOption(QSsl::SslOption option) const;
|
bool testSslOption(QSsl::SslOption option) const;
|
||||||
|
|
||||||
|
QByteArray session() const;
|
||||||
|
void setSession(const QByteArray &session);
|
||||||
|
int sessionTicketLifeTimeHint() const;
|
||||||
|
|
||||||
static QSslConfiguration defaultConfiguration();
|
static QSslConfiguration defaultConfiguration();
|
||||||
static void setDefaultConfiguration(const QSslConfiguration &configuration);
|
static void setDefaultConfiguration(const QSslConfiguration &configuration);
|
||||||
|
|
||||||
|
@ -85,7 +85,8 @@ public:
|
|||||||
peerVerifyDepth(0),
|
peerVerifyDepth(0),
|
||||||
allowRootCertOnDemandLoading(true),
|
allowRootCertOnDemandLoading(true),
|
||||||
peerSessionShared(false),
|
peerSessionShared(false),
|
||||||
sslOptions(QSslConfigurationPrivate::defaultSslOptions)
|
sslOptions(QSslConfigurationPrivate::defaultSslOptions),
|
||||||
|
sslSessionTicketLifeTimeHint(-1)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
QSslCertificate peerCertificate;
|
QSslCertificate peerCertificate;
|
||||||
@ -110,6 +111,9 @@ public:
|
|||||||
|
|
||||||
Q_AUTOTEST_EXPORT static const QSsl::SslOptions defaultSslOptions;
|
Q_AUTOTEST_EXPORT static const QSsl::SslOptions defaultSslOptions;
|
||||||
|
|
||||||
|
QByteArray sslSession;
|
||||||
|
int sslSessionTicketLifeTimeHint;
|
||||||
|
|
||||||
// in qsslsocket.cpp:
|
// in qsslsocket.cpp:
|
||||||
static QSslConfiguration defaultConfiguration();
|
static QSslConfiguration defaultConfiguration();
|
||||||
static void setDefaultConfiguration(const QSslConfiguration &configuration);
|
static void setDefaultConfiguration(const QSslConfiguration &configuration);
|
||||||
|
@ -57,7 +57,8 @@ extern QString getErrorsFromOpenSsl();
|
|||||||
QSslContext::QSslContext()
|
QSslContext::QSslContext()
|
||||||
: ctx(0),
|
: ctx(0),
|
||||||
pkey(0),
|
pkey(0),
|
||||||
session(0)
|
session(0),
|
||||||
|
m_sessionTicketLifeTimeHint(-1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +259,10 @@ init_context:
|
|||||||
if (sslContext->sslConfiguration.peerVerifyDepth() != 0)
|
if (sslContext->sslConfiguration.peerVerifyDepth() != 0)
|
||||||
q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth());
|
q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth());
|
||||||
|
|
||||||
|
// set persisted session if the user set it
|
||||||
|
if (!configuration.session().isEmpty())
|
||||||
|
sslContext->setSessionASN1(configuration.session());
|
||||||
|
|
||||||
return sslContext;
|
return sslContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +272,12 @@ SSL* QSslContext::createSsl()
|
|||||||
SSL* ssl = q_SSL_new(ctx);
|
SSL* ssl = q_SSL_new(ctx);
|
||||||
q_SSL_clear(ssl);
|
q_SSL_clear(ssl);
|
||||||
|
|
||||||
|
if (!session && !sessionASN1().isEmpty()
|
||||||
|
&& !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
|
||||||
|
const unsigned char *data = reinterpret_cast<const unsigned char *>(m_sessionASN1.constData());
|
||||||
|
session = q_d2i_SSL_SESSION(0, &data, m_sessionASN1.size()); // refcount is 1 already, set by function above
|
||||||
|
}
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
// Try to resume the last session we cached
|
// Try to resume the last session we cached
|
||||||
if (!q_SSL_set_session(ssl, session)) {
|
if (!q_SSL_set_session(ssl, session)) {
|
||||||
@ -292,8 +303,34 @@ bool QSslContext::cacheSession(SSL* ssl)
|
|||||||
|
|
||||||
// cache the session the caller gave us and increase reference count
|
// cache the session the caller gave us and increase reference count
|
||||||
session = q_SSL_get1_session(ssl);
|
session = q_SSL_get1_session(ssl);
|
||||||
return (session != NULL);
|
|
||||||
|
|
||||||
|
if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
|
||||||
|
int sessionSize = q_i2d_SSL_SESSION(session, 0);
|
||||||
|
if (sessionSize > 0) {
|
||||||
|
m_sessionASN1.resize(sessionSize);
|
||||||
|
unsigned char *data = reinterpret_cast<unsigned char *>(m_sessionASN1.data());
|
||||||
|
if (!q_i2d_SSL_SESSION(session, &data))
|
||||||
|
qWarning("could not store persistent version of SSL session");
|
||||||
|
m_sessionTicketLifeTimeHint = session->tlsext_tick_lifetime_hint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (session != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray QSslContext::sessionASN1() const
|
||||||
|
{
|
||||||
|
return m_sessionASN1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QSslContext::setSessionASN1(const QByteArray &session)
|
||||||
|
{
|
||||||
|
m_sessionASN1 = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QSslContext::sessionTicketLifeTimeHint() const
|
||||||
|
{
|
||||||
|
return m_sessionTicketLifeTimeHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSslError::SslError QSslContext::error() const
|
QSslError::SslError QSslContext::error() const
|
||||||
|
@ -69,6 +69,9 @@ public:
|
|||||||
SSL* createSsl();
|
SSL* createSsl();
|
||||||
bool cacheSession(SSL*); // should be called when handshake completed
|
bool cacheSession(SSL*); // should be called when handshake completed
|
||||||
|
|
||||||
|
QByteArray sessionASN1() const;
|
||||||
|
void setSessionASN1(const QByteArray &sessionASN1);
|
||||||
|
int sessionTicketLifeTimeHint() const;
|
||||||
protected:
|
protected:
|
||||||
QSslContext();
|
QSslContext();
|
||||||
|
|
||||||
@ -76,6 +79,8 @@ private:
|
|||||||
SSL_CTX* ctx;
|
SSL_CTX* ctx;
|
||||||
EVP_PKEY *pkey;
|
EVP_PKEY *pkey;
|
||||||
SSL_SESSION *session;
|
SSL_SESSION *session;
|
||||||
|
QByteArray m_sessionASN1;
|
||||||
|
int m_sessionTicketLifeTimeHint;
|
||||||
QSslError::SslError errorCode;
|
QSslError::SslError errorCode;
|
||||||
QString errorStr;
|
QString errorStr;
|
||||||
QSslConfiguration sslConfiguration;
|
QSslConfiguration sslConfiguration;
|
||||||
|
@ -903,6 +903,8 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
|
|||||||
d->configuration.peerVerifyMode = configuration.peerVerifyMode();
|
d->configuration.peerVerifyMode = configuration.peerVerifyMode();
|
||||||
d->configuration.protocol = configuration.protocol();
|
d->configuration.protocol = configuration.protocol();
|
||||||
d->configuration.sslOptions = configuration.d->sslOptions;
|
d->configuration.sslOptions = configuration.d->sslOptions;
|
||||||
|
d->configuration.sslSession = configuration.session();
|
||||||
|
d->configuration.sslSessionTicketLifeTimeHint = configuration.sessionTicketLifeTimeHint();
|
||||||
|
|
||||||
// if the CA certificates were set explicitly (either via
|
// if the CA certificates were set explicitly (either via
|
||||||
// QSslConfiguration::setCaCertificates() or QSslSocket::setCaCertificates(),
|
// QSslConfiguration::setCaCertificates() or QSslSocket::setCaCertificates(),
|
||||||
|
@ -1450,8 +1450,16 @@ void QSslSocketBackendPrivate::continueHandshake()
|
|||||||
|
|
||||||
// Cache this SSL session inside the QSslContext
|
// Cache this SSL session inside the QSslContext
|
||||||
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionSharing)) {
|
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionSharing)) {
|
||||||
if (!sslContextPointer->cacheSession(ssl))
|
if (!sslContextPointer->cacheSession(ssl)) {
|
||||||
sslContextPointer.clear(); // we could not cache the session
|
sslContextPointer.clear(); // we could not cache the session
|
||||||
|
} else {
|
||||||
|
// Cache the session for permanent usage as well
|
||||||
|
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionPersistence)) {
|
||||||
|
if (!sslContextPointer->sessionASN1().isEmpty())
|
||||||
|
configuration.sslSession = sslContextPointer->sessionASN1();
|
||||||
|
configuration.sslSessionTicketLifeTimeHint = sslContextPointer->sessionTicketLifeTimeHint();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionEncrypted = true;
|
connectionEncrypted = true;
|
||||||
|
@ -328,6 +328,8 @@ DEFINEFUNC(void, OPENSSL_add_all_algorithms_conf, void, DUMMYARG, return, DUMMYA
|
|||||||
DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return)
|
DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return)
|
||||||
DEFINEFUNC(long, SSLeay, void, DUMMYARG, return 0, return)
|
DEFINEFUNC(long, SSLeay, void, DUMMYARG, return 0, return)
|
||||||
DEFINEFUNC(const char *, SSLeay_version, int a, a, return 0, return)
|
DEFINEFUNC(const char *, SSLeay_version, int a, a, return 0, return)
|
||||||
|
DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return)
|
||||||
|
DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return 0, return)
|
||||||
|
|
||||||
#define RESOLVEFUNC(func) \
|
#define RESOLVEFUNC(func) \
|
||||||
if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func))) \
|
if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func))) \
|
||||||
@ -797,6 +799,8 @@ bool q_resolveOpenSslSymbols()
|
|||||||
RESOLVEFUNC(SSL_CTX_load_verify_locations)
|
RESOLVEFUNC(SSL_CTX_load_verify_locations)
|
||||||
RESOLVEFUNC(SSLeay)
|
RESOLVEFUNC(SSLeay)
|
||||||
RESOLVEFUNC(SSLeay_version)
|
RESOLVEFUNC(SSLeay_version)
|
||||||
|
RESOLVEFUNC(i2d_SSL_SESSION)
|
||||||
|
RESOLVEFUNC(d2i_SSL_SESSION)
|
||||||
|
|
||||||
symbolsResolved = true;
|
symbolsResolved = true;
|
||||||
delete libs.first;
|
delete libs.first;
|
||||||
|
@ -465,6 +465,8 @@ void q_OPENSSL_add_all_algorithms_conf();
|
|||||||
int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
|
int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
|
||||||
long q_SSLeay();
|
long q_SSLeay();
|
||||||
const char *q_SSLeay_version(int type);
|
const char *q_SSLeay_version(int type);
|
||||||
|
int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp);
|
||||||
|
SSL_SESSION *q_d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length);
|
||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
class QDateTime;
|
class QDateTime;
|
||||||
|
@ -367,6 +367,8 @@ private Q_SLOTS:
|
|||||||
#ifdef QT_BUILD_INTERNAL
|
#ifdef QT_BUILD_INTERNAL
|
||||||
void sslSessionSharing_data();
|
void sslSessionSharing_data();
|
||||||
void sslSessionSharing();
|
void sslSessionSharing();
|
||||||
|
void sslSessionSharingFromPersistentSession_data();
|
||||||
|
void sslSessionSharingFromPersistentSession();
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -5966,6 +5968,63 @@ void tst_QNetworkReply::sslSessionSharingHelperSlot()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QNetworkReply::sslSessionSharingFromPersistentSession_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<bool>("sessionPersistenceEnabled");
|
||||||
|
QTest::newRow("enabled") << true;
|
||||||
|
QTest::newRow("disabled") << false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QNetworkReply::sslSessionSharingFromPersistentSession()
|
||||||
|
{
|
||||||
|
QString urlString("https://" + QtNetworkSettings::serverName());
|
||||||
|
|
||||||
|
// warm up SSL session cache to get a working session
|
||||||
|
QNetworkRequest warmupRequest(urlString);
|
||||||
|
QFETCH(bool, sessionPersistenceEnabled);
|
||||||
|
if (sessionPersistenceEnabled) {
|
||||||
|
QSslConfiguration warmupConfiguration(QSslConfiguration::defaultConfiguration());
|
||||||
|
warmupConfiguration.setSslOption(QSsl::SslOptionDisableSessionPersistence, false);
|
||||||
|
warmupRequest.setSslConfiguration(warmupConfiguration);
|
||||||
|
}
|
||||||
|
QNetworkReply *warmupReply = manager.get(warmupRequest);
|
||||||
|
warmupReply->ignoreSslErrors();
|
||||||
|
connect(warmupReply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||||
|
QTestEventLoop::instance().enterLoop(20);
|
||||||
|
QVERIFY(!QTestEventLoop::instance().timeout());
|
||||||
|
QCOMPARE(warmupReply->error(), QNetworkReply::NoError);
|
||||||
|
QByteArray sslSession = warmupReply->sslConfiguration().session();
|
||||||
|
QCOMPARE(!sslSession.isEmpty(), sessionPersistenceEnabled);
|
||||||
|
|
||||||
|
// test server sends a life time hint of 0, which is not common
|
||||||
|
// practice; however it is good enough because the default is -1
|
||||||
|
int expectedSessionTicketLifeTimeHint = sessionPersistenceEnabled ? 0 : -1;
|
||||||
|
QCOMPARE(warmupReply->sslConfiguration().sessionTicketLifeTimeHint(),
|
||||||
|
expectedSessionTicketLifeTimeHint);
|
||||||
|
|
||||||
|
warmupReply->deleteLater();
|
||||||
|
|
||||||
|
// now send another request with a new QNAM and the persisted session,
|
||||||
|
// to verify it can be resumed without any internal state
|
||||||
|
QNetworkRequest request(warmupRequest);
|
||||||
|
if (sessionPersistenceEnabled) {
|
||||||
|
QSslConfiguration configuration = request.sslConfiguration();
|
||||||
|
configuration.setSession(sslSession);
|
||||||
|
request.setSslConfiguration(configuration);
|
||||||
|
}
|
||||||
|
QNetworkAccessManager newManager;
|
||||||
|
QNetworkReply *reply = newManager.get(request);
|
||||||
|
reply->ignoreSslErrors();
|
||||||
|
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||||
|
QTestEventLoop::instance().enterLoop(20);
|
||||||
|
QVERIFY(!QTestEventLoop::instance().timeout());
|
||||||
|
QCOMPARE(reply->error(), QNetworkReply::NoError);
|
||||||
|
|
||||||
|
bool sslSessionSharingWasUsedInReply = QSslConfigurationPrivate::peerSessionWasShared(
|
||||||
|
reply->sslConfiguration());
|
||||||
|
QCOMPARE(sessionPersistenceEnabled, sslSessionSharingWasUsedInReply);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // QT_BUILD_INTERNAL
|
#endif // QT_BUILD_INTERNAL
|
||||||
#endif // QT_NO_SSL
|
#endif // QT_NO_SSL
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user