improve sending and receiving UI and fix issue sending to new client
This commit is contained in:
@@ -21,3 +21,7 @@ add_test(NAME TestSessionManager COMMAND TestSessionManager)
|
||||
add_executable(TestDtoTypesExtended core/TestDtoTypesExtended.cpp)
|
||||
target_link_libraries(TestDtoTypesExtended PRIVATE LocalSendCore Qt6::Test)
|
||||
add_test(NAME TestDtoTypesExtended COMMAND TestDtoTypesExtended)
|
||||
|
||||
add_executable(TestHttpClientMtls core/TestHttpClientMtls.cpp)
|
||||
target_link_libraries(TestHttpClientMtls PRIVATE LocalSendCore Qt6::Test)
|
||||
add_test(NAME TestHttpClientMtls COMMAND TestHttpClientMtls)
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <QtTest>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QHostAddress>
|
||||
#include <QSslServer>
|
||||
#include <QSslConfiguration>
|
||||
#include <QDir>
|
||||
|
||||
#include <LocalSendCore/HttpClient.h>
|
||||
#include <LocalSendCore/SecurityContext.h>
|
||||
#include <LocalSendCore/Device.h>
|
||||
#include <LocalSendCore/Constants.h>
|
||||
|
||||
// The modern LocalSend protocol server (the Rust implementation) requires
|
||||
// mutual TLS: it only accepts a sender that presents a valid client
|
||||
// certificate during the TLS handshake. These tests spin up a QSslServer with
|
||||
// mandatory client-certificate verification and assert that LocalSend's
|
||||
// HttpClient can complete the handshake *only when* its SSL configuration
|
||||
// carries the device certificate (regression test for
|
||||
// "tlsv13 alert certificate required" when sending files).
|
||||
class TestHttpClientMtls : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testSslConfigurationCarriesDeviceIdentity();
|
||||
void testGetInfoSucceedsWithClientCertificate();
|
||||
void testGetInfoFailsWithoutClientCertificate();
|
||||
|
||||
private:
|
||||
// Starts a QHttpServer over TLS that REQUIRES a client certificate.
|
||||
// The trust store is seeded with our device certificate, so only a client
|
||||
// presenting that certificate passes. Returns the listening port.
|
||||
quint16 startMtlsServer(QHttpServer& http, QSslServer& ssl,
|
||||
const LocalSend::SecurityContext& sec);
|
||||
|
||||
QString m_configDir;
|
||||
LocalSend::SecurityContext* m_sec = nullptr;
|
||||
};
|
||||
|
||||
void TestHttpClientMtls::initTestCase()
|
||||
{
|
||||
// Isolated storage in the (writable) build directory so the test never
|
||||
// touches the real app certificate or depends on the user's HOME.
|
||||
m_configDir = QDir::currentPath() + QStringLiteral("/.mtls-test-cfg");
|
||||
QDir().mkpath(m_configDir);
|
||||
qputenv("XDG_CONFIG_HOME", m_configDir.toUtf8());
|
||||
|
||||
m_sec = new LocalSend::SecurityContext(this);
|
||||
m_sec->initialize();
|
||||
}
|
||||
|
||||
void TestHttpClientMtls::cleanupTestCase()
|
||||
{
|
||||
delete m_sec;
|
||||
m_sec = nullptr;
|
||||
QDir(m_configDir).removeRecursively();
|
||||
}
|
||||
|
||||
void TestHttpClientMtls::testSslConfigurationCarriesDeviceIdentity()
|
||||
{
|
||||
QVERIFY(m_sec);
|
||||
QSslConfiguration config = m_sec->sslConfiguration();
|
||||
QVERIFY(!config.isNull());
|
||||
QVERIFY(!config.localCertificate().isNull());
|
||||
QVERIFY(!m_sec->privateKey().isNull());
|
||||
// The config doubles as the client identity: it must carry the device
|
||||
// certificate that the handshake presents to the receiver.
|
||||
QCOMPARE(config.localCertificate().digest(QCryptographicHash::Sha256),
|
||||
m_sec->certificate().digest(QCryptographicHash::Sha256));
|
||||
}
|
||||
|
||||
void TestHttpClientMtls::testGetInfoSucceedsWithClientCertificate()
|
||||
{
|
||||
QVERIFY(m_sec);
|
||||
QVERIFY(!m_sec->sslConfiguration().isNull());
|
||||
|
||||
QHttpServer http;
|
||||
QSslServer ssl;
|
||||
quint16 port = startMtlsServer(http, ssl, *m_sec);
|
||||
QVERIFY(port != 0);
|
||||
|
||||
LocalSend::HttpClient client;
|
||||
// The app wires the same security context into the client (AppController).
|
||||
client.setSslConfiguration(m_sec->sslConfiguration());
|
||||
|
||||
LocalSend::Device device(QStringLiteral("127.0.0.1"), port);
|
||||
device.protocol = LocalSend::ProtocolType::Https;
|
||||
|
||||
bool gotInfo = false;
|
||||
bool gotError = false;
|
||||
QObject::connect(&client, &LocalSend::HttpClient::infoReceived,
|
||||
[&](const LocalSend::InfoDto&) { gotInfo = true; });
|
||||
QObject::connect(&client, &LocalSend::HttpClient::infoError,
|
||||
[&](const QString&) { gotError = true; });
|
||||
|
||||
client.getInfo(device);
|
||||
QTRY_VERIFY_WITH_TIMEOUT(gotInfo || gotError, 5000);
|
||||
|
||||
QVERIFY(!gotError);
|
||||
QVERIFY(gotInfo);
|
||||
}
|
||||
|
||||
void TestHttpClientMtls::testGetInfoFailsWithoutClientCertificate()
|
||||
{
|
||||
QVERIFY(m_sec);
|
||||
|
||||
QHttpServer http;
|
||||
QSslServer ssl;
|
||||
quint16 port = startMtlsServer(http, ssl, *m_sec);
|
||||
QVERIFY(port != 0);
|
||||
|
||||
// NO client SSL configuration: like the app before the fix, the client
|
||||
// does not present a certificate and the handshake must be rejected.
|
||||
LocalSend::HttpClient client;
|
||||
|
||||
LocalSend::Device device(QStringLiteral("127.0.0.1"), port);
|
||||
device.protocol = LocalSend::ProtocolType::Https;
|
||||
|
||||
bool gotInfo = false;
|
||||
bool gotError = false;
|
||||
QObject::connect(&client, &LocalSend::HttpClient::infoReceived,
|
||||
[&](const LocalSend::InfoDto&) { gotInfo = true; });
|
||||
QObject::connect(&client, &LocalSend::HttpClient::infoError,
|
||||
[&](const QString&) { gotError = true; });
|
||||
|
||||
client.getInfo(device);
|
||||
QTRY_VERIFY_WITH_TIMEOUT(gotInfo || gotError, 5000);
|
||||
|
||||
QVERIFY(!gotInfo);
|
||||
QVERIFY(gotError);
|
||||
}
|
||||
|
||||
quint16 TestHttpClientMtls::startMtlsServer(QHttpServer& http, QSslServer& ssl,
|
||||
const LocalSend::SecurityContext& sec)
|
||||
{
|
||||
http.route(QString::fromLatin1(LocalSend::ApiRoute::INFO), QHttpServerRequest::Method::Get,
|
||||
[](const QHttpServerRequest&) {
|
||||
QJsonObject o;
|
||||
o[QStringLiteral("alias")] = QStringLiteral("test-receiver");
|
||||
o[QStringLiteral("version")] = QStringLiteral("1.0");
|
||||
o[QStringLiteral("fingerprint")] = QStringLiteral("AABB");
|
||||
return QHttpServerResponse(QJsonDocument(o).toJson(QJsonDocument::Compact),
|
||||
QHttpServerResponse::StatusCode::Ok);
|
||||
});
|
||||
|
||||
QSslConfiguration serverSsl;
|
||||
serverSsl.setLocalCertificate(sec.certificate());
|
||||
serverSsl.setPrivateKey(sec.privateKey());
|
||||
serverSsl.setPeerVerifyMode(QSslSocket::VerifyPeer); // mandatory client cert
|
||||
serverSsl.setCaCertificates({ sec.certificate() }); // trust our device cert
|
||||
ssl.setSslConfiguration(serverSsl);
|
||||
|
||||
if (!ssl.listen(QHostAddress::LocalHost, 0)) {
|
||||
qWarning() << "Failed to listen for MTLS test server";
|
||||
return 0;
|
||||
}
|
||||
http.bind(&ssl);
|
||||
return ssl.serverPort();
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestHttpClientMtls)
|
||||
#include "TestHttpClientMtls.moc"
|
||||
Reference in New Issue
Block a user