diff options
-rw-r--r-- | src/network/ssl/qsslconfiguration.cpp | 28 | ||||
-rw-r--r-- | src/network/ssl/qsslconfiguration.h | 3 | ||||
-rw-r--r-- | src/network/ssl/qsslconfiguration_p.h | 3 | ||||
-rw-r--r-- | src/network/ssl/qsslcontext_openssl.cpp | 5 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket.cpp | 1 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 42 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_p.h | 1 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_symbols.cpp | 4 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_symbols_p.h | 3 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp | 154 |
10 files changed, 236 insertions, 8 deletions
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp index 1fff2c31dd..1eb253d202 100644 --- a/src/network/ssl/qsslconfiguration.cpp +++ b/src/network/ssl/qsslconfiguration.cpp @@ -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); @@ -810,6 +812,32 @@ QVector<QSslEllipticCurve> QSslConfiguration::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 This function returns the protocol negotiated with the server diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h index f0754d7ef5..1f39e1a06f 100644 --- a/src/network/ssl/qsslconfiguration.h +++ b/src/network/ssl/qsslconfiguration.h @@ -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); diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h index 093c9d6598..113954d7d1 100644 --- a/src/network/ssl/qsslconfiguration_p.h +++ b/src/network/ssl/qsslconfiguration_p.h @@ -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; diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp index b24fdad1f9..0db7e10409 100644 --- a/src/network/ssl/qsslcontext_openssl.cpp +++ b/src/network/ssl/qsslcontext_openssl.cpp @@ -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) diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index bbc62c47ff..82df861859 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -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(); diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index 5c0c8674cd..5cbd2af323 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -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) diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h index 0674c05d71..c6572315f0 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -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; diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index 05b7e2da7f..f625fd3e96 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -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 diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index 5a6c934d1a..45e4380580 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -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 diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index c3cb477298..f1d66be442 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -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 |