summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hartmann <phartmann@blackberry.com>2013-04-30 14:48:22 +0200
committerPeter Hartmann <phartmann@blackberry.com>2013-06-03 12:50:15 +0200
commit054e85bfdae2af37f0c2ee17d131e0b3cb2d2f25 (patch)
treeab51c7b9c845455c94a3997c23da2bb77d0673f1
parent84eb6200ff0850db8bab8d32b70e571ca9db2bda (diff)
downloadqt4-tools-4.8-bb10.tar.gz
[BB10-internal] QSslConfiguration: add API to persist and resume SSL sessions4.8-bb10
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 (backport of commit 3be197881f100d1c3c8f3ce00501d7a32eb51119) Change-Id: I4c7f3a749edf0012b52deeb495706e550d24c42d Signed-off-by: Peter Hartmann <phartmann@blackberry.com>
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp15
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h4
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp3
-rw-r--r--src/network/access/qnetworkaccesshttpbackend.cpp27
-rw-r--r--src/network/ssl/qsslconfiguration.cpp10
-rw-r--r--src/network/ssl/qsslconfiguration.h4
-rw-r--r--src/network/ssl/qsslconfiguration_p.h8
-rw-r--r--src/network/ssl/qsslcontext.cpp42
-rw-r--r--src/network/ssl/qsslcontext_p.h5
-rw-r--r--src/network/ssl/qsslsocket.cpp3
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp10
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols.cpp4
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols_p.h2
-rw-r--r--tests/auto/qnetworkreply/tst_qnetworkreply.cpp66
14 files changed, 195 insertions, 8 deletions
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
index 3fc6229e29..d437f6363a 100644
--- a/src/network/access/qhttpnetworkrequest.cpp
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -50,6 +50,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Oper
QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
: QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0),
autoDecompress(false), pipeliningAllowed(false), withCredentials(true)
+ , m_cacheSslSession(false)
{
}
@@ -64,6 +65,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
customVerb = other.customVerb;
withCredentials = other.withCredentials;
ssl = other.ssl;
+ m_cacheSslSession = other.m_cacheSslSession;
}
QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate()
@@ -75,7 +77,8 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot
return QHttpNetworkHeaderPrivate::operator==(other)
&& (operation == other.operation)
&& (ssl == other.ssl)
- && (uploadByteDevice == other.uploadByteDevice);
+ && (uploadByteDevice == other.uploadByteDevice)
+ && (m_cacheSslSession == other.m_cacheSslSession);
}
QByteArray QHttpNetworkRequestPrivate::methodName() const
@@ -311,6 +314,16 @@ QNonContiguousByteDevice* QHttpNetworkRequest::uploadByteDevice() const
return d->uploadByteDevice;
}
+bool QHttpNetworkRequest::cacheSslSession()
+{
+ return d->m_cacheSslSession;
+}
+
+void QHttpNetworkRequest::setCacheSslSession(bool cacheSession)
+{
+ d->m_cacheSslSession = cacheSession;
+}
+
int QHttpNetworkRequest::majorVersion() const
{
return 1;
diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h
index e6216db2e3..09dd76e1fa 100644
--- a/src/network/access/qhttpnetworkrequest_p.h
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -122,6 +122,9 @@ public:
void setUploadByteDevice(QNonContiguousByteDevice *bd);
QNonContiguousByteDevice* uploadByteDevice() const;
+ bool cacheSslSession();
+ void setCacheSslSession(bool cacheSession);
+
private:
QSharedDataPointer<QHttpNetworkRequestPrivate> d;
friend class QHttpNetworkRequestPrivate;
@@ -150,6 +153,7 @@ public:
bool pipeliningAllowed;
bool withCredentials;
bool ssl;
+ bool m_cacheSslSession;
};
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index 38d9bc1177..7ace6279d7 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -279,6 +279,9 @@ void QHttpThreadDelegate::startRequest()
#endif
#ifndef QT_NO_OPENSSL
// Set the QSslConfiguration from this QNetworkRequest.
+ if (httpRequest.cacheSslSession())
+ incomingSslConfiguration.d->cacheSslSession = true;
+
if (ssl && incomingSslConfiguration != QSslConfiguration::defaultConfiguration()) {
httpConnection->setSslConfiguration(incomingSslConfiguration);
}
diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp
index f3ae1bcb7f..68200b1f04 100644
--- a/src/network/access/qnetworkaccesshttpbackend.cpp
+++ b/src/network/access/qnetworkaccesshttpbackend.cpp
@@ -518,6 +518,9 @@ void QNetworkAccessHttpBackend::postRequest()
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
httpRequest.setWithCredentials(false);
+ if (request().attribute(static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-1)).toBool() == true)
+ httpRequest.setCacheSslSession(true);
// Create the HTTP thread delegate
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
@@ -544,8 +547,15 @@ void QNetworkAccessHttpBackend::postRequest()
#endif
delegate->ssl = ssl;
#ifndef QT_NO_OPENSSL
- if (ssl)
+ if (ssl) {
delegate->incomingSslConfiguration = request().sslConfiguration();
+ QNetworkRequest::Attribute sslSessionAttribute =
+ static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-3);
+ QByteArray sslSession = request().attribute(sslSessionAttribute).toByteArray();
+ if (!sslSession.isEmpty())
+ delegate->incomingSslConfiguration.d->sslSession = sslSession;
+ }
#endif
// Do we use synchronous HTTP?
@@ -913,6 +923,21 @@ void QNetworkAccessHttpBackend::replySslConfigurationChanged(const QSslConfigura
*pendingSslConfiguration = c;
else if (!c.isNull())
pendingSslConfiguration = new QSslConfiguration(c);
+
+ if (c.d->sslSession.size() > 0) {
+ QNetworkRequest::Attribute sslSessionAttribute =
+ static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-3);
+ QNetworkRequest::Attribute sslSessionTicketLifeTimeHintAttribute =
+ static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-2);
+ // only set the attribute once; this method is called several times
+ if (attribute(sslSessionAttribute).toByteArray().isEmpty()) {
+ setAttribute(sslSessionAttribute, c.d->sslSession);
+ setAttribute(sslSessionTicketLifeTimeHintAttribute,
+ c.d->sslSessionTicketLifeTimeHint);
+ }
+ }
}
#endif
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index 54e6751664..d1d4b87231 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -169,7 +169,10 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
d->peerVerifyMode == other.d->peerVerifyMode &&
d->peerVerifyDepth == other.d->peerVerifyDepth &&
d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading &&
- d->sslOptions == other.d->sslOptions;
+ d->sslOptions == other.d->sslOptions &&
+ d->sslSession == other.d->sslSession &&
+ d->cacheSslSession == other.d->cacheSslSession &&
+ d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint;
}
/*!
@@ -205,7 +208,10 @@ bool QSslConfiguration::isNull() const
d->peerCertificateChain.count() == 0 &&
d->sslOptions == ( QSsl::SslOptionDisableEmptyFragments
|QSsl::SslOptionDisableLegacyRenegotiation
- |QSsl::SslOptionDisableCompression));
+ |QSsl::SslOptionDisableCompression) &&
+ d->sslSession.isNull() &&
+ d->cacheSslSession == false &&
+ d->sslSessionTicketLifeTimeHint == -1);
}
/*!
diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h
index e27b99fc0e..dc37ba2af0 100644
--- a/src/network/ssl/qsslconfiguration.h
+++ b/src/network/ssl/qsslconfiguration.h
@@ -130,6 +130,10 @@ private:
friend class QSslConfigurationPrivate;
friend class QSslSocketBackendPrivate;
friend class QSslContext;
+ // hack to set the SSL session from a QNAM attribute:
+ friend class QHttpThreadDelegate;
+ friend class QNetworkAccessHttpBackend;
+
QSslConfiguration(QSslConfigurationPrivate *dd);
QSharedDataPointer<QSslConfigurationPrivate> d;
};
diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h
index cf871c8c71..40f4729a78 100644
--- a/src/network/ssl/qsslconfiguration_p.h
+++ b/src/network/ssl/qsslconfiguration_p.h
@@ -87,7 +87,9 @@ public:
peerSessionShared(false),
sslOptions(QSsl::SslOptionDisableEmptyFragments
|QSsl::SslOptionDisableLegacyRenegotiation
- |QSsl::SslOptionDisableCompression)
+ |QSsl::SslOptionDisableCompression),
+ cacheSslSession(false),
+ sslSessionTicketLifeTimeHint(-1)
{ }
QSslCertificate peerCertificate;
@@ -110,6 +112,10 @@ public:
QSsl::SslOptions sslOptions;
+ bool cacheSslSession;
+ QByteArray sslSession;
+ int sslSessionTicketLifeTimeHint;
+
// in qsslsocket.cpp:
static QSslConfiguration defaultConfiguration();
static void setDefaultConfiguration(const QSslConfiguration &configuration);
diff --git a/src/network/ssl/qsslcontext.cpp b/src/network/ssl/qsslcontext.cpp
index e0a9cbc12c..c73fb939cb 100644
--- a/src/network/ssl/qsslcontext.cpp
+++ b/src/network/ssl/qsslcontext.cpp
@@ -58,7 +58,8 @@ extern QString getErrorsFromOpenSsl();
QSslContext::QSslContext()
: ctx(0),
pkey(0),
- session(0)
+ session(0),
+ m_sessionTicketLifeTimeHint(-1)
{
}
@@ -259,6 +260,10 @@ init_context:
if (sslContext->sslConfiguration.peerVerifyDepth() != 0)
q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth());
+ // set persisted session if the user set it
+ if (!configuration.d->sslSession.isEmpty())
+ sslContext->setSessionASN1(configuration.d->sslSession);
+
return sslContext;
}
@@ -268,6 +273,12 @@ SSL* QSslContext::createSsl()
SSL* ssl = q_SSL_new(ctx);
q_SSL_clear(ssl);
+ if (!session && !sessionASN1().isEmpty()
+ && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionTickets)) {
+ 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) {
// Try to resume the last session we cached
if (!q_SSL_set_session(ssl, session)) {
@@ -293,8 +304,35 @@ bool QSslContext::cacheSession(SSL* ssl)
// cache the session the caller gave us and increase reference count
session = q_SSL_get1_session(ssl);
- return (session != NULL);
+ if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionTickets)
+ && sslConfiguration.d->cacheSslSession == true) {
+ 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
diff --git a/src/network/ssl/qsslcontext_p.h b/src/network/ssl/qsslcontext_p.h
index 7f2fc4bc9c..b54a833cb6 100644
--- a/src/network/ssl/qsslcontext_p.h
+++ b/src/network/ssl/qsslcontext_p.h
@@ -69,6 +69,9 @@ public:
SSL* createSsl();
bool cacheSession(SSL*); // should be called when handshake completed
+ QByteArray sessionASN1() const;
+ void setSessionASN1(const QByteArray &sessionASN1);
+ int sessionTicketLifeTimeHint() const;
protected:
QSslContext();
@@ -76,6 +79,8 @@ private:
SSL_CTX* ctx;
EVP_PKEY *pkey;
SSL_SESSION *session;
+ QByteArray m_sessionASN1;
+ int m_sessionTicketLifeTimeHint;
QSslError::SslError errorCode;
QString errorStr;
QSslConfiguration sslConfiguration;
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index be46ca5ffb..fc75fa52e1 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -897,6 +897,9 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
d->configuration.peerVerifyMode = configuration.peerVerifyMode();
d->configuration.protocol = configuration.protocol();
d->configuration.sslOptions = configuration.d->sslOptions;
+ d->configuration.cacheSslSession = configuration.d->cacheSslSession;
+ d->configuration.sslSession = configuration.d->sslSession;
+ d->configuration.sslSessionTicketLifeTimeHint = configuration.d->sslSessionTicketLifeTimeHint;
// if the CA certificates were set explicitly (either via
// QSslConfiguration::setCaCertificates() or QSslSocket::setCaCertificates(),
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 073ad27a4b..d3b8ba979e 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -1271,8 +1271,16 @@ bool QSslSocketBackendPrivate::startHandshake()
// Cache this SSL session inside the QSslContext
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionTickets)) {
- if (!sslContextPointer->cacheSession(ssl))
+ if (!sslContextPointer->cacheSession(ssl)) {
sslContextPointer.clear(); // we could not cache the session
+ } else {
+ // Cache the session for permanent usage as well
+ if (!sslContextPointer->sessionASN1().isEmpty()) {
+ configuration.sslSession = sslContextPointer->sessionASN1();
+ configuration.sslSessionTicketLifeTimeHint =
+ sslContextPointer->sessionTicketLifeTimeHint();
+ }
+ }
}
connectionEncrypted = true;
diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp
index e7c9077e85..1c32dbed90 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp
@@ -286,6 +286,8 @@ DEFINEFUNC(void, OPENSSL_add_all_algorithms_noconf, void, DUMMYARG, return, DUMM
DEFINEFUNC(void, OPENSSL_add_all_algorithms_conf, void, DUMMYARG, return, DUMMYARG)
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)
+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)
#ifdef Q_OS_SYMBIAN
#define RESOLVEFUNC(func, ordinal, lib) \
@@ -872,6 +874,8 @@ bool q_resolveOpenSslSymbols()
RESOLVEFUNC(OPENSSL_add_all_algorithms_conf)
RESOLVEFUNC(SSL_CTX_load_verify_locations)
RESOLVEFUNC(SSLeay)
+ RESOLVEFUNC(i2d_SSL_SESSION)
+ RESOLVEFUNC(d2i_SSL_SESSION)
#endif // Q_OS_SYMBIAN
symbolsResolved = true;
delete libs.first;
diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h
index c740fa445c..761b8ba04f 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h
@@ -429,6 +429,8 @@ void q_OPENSSL_add_all_algorithms_noconf();
void q_OPENSSL_add_all_algorithms_conf();
int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
long q_SSLeay();
+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
class QDateTime;
diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
index c0975817ac..0f60e31086 100644
--- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
@@ -349,6 +349,8 @@ private Q_SLOTS:
#ifdef QT_BUILD_INTERNAL
void sslSessionSharing_data();
void sslSessionSharing();
+ void sslSessionSharingFromPersistentSession_data();
+ void sslSessionSharingFromPersistentSession();
#endif
#endif
@@ -5742,6 +5744,70 @@ 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()
+{
+ QNetworkRequest::Attribute sslSessionEnablePersistenceAttribute =
+ static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-1);
+ QNetworkRequest::Attribute sslSessionTicketLifeTimeHintAttribute =
+ static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-2);
+ QNetworkRequest::Attribute sslSessionAttribute =
+ static_cast<QNetworkRequest::Attribute>(
+ static_cast<int>(QNetworkRequest::User)-3);
+
+ QString urlString("https://" + QtNetworkSettings::serverName());
+
+ // warm up SSL session cache to get a working session
+ QNetworkRequest warmupRequest(urlString);
+ QFETCH(bool, sessionPersistenceEnabled);
+ if (sessionPersistenceEnabled)
+ warmupRequest.setAttribute(sslSessionEnablePersistenceAttribute, true);
+
+ 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->attribute(sslSessionAttribute).toByteArray();
+ QCOMPARE(!sslSession.isEmpty(), sessionPersistenceEnabled);
+
+ QVariant sessionTicketLifeTimeHint = warmupReply->attribute(
+ sslSessionTicketLifeTimeHintAttribute);
+ // the value will always be 0: If enabled the server sends a value of 0,
+ // but in that case the attribute is defined at least
+ QCOMPARE(sessionPersistenceEnabled, !sessionTicketLifeTimeHint.isNull());
+ QCOMPARE(sessionTicketLifeTimeHint.toInt(), 0);
+
+ 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)
+ request.setAttribute(sslSessionAttribute, sslSession);
+
+ 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_NO_OPENSSL