From bd26defd9bdddf8619a8d6ce1c6cb90b28c27d88 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Wed, 15 Oct 2014 15:13:47 +0200 Subject: QSslSocket: introduce support for TLS PSK (client side) [ChangeLog][QtNetwork][QSslSocket] It is now possible to use TLS PSK ciphersuites in client sockets. Task-number: QTBUG-39077 Change-Id: I5523a2be33d46230c6f4106c322fab8a5afa37b4 Reviewed-by: Richard J. Moore --- src/network/ssl/qsslpresharedkeyauthenticator.cpp | 291 +++++++++++++++++ src/network/ssl/qsslpresharedkeyauthenticator.h | 101 ++++++ src/network/ssl/qsslpresharedkeyauthenticator_p.h | 65 ++++ src/network/ssl/qsslsocket.cpp | 22 ++ src/network/ssl/qsslsocket.h | 2 + src/network/ssl/qsslsocket_openssl.cpp | 68 ++++ src/network/ssl/qsslsocket_openssl_p.h | 4 + src/network/ssl/qsslsocket_openssl_symbols.cpp | 16 + src/network/ssl/qsslsocket_openssl_symbols_p.h | 9 + src/network/ssl/ssl.pri | 3 + .../auto/network/ssl/qsslsocket/tst_qsslsocket.cpp | 354 +++++++++++++++++++++ 11 files changed, 935 insertions(+) create mode 100644 src/network/ssl/qsslpresharedkeyauthenticator.cpp create mode 100644 src/network/ssl/qsslpresharedkeyauthenticator.h create mode 100644 src/network/ssl/qsslpresharedkeyauthenticator_p.h diff --git a/src/network/ssl/qsslpresharedkeyauthenticator.cpp b/src/network/ssl/qsslpresharedkeyauthenticator.cpp new file mode 100644 index 0000000000..3b109c580a --- /dev/null +++ b/src/network/ssl/qsslpresharedkeyauthenticator.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Governikus GmbH & Co. KG. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module 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 "qsslpresharedkeyauthenticator.h" +#include "qsslpresharedkeyauthenticator_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \internal +*/ +QSslPreSharedKeyAuthenticatorPrivate::QSslPreSharedKeyAuthenticatorPrivate() + : maximumIdentityLength(0), + maximumPreSharedKeyLength(0) +{ +} + +/*! + \class QSslPreSharedKeyAuthenticator + + \brief The QSslPreSharedKeyAuthenticator class provides authentication data for pre + shared keys (PSK) ciphersuites. + + \inmodule QtNetwork + + \reentrant + + \ingroup network + \ingroup ssl + \ingroup shared + + \since 5.5 + + The QSslPreSharedKeyAuthenticator class is used by an SSL socket to provide + the required authentication data in a pre shared key (PSK) ciphersuite. + + In a PSK handshake, the client must derive a key, which must match the key + set on the server. The exact algorithm of deriving the key depends on the + application; however, for this purpose, the server may send an \e{identity + hint} to the client. This hint, combined with other information (for + instance a passphrase), is then used by the client to construct the shared + key. + + The QSslPreSharedKeyAuthenticator provides means to client applications for + completing the PSK handshake. The client application needs to connect a + slot to the QSslSocket::preSharedKeyAuthenticationRequired() signal: + + \code + + connect(socket, &QSslSocket::preSharedKeyAuthenticationRequired, + this, &AuthManager::handlePreSharedKeyAuthentication); + + \endcode + + The signal carries a QSslPreSharedKeyAuthenticator object containing the + identity hint the server sent to the client, and which must be filled with the + corresponding client identity and the derived key: + + \code + + void AuthManager::handlePreSharedKeyAuthentication(QSslPreSharedKeyAuthenticator *authenticator) + { + authenticator->setIdentity("My Qt App"); + + const QByteArray key = deriveKey(authenticator->identityHint(), passphrase); + authenticator->setPreSharedKey(key); + } + + \endcode + + \note PSK ciphersuites are supported only when using OpenSSL 1.0.1 (or + greater) as the SSL backend. + + \sa QSslSocket +*/ + +/*! + Constructs a default QSslPreSharedKeyAuthenticator object. + + The identity hint, the identity and the key will be initialized to empty + byte arrays; the maximum length for both the identity and the key will be + initialized to 0. +*/ +QSslPreSharedKeyAuthenticator::QSslPreSharedKeyAuthenticator() + : d(new QSslPreSharedKeyAuthenticatorPrivate) +{ +} + +/*! + Destroys the QSslPreSharedKeyAuthenticator object. +*/ +QSslPreSharedKeyAuthenticator::~QSslPreSharedKeyAuthenticator() +{ +} + +/*! + Constructs a QSslPreSharedKeyAuthenticator object as a copy of \a authenticator. + + \sa operator=() +*/ +QSslPreSharedKeyAuthenticator::QSslPreSharedKeyAuthenticator(const QSslPreSharedKeyAuthenticator &authenticator) + : d(authenticator.d) +{ +} + +/*! + Assigns the QSslPreSharedKeyAuthenticator object \a authenticator to this object, + and returns a reference to the copy. +*/ +QSslPreSharedKeyAuthenticator &QSslPreSharedKeyAuthenticator::operator=(const QSslPreSharedKeyAuthenticator &authenticator) +{ + d = authenticator.d; + return *this; +} + +/*! + \fn QSslPreSharedKeyAuthenticator &QSslPreSharedKeyAuthenticator::operator=(QSslPreSharedKeyAuthenticator &&authenticator) + + Move-assigns the the QSslPreSharedKeyAuthenticator object \a authenticator to this + object, and returns a reference to the moved instance. +*/ + +/*! + \fn void QSslPreSharedKeyAuthenticator::swap(QSslPreSharedKeyAuthenticator &authenticator) + + Swaps the QSslPreSharedKeyAuthenticator object \a authenticator with this object. + This operation is very fast and never fails. +*/ + +/*! + Returns the PSK identity hint as provided by the server. The interpretation + of this hint is left to the application. +*/ +QByteArray QSslPreSharedKeyAuthenticator::identityHint() const +{ + return d->identityHint; +} + +/*! + Sets the PSK client identity (to be advised to the server) to \a identity. + + \note it is possible to set an identity whose length is greater than + maximumIdentityLength(); in this case, only the first maximumIdentityLength() + bytes will be actually sent to the server. + + \sa identity(), maximumIdentityLength() +*/ +void QSslPreSharedKeyAuthenticator::setIdentity(const QByteArray &identity) +{ + d->identity = identity; +} + +/*! + Returns the PSK client identity. + + \sa setIdentity() +*/ +QByteArray QSslPreSharedKeyAuthenticator::identity() const +{ + return d->identity; +} + + +/*! + Returns the maximum length, in bytes, of the PSK client identity. + + \note it is possible to set an identity whose length is greater than + maximumIdentityLength(); in this case, only the first maximumIdentityLength() + bytes will be actually sent to the server. + + \sa setIdentity() +*/ +int QSslPreSharedKeyAuthenticator::maximumIdentityLength() const +{ + return d->maximumIdentityLength; +} + + +/*! + Sets the pre shared key to \a preSharedKey. + + \note it is possible to set a key whose length is greater than the + maximumPreSharedKeyLength(); in this case, only the first + maximumPreSharedKeyLength() bytes will be actually sent to the server. + + \sa preSharedKey(), maximumPreSharedKeyLength(), QByteArray::fromHex() +*/ +void QSslPreSharedKeyAuthenticator::setPreSharedKey(const QByteArray &preSharedKey) +{ + d->preSharedKey = preSharedKey; +} + +/*! + Returns the pre shared key. + + \sa setPreSharedKey() +*/ +QByteArray QSslPreSharedKeyAuthenticator::preSharedKey() const +{ + return d->preSharedKey; +} + +/*! + Returns the maximum length, in bytes, of the pre shared key. + + \note it is possible to set a key whose length is greater than the + maximumPreSharedKeyLength(); in this case, only the first + maximumPreSharedKeyLength() bytes will be actually sent to the server. + + \sa setPreSharedKey() +*/ +int QSslPreSharedKeyAuthenticator::maximumPreSharedKeyLength() const +{ + return d->maximumPreSharedKeyLength; +} + +/*! + \relates QSslPreSharedKeyAuthenticator + \since 5.5 + + Returns true if the authenticator object \a lhs is equal to \a rhs; false + otherwise. + + Two authenticator objects are equal if and only if they have the same + identity hint, identity, pre shared key, maximum length for the identity + and maximum length for the pre shared key. + + \sa operator!=(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs) +*/ +bool operator==(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs) +{ + return ((lhs.d == rhs.d) || + (lhs.d->identityHint == rhs.d->identityHint && + lhs.d->identity == rhs.d->identity && + lhs.d->maximumIdentityLength == rhs.d->maximumIdentityLength && + lhs.d->preSharedKey == rhs.d->preSharedKey && + lhs.d->maximumPreSharedKeyLength == rhs.d->maximumPreSharedKeyLength)); +} + +/*! + \fn bool operator!=(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs) + \relates QSslPreSharedKeyAuthenticator + \since 5.5 + + Returns true if the authenticator object \a lhs is different than \a rhs; + false otherwise. + + \sa operator==(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs) +*/ + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslpresharedkeyauthenticator.h b/src/network/ssl/qsslpresharedkeyauthenticator.h new file mode 100644 index 0000000000..cf2cd4989a --- /dev/null +++ b/src/network/ssl/qsslpresharedkeyauthenticator.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Governikus GmbH & Co. KG. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module 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$ +** +****************************************************************************/ + +#ifndef QSSLPRESHAREDKEYAUTHENTICATOR_H +#define QSSLPRESHAREDKEYAUTHENTICATOR_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QSslPreSharedKeyAuthenticatorPrivate; + +class Q_NETWORK_EXPORT QSslPreSharedKeyAuthenticator +{ +public: + QSslPreSharedKeyAuthenticator(); + ~QSslPreSharedKeyAuthenticator(); + QSslPreSharedKeyAuthenticator(const QSslPreSharedKeyAuthenticator &authenticator); + QSslPreSharedKeyAuthenticator &operator=(const QSslPreSharedKeyAuthenticator &authenticator); + +#ifdef Q_COMPILER_RVALUE_REFS + inline QSslPreSharedKeyAuthenticator &operator=(QSslPreSharedKeyAuthenticator &&authenticator) + { d.swap(authenticator.d); return *this; } +#endif + + void swap(QSslPreSharedKeyAuthenticator &authenticator) + { + d.swap(authenticator.d); + } + + QByteArray identityHint() const; + + void setIdentity(const QByteArray &identity); + QByteArray identity() const; + int maximumIdentityLength() const; + + void setPreSharedKey(const QByteArray &preSharedKey); + QByteArray preSharedKey() const; + int maximumPreSharedKeyLength() const; + +private: + friend Q_NETWORK_EXPORT bool operator==(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs); + friend class QSslSocketBackendPrivate; + + QSharedDataPointer d; +}; + +inline bool operator!=(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs) +{ + return !operator==(lhs, rhs); +} + +Q_DECLARE_SHARED(QSslPreSharedKeyAuthenticator) + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSslPreSharedKeyAuthenticator) +Q_DECLARE_METATYPE(QSslPreSharedKeyAuthenticator*) + +#endif // QSSLPRESHAREDKEYAUTHENTICATOR_H diff --git a/src/network/ssl/qsslpresharedkeyauthenticator_p.h b/src/network/ssl/qsslpresharedkeyauthenticator_p.h new file mode 100644 index 0000000000..9b933a1b1f --- /dev/null +++ b/src/network/ssl/qsslpresharedkeyauthenticator_p.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Governikus GmbH & Co. KG. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module 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$ +** +****************************************************************************/ + +#ifndef QSSLPRESHAREDKEYAUTHENTICATOR_P_H +#define QSSLPRESHAREDKEYAUTHENTICATOR_P_H + +#include + +QT_BEGIN_NAMESPACE + +class QSslPreSharedKeyAuthenticatorPrivate : public QSharedData +{ +public: + QSslPreSharedKeyAuthenticatorPrivate(); + + QByteArray identityHint; + + QByteArray identity; + int maximumIdentityLength; + + QByteArray preSharedKey; + int maximumPreSharedKeyLength; +}; + +QT_END_NAMESPACE + +#endif // QSSLPRESHAREDKEYAUTHENTICATOR_P_H diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 3838e70310..98972f526a 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -281,6 +281,28 @@ \sa peerVerifyError() */ +/*! + \fn void QSslSocket::preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator) + \since 5.5 + + QSslSocket emits this signal when it negotiates a PSK ciphersuite, and + therefore a PSK authentication is then required. + + When using PSK, the client must send to the server a valid identity and a + valid pre shared key, in order for the SSL handshake to continue. + Applications can provide this information in a slot connected to this + signal, by filling in the passed \a authenticator object according to their + needs. + + \note Ignoring this signal, or failing to provide the required credentials, + will cause the handshake to fail, and therefore the connection to be aborted. + + \note The \a authenticator object is owned by the socket and must not be + deleted by the application. + + \sa QSslPreSharedKeyAuthenticator +*/ + #include "qssl_p.h" #include "qsslsocket.h" #include "qsslcipher.h" diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h index f5ab1527dc..16ae97e49e 100644 --- a/src/network/ssl/qsslsocket.h +++ b/src/network/ssl/qsslsocket.h @@ -52,6 +52,7 @@ class QSslCipher; class QSslCertificate; class QSslConfiguration; class QSslEllipticCurve; +class QSslPreSharedKeyAuthenticator; class QSslSocketPrivate; class Q_NETWORK_EXPORT QSslSocket : public QTcpSocket @@ -199,6 +200,7 @@ Q_SIGNALS: void sslErrors(const QList &errors); void modeChanged(QSslSocket::SslMode newMode); void encryptedBytesWritten(qint64 totalBytes); + void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator); protected: qint64 readData(char *data, qint64 maxlen) Q_DECL_OVERRIDE; diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index bf348f6f9f..509db38672 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -58,6 +58,8 @@ #include "qsslcipher_p.h" #include "qsslkey_p.h" #include "qsslellipticcurve.h" +#include "qsslpresharedkeyauthenticator.h" +#include "qsslpresharedkeyauthenticator_p.h" #include #include @@ -72,6 +74,8 @@ #include #include // for loading the security lib for the CA store +#include + QT_BEGIN_NAMESPACE #if defined(Q_OS_MACX) @@ -89,6 +93,10 @@ bool QSslSocketPrivate::s_libraryLoaded = false; bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; +#if OPENSSL_VERSION_NUMBER >= 0x10001000L +int QSslSocketBackendPrivate::s_indexForSSLExtraData = -1; +#endif + /* \internal From OpenSSL's thread(3) manual page: @@ -181,6 +189,18 @@ static unsigned long id_function() { return (quintptr)QThread::currentThreadId(); } + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) +static unsigned int q_ssl_psk_client_callback(SSL *ssl, + const char *hint, + char *identity, unsigned int max_identity_len, + unsigned char *psk, unsigned int max_psk_len) +{ + QSslSocketBackendPrivate *d = reinterpret_cast(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); + Q_ASSERT(d); + return d->tlsPskClientCallback(hint, identity, max_identity_len, psk, max_psk_len); +} +#endif } // extern "C" QSslSocketBackendPrivate::QSslSocketBackendPrivate() @@ -390,6 +410,18 @@ bool QSslSocketBackendPrivate::initSslContext() else q_SSL_set_accept_state(ssl); +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + // Save a pointer to this object into the SSL structure. + if (q_SSLeay() >= 0x10001000L) + q_SSL_set_ex_data(ssl, s_indexForSSLExtraData, this); +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) + // Set the client callback for PSK + if (q_SSLeay() >= 0x10001000L && mode == QSslSocket::SslClientMode) + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); +#endif + return true; } @@ -443,6 +475,11 @@ bool QSslSocketPrivate::ensureLibraryLoaded() q_SSL_load_error_strings(); q_OpenSSL_add_all_algorithms(); +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + if (q_SSLeay() >= 0x10001000L) + QSslSocketBackendPrivate::s_indexForSSLExtraData = q_SSL_get_ex_new_index(0L, NULL, NULL, NULL, NULL); +#endif + // Initialize OpenSSL's random seed. if (!q_RAND_status()) { struct { @@ -1262,6 +1299,37 @@ bool QSslSocketBackendPrivate::checkSslErrors() return true; } +unsigned int QSslSocketBackendPrivate::tlsPskClientCallback(const char *hint, + char *identity, unsigned int max_identity_len, + unsigned char *psk, unsigned int max_psk_len) +{ + QSslPreSharedKeyAuthenticator authenticator; + + // Fill in some read-only fields (for the user) + if (hint) + authenticator.d->identityHint = QByteArray::fromRawData(hint, int(::strlen(hint))); // it's NUL terminated, but do not include the NUL + + authenticator.d->maximumIdentityLength = int(max_identity_len) - 1; // needs to be NUL terminated + authenticator.d->maximumPreSharedKeyLength = int(max_psk_len); + + // Let the client provide the remaining bits... + Q_Q(QSslSocket); + emit q->preSharedKeyAuthenticationRequired(&authenticator); + + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength()); + ::memcpy(identity, authenticator.identity().constData(), identityLength); + identity[identityLength] = 0; + + const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); + ::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; +} + #ifdef Q_OS_WIN void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert) diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h index 69634ceab5..7fbccff802 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -114,6 +114,9 @@ public: BIO *writeBio; SSL_SESSION *session; QList > errorList; +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + static int s_indexForSSLExtraData; // index used in SSL_get_ex_data to get the matching QSslSocketBackendPrivate +#endif // Platform specific functions void startClientEncryption() Q_DECL_OVERRIDE; @@ -126,6 +129,7 @@ public: QSsl::SslProtocol sessionProtocol() const Q_DECL_OVERRIDE; void continueHandshake() Q_DECL_OVERRIDE; bool checkSslErrors(); + unsigned int tlsPskClientCallback(const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); #ifdef Q_OS_WIN void fetchCaRootForCert(const QSslCertificate &cert); void _q_caRootLoaded(QSslCertificate,QSslCertificate); diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index a2f8c7f592..2f022bd6e4 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -290,6 +290,14 @@ DEFINEFUNC2(int, SSL_set_session, SSL* to, to, SSL_SESSION *session, session, re DEFINEFUNC(void, SSL_SESSION_free, SSL_SESSION *ses, ses, return, DUMMYARG) DEFINEFUNC(SSL_SESSION*, SSL_get1_session, SSL *ssl, ssl, return 0, return) DEFINEFUNC(SSL_SESSION*, SSL_get_session, const SSL *ssl, ssl, return 0, return) +#if OPENSSL_VERSION_NUMBER >= 0x10001000L +DEFINEFUNC5(int, SSL_get_ex_new_index, long argl, argl, void *argp, argp, CRYPTO_EX_new *new_func, new_func, CRYPTO_EX_dup *dup_func, dup_func, CRYPTO_EX_free *free_func, free_func, return -1, return) +DEFINEFUNC3(int, SSL_set_ex_data, SSL *ssl, ssl, int idx, idx, void *arg, arg, return 0, return) +DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return NULL, return) +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) +DEFINEFUNC2(void, SSL_set_psk_client_callback, SSL* ssl, ssl, q_psk_client_callback_t callback, callback, return, DUMMYARG) +#endif #if OPENSSL_VERSION_NUMBER >= 0x10000000L #ifndef OPENSSL_NO_SSL2 DEFINEFUNC(const SSL_METHOD *, SSLv2_client_method, DUMMYARG, DUMMYARG, return 0, return) @@ -854,6 +862,14 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(SSL_SESSION_free) RESOLVEFUNC(SSL_get1_session) RESOLVEFUNC(SSL_get_session) +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + RESOLVEFUNC(SSL_get_ex_new_index) + RESOLVEFUNC(SSL_set_ex_data) + RESOLVEFUNC(SSL_get_ex_data) +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) + RESOLVEFUNC(SSL_set_psk_client_callback) +#endif RESOLVEFUNC(SSL_write) #ifndef OPENSSL_NO_SSL2 RESOLVEFUNC(SSLv2_client_method) diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index f2cc8a87fc..53b3ffde1b 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -374,6 +374,15 @@ int q_SSL_set_session(SSL *to, SSL_SESSION *session); void q_SSL_SESSION_free(SSL_SESSION *ses); SSL_SESSION *q_SSL_get1_session(SSL *ssl); SSL_SESSION *q_SSL_get_session(const SSL *ssl); +#if OPENSSL_VERSION_NUMBER >= 0x10001000L +int q_SSL_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); +int q_SSL_set_ex_data(SSL *ssl, int idx, void *arg); +void *q_SSL_get_ex_data(const SSL *ssl, int idx); +#endif +#ifndef OPENSSL_NO_PSK +typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback); +#endif // OPENSSL_NO_PSK #if OPENSSL_VERSION_NUMBER >= 0x10000000L const SSL_METHOD *q_SSLv2_client_method(); const SSL_METHOD *q_SSLv3_client_method(); diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri index 961e29c062..210606a1a0 100644 --- a/src/network/ssl/ssl.pri +++ b/src/network/ssl/ssl.pri @@ -15,6 +15,8 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op ssl/qsslkey_p.h \ ssl/qsslsocket.h \ ssl/qsslsocket_p.h \ + ssl/qsslpresharedkeyauthenticator.h \ + ssl/qsslpresharedkeyauthenticator_p.h \ ssl/qsslcertificateextension.h \ ssl/qsslcertificateextension_p.h SOURCES += ssl/qasn1element.cpp \ @@ -26,6 +28,7 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op ssl/qsslkey_p.cpp \ ssl/qsslerror.cpp \ ssl/qsslsocket.cpp \ + ssl/qsslpresharedkeyauthenticator.cpp \ ssl/qsslcertificateextension.cpp winrt { diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index 868a6119a8..6e58df556d 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Governikus GmbH & Co. KG. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the test suite of the Qt Toolkit. @@ -41,6 +42,7 @@ #include #include #include +#include #include #include @@ -81,6 +83,15 @@ typedef QSharedPointer QSslSocketPtr; #define QSSLSOCKET_CERTUNTRUSTED_WORKAROUND #endif +// Use this cipher to force PSK key sharing. +// Also, it's a cipher w/o auth, to check that we emit the signals warning +// about the identity of the peer. +static const QString PSK_CIPHER_WITHOUT_AUTH = QStringLiteral("PSK-AES256-CBC-SHA"); +static const quint16 PSK_SERVER_PORT = 4433; +static const QByteArray PSK_CLIENT_PRESHAREDKEY = QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"); +static const QByteArray PSK_SERVER_IDENTITY_HINT = QByteArrayLiteral("QtTestServerHint"); +static const QByteArray PSK_CLIENT_IDENTITY = QByteArrayLiteral("Client_identity"); + class tst_QSslSocket : public QObject { Q_OBJECT @@ -104,6 +115,19 @@ public: #ifndef QT_NO_SSL QSslSocketPtr newSocket(); + +#ifndef QT_NO_OPENSSL + enum PskConnectTestType { + PskConnectDoNotHandlePsk, + PskConnectEmptyCredentials, + PskConnectWrongCredentials, + PskConnectWrongIdentity, + PskConnectWrongPreSharedKey, + PskConnectRightCredentialsPeerVerifyFailure, + PskConnectRightCredentialsVerifyPeer, + PskConnectRightCredentialsDoNotVerifyPeer, + }; +#endif #endif public slots: @@ -199,6 +223,11 @@ private slots: void ecdhServer(); void setEmptyDefaultConfiguration(); // this test should be last +#ifndef QT_NO_OPENSSL + void simplePskConnect_data(); + void simplePskConnect(); +#endif + static void exitLoop() { // Safe exit - if we aren't in an event loop, don't @@ -231,6 +260,12 @@ private: static int loopLevel; }; +#ifndef QT_NO_SSL +#ifndef QT_NO_OPENSSL +Q_DECLARE_METATYPE(tst_QSslSocket::PskConnectTestType) +#endif +#endif + int tst_QSslSocket::loopLevel = 0; tst_QSslSocket::tst_QSslSocket() @@ -240,6 +275,11 @@ tst_QSslSocket::tst_QSslSocket() qRegisterMetaType("QSslError"); qRegisterMetaType("QAbstractSocket::SocketState"); qRegisterMetaType("QAbstractSocket::SocketError"); + +#ifndef QT_NO_OPENSSL + qRegisterMetaType(); + qRegisterMetaType(); +#endif #endif } @@ -2762,6 +2802,320 @@ void tst_QSslSocket::setEmptyDefaultConfiguration() // this test should be last, QSKIP("Skipping flaky test - See QTBUG-29941"); } +#ifndef QT_NO_OPENSSL +class PskProvider : public QObject +{ + Q_OBJECT + +public: + explicit PskProvider(QObject *parent = 0) + : QObject(parent) + { + } + + void setIdentity(const QByteArray &identity) + { + m_identity = identity; + } + + void setPreSharedKey(const QByteArray &psk) + { + m_psk = psk; + } + +public slots: + void providePsk(QSslPreSharedKeyAuthenticator *authenticator) + { + QVERIFY(authenticator); + QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT); + QVERIFY(authenticator->maximumIdentityLength() > 0); + QVERIFY(authenticator->maximumPreSharedKeyLength() > 0); + + if (!m_identity.isEmpty()) { + authenticator->setIdentity(m_identity); + QCOMPARE(authenticator->identity(), m_identity); + } + + if (!m_psk.isEmpty()) { + authenticator->setPreSharedKey(m_psk); + QCOMPARE(authenticator->preSharedKey(), m_psk); + } + } + +private: + QByteArray m_identity; + QByteArray m_psk; +}; + +void tst_QSslSocket::simplePskConnect_data() +{ + QTest::addColumn("pskTestType"); + QTest::newRow("PskConnectDoNotHandlePsk") << PskConnectDoNotHandlePsk; + QTest::newRow("PskConnectEmptyCredentials") << PskConnectEmptyCredentials; + QTest::newRow("PskConnectWrongCredentials") << PskConnectWrongCredentials; + QTest::newRow("PskConnectWrongIdentity") << PskConnectWrongIdentity; + QTest::newRow("PskConnectWrongPreSharedKey") << PskConnectWrongPreSharedKey; + QTest::newRow("PskConnectRightCredentialsPeerVerifyFailure") << PskConnectRightCredentialsPeerVerifyFailure; + QTest::newRow("PskConnectRightCredentialsVerifyPeer") << PskConnectRightCredentialsVerifyPeer; + QTest::newRow("PskConnectRightCredentialsDoNotVerifyPeer") << PskConnectRightCredentialsDoNotVerifyPeer; +} + +void tst_QSslSocket::simplePskConnect() +{ + QFETCH(PskConnectTestType, pskTestType); + QSKIP("This test requires change 1f8cab2c3bcd91335684c95afa95ae71e00a94e4 on the network test server, QTQAINFRA-917"); + + if (!QSslSocket::supportsSsl()) + QSKIP("No SSL support"); + + bool pskCipherFound = false; + const QList supportedCiphers = QSslSocket::supportedCiphers(); + foreach (const QSslCipher &cipher, supportedCiphers) { + if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) { + pskCipherFound = true; + break; + } + } + + if (!pskCipherFound) + QSKIP("SSL implementation does not support the necessary PSK cipher(s)"); + + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + QSKIP("This test must not be going through a proxy"); + + QSslSocket socket; + this->socket = &socket; + + QSignalSpy connectedSpy(&socket, SIGNAL(connected())); + QVERIFY(connectedSpy.isValid()); + + QSignalSpy hostFoundSpy(&socket, SIGNAL(hostFound())); + QVERIFY(hostFoundSpy.isValid()); + + QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected())); + QVERIFY(disconnectedSpy.isValid()); + + QSignalSpy connectionEncryptedSpy(&socket, SIGNAL(encrypted())); + QVERIFY(connectionEncryptedSpy.isValid()); + + QSignalSpy sslErrorsSpy(&socket, SIGNAL(sslErrors(QList))); + QVERIFY(sslErrorsSpy.isValid()); + + QSignalSpy socketErrorsSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError))); + QVERIFY(socketErrorsSpy.isValid()); + + QSignalSpy peerVerifyErrorSpy(&socket, SIGNAL(peerVerifyError(QSslError))); + QVERIFY(peerVerifyErrorSpy.isValid()); + + QSignalSpy pskAuthenticationRequiredSpy(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*))); + QVERIFY(pskAuthenticationRequiredSpy.isValid()); + + connect(&socket, SIGNAL(connected()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(disconnected()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(encrypted()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(sslErrors(QList)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(peerVerifyError(QSslError)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(exitLoop())); + + // force a PSK cipher w/o auth + socket.setCiphers(PSK_CIPHER_WITHOUT_AUTH); + + PskProvider provider; + + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + // don't connect to the provider + break; + + case PskConnectEmptyCredentials: + // connect to the psk provider, but don't actually provide any PSK nor identity + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectWrongCredentials: + // provide totally wrong credentials + provider.setIdentity(PSK_CLIENT_IDENTITY.left(PSK_CLIENT_IDENTITY.length() - 1)); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY.left(PSK_CLIENT_PRESHAREDKEY.length() - 1)); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectWrongIdentity: + // right PSK, wrong identity + provider.setIdentity(PSK_CLIENT_IDENTITY.left(PSK_CLIENT_IDENTITY.length() - 1)); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectWrongPreSharedKey: + // right identity, wrong PSK + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY.left(PSK_CLIENT_PRESHAREDKEY.length() - 1)); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectRightCredentialsPeerVerifyFailure: + // right identity, right PSK, but since we can't verify the other peer, we'll fail + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectRightCredentialsVerifyPeer: + // right identity, right PSK, verify the peer (but ignore the failure) and establish the connection + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + connect(&socket, SIGNAL(peerVerifyError(QSslError)), this, SLOT(ignoreErrorSlot())); + break; + + case PskConnectRightCredentialsDoNotVerifyPeer: + // right identity, right PSK, do not verify the peer and establish the connection + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + socket.setPeerVerifyMode(QSslSocket::VerifyNone); + break; + } + + // check the peer verification mode + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + case PskConnectRightCredentialsPeerVerifyFailure: + case PskConnectRightCredentialsVerifyPeer: + QCOMPARE(socket.peerVerifyMode(), QSslSocket::AutoVerifyPeer); + break; + + case PskConnectRightCredentialsDoNotVerifyPeer: + QCOMPARE(socket.peerVerifyMode(), QSslSocket::VerifyNone); + break; + } + + // Start connecting + socket.connectToHost(QtNetworkSettings::serverName(), PSK_SERVER_PORT); + QCOMPARE(socket.state(), QAbstractSocket::HostLookupState); + enterLoop(10); + + // Entered connecting state + QCOMPARE(socket.state(), QAbstractSocket::ConnectingState); + QCOMPARE(connectedSpy.count(), 0); + QCOMPARE(hostFoundSpy.count(), 1); + QCOMPARE(disconnectedSpy.count(), 0); + enterLoop(10); + + // Entered connected state + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.mode(), QSslSocket::UnencryptedMode); + QVERIFY(!socket.isEncrypted()); + QCOMPARE(connectedSpy.count(), 1); + QCOMPARE(hostFoundSpy.count(), 1); + QCOMPARE(disconnectedSpy.count(), 0); + + // Enter encrypted mode + socket.startClientEncryption(); + QCOMPARE(socket.mode(), QSslSocket::SslClientMode); + QVERIFY(!socket.isEncrypted()); + QCOMPARE(connectionEncryptedSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(peerVerifyErrorSpy.count(), 0); + + // Start handshake. + enterLoop(10); + + // We must get the PSK signal in all cases + QCOMPARE(pskAuthenticationRequiredSpy.count(), 1); + + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + // Handshake failure + QCOMPARE(socketErrorsSpy.count(), 1); + QCOMPARE(qvariant_cast(socketErrorsSpy.at(0).at(0)), QAbstractSocket::SslHandshakeFailedError); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(connectionEncryptedSpy.count(), 0); + QVERIFY(!socket.isEncrypted()); + break; + + case PskConnectRightCredentialsPeerVerifyFailure: + // Peer verification failure + QCOMPARE(socketErrorsSpy.count(), 1); + QCOMPARE(qvariant_cast(socketErrorsSpy.at(0).at(0)), QAbstractSocket::SslHandshakeFailedError); + QCOMPARE(sslErrorsSpy.count(), 1); + QCOMPARE(peerVerifyErrorSpy.count(), 1); + QCOMPARE(connectionEncryptedSpy.count(), 0); + QVERIFY(!socket.isEncrypted()); + break; + + case PskConnectRightCredentialsVerifyPeer: + // Peer verification failure, but ignore it and keep connecting + QCOMPARE(socketErrorsSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 1); + QCOMPARE(peerVerifyErrorSpy.count(), 1); + QCOMPARE(connectionEncryptedSpy.count(), 1); + QVERIFY(socket.isEncrypted()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + break; + + case PskConnectRightCredentialsDoNotVerifyPeer: + // No peer verification => no failure + QCOMPARE(socketErrorsSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(connectionEncryptedSpy.count(), 1); + QVERIFY(socket.isEncrypted()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + break; + } + + // check writing + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + case PskConnectRightCredentialsPeerVerifyFailure: + break; + + case PskConnectRightCredentialsVerifyPeer: + case PskConnectRightCredentialsDoNotVerifyPeer: + socket.write("Hello from Qt TLS/PSK!"); + QVERIFY(socket.waitForBytesWritten()); + break; + } + + // disconnect + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + case PskConnectRightCredentialsPeerVerifyFailure: + break; + + case PskConnectRightCredentialsVerifyPeer: + case PskConnectRightCredentialsDoNotVerifyPeer: + socket.disconnectFromHost(); + enterLoop(10); + break; + } + + QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState); + QCOMPARE(disconnectedSpy.count(), 1); +} +#endif // QT_NO_OPENSSL + #endif // QT_NO_SSL QTEST_MAIN(tst_QSslSocket) -- cgit v1.2.3