SSL NPN negotiation: Do not abort on unmatched protocols

... but choose HTTP/1.1 and continue connecting anyhow. According to
the NPN spec, actually we should choose SPDY:

"In the event that the client doesn't support any of server's protocols,
or the server doesn't advertise any, it SHOULD select the first protocol
that it supports."

However, some tested servers did not advertise anything and did not
support SPDY, so blindly trying the newest protocol would fail. We are
conservative in that case and choose HTTP.

Task-number: QTBUG-40714
Change-Id: Ia8aaf01fea74e13d9e4416306f85f1890b25559e
Reviewed-by: Richard J. Moore <rich@kde.org>
This commit is contained in:
Peter Hartmann 2015-02-05 14:05:21 +01:00
parent 239f67f158
commit 3c24dddaf9
3 changed files with 44 additions and 12 deletions

View File

@ -931,7 +931,8 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
if (!protocolHandler) { if (!protocolHandler) {
switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) { switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) {
case QSslConfiguration::NextProtocolNegotiationNegotiated: { case QSslConfiguration::NextProtocolNegotiationNegotiated: /* fall through */
case QSslConfiguration::NextProtocolNegotiationUnsupported: {
QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol(); QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol();
if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) { if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) {
// fall through to create a QHttpProtocolHandler // fall through to create a QHttpProtocolHandler
@ -953,10 +954,6 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
// re-queue requests from SPDY queue to HTTP queue, if any // re-queue requests from SPDY queue to HTTP queue, if any
requeueSpdyRequests(); requeueSpdyRequests();
break; break;
case QSslConfiguration::NextProtocolNegotiationUnsupported:
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"chosen Next Protocol Negotiation value unsupported");
break;
default: default:
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"detected unknown Next Protocol Negotiation protocol"); "detected unknown Next Protocol Negotiation protocol");

View File

@ -1518,14 +1518,20 @@ void QSslSocketBackendPrivate::continueHandshake()
} }
#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) #if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
const unsigned char *proto = 0;
unsigned int proto_len = 0;
q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len);
if (proto_len)
configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len);
else
configuration.nextNegotiatedProtocol.clear();
configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status; configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status;
if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) {
// we could not agree -> be conservative and use HTTP/1.1
configuration.nextNegotiatedProtocol = QByteArrayLiteral("http/1.1");
} else {
const unsigned char *proto = 0;
unsigned int proto_len = 0;
q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len);
if (proto_len)
configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len);
else
configuration.nextNegotiatedProtocol.clear();
}
#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... #endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
connectionEncrypted = true; connectionEncrypted = true;

View File

@ -69,6 +69,7 @@ private slots:
void proxyAuthentication_data(); void proxyAuthentication_data();
void proxyAuthentication(); void proxyAuthentication();
void authentication(); void authentication();
void npnWithEmptyList(); // QTBUG-40714
protected slots: protected slots:
void spdyReplyFinished(); // only used by spdyMultipleRequestsPerHost test void spdyReplyFinished(); // only used by spdyMultipleRequestsPerHost test
@ -577,6 +578,34 @@ void tst_qnetworkreply::authentication()
QVERIFY(statusCode >= 200 && statusCode < 400); QVERIFY(statusCode >= 200 && statusCode < 400);
} }
void tst_qnetworkreply::npnWithEmptyList() // QTBUG-40714
{
#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
// The server does not send a list of Next Protocols, so we test
// that we continue anyhow and it works
m_manager.clearAccessCache();
QUrl url(QStringLiteral("https://www.ossifrage.net/"));
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, QVariant(true));
QNetworkReply *reply = m_manager.get(request);
QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QVERIFY(statusCode == 200);
QCOMPARE(reply->sslConfiguration().nextProtocolNegotiationStatus(),
QSslConfiguration::NextProtocolNegotiationUnsupported);
#else
QSKIP("Qt built withouth OpenSSL, or the OpenSSL version is too old");
#endif // defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ...
}
QTEST_MAIN(tst_qnetworkreply) QTEST_MAIN(tst_qnetworkreply)
#include "main.moc" #include "main.moc"