From 42cfb5fe4daa586f382bde6936b0ee33b5298f4d Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Wed, 28 Aug 2013 10:56:24 +0200 Subject: 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 --- 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 +++ src/network/ssl/qsslsocket_openssl_symbols.cpp | 20 +++++ src/network/ssl/qsslsocket_openssl_symbols_p.h | 15 ++++ 9 files changed, 255 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp index 3d7656262b..1e859ae6e6 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 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 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 a48eceb63e..587187ca06 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 protocols); + QList 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 71ee8d2bfe..d183c3335c 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 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 adf42fb79a..551804ec79 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(arg); + + // comment out to debug: +// QList supportedVersions; +// for (unsigned int i = 0; i < inlen; ) { +// QByteArray version(reinterpret_cast(&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 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(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 2b596798a6..20b27c1ce7 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 e107d94cf2..6edf4efae0 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 ce6b6562b9..3421154114 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(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 ddf53f18f4..79bce22b0d 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 79d2aecf5e..500fe9493b 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); -- cgit v1.2.3