From 42cfb5fe4daa586f382bde6936b0ee33b5298f4d Mon Sep 17 00:00:00 2001
From: Peter Hartmann <phartmann@blackberry.com>
Date: Wed, 28 Aug 2013 10:56:24 +0200
Subject: [PATCH] SSL: add support for the Next Protocol Negotiation extension

... which is needed to negotiate the SPDY protocol.

[ChangeLog][QtNetwork][QSslConfiguration] Added support for the Next
Protocol Negotiation (NPN) TLS extension.

Task-number: QTBUG-33208

Change-Id: I3c945f9b7e2d2ffb0814bfdd3e87de1dae6c20ef
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
---
 src/network/ssl/qsslconfiguration.cpp         | 106 ++++++++++-
 src/network/ssl/qsslconfiguration.h           |  16 ++
 src/network/ssl/qsslconfiguration_p.h         |   8 +-
 src/network/ssl/qsslcontext.cpp               |  60 +++++++
 src/network/ssl/qsslcontext_p.h               |  20 +++
 src/network/ssl/qsslsocket.cpp                |   4 +
 src/network/ssl/qsslsocket_openssl.cpp        |   9 +
 .../ssl/qsslsocket_openssl_symbols.cpp        |  20 +++
 .../ssl/qsslsocket_openssl_symbols_p.h        |  15 ++
 tests/manual/manual.pro                       |   1 +
 tests/manual/qsslsocket/main.cpp              | 164 ++++++++++++++++++
 tests/manual/qsslsocket/qsslsocket.pro        |   6 +
 12 files changed, 426 insertions(+), 3 deletions(-)
 create mode 100644 tests/manual/qsslsocket/main.cpp
 create mode 100644 tests/manual/qsslsocket/qsslsocket.pro

diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index 3d7656262b2..1e859ae6e61 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -52,6 +53,9 @@ const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOp
                                                                     |QSsl::SslOptionDisableCompression
                                                                     |QSsl::SslOptionDisableSessionPersistence;
 
+const char QSslConfiguration::NextProtocolSpdy3_0[] = "spdy/3";
+const char QSslConfiguration::NextProtocolHttp1_1[] = "http/1.1";
+
 /*!
     \class QSslConfiguration
     \brief The QSslConfiguration class holds the configuration and state of an SSL connection
@@ -112,6 +116,33 @@ const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOp
         QSslSocket::sslConfiguration(), QSslSocket::setSslConfiguration()
 */
 
+/*!
+    \enum QSslConfiguration::NextProtocolNegotiationStatus
+
+    Describes the status of the Next Protocol Negotiation (NPN).
+
+    \value NextProtocolNegotiationNone No application protocol
+    has been negotiated (yet).
+
+    \value NextProtocolNegotiationNegotiated A next protocol
+    has been negotiated (see nextNegotiatedProtocol()).
+
+    \value NextProtocolNegotiationUnsupported The client and
+    server could not agree on a common next application protocol.
+*/
+
+/*!
+    \variable QSslConfiguration::NextProtocolSpdy3_0
+    \brief The value used for negotiating SPDY 3.0 during the Next
+    Protocol Negotiation.
+*/
+
+/*!
+    \variable QSslConfiguration::NextProtocolHttp1_1
+    \brief The value used for negotiating HTTP 1.1 during the Next
+    Protocol Negotiation.
+*/
+
 /*!
     Constructs an empty SSL configuration. This configuration contains
     no valid settings and the state will be empty. isNull() will
@@ -185,7 +216,10 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
         d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading &&
         d->sslOptions == other.d->sslOptions &&
         d->sslSession == other.d->sslSession &&
-        d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint;
+        d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint &&
+        d->nextAllowedProtocols == other.d->nextAllowedProtocols &&
+        d->nextNegotiatedProtocol == other.d->nextNegotiatedProtocol &&
+        d->nextProtocolNegotiationStatus == other.d->nextProtocolNegotiationStatus;
 }
 
 /*!
@@ -221,7 +255,10 @@ bool QSslConfiguration::isNull() const
             d->peerCertificateChain.count() == 0 &&
             d->sslOptions == QSslConfigurationPrivate::defaultSslOptions &&
             d->sslSession.isNull() &&
-            d->sslSessionTicketLifeTimeHint == -1);
+            d->sslSessionTicketLifeTimeHint == -1 &&
+            d->nextAllowedProtocols.isEmpty() &&
+            d->nextNegotiatedProtocol.isNull() &&
+            d->nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNone);
 }
 
 /*!
@@ -652,6 +689,71 @@ int QSslConfiguration::sessionTicketLifeTimeHint() const
     return d->sslSessionTicketLifeTimeHint;
 }
 
+/*!
+  \since 5.3
+
+  This function returns the protocol negotiated with the server
+  if the Next Protocol Negotiation (NPN) TLS extension was enabled.
+  In order for the NPN extension to be enabled, setAllowedNextProtocols()
+  needs to be called explicitly before connecting to the server.
+
+  If no protocol could be negotiated or the extension was not enabled,
+  this function returns a QByteArray which is null.
+
+  \sa setAllowedNextProtocols(), nextProtocolNegotiationStatus()
+ */
+QByteArray QSslConfiguration::nextNegotiatedProtocol() const
+{
+    return d->nextNegotiatedProtocol;
+}
+
+/*!
+  \since 5.3
+
+  This function sets the allowed \a protocols to be negotiated with the
+  server through the Next Protocol Negotiation (NPN) TLS extension; each
+  element in \a protocols must define one allowed protocol.
+  The function must be called explicitly before connecting to send the NPN
+  extension in the SSL handshake.
+  Whether or not the negotiation succeeded can be queried through
+  nextProtocolNegotiationStatus().
+
+  \sa nextNegotiatedProtocol(), nextProtocolNegotiationStatus(), allowedNextProtocols(), QSslConfiguration::NextProtocolSpdy3_0, QSslConfiguration::NextProtocolHttp1_1
+ */
+void QSslConfiguration::setAllowedNextProtocols(QList<QByteArray> protocols)
+{
+    d->nextAllowedProtocols = protocols;
+}
+
+/*!
+  \since 5.3
+
+  This function returns the allowed protocols to be negotiated with the
+  server through the Next Protocol Negotiation (NPN) TLS extension, as set
+  by setAllowedNextProtocols().
+
+  \sa nextNegotiatedProtocol(), nextProtocolNegotiationStatus(), setAllowedNextProtocols(), QSslConfiguration::NextProtocolSpdy3_0, QSslConfiguration::NextProtocolHttp1_1
+ */
+QList<QByteArray> QSslConfiguration::allowedNextProtocols() const
+{
+    return d->nextAllowedProtocols;
+}
+
+/*!
+  \since 5.3
+
+  This function returns the status of the Next Protocol Negotiation (NPN).
+  If the feature has not been enabled through setAllowedNextProtocols(),
+  this function returns NextProtocolNegotiationNone.
+  The status will be set before emitting the encrypted() signal.
+
+  \sa setAllowedNextProtocols(), allowedNextProtocols(), nextNegotiatedProtocol(), QSslConfiguration::NextProtocolNegotiationStatus
+ */
+QSslConfiguration::NextProtocolNegotiationStatus QSslConfiguration::nextProtocolNegotiationStatus() const
+{
+    return d->nextProtocolNegotiationStatus;
+}
+
 /*!
     Returns the default SSL configuration to be used in new SSL
     connections.
diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h
index a48eceb63e0..587187ca060 100644
--- a/src/network/ssl/qsslconfiguration.h
+++ b/src/network/ssl/qsslconfiguration.h
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -131,6 +132,21 @@ public:
     static QSslConfiguration defaultConfiguration();
     static void setDefaultConfiguration(const QSslConfiguration &configuration);
 
+    enum NextProtocolNegotiationStatus {
+        NextProtocolNegotiationNone,
+        NextProtocolNegotiationNegotiated,
+        NextProtocolNegotiationUnsupported
+    };
+
+    void setAllowedNextProtocols(QList<QByteArray> protocols);
+    QList<QByteArray> allowedNextProtocols() const;
+
+    QByteArray nextNegotiatedProtocol() const;
+    NextProtocolNegotiationStatus nextProtocolNegotiationStatus() const;
+
+    static const char NextProtocolSpdy3_0[];
+    static const char NextProtocolHttp1_1[];
+
 private:
     friend class QSslSocket;
     friend class QSslConfigurationPrivate;
diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h
index 71ee8d2bfe6..d183c3335cc 100644
--- a/src/network/ssl/qsslconfiguration_p.h
+++ b/src/network/ssl/qsslconfiguration_p.h
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -86,7 +87,8 @@ public:
           allowRootCertOnDemandLoading(true),
           peerSessionShared(false),
           sslOptions(QSslConfigurationPrivate::defaultSslOptions),
-          sslSessionTicketLifeTimeHint(-1)
+          sslSessionTicketLifeTimeHint(-1),
+          nextProtocolNegotiationStatus(QSslConfiguration::NextProtocolNegotiationNone)
     { }
 
     QSslCertificate peerCertificate;
@@ -114,6 +116,10 @@ public:
     QByteArray sslSession;
     int sslSessionTicketLifeTimeHint;
 
+    QList<QByteArray> nextAllowedProtocols;
+    QByteArray nextNegotiatedProtocol;
+    QSslConfiguration::NextProtocolNegotiationStatus nextProtocolNegotiationStatus;
+
     // 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 adf42fb79a9..551804ec794 100644
--- a/src/network/ssl/qsslcontext.cpp
+++ b/src/network/ssl/qsslcontext.cpp
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -263,6 +264,45 @@ init_context:
     return sslContext;
 }
 
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+
+static int next_proto_cb(SSL *, unsigned char **out, unsigned char *outlen,
+                         const unsigned char *in, unsigned int inlen, void *arg)
+{
+    QSslContext::NPNContext *ctx = reinterpret_cast<QSslContext::NPNContext *>(arg);
+
+    // comment out to debug:
+//    QList<QByteArray> supportedVersions;
+//    for (unsigned int i = 0; i < inlen; ) {
+//        QByteArray version(reinterpret_cast<const char *>(&in[i+1]), in[i]);
+//        supportedVersions << version;
+//        i += in[i] + 1;
+//    }
+
+    int proto = q_SSL_select_next_proto(out, outlen, in, inlen, ctx->data, ctx->len);
+    switch (proto) {
+    case OPENSSL_NPN_UNSUPPORTED:
+        ctx->status = QSslConfiguration::NextProtocolNegotiationNone;
+        break;
+    case OPENSSL_NPN_NEGOTIATED:
+        ctx->status = QSslConfiguration::NextProtocolNegotiationNegotiated;
+        break;
+    case OPENSSL_NPN_NO_OVERLAP:
+        ctx->status = QSslConfiguration::NextProtocolNegotiationUnsupported;
+        break;
+    default:
+        qWarning("OpenSSL sent unknown NPN status");
+    }
+
+    return SSL_TLSEXT_ERR_OK;
+}
+
+QSslContext::NPNContext QSslContext::npnContext() const
+{
+    return m_npnContext;
+}
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
+
 // Needs to be deleted by caller
 SSL* QSslContext::createSsl()
 {
@@ -283,6 +323,26 @@ SSL* QSslContext::createSsl()
             session = 0;
         }
     }
+
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+    QList<QByteArray> protocols = sslConfiguration.d->nextAllowedProtocols;
+    if (!protocols.isEmpty()) {
+        m_supportedNPNVersions.clear();
+        for (int a = 0; a < protocols.count(); ++a) {
+            if (protocols.at(a).size() > 255) {
+                qWarning() << "TLS NPN extension" << protocols.at(a)
+                           << "is too long and will be truncated to 255 characters.";
+                protocols[a] = protocols.at(a).left(255);
+            }
+            m_supportedNPNVersions.append(protocols.at(a).size()).append(protocols.at(a));
+        }
+        m_npnContext.data = reinterpret_cast<unsigned char *>(m_supportedNPNVersions.data());
+        m_npnContext.len = m_supportedNPNVersions.count();
+        m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone;
+        q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext);
+    }
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
+
     return ssl;
 }
 
diff --git a/src/network/ssl/qsslcontext_p.h b/src/network/ssl/qsslcontext_p.h
index 2b596798a65..20b27c1ce71 100644
--- a/src/network/ssl/qsslcontext_p.h
+++ b/src/network/ssl/qsslcontext_p.h
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -72,6 +73,21 @@ public:
     QByteArray sessionASN1() const;
     void setSessionASN1(const QByteArray &sessionASN1);
     int sessionTicketLifeTimeHint() const;
+
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+    // must be public because we want to use it from an OpenSSL callback
+    struct NPNContext {
+        NPNContext() : data(0),
+            len(0),
+            status(QSslConfiguration::NextProtocolNegotiationNone)
+        { }
+        unsigned char *data;
+        unsigned short len;
+        QSslConfiguration::NextProtocolNegotiationStatus status;
+    };
+    NPNContext npnContext() const;
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
+
 protected:
     QSslContext();
 
@@ -84,6 +100,10 @@ private:
     QSslError::SslError errorCode;
     QString errorStr;
     QSslConfiguration sslConfiguration;
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+    QByteArray m_supportedNPNVersions;
+    NPNContext m_npnContext;
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
 };
 
 #endif // QT_NO_SSL
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index e107d94cf2b..6edf4efae06 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -905,6 +906,9 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
     d->configuration.sslOptions = configuration.d->sslOptions;
     d->configuration.sslSession = configuration.sessionTicket();
     d->configuration.sslSessionTicketLifeTimeHint = configuration.sessionTicketLifeTimeHint();
+    d->configuration.nextAllowedProtocols = configuration.allowedNextProtocols();
+    d->configuration.nextNegotiatedProtocol = configuration.nextNegotiatedProtocol();
+    d->configuration.nextProtocolNegotiationStatus = configuration.nextProtocolNegotiationStatus();
 
     // 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 ce6b6562b92..3421154114e 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -1486,6 +1486,15 @@ void QSslSocketBackendPrivate::continueHandshake()
         }
     }
 
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+    const unsigned char *proto;
+    unsigned int proto_len;
+    q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len);
+    QByteArray nextProtocol(reinterpret_cast<const char *>(proto), proto_len);
+    configuration.nextNegotiatedProtocol = nextProtocol;
+    configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status;
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
+
     connectionEncrypted = true;
     emit q->encrypted();
     if (autoStartHandshake && pendingClose) {
diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp
index ddf53f18f4e..79bce22b0dd 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -346,6 +347,20 @@ DEFINEFUNC(long, SSLeay, void, DUMMYARG, 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)
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+DEFINEFUNC6(int, SSL_select_next_proto, unsigned char **out, out, unsigned char *outlen, outlen,
+            const unsigned char *in, in, unsigned int inlen, inlen,
+            const unsigned char *client, client, unsigned int client_len, client_len,
+            return -1, return)
+DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s,
+            int (*cb) (SSL *ssl, unsigned char **out,
+                       unsigned char *outlen,
+                       const unsigned char *in,
+                       unsigned int inlen, void *arg), cb,
+            void *arg, arg, return, DUMMYARG)
+DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s,
+            const unsigned char **data, data, unsigned *len, len, return, DUMMYARG)
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
 
 #define RESOLVEFUNC(func) \
     if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func)))     \
@@ -815,6 +830,11 @@ bool q_resolveOpenSslSymbols()
     RESOLVEFUNC(SSLeay_version)
     RESOLVEFUNC(i2d_SSL_SESSION)
     RESOLVEFUNC(d2i_SSL_SESSION)
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+    RESOLVEFUNC(SSL_select_next_proto)
+    RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb)
+    RESOLVEFUNC(SSL_get0_next_proto_negotiated)
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
 
     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 79d2aecf5e7..500fe9493bc 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -473,6 +474,20 @@ 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);
 
+#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
+int q_SSL_select_next_proto(unsigned char **out, unsigned char *outlen,
+                            const unsigned char *in, unsigned int inlen,
+                            const unsigned char *client, unsigned int client_len);
+void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s,
+                                        int (*cb) (SSL *ssl, unsigned char **out,
+                                                   unsigned char *outlen,
+                                                   const unsigned char *in,
+                                                   unsigned int inlen, void *arg),
+                                        void *arg);
+void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data,
+                                      unsigned *len);
+#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
+
 // Helper function
 class QDateTime;
 QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime);
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
index c9072c4e9ea..62722ea62b1 100644
--- a/tests/manual/manual.pro
+++ b/tests/manual/manual.pro
@@ -25,6 +25,7 @@ qnetworkreply \
 qpainfo \
 qscreen \
 qssloptions \
+qsslsocket \
 qtabletevent \
 qtexteditlist \
 qtbug-8933 \
diff --git a/tests/manual/qsslsocket/main.cpp b/tests/manual/qsslsocket/main.cpp
new file mode 100644
index 00000000000..67726b58971
--- /dev/null
+++ b/tests/manual/qsslsocket/main.cpp
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite 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/qsslconfiguration.h>
+#include <QtNetwork/qsslsocket.h>
+#include <QtTest/QtTest>
+
+#ifndef QT_NO_SSL
+Q_DECLARE_METATYPE(QSslConfiguration::NextProtocolNegotiationStatus)
+#endif
+
+class tst_QSslSocket : public QObject
+{
+    Q_OBJECT
+
+#ifndef QT_NO_SSL
+private slots:
+    void nextProtocolNegotiation_data();
+    void nextProtocolNegotiation();
+#endif // QT_NO_SSL
+};
+
+#ifndef QT_NO_SSL
+void tst_QSslSocket::nextProtocolNegotiation_data()
+{
+    QTest::addColumn<bool>("setConfiguration");
+    QTest::addColumn<QString>("host");
+    QTest::addColumn<QList<QByteArray> >("allowedProtocols");
+    QTest::addColumn<QByteArray>("expectedProtocol");
+    QTest::addColumn<QSslConfiguration::NextProtocolNegotiationStatus>("expectedStatus");
+
+    QList<QString> hosts = QList<QString>()
+            << QStringLiteral("www.google.com")
+            << QStringLiteral("www.facebook.com")
+            << QStringLiteral("www.twitter.com")
+            << QStringLiteral("graph.facebook.com")
+            << QStringLiteral("api.twitter.com");
+
+    foreach (QString host, hosts) {
+        QByteArray tag = host.toLocal8Bit();
+        tag.append("-none");
+        QTest::newRow(tag)
+                << false
+                << host
+                << QList<QByteArray>()
+                << QByteArray()
+                << QSslConfiguration::NextProtocolNegotiationNone;
+
+        tag = host.toLocal8Bit();
+        tag.append("-none-explicit");
+        QTest::newRow(tag)
+                << true
+                << host
+                << QList<QByteArray>()
+                << QByteArray()
+                << QSslConfiguration::NextProtocolNegotiationNone;
+
+        tag = host.toLocal8Bit();
+        tag.append("-http/1.1");
+        QTest::newRow(tag)
+                << true
+                << host
+                << (QList<QByteArray>() << QSslConfiguration::NextProtocolHttp1_1)
+                << QByteArray(QSslConfiguration::NextProtocolHttp1_1)
+                << QSslConfiguration::NextProtocolNegotiationNegotiated;
+
+        tag = host.toLocal8Bit();
+        tag.append("-spdy/3");
+        QTest::newRow(tag)
+                << true
+                << host
+                << (QList<QByteArray>() << QSslConfiguration::NextProtocolSpdy3_0)
+                << QByteArray(QSslConfiguration::NextProtocolSpdy3_0)
+                << QSslConfiguration::NextProtocolNegotiationNegotiated;
+
+        tag = host.toLocal8Bit();
+        tag.append("-spdy/3-and-http/1.1");
+        QTest::newRow(tag)
+                << true
+                << host
+                << (QList<QByteArray>() << QSslConfiguration::NextProtocolSpdy3_0 << QSslConfiguration::NextProtocolHttp1_1)
+                << QByteArray(QSslConfiguration::NextProtocolSpdy3_0)
+                << QSslConfiguration::NextProtocolNegotiationNegotiated;
+    }
+}
+
+void tst_QSslSocket::nextProtocolNegotiation()
+{
+    if (!QSslSocket::supportsSsl())
+        return;
+
+    QSslSocket socket;
+
+    QFETCH(bool, setConfiguration);
+
+    if (setConfiguration) {
+        QSslConfiguration conf = socket.sslConfiguration();
+        QFETCH(QList<QByteArray>, allowedProtocols);
+        conf.setAllowedNextProtocols(allowedProtocols);
+        socket.setSslConfiguration(conf);
+    }
+
+    QFETCH(QString, host);
+
+    socket.connectToHostEncrypted(host, 443);
+    socket.ignoreSslErrors();
+
+    QVERIFY(socket.waitForEncrypted(10000));
+
+    QFETCH(QByteArray, expectedProtocol);
+    QCOMPARE(socket.sslConfiguration().nextNegotiatedProtocol(), expectedProtocol);
+
+    QFETCH(QSslConfiguration::NextProtocolNegotiationStatus, expectedStatus);
+    QCOMPARE(socket.sslConfiguration().nextProtocolNegotiationStatus(), expectedStatus);
+
+    socket.disconnectFromHost();
+    QVERIFY(socket.waitForDisconnected());
+
+}
+
+#endif // QT_NO_SSL
+
+QTEST_MAIN(tst_QSslSocket)
+
+#include "main.moc"
diff --git a/tests/manual/qsslsocket/qsslsocket.pro b/tests/manual/qsslsocket/qsslsocket.pro
new file mode 100644
index 00000000000..c297d887ba4
--- /dev/null
+++ b/tests/manual/qsslsocket/qsslsocket.pro
@@ -0,0 +1,6 @@
+CONFIG += testcase
+
+SOURCES += main.cpp
+QT = core network testlib
+
+TARGET = tst_qsslsocket
-- 
GitLab