QSsl: do not wait for 'connected'/'encrypted' if a protocol is disabled

since we'll refuse to continue with a handshake, failing in initSslContext()
on a disabled protocol versions. Then, functions like waitForEncrypted,
connectToHostEncrypted, startServerEncryption and startClientEncryption
should either bail out early (who needs a TCP connection which we'll
abort anyway?) or bail out whenever we can, as soon as a disabled protocol
was found in a configuration. This change also makes the behavior
of different back-ends consistent, since it's a general code-path
that reports the same SslInvalidUserData error. Update auto-test to
... actually test what it claims it tests.

Task-number: QTBUG-72196
Task-number: QTBUG-72179
Change-Id: I548468993410f10c07ce5773b78f38132be8e3e0
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Timur Pocheptsov 2018-12-03 15:40:53 +01:00
parent 50d53533e5
commit 3e1758e35d
3 changed files with 82 additions and 44 deletions

View File

@ -460,6 +460,9 @@ void QSslSocket::connectToHostEncrypted(const QString &hostName, quint16 port, O
return;
}
if (!d->verifyProtocolSupported("QSslSocket::connectToHostEncrypted:"))
return;
d->init();
d->autoStartHandshake = true;
d->initialized = true;
@ -1607,6 +1610,8 @@ bool QSslSocket::waitForEncrypted(int msecs)
return false;
if (d->mode == UnencryptedMode && !d->autoStartHandshake)
return false;
if (!d->verifyProtocolSupported("QSslSocket::waitForEncrypted:"))
return false;
QElapsedTimer stopWatch;
stopWatch.start();
@ -1856,6 +1861,10 @@ void QSslSocket::startClientEncryption()
d->setErrorAndEmit(QAbstractSocket::SslInternalError, tr("TLS initialization failed"));
return;
}
if (!d->verifyProtocolSupported("QSslSocket::startClientEncryption:"))
return;
#ifdef QSSLSOCKET_DEBUG
qCDebug(lcSsl) << "QSslSocket::startClientEncryption()";
#endif
@ -1899,6 +1908,9 @@ void QSslSocket::startServerEncryption()
d->setErrorAndEmit(QAbstractSocket::SslInternalError, tr("TLS initialization failed"));
return;
}
if (!d->verifyProtocolSupported("QSslSocket::startServerEncryption"))
return;
d->mode = SslServerMode;
emit modeChanged(d->mode);
d->startServerEncryption();
@ -2130,6 +2142,20 @@ void QSslSocketPrivate::init()
configuration.peerCertificateChain.clear();
}
/*!
\internal
*/
bool QSslSocketPrivate::verifyProtocolSupported(const char *where)
{
if (configuration.protocol == QSsl::SslV2 || configuration.protocol == QSsl::SslV3) {
qCWarning(lcSsl) << where << "Attempted to use an unsupported protocol.";
setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError,
QSslSocket::tr("Attempted to use an unsupported protocol."));
return false;
}
return true;
}
/*!
\internal
*/

View File

@ -97,6 +97,7 @@ public:
virtual ~QSslSocketPrivate();
void init();
bool verifyProtocolSupported(const char *where);
bool initialized;
QSslSocket::SslMode mode;

View File

@ -245,8 +245,8 @@ private slots:
void signatureAlgorithm();
#endif
void deprecatedProtocols_data();
void deprecatedProtocols();
void disabledProtocols_data();
void disabledProtocols();
void setEmptyDefaultConfiguration(); // this test should be last
@ -4051,60 +4051,71 @@ void tst_QSslSocket::forwardReadChannelFinished()
#endif // QT_NO_OPENSSL
void tst_QSslSocket::deprecatedProtocols_data()
void tst_QSslSocket::disabledProtocols_data()
{
QTest::addColumn<QSsl::SslProtocol>("protocol");
QTest::addColumn<bool>("succeeds");
QTest::newRow("SecureProtocols") << QSsl::SecureProtocols << true;
QTest::newRow("SslV2") << QSsl::SslV2 << false;
QTest::newRow("SslV3") << QSsl::SslV3 << false;
QTest::addColumn<QSsl::SslProtocol>("disabledProtocol");
QTest::newRow("SslV2") << QSsl::SslV2;
QTest::newRow("SslV3") << QSsl::SslV3;
}
void tst_QSslSocket::deprecatedProtocols()
void tst_QSslSocket::disabledProtocols()
{
QFETCH_GLOBAL(bool, setProxy);
QFETCH_GLOBAL(const bool, setProxy);
if (setProxy)
QSKIP("This test does not work under a proxy");
return;
QFETCH(QSsl::SslProtocol, protocol);
QFETCH(bool, succeeds);
QFETCH(const QSsl::SslProtocol, disabledProtocol);
const int timeoutMS = 500;
// Test a client socket.
{
// 0. connectToHostEncrypted: client-side, non-blocking API, error is discovered
// early, preventing any real connection from ever starting.
QSslSocket socket;
socket.setProtocol(disabledProtocol);
QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError);
socket.connectToHostEncrypted(QStringLiteral("doesnotmatter.org"), 1010);
QCOMPARE(socket.error(), QAbstractSocket::SslInvalidUserDataError);
QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState);
}
{
// 1. startClientEncryption: client-side, non blocking API, but wants a socket in
// the 'connected' state (otherwise just returns false not setting any error code).
SslServer server;
QVERIFY(server.listen());
QSslSocket socket;
socket.setProtocol(protocol);
QSslSocket socket;
QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError);
QSignalSpy connectedSpy(&socket, &QSslSocket::connected);
QVERIFY(connectedSpy.isValid());
socket.connectToHost(server.serverAddress(), server.serverPort());
QVERIFY(socket.waitForConnected(timeoutMS));
QSignalSpy encryptedSpy(&socket, &QSslSocket::encrypted);
QVERIFY(encryptedSpy.isValid());
socket.setProtocol(disabledProtocol);
socket.startClientEncryption();
QCOMPARE(socket.error(), QAbstractSocket::SslInvalidUserDataError);
}
{
// 2. waitForEncrypted: client-side, blocking API plus requires from us
// to call ... connectToHostEncrypted(), which will notice an error and
// will prevent any connect at all. Nothing to test.
}
QSignalSpy errorSpy(&socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error));
QVERIFY(errorSpy.isValid());
// Test a server side, relatively simple: server does not connect, it listens/accepts
// and then calls startServerEncryption() (which must fall).
{
SslServer server;
server.protocol = disabledProtocol;
QVERIFY(server.listen());
connect(&socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
&socket, QOverload<>::of(&QSslSocket::ignoreSslErrors));
QTestEventLoop loop;
connect(&server, &SslServer::socketError, [&loop](QAbstractSocket::SocketError)
{loop.exitLoop();});
SslServer server;
QVERIFY(server.listen());
socket.connectToHost(server.serverAddress(), server.serverPort());
// Can't use waitForConnected / waitForEncrypted as they wait forever,
// so do this asynchronously via QTRY_ macros (QTBUG-72179)
QTRY_COMPARE(connectedSpy.size(), 1);
QCOMPARE(encryptedSpy.size(), 0);
QCOMPARE(errorSpy.size(), 0);
socket.startClientEncryption();
if (succeeds) {
QTRY_COMPARE(encryptedSpy.size(), 1);
QCOMPARE(errorSpy.size(), 0);
} else {
// The various backends differ in the errors fired here (QTBUG-72196),
// so just check that we did get an error (and we're not encrypted)
QTRY_VERIFY(errorSpy.size() > 0);
QCOMPARE(encryptedSpy.size(), 0);
QTcpSocket client;
client.connectToHost(server.serverAddress(), server.serverPort());
loop.enterLoopMSecs(timeoutMS);
QVERIFY(!loop.timeout());
QVERIFY(server.socket);
QCOMPARE(server.socket->error(), QAbstractSocket::SslInvalidUserDataError);
}
}