Skip to content

Commit 054e85b

Browse files
committed
[BB10-internal] 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 (backport of commit 3be197881f100d1c3c8f3ce00501d7a32eb51119) Change-Id: I4c7f3a749edf0012b52deeb495706e550d24c42d Signed-off-by: Peter Hartmann <[email protected]>
1 parent 84eb620 commit 054e85b

14 files changed

+195
-8
lines changed

src/network/access/qhttpnetworkrequest.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Oper
5050
QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
5151
: QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0),
5252
autoDecompress(false), pipeliningAllowed(false), withCredentials(true)
53+
, m_cacheSslSession(false)
5354
{
5455
}
5556

@@ -64,6 +65,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
6465
customVerb = other.customVerb;
6566
withCredentials = other.withCredentials;
6667
ssl = other.ssl;
68+
m_cacheSslSession = other.m_cacheSslSession;
6769
}
6870

6971
QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate()
@@ -75,7 +77,8 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot
7577
return QHttpNetworkHeaderPrivate::operator==(other)
7678
&& (operation == other.operation)
7779
&& (ssl == other.ssl)
78-
&& (uploadByteDevice == other.uploadByteDevice);
80+
&& (uploadByteDevice == other.uploadByteDevice)
81+
&& (m_cacheSslSession == other.m_cacheSslSession);
7982
}
8083

8184
QByteArray QHttpNetworkRequestPrivate::methodName() const
@@ -311,6 +314,16 @@ QNonContiguousByteDevice* QHttpNetworkRequest::uploadByteDevice() const
311314
return d->uploadByteDevice;
312315
}
313316

317+
bool QHttpNetworkRequest::cacheSslSession()
318+
{
319+
return d->m_cacheSslSession;
320+
}
321+
322+
void QHttpNetworkRequest::setCacheSslSession(bool cacheSession)
323+
{
324+
d->m_cacheSslSession = cacheSession;
325+
}
326+
314327
int QHttpNetworkRequest::majorVersion() const
315328
{
316329
return 1;

src/network/access/qhttpnetworkrequest_p.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class Q_AUTOTEST_EXPORT QHttpNetworkRequest: public QHttpNetworkHeader
122122
void setUploadByteDevice(QNonContiguousByteDevice *bd);
123123
QNonContiguousByteDevice* uploadByteDevice() const;
124124

125+
bool cacheSslSession();
126+
void setCacheSslSession(bool cacheSession);
127+
125128
private:
126129
QSharedDataPointer<QHttpNetworkRequestPrivate> d;
127130
friend class QHttpNetworkRequestPrivate;
@@ -150,6 +153,7 @@ class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate
150153
bool pipeliningAllowed;
151154
bool withCredentials;
152155
bool ssl;
156+
bool m_cacheSslSession;
153157
};
154158

155159

src/network/access/qhttpthreaddelegate.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ void QHttpThreadDelegate::startRequest()
279279
#endif
280280
#ifndef QT_NO_OPENSSL
281281
// Set the QSslConfiguration from this QNetworkRequest.
282+
if (httpRequest.cacheSslSession())
283+
incomingSslConfiguration.d->cacheSslSession = true;
284+
282285
if (ssl && incomingSslConfiguration != QSslConfiguration::defaultConfiguration()) {
283286
httpConnection->setSslConfiguration(incomingSslConfiguration);
284287
}

src/network/access/qnetworkaccesshttpbackend.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,9 @@ void QNetworkAccessHttpBackend::postRequest()
518518
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
519519
httpRequest.setWithCredentials(false);
520520

521+
if (request().attribute(static_cast<QNetworkRequest::Attribute>(
522+
static_cast<int>(QNetworkRequest::User)-1)).toBool() == true)
523+
httpRequest.setCacheSslSession(true);
521524

522525
// Create the HTTP thread delegate
523526
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
@@ -544,8 +547,15 @@ void QNetworkAccessHttpBackend::postRequest()
544547
#endif
545548
delegate->ssl = ssl;
546549
#ifndef QT_NO_OPENSSL
547-
if (ssl)
550+
if (ssl) {
548551
delegate->incomingSslConfiguration = request().sslConfiguration();
552+
QNetworkRequest::Attribute sslSessionAttribute =
553+
static_cast<QNetworkRequest::Attribute>(
554+
static_cast<int>(QNetworkRequest::User)-3);
555+
QByteArray sslSession = request().attribute(sslSessionAttribute).toByteArray();
556+
if (!sslSession.isEmpty())
557+
delegate->incomingSslConfiguration.d->sslSession = sslSession;
558+
}
549559
#endif
550560

551561
// Do we use synchronous HTTP?
@@ -913,6 +923,21 @@ void QNetworkAccessHttpBackend::replySslConfigurationChanged(const QSslConfigura
913923
*pendingSslConfiguration = c;
914924
else if (!c.isNull())
915925
pendingSslConfiguration = new QSslConfiguration(c);
926+
927+
if (c.d->sslSession.size() > 0) {
928+
QNetworkRequest::Attribute sslSessionAttribute =
929+
static_cast<QNetworkRequest::Attribute>(
930+
static_cast<int>(QNetworkRequest::User)-3);
931+
QNetworkRequest::Attribute sslSessionTicketLifeTimeHintAttribute =
932+
static_cast<QNetworkRequest::Attribute>(
933+
static_cast<int>(QNetworkRequest::User)-2);
934+
// only set the attribute once; this method is called several times
935+
if (attribute(sslSessionAttribute).toByteArray().isEmpty()) {
936+
setAttribute(sslSessionAttribute, c.d->sslSession);
937+
setAttribute(sslSessionTicketLifeTimeHintAttribute,
938+
c.d->sslSessionTicketLifeTimeHint);
939+
}
940+
}
916941
}
917942
#endif
918943

src/network/ssl/qsslconfiguration.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
169169
d->peerVerifyMode == other.d->peerVerifyMode &&
170170
d->peerVerifyDepth == other.d->peerVerifyDepth &&
171171
d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading &&
172-
d->sslOptions == other.d->sslOptions;
172+
d->sslOptions == other.d->sslOptions &&
173+
d->sslSession == other.d->sslSession &&
174+
d->cacheSslSession == other.d->cacheSslSession &&
175+
d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint;
173176
}
174177

175178
/*!
@@ -205,7 +208,10 @@ bool QSslConfiguration::isNull() const
205208
d->peerCertificateChain.count() == 0 &&
206209
d->sslOptions == ( QSsl::SslOptionDisableEmptyFragments
207210
|QSsl::SslOptionDisableLegacyRenegotiation
208-
|QSsl::SslOptionDisableCompression));
211+
|QSsl::SslOptionDisableCompression) &&
212+
d->sslSession.isNull() &&
213+
d->cacheSslSession == false &&
214+
d->sslSessionTicketLifeTimeHint == -1);
209215
}
210216

211217
/*!

src/network/ssl/qsslconfiguration.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ class Q_NETWORK_EXPORT QSslConfiguration
130130
friend class QSslConfigurationPrivate;
131131
friend class QSslSocketBackendPrivate;
132132
friend class QSslContext;
133+
// hack to set the SSL session from a QNAM attribute:
134+
friend class QHttpThreadDelegate;
135+
friend class QNetworkAccessHttpBackend;
136+
133137
QSslConfiguration(QSslConfigurationPrivate *dd);
134138
QSharedDataPointer<QSslConfigurationPrivate> d;
135139
};

src/network/ssl/qsslconfiguration_p.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ class QSslConfigurationPrivate: public QSharedData
8787
peerSessionShared(false),
8888
sslOptions(QSsl::SslOptionDisableEmptyFragments
8989
|QSsl::SslOptionDisableLegacyRenegotiation
90-
|QSsl::SslOptionDisableCompression)
90+
|QSsl::SslOptionDisableCompression),
91+
cacheSslSession(false),
92+
sslSessionTicketLifeTimeHint(-1)
9193
{ }
9294

9395
QSslCertificate peerCertificate;
@@ -110,6 +112,10 @@ class QSslConfigurationPrivate: public QSharedData
110112

111113
QSsl::SslOptions sslOptions;
112114

115+
bool cacheSslSession;
116+
QByteArray sslSession;
117+
int sslSessionTicketLifeTimeHint;
118+
113119
// in qsslsocket.cpp:
114120
static QSslConfiguration defaultConfiguration();
115121
static void setDefaultConfiguration(const QSslConfiguration &configuration);

src/network/ssl/qsslcontext.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ extern QString getErrorsFromOpenSsl();
5858
QSslContext::QSslContext()
5959
: ctx(0),
6060
pkey(0),
61-
session(0)
61+
session(0),
62+
m_sessionTicketLifeTimeHint(-1)
6263
{
6364
}
6465

@@ -259,6 +260,10 @@ QSslContext* QSslContext::fromConfiguration(QSslSocket::SslMode mode, const QSsl
259260
if (sslContext->sslConfiguration.peerVerifyDepth() != 0)
260261
q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth());
261262

263+
// set persisted session if the user set it
264+
if (!configuration.d->sslSession.isEmpty())
265+
sslContext->setSessionASN1(configuration.d->sslSession);
266+
262267
return sslContext;
263268
}
264269

@@ -268,6 +273,12 @@ SSL* QSslContext::createSsl()
268273
SSL* ssl = q_SSL_new(ctx);
269274
q_SSL_clear(ssl);
270275

276+
if (!session && !sessionASN1().isEmpty()
277+
&& !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionTickets)) {
278+
const unsigned char *data = reinterpret_cast<const unsigned char *>(m_sessionASN1.constData());
279+
session = q_d2i_SSL_SESSION(0, &data, m_sessionASN1.size()); // refcount is 1 already, set by function above
280+
}
281+
271282
if (session) {
272283
// Try to resume the last session we cached
273284
if (!q_SSL_set_session(ssl, session)) {
@@ -293,8 +304,35 @@ bool QSslContext::cacheSession(SSL* ssl)
293304

294305
// cache the session the caller gave us and increase reference count
295306
session = q_SSL_get1_session(ssl);
296-
return (session != NULL);
297307

308+
if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionTickets)
309+
&& sslConfiguration.d->cacheSslSession == true) {
310+
int sessionSize = q_i2d_SSL_SESSION(session, 0);
311+
if (sessionSize > 0) {
312+
m_sessionASN1.resize(sessionSize);
313+
unsigned char *data = reinterpret_cast<unsigned char *>(m_sessionASN1.data());
314+
if (!q_i2d_SSL_SESSION(session, &data))
315+
qWarning("could not store persistent version of SSL session");
316+
m_sessionTicketLifeTimeHint = session->tlsext_tick_lifetime_hint;
317+
}
318+
}
319+
320+
return (session != 0);
321+
}
322+
323+
QByteArray QSslContext::sessionASN1() const
324+
{
325+
return m_sessionASN1;
326+
}
327+
328+
void QSslContext::setSessionASN1(const QByteArray &session)
329+
{
330+
m_sessionASN1 = session;
331+
}
332+
333+
int QSslContext::sessionTicketLifeTimeHint() const
334+
{
335+
return m_sessionTicketLifeTimeHint;
298336
}
299337

300338
QSslError::SslError QSslContext::error() const

src/network/ssl/qsslcontext_p.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,18 @@ class QSslContext
6969
SSL* createSsl();
7070
bool cacheSession(SSL*); // should be called when handshake completed
7171

72+
QByteArray sessionASN1() const;
73+
void setSessionASN1(const QByteArray &sessionASN1);
74+
int sessionTicketLifeTimeHint() const;
7275
protected:
7376
QSslContext();
7477

7578
private:
7679
SSL_CTX* ctx;
7780
EVP_PKEY *pkey;
7881
SSL_SESSION *session;
82+
QByteArray m_sessionASN1;
83+
int m_sessionTicketLifeTimeHint;
7984
QSslError::SslError errorCode;
8085
QString errorStr;
8186
QSslConfiguration sslConfiguration;

src/network/ssl/qsslsocket.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,9 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
897897
d->configuration.peerVerifyMode = configuration.peerVerifyMode();
898898
d->configuration.protocol = configuration.protocol();
899899
d->configuration.sslOptions = configuration.d->sslOptions;
900+
d->configuration.cacheSslSession = configuration.d->cacheSslSession;
901+
d->configuration.sslSession = configuration.d->sslSession;
902+
d->configuration.sslSessionTicketLifeTimeHint = configuration.d->sslSessionTicketLifeTimeHint;
900903

901904
// if the CA certificates were set explicitly (either via
902905
// QSslConfiguration::setCaCertificates() or QSslSocket::setCaCertificates(),

src/network/ssl/qsslsocket_openssl.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1271,8 +1271,16 @@ bool QSslSocketBackendPrivate::startHandshake()
12711271

12721272
// Cache this SSL session inside the QSslContext
12731273
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionTickets)) {
1274-
if (!sslContextPointer->cacheSession(ssl))
1274+
if (!sslContextPointer->cacheSession(ssl)) {
12751275
sslContextPointer.clear(); // we could not cache the session
1276+
} else {
1277+
// Cache the session for permanent usage as well
1278+
if (!sslContextPointer->sessionASN1().isEmpty()) {
1279+
configuration.sslSession = sslContextPointer->sessionASN1();
1280+
configuration.sslSessionTicketLifeTimeHint =
1281+
sslContextPointer->sessionTicketLifeTimeHint();
1282+
}
1283+
}
12761284
}
12771285

12781286
connectionEncrypted = true;

src/network/ssl/qsslsocket_openssl_symbols.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ DEFINEFUNC(void, OPENSSL_add_all_algorithms_noconf, void, DUMMYARG, return, DUMM
286286
DEFINEFUNC(void, OPENSSL_add_all_algorithms_conf, void, DUMMYARG, return, DUMMYARG)
287287
DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return)
288288
DEFINEFUNC(long, SSLeay, void, DUMMYARG, return 0, return)
289+
DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return)
290+
DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return 0, return)
289291

290292
#ifdef Q_OS_SYMBIAN
291293
#define RESOLVEFUNC(func, ordinal, lib) \
@@ -872,6 +874,8 @@ bool q_resolveOpenSslSymbols()
872874
RESOLVEFUNC(OPENSSL_add_all_algorithms_conf)
873875
RESOLVEFUNC(SSL_CTX_load_verify_locations)
874876
RESOLVEFUNC(SSLeay)
877+
RESOLVEFUNC(i2d_SSL_SESSION)
878+
RESOLVEFUNC(d2i_SSL_SESSION)
875879
#endif // Q_OS_SYMBIAN
876880
symbolsResolved = true;
877881
delete libs.first;

src/network/ssl/qsslsocket_openssl_symbols_p.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ void q_OPENSSL_add_all_algorithms_noconf();
429429
void q_OPENSSL_add_all_algorithms_conf();
430430
int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
431431
long q_SSLeay();
432+
int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp);
433+
SSL_SESSION *q_d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length);
432434

433435
// Helper function
434436
class QDateTime;

0 commit comments

Comments
 (0)