Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Native Development Knowledge Base

re-using SSL sessions in Cascades apps at startup

by Retired on ‎06-26-2013 02:08 PM (3,207 Views)

Cascades™ apps that use a Web service (e.g. Facebook® or Twitter®) usually connect to the same server(s) at startup. Connections to sites that offer their API over SSL (eg: https://graph.facebook.com or https://api.twitter.com) can be made faster by persisting and re-using SSL sessions via TLS session tickets (RFC 5077).

 

Re-using a TLS/SSL session has the benefits of saving one network roundtrip of the SSL handshake. This could result in your app being able to display content 200-300 milliseconds faster.

 

This new feature will be available from the BlackBerry 10 OS version 10.2 update onwards.

 

Below is sample code that checks whether it has a SSL session in its store and re-uses it if the session is still fresh. If this code is executed on a device containing BlackBerry 10 OS version 10.1 or earlier software, the session attributes used in the code sample below are just empty. In other words, with sane checking for session attributes (which needs to be done anyhow), this code is safe on 10.1 and earlier, it will just not do anything useful. So an app could safely ship this code already on devices with 10.1 OS.

 

When implementing this feature in an app, the most important thing to remember is that the persisted session must not be readable by anybody except the app using it. Also, it is good practice to respect the life time hint sent by the server and have a maximum life time hint defined, because the server might not send one. In general, the server has final say on session lifetime, i.e. it might discard a fresh session.

 

Caution: The code below stores the SSL session in a QSettings instance, which is safe on BlackBerry 10. Applications cannot break out of their sandbox and read files of other apps. In other words, the example below is safe on BlackBerry 10, but might be unsafe on other platforms.

 

Step 0: code skeleton

 

The code below is a standalone console program that just sends a network request, and does not make use of SSL session persistence yet. In addition to the standard app setup routines, there are 2 methods: One "sendRequest()" method that is called at app startup and sends a HTTPS network request, and a "replyFinished()" slot that is called when that reply has finished.

 

#include <QtCore>
#include <QtNetwork>

class SessionPersistenceSample : public QObject {

    Q_OBJECT

public:
    SessionPersistenceSample() : QObject()
    {
        // make sure the event loop is running when the network request is sent
        QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
    }

public slots:
    void replyFinished(QNetworkReply *reply);

private:
    Q_INVOKABLE void init();
    void sendRequest(const QUrl &url);

    QNetworkAccessManager m_networkAccessManager;
};

void SessionPersistenceSample::init() {

    connect(&m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
        this, SLOT(replyFinished(QNetworkReply*)));
    QUrl url("https://graph.facebook.com");
    sendRequest(url);
}

void SessionPersistenceSample::sendRequest(const QUrl &url) {

    QNetworkRequest request(url);

    m_networkAccessManager.get(request);
}

void SessionPersistenceSample::replyFinished(QNetworkReply *reply) {

}

int main(int argc, char **argv) {

    QCoreApplication app(argc, argv);
    SessionPersistenceSample sample;
    return app.exec();
}

#include "main.moc"

 

Step 1: saving and using the SSL session

 

The code below adds functionality to save the SSL session to disk, in case the server supports it. Before sending the request, the code is checking whether there is already a session stored in a QSettings instance. If yes, it tries to reuse the session by setting it on the network request. Once the request has finished, the code checks whether the server has sent a new session; in that case, it updates the settings object with the new session. New code is marked with green background.

 

#include <QtCore>
#include <QtNetwork>

class SessionPersistenceSample : public QObject {

    Q_OBJECT

public:
    SessionPersistenceSample() : QObject()
    {
        // make sure the event loop is running when the network request is sent
        QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
    }

public slots:
    void replyFinished(QNetworkReply *reply);

private:
    Q_INVOKABLE void init();
    void sendRequest(const QUrl &url);

    QByteArray m_session;
    QNetworkAccessManager m_networkAccessManager;
    QSettings m_settings;

    static QNetworkRequest::Attribute sslSessionEnablePersistenceAttribute;
    static QNetworkRequest::Attribute sslSessionAttribute;
};

QNetworkRequest::Attribute SessionPersistenceSample::sslSessionEnablePersistenceAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-1);
QNetworkRequest::Attribute SessionPersistenceSample::sslSessionAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-3);

void SessionPersistenceSample::init() {

    connect(&m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
        this, SLOT(replyFinished(QNetworkReply*)));
    QUrl url("https://graph.facebook.com");
    sendRequest(url);
}

void SessionPersistenceSample::sendRequest(const QUrl &url) {

    QNetworkRequest request(url);

    // storing the SSL session needs to be enabled manually
    request.setAttribute(sslSessionEnablePersistenceAttribute, true);
    
    // check whether we can re-use a session from our store
    QString settingsKeySession =
        QString::fromAscii("ssl-session-").append(url.host());
    QByteArray session = m_settings.value(settingsKeySession).toByteArray();

    if (session.size() > 0) {
            qDebug("found fresh SSL session in store, trying to re-use it...");
            request.setAttribute(sslSessionAttribute, session);
    } else {
        qDebug("no SSL session found in store, need to make full handshake.");
    }
    m_networkAccessManager.get(request);
}

void SessionPersistenceSample::replyFinished(QNetworkReply *reply) {

    // if the server sent a session, compare it to the one from our store
    QByteArray usedSession = reply->attribute(sslSessionAttribute).toByteArray();

    if (usedSession.size() > 0) {
        QString settingsKeySession =
            QString::fromAscii("ssl-session-").append(reply->url().host());
        QByteArray storedSession =
            m_settings.value(settingsKeySession).toByteArray();
        if (usedSession == storedSession) {
            qDebug("SSL session was re-used, nothing to do.");
        } else {
            qDebug("server sent a new SSL session,"
                "updating SSL session store...");
            m_settings.setValue(settingsKeySession, usedSession);
        }
    } else {
        qDebug("server did not send a SSL session, nothing to do.");
    }
}

int main(int argc, char **argv) {

    QCoreApplication app(argc, argv);
    SessionPersistenceSample sample;
    return app.exec();
}

#include "main.moc"

 

Step 2: using the session life time hint

 

The code below adds functionality to respect the session life time hint sent by the server. This code can be used as reference to implement session sharing in a Cascades app. Again, changes are marked with green background.

 

#include <QtCore>
#include <QtNetwork>

class SessionPersistenceSample : public QObject {

    Q_OBJECT

public:
    SessionPersistenceSample() : QObject()
    {
        // make sure the event loop is running when the network request is sent
        QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
    }

public slots:
    void replyFinished(QNetworkReply *reply);

private:
    Q_INVOKABLE void init();
    void sendRequest(const QUrl &url);

    QByteArray m_session;
    QNetworkAccessManager m_networkAccessManager;
    QSettings m_settings;

    static QNetworkRequest::Attribute sslSessionEnablePersistenceAttribute;
    static QNetworkRequest::Attribute sslSessionTicketLifeTimeHintAttribute;
    static QNetworkRequest::Attribute sslSessionAttribute;
};

QNetworkRequest::Attribute SessionPersistenceSample::sslSessionEnablePersistenceAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-1);
QNetworkRequest::Attribute SessionPersistenceSample::sslSessionTicketLifeTimeHintAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-2);
QNetworkRequest::Attribute SessionPersistenceSample::sslSessionAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-3);

void SessionPersistenceSample::init() {

    connect(&m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
        this, SLOT(replyFinished(QNetworkReply*)));
    QUrl url("https://graph.facebook.com");
    sendRequest(url);
}

void SessionPersistenceSample::sendRequest(const QUrl &url) {

    QNetworkRequest request(url);

    // storing the SSL session needs to be enabled manually
    request.setAttribute(sslSessionEnablePersistenceAttribute, true);
    
    // check whether we can re-use a session from our store
    QString settingsKeySession =
        QString::fromAscii("ssl-session-").append(url.host());
    QByteArray session = m_settings.value(settingsKeySession).toByteArray();

    if (session.size() > 0) {
        // we have a session stored from earlier connections,
        // now check whether it is still fresh
        QString settingsKeyLifeTimeHint = settingsKeySession
            + QLatin1String("-expirationDate");
        QDateTime expirationDate =
            m_settings.value(settingsKeyLifeTimeHint).toDateTime();
        if (expirationDate < QDateTime::currentDateTime()) {
            qWarning("SSL session has expired, deleting it from the store"
                "and falling back to full handshake.");
            m_settings.remove(settingsKeySession);
            m_settings.remove(settingsKeyLifeTimeHint);
        } else {
            qDebug("found fresh SSL session in store, trying to re-use it...");
            request.setAttribute(sslSessionAttribute, session);
        }
    } else {
        qDebug("no SSL session found in store, need to make full handshake.");
    }
    m_networkAccessManager.get(request);
}

void SessionPersistenceSample::replyFinished(QNetworkReply *reply) {

    // if the server sent a session, compare it to the one from our store
    QByteArray usedSession = reply->attribute(sslSessionAttribute).toByteArray();

    if (usedSession.size() > 0) {
        QString settingsKeySession =
            QString::fromAscii("ssl-session-").append(reply->url().host());
        QByteArray storedSession =
            m_settings.value(settingsKeySession).toByteArray();
        if (usedSession == storedSession) {
            qDebug("SSL session was re-used, nothing to do.");
        } else {
            qDebug("server sent a new SSL session,"
                "updating SSL session store...");
            m_settings.setValue(settingsKeySession, usedSession);
            int lifeTimeHint =
                reply->attribute(sslSessionTicketLifeTimeHintAttribute).toInt();
            if (lifeTimeHint <= 0) {
                qDebug("no life time hint given,"
                    "falling back to life time hint of 1 day.");
                lifeTimeHint = 24 * 60 * 60;
            }

            // calculate the expiration date and store it with the session
            QDateTime sessionExpirationDate =
                QDateTime::currentDateTime().addSecs(lifeTimeHint);
            QString settingsKeyLifeTimeHint = settingsKeySession
                + QLatin1String("-expirationDate");
            m_settings.setValue(settingsKeyLifeTimeHint, sessionExpirationDate);
            qDebug() << "storing SSL session expiration date:"
                << sessionExpirationDate.toString();
        }
    } else {
        qDebug("server did not send a SSL session, nothing to do.");
    }
}

int main(int argc, char **argv) {

    QCoreApplication app(argc, argv);
    SessionPersistenceSample sample;
    return app.exec();
}

#include "main.moc"

 

Summary

 

If a server supports TLS session tickets, it might well be worth enabling it in an app with the code presented above. The longer a session life time is, the higher is the probability that a session can be reused (e.g. life time of graph.facebook.com: almost one day, api.twitter.com: 4 hours).

 

Whether or not a server supports session tickets can be found out by e.g. tracing network traffic with Wireshark® and filter for the "ssl.handshake.session_ticket" field.

Users Online
Currently online: 4 members 1,796 guests
Recent signins:
Please welcome our newest community members: