Add support for PSK on server side

[ChangeLog][QtNetwork][QSslSocket] TLS PSK ciphers are possible in server sockets.

Task-number: QTBUG-39077
Change-Id: Iaa854a6f50242deae5492f2e4759c727488995f5
Reviewed-by: Richard J. Moore <rich@kde.org>
This commit is contained in:
André Klitzing 2016-03-23 12:27:11 +01:00
parent 9f416fad36
commit 0eaac0a3a9
10 changed files with 236 additions and 8 deletions

View File

@ -210,6 +210,7 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
d->privateKey == other.d->privateKey &&
d->sessionCipher == other.d->sessionCipher &&
d->sessionProtocol == other.d->sessionProtocol &&
d->preSharedKeyIdentityHint == other.d->preSharedKeyIdentityHint &&
d->ciphers == other.d->ciphers &&
d->ellipticCurves == other.d->ellipticCurves &&
d->caCertificates == other.d->caCertificates &&
@ -260,6 +261,7 @@ bool QSslConfiguration::isNull() const
d->sslOptions == QSslConfigurationPrivate::defaultSslOptions &&
d->sslSession.isNull() &&
d->sslSessionTicketLifeTimeHint == -1 &&
d->preSharedKeyIdentityHint.isNull() &&
d->nextAllowedProtocols.isEmpty() &&
d->nextNegotiatedProtocol.isNull() &&
d->nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNone);
@ -809,6 +811,32 @@ QVector<QSslEllipticCurve> QSslConfiguration::supportedEllipticCurves()
return QSslSocketPrivate::supportedEllipticCurves();
}
/*!
\since 5.8
Returns the identity hint.
\sa setPreSharedKeyIdentityHint()
*/
QByteArray QSslConfiguration::preSharedKeyIdentityHint() const
{
return d->preSharedKeyIdentityHint;
}
/*!
\since 5.8
Sets the identity hint for a preshared key authentication. This will affect the next
initiated handshake; calling this function on an already-encrypted socket
will not affect the socket's identity hint.
The identity hint is used in QSslSocket::SslServerMode only!
*/
void QSslConfiguration::setPreSharedKeyIdentityHint(const QByteArray &hint)
{
d->preSharedKeyIdentityHint = hint;
}
/*!
\since 5.3

View File

@ -141,6 +141,9 @@ public:
void setEllipticCurves(const QVector<QSslEllipticCurve> &curves);
static QVector<QSslEllipticCurve> supportedEllipticCurves();
QByteArray preSharedKeyIdentityHint() const;
void setPreSharedKeyIdentityHint(const QByteArray &hint);
static QSslConfiguration defaultConfiguration();
static void setDefaultConfiguration(const QSslConfiguration &configuration);

View File

@ -88,6 +88,7 @@ public:
peerSessionShared(false),
sslOptions(QSslConfigurationPrivate::defaultSslOptions),
sslSessionTicketLifeTimeHint(-1),
preSharedKeyIdentityHint(),
nextProtocolNegotiationStatus(QSslConfiguration::NextProtocolNegotiationNone)
{ }
@ -121,6 +122,8 @@ public:
QSslKey ephemeralServerKey;
QByteArray preSharedKeyIdentityHint;
QList<QByteArray> nextAllowedProtocols;
QByteArray nextNegotiatedProtocol;
QSslConfiguration::NextProtocolNegotiationStatus nextProtocolNegotiationStatus;

View File

@ -344,6 +344,11 @@ init_context:
}
#endif // OPENSSL_NO_EC
#ifndef OPENSSL_NO_PSK
if (!client)
q_SSL_CTX_use_psk_identity_hint(sslContext->ctx, sslContext->sslConfiguration.preSharedKeyIdentityHint().constData());
#endif // OPENSSL_NO_PSK
const QVector<QSslEllipticCurve> qcurves = sslContext->sslConfiguration.ellipticCurves();
if (!qcurves.isEmpty()) {
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_EC)

View File

@ -915,6 +915,7 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
d->configuration.privateKey = configuration.privateKey();
d->configuration.ciphers = configuration.ciphers();
d->configuration.ellipticCurves = configuration.ellipticCurves();
d->configuration.preSharedKeyIdentityHint = configuration.preSharedKeyIdentityHint();
d->configuration.caCertificates = configuration.caCertificates();
d->configuration.peerVerifyDepth = configuration.peerVerifyDepth();
d->configuration.peerVerifyMode = configuration.peerVerifyMode();

View File

@ -201,6 +201,15 @@ static unsigned int q_ssl_psk_client_callback(SSL *ssl,
Q_ASSERT(d);
return d->tlsPskClientCallback(hint, identity, max_identity_len, psk, max_psk_len);
}
static unsigned int q_ssl_psk_server_callback(SSL *ssl,
const char *identity,
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->tlsPskServerCallback(identity, psk, max_psk_len);
}
#endif
} // extern "C"
@ -436,8 +445,12 @@ bool QSslSocketBackendPrivate::initSslContext()
#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);
if (q_SSLeay() >= 0x10001000L) {
if (mode == QSslSocket::SslClientMode)
q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback);
else if (mode == QSslSocket::SslServerMode)
q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback);
}
#endif
return true;
@ -1260,6 +1273,31 @@ unsigned int QSslSocketBackendPrivate::tlsPskClientCallback(const char *hint,
return pskLength;
}
unsigned int QSslSocketBackendPrivate::tlsPskServerCallback(const char *identity,
unsigned char *psk, unsigned int max_psk_len)
{
QSslPreSharedKeyAuthenticator authenticator;
// Fill in some read-only fields (for the user)
authenticator.d->identityHint = configuration.preSharedKeyIdentityHint;
authenticator.d->identity = identity;
authenticator.d->maximumIdentityLength = 0; // user cannot set an identity
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 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

@ -143,6 +143,7 @@ public:
bool checkSslErrors();
void storePeerCertificates();
unsigned int tlsPskClientCallback(const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len);
unsigned int tlsPskServerCallback(const char *identity, unsigned char *psk, unsigned int max_psk_len);
#ifdef Q_OS_WIN
void fetchCaRootForCert(const QSslCertificate &cert);
void _q_caRootLoaded(QSslCertificate,QSslCertificate) Q_DECL_OVERRIDE;

View File

@ -300,6 +300,8 @@ DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return N
#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)
DEFINEFUNC2(void, SSL_set_psk_server_callback, SSL* ssl, ssl, q_psk_server_callback_t callback, callback, return, DUMMYARG)
DEFINEFUNC2(int, SSL_CTX_use_psk_identity_hint, SSL_CTX* ctx, ctx, const char *hint, hint, return 0, return)
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
#ifndef OPENSSL_NO_SSL2
@ -901,6 +903,8 @@ bool q_resolveOpenSslSymbols()
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK)
RESOLVEFUNC(SSL_set_psk_client_callback)
RESOLVEFUNC(SSL_set_psk_server_callback)
RESOLVEFUNC(SSL_CTX_use_psk_identity_hint)
#endif
RESOLVEFUNC(SSL_write)
#ifndef OPENSSL_NO_SSL2

View File

@ -376,6 +376,9 @@ void *q_SSL_get_ex_data(const SSL *ssl, int idx);
#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);
typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len);
void q_SSL_set_psk_server_callback(SSL *ssl, q_psk_server_callback_t callback);
int q_SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint);
#endif // OPENSSL_NO_PSK
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
#ifndef OPENSSL_NO_SSL2

View File

@ -230,6 +230,7 @@ private slots:
void ephemeralServerKey_data();
void ephemeralServerKey();
void allowedProtocolNegotiation();
void pskServer();
#endif
static void exitLoop()
@ -3036,8 +3037,12 @@ class PskProvider : public QObject
Q_OBJECT
public:
bool m_server;
QByteArray m_identity;
QByteArray m_psk;
explicit PskProvider(QObject *parent = 0)
: QObject(parent)
: QObject(parent), m_server(false)
{
}
@ -3056,7 +3061,11 @@ public slots:
{
QVERIFY(authenticator);
QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT);
QVERIFY(authenticator->maximumIdentityLength() > 0);
if (m_server)
QCOMPARE(authenticator->maximumIdentityLength(), 0);
else
QVERIFY(authenticator->maximumIdentityLength() > 0);
QVERIFY(authenticator->maximumPreSharedKeyLength() > 0);
if (!m_identity.isEmpty()) {
@ -3069,12 +3078,61 @@ public slots:
QCOMPARE(authenticator->preSharedKey(), m_psk);
}
}
private:
QByteArray m_identity;
QByteArray m_psk;
};
class PskServer : public QTcpServer
{
Q_OBJECT
public:
PskServer()
: socket(0),
config(QSslConfiguration::defaultConfiguration()),
ignoreSslErrors(true),
peerVerifyMode(QSslSocket::AutoVerifyPeer),
protocol(QSsl::TlsV1_0),
m_pskProvider()
{
m_pskProvider.m_server = true;
}
QSslSocket *socket;
QSslConfiguration config;
bool ignoreSslErrors;
QSslSocket::PeerVerifyMode peerVerifyMode;
QSsl::SslProtocol protocol;
QString ciphers;
PskProvider m_pskProvider;
protected:
void incomingConnection(qintptr socketDescriptor)
{
socket = new QSslSocket(this);
socket->setSslConfiguration(config);
socket->setPeerVerifyMode(peerVerifyMode);
socket->setProtocol(protocol);
if (ignoreSslErrors)
connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot()));
if (!ciphers.isEmpty()) {
socket->setCiphers(ciphers);
}
QVERIFY(socket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState));
QVERIFY(!socket->peerAddress().isNull());
QVERIFY(socket->peerPort() != 0);
QVERIFY(!socket->localAddress().isNull());
QVERIFY(socket->localPort() != 0);
connect(socket, &QSslSocket::preSharedKeyAuthenticationRequired, &m_pskProvider, &PskProvider::providePsk);
socket->startServerEncryption();
}
protected slots:
void ignoreErrorSlot()
{
socket->ignoreSslErrors();
}
};
void tst_QSslSocket::simplePskConnect_data()
{
QTest::addColumn<PskConnectTestType>("pskTestType");
@ -3415,6 +3473,90 @@ void tst_QSslSocket::allowedProtocolNegotiation()
#endif // OPENSSL_VERSION_NUMBER
}
void tst_QSslSocket::pskServer()
{
QFETCH_GLOBAL(bool, setProxy);
if (!QSslSocket::supportsSsl() || setProxy)
return;
QSslSocket socket;
this->socket = &socket;
QSignalSpy connectedSpy(&socket, SIGNAL(connected()));
QVERIFY(connectedSpy.isValid());
QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected()));
QVERIFY(disconnectedSpy.isValid());
QSignalSpy connectionEncryptedSpy(&socket, SIGNAL(encrypted()));
QVERIFY(connectionEncryptedSpy.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;
provider.setIdentity(PSK_CLIENT_IDENTITY);
provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY);
connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*)));
socket.setPeerVerifyMode(QSslSocket::VerifyNone);
PskServer server;
server.m_pskProvider.setIdentity(provider.m_identity);
server.m_pskProvider.setPreSharedKey(provider.m_psk);
server.config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT);
QVERIFY(server.listen());
// Start connecting
socket.connectToHost(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort());
enterLoop(5);
// Entered connected state
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
QCOMPARE(socket.mode(), QSslSocket::UnencryptedMode);
QVERIFY(!socket.isEncrypted());
QCOMPARE(connectedSpy.count(), 1);
QCOMPARE(disconnectedSpy.count(), 0);
// Enter encrypted mode
socket.startClientEncryption();
QCOMPARE(socket.mode(), QSslSocket::SslClientMode);
QVERIFY(!socket.isEncrypted());
QCOMPARE(connectionEncryptedSpy.count(), 0);
// Start handshake.
enterLoop(10);
// We must get the PSK signal in all cases
QCOMPARE(pskAuthenticationRequiredSpy.count(), 1);
QCOMPARE(connectionEncryptedSpy.count(), 1);
QVERIFY(socket.isEncrypted());
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
// check writing
socket.write("Hello from Qt TLS/PSK!");
QVERIFY(socket.waitForBytesWritten());
// disconnect
socket.disconnectFromHost();
enterLoop(10);
QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState);
QCOMPARE(disconnectedSpy.count(), 1);
}
#endif // QT_NO_OPENSSL
#endif // QT_NO_SSL