qsslsocket/qsslcontext - add ALPN (OpenSSL only)

Application-Layer Protocol Negotiation (ALPN) - is a reworked revision
of Next Protocol Negotiation (NPN) we have in our OpenSSL code.
Can be used as a part of HTTP2 negotiation during TLS handshake.

Change-Id: I484ec528c81d4887a64749095ec292dfaec18330
Reviewed-by: Richard J. Moore <rich@kde.org>
This commit is contained in:
Timur Pocheptsov 2016-02-19 15:06:37 +01:00
parent fb6000a74f
commit 765eab5103
6 changed files with 110 additions and 10 deletions

View File

@ -119,7 +119,8 @@ const char QSslConfiguration::NextProtocolHttp1_1[] = "http/1.1";
/*!
\enum QSslConfiguration::NextProtocolNegotiationStatus
Describes the status of the Next Protocol Negotiation (NPN).
Describes the status of the Next Protocol Negotiation (NPN) or
Application-Layer Protocol Negotiation (ALPN).
\value NextProtocolNegotiationNone No application protocol
has been negotiated (yet).
@ -812,8 +813,9 @@ QVector<QSslEllipticCurve> QSslConfiguration::supportedEllipticCurves()
\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()
if the Next Protocol Negotiation (NPN) or Application-Layer Protocol
Negotiation (ALPN) TLS extension was enabled.
In order for the NPN/ALPN 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,
@ -830,9 +832,10 @@ QByteArray QSslConfiguration::nextNegotiatedProtocol() const
\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
server through the Next Protocol Negotiation (NPN) or Application-Layer
Protocol Negotiation (ALPN) TLS extension; each
element in \a protocols must define one allowed protocol.
The function must be called explicitly before connecting to send the NPN
The function must be called explicitly before connecting to send the NPN/ALPN
extension in the SSL handshake.
Whether or not the negotiation succeeded can be queried through
nextProtocolNegotiationStatus().
@ -852,8 +855,8 @@ void QSslConfiguration::setAllowedNextProtocols(QList<QByteArray> 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().
server through the Next Protocol Negotiation (NPN) or Application-Layer
Protocol Negotiation (ALPN) TLS extension, as set by setAllowedNextProtocols().
\sa nextNegotiatedProtocol(), nextProtocolNegotiationStatus(), setAllowedNextProtocols(), QSslConfiguration::NextProtocolSpdy3_0, QSslConfiguration::NextProtocolHttp1_1
*/
@ -865,7 +868,8 @@ QList<QByteArray> QSslConfiguration::allowedNextProtocols() const
/*!
\since 5.3
This function returns the status of the Next Protocol Negotiation (NPN).
This function returns the status of the Next Protocol Negotiation (NPN)
or Application-Layer Protocol Negotiation (ALPN).
If the feature has not been enabled through setAllowedNextProtocols(),
this function returns NextProtocolNegotiationNone.
The status will be set before emitting the encrypted() signal.

View File

@ -457,7 +457,25 @@ SSL* QSslContext::createSsl()
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);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (q_SSLeay() >= 0x10002000L) {
// Callback's type has a parameter 'const unsigned char ** out'
// since it was introduced in 1.0.2. Internally, OpenSSL's own code
// (tests/examples) cast it to unsigned char * (since it's 'out').
// We just re-use our NPN callback and cast here:
typedef int (*alpn_callback_t) (SSL *, const unsigned char **, unsigned char *,
const unsigned char *, unsigned int, void *);
// With ALPN callback is for a server side only, for a client m_npnContext.status
// will stay in NextProtocolNegotiationNone.
q_SSL_CTX_set_alpn_select_cb(ctx, alpn_callback_t(next_proto_cb), &m_npnContext);
// Client:
q_SSL_set_alpn_protos(ssl, m_npnContext.data, m_npnContext.len);
} else {
#else
{
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ...
q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext);
}
}
#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...

View File

@ -1565,7 +1565,20 @@ void QSslSocketBackendPrivate::continueHandshake()
} else {
const unsigned char *proto = 0;
unsigned int proto_len = 0;
q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (q_SSLeay() >= 0x10002000L) {
q_SSL_get0_alpn_selected(ssl, &proto, &proto_len);
if (proto_len && mode == QSslSocket::SslClientMode) {
// Client does not have a callback that sets it ...
configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated;
}
} else {
#else
{
#endif
q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len);
}
if (proto_len)
configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len);
else

View File

@ -418,6 +418,18 @@ DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s,
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)
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
DEFINEFUNC3(int, SSL_set_alpn_protos, SSL *s, s, const unsigned char *protos, protos,
unsigned protos_len, protos_len, return -1, return)
DEFINEFUNC3(void, SSL_CTX_set_alpn_select_cb, SSL_CTX *s, s,
int (*cb) (SSL *ssl, const 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_alpn_selected, const SSL *s, s, const unsigned char **data, data,
unsigned *len, len, return, DUMMYARG)
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ...
#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return 0, return)
DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG)

View File

@ -558,6 +558,19 @@ void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s,
void *arg);
void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data,
unsigned *len);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
int q_SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
unsigned protos_len);
void q_SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx,
int (*cb) (SSL *ssl,
const unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg), void *arg);
void q_SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
unsigned *len);
#endif
#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
// Helper function

View File

@ -229,6 +229,7 @@ private slots:
void simplePskConnect();
void ephemeralServerKey_data();
void ephemeralServerKey();
void allowedProtocolNegotiation();
#endif
static void exitLoop()
@ -3375,6 +3376,45 @@ void tst_QSslSocket::ephemeralServerKey()
QCOMPARE(client->sslConfiguration().ephemeralServerKey().isNull(), emptyKey);
}
void tst_QSslSocket::allowedProtocolNegotiation()
{
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
const QByteArray expectedNegotiated("cool-protocol");
QList<QByteArray> serverProtos;
serverProtos << expectedNegotiated << "not-so-cool-protocol";
QList<QByteArray> clientProtos;
clientProtos << "uber-cool-protocol" << expectedNegotiated << "not-so-cool-protocol";
SslServer server;
server.config.setAllowedNextProtocols(serverProtos);
QVERIFY(server.listen());
QSslSocket clientSocket;
auto configuration = clientSocket.sslConfiguration();
configuration.setAllowedNextProtocols(clientProtos);
clientSocket.setSslConfiguration(configuration);
clientSocket.connectToHostEncrypted("127.0.0.1", server.serverPort());
clientSocket.ignoreSslErrors();
QEventLoop loop;
QTimer::singleShot(5000, &loop, SLOT(quit()));
connect(&clientSocket, SIGNAL(encrypted()), &loop, SLOT(quit()));
loop.exec();
QVERIFY(server.socket->sslConfiguration().nextNegotiatedProtocol() ==
clientSocket.sslConfiguration().nextNegotiatedProtocol());
QVERIFY(server.socket->sslConfiguration().nextNegotiatedProtocol() == expectedNegotiated);
#endif // OPENSSL_VERSION_NUMBER
}
#endif // QT_NO_OPENSSL
#endif // QT_NO_SSL