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 <rich@kde.org>
This commit is contained in:
Giuseppe D'Angelo 2014-10-15 15:13:47 +02:00
parent e267505d54
commit bd26defd9b
11 changed files with 935 additions and 0 deletions

View File

@ -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 <QSharedData>
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

View File

@ -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 <QtCore/QtGlobal>
#include <QtCore/QString>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QMetaType>
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<QSslPreSharedKeyAuthenticatorPrivate> 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

View File

@ -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 <QSharedData>
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

View File

@ -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"

View File

@ -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<QSslError> &errors);
void modeChanged(QSslSocket::SslMode newMode);
void encryptedBytesWritten(qint64 totalBytes);
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
protected:
qint64 readData(char *data, qint64 maxlen) Q_DECL_OVERRIDE;

View File

@ -58,6 +58,8 @@
#include "qsslcipher_p.h"
#include "qsslkey_p.h"
#include "qsslellipticcurve.h"
#include "qsslpresharedkeyauthenticator.h"
#include "qsslpresharedkeyauthenticator_p.h"
#include <QtCore/qdatetime.h>
#include <QtCore/qdebug.h>
@ -72,6 +74,8 @@
#include <QtCore/qvarlengtharray.h>
#include <QLibrary> // for loading the security lib for the CA store
#include <string.h>
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<QSslSocketBackendPrivate *>(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)

View File

@ -114,6 +114,9 @@ public:
BIO *writeBio;
SSL_SESSION *session;
QList<QPair<int, int> > 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);

View File

@ -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)

View File

@ -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();

View File

@ -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 {

View File

@ -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 <QtNetwork/qsslkey.h>
#include <QtNetwork/qsslsocket.h>
#include <QtNetwork/qtcpserver.h>
#include <QtNetwork/qsslpresharedkeyauthenticator.h>
#include <QtTest/QtTest>
#include <QNetworkProxy>
@ -81,6 +83,15 @@ typedef QSharedPointer<QSslSocket> 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>("QSslError");
qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
#ifndef QT_NO_OPENSSL
qRegisterMetaType<QSslPreSharedKeyAuthenticator *>();
qRegisterMetaType<tst_QSslSocket::PskConnectTestType>();
#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<PskConnectTestType>("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<QSslCipher> 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<QSslError>)));
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<QSslError>)), 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<QAbstractSocket::SocketError>(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<QAbstractSocket::SocketError>(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)