diff options
author | Rainer Keller <Rainer.Keller@qt.io> | 2019-02-21 15:48:59 +0100 |
---|---|---|
committer | Aapo Keskimolo <aapo.keskimolo@qt.io> | 2019-03-19 19:50:55 +0000 |
commit | f4a67133998ba192b92c509be863c1d0dd0e1ce4 (patch) | |
tree | 8106da4bd397861d2590a64a646aa8e3b79c2769 | |
parent | c21c7641f77da03b8a8b32060b2be97cb1e3c4f9 (diff) |
tests: Add auto test for security
Change-Id: I02dc734e39177c18aad3689f82a3512830e7bff2
Reviewed-by: Jannis Völker <jannis.voelker@basyskom.com>
-rw-r--r-- | tests/auto/auto.pro | 3 | ||||
-rw-r--r-- | tests/auto/security/certs.qrc | 9 | ||||
-rw-r--r-- | tests/auto/security/pki/own/certs/tst_security.der | bin | 0 -> 1119 bytes | |||
-rw-r--r-- | tests/auto/security/pki/own/private/privateKeyWithoutPassword.pem | 28 | ||||
-rw-r--r-- | tests/auto/security/pki/trusted/certs/ca.der | bin | 0 -> 843 bytes | |||
-rw-r--r-- | tests/auto/security/pki/trusted/certs/open62541-testserver.der | bin | 0 -> 1041 bytes | |||
-rw-r--r-- | tests/auto/security/pki/trusted/crl/ca.crl.pem | 12 | ||||
-rw-r--r-- | tests/auto/security/security.pro | 9 | ||||
-rw-r--r-- | tests/auto/security/tst_security.cpp | 331 | ||||
-rw-r--r-- | tests/open62541-testserver/certs.qrc | 1 | ||||
-rw-r--r-- | tests/open62541-testserver/pki/own/certs/open62541-testserver.der | bin | 1031 -> 1041 bytes | |||
-rw-r--r-- | tests/open62541-testserver/pki/trusted/certs/tst_security.der | bin | 0 -> 1119 bytes |
12 files changed, 393 insertions, 0 deletions
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 47c3dd6..583603d 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -7,3 +7,6 @@ QT_FOR_CONFIG += opcua-private qtConfig(open62541)|qtConfig(uacpp) { SUBDIRS += declarative } + +# This tries to check if the server supports security +qtConfig(mbedtls): SUBDIRS += security diff --git a/tests/auto/security/certs.qrc b/tests/auto/security/certs.qrc new file mode 100644 index 0000000..03d1756 --- /dev/null +++ b/tests/auto/security/certs.qrc @@ -0,0 +1,9 @@ +<RCC> + <qresource prefix="/"> + <file>pki/own/certs/tst_security.der</file> + <file>pki/own/private/privateKeyWithoutPassword.pem</file> + <file>pki/trusted/certs/ca.der</file> + <file>pki/trusted/certs/open62541-testserver.der</file> + <file>pki/trusted/crl/ca.crl.pem</file> + </qresource> +</RCC> diff --git a/tests/auto/security/pki/own/certs/tst_security.der b/tests/auto/security/pki/own/certs/tst_security.der Binary files differnew file mode 100644 index 0000000..f7527ba --- /dev/null +++ b/tests/auto/security/pki/own/certs/tst_security.der diff --git a/tests/auto/security/pki/own/private/privateKeyWithoutPassword.pem b/tests/auto/security/pki/own/private/privateKeyWithoutPassword.pem new file mode 100644 index 0000000..1ac9d84 --- /dev/null +++ b/tests/auto/security/pki/own/private/privateKeyWithoutPassword.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDwfehGxb7k+Z8T +GBkhwVHk4A8qi1w5vG7oXF9QtE5vNcl6JJCbZK7bVIDzDaNb2tk7FidaI5mQsZHz +ptlRhuV8jLGzrvSuxv4bpJDC7MOaOZasbc5g90JU24v7tKEa+k17tcn+Tk8ePRIR +JBuf8TLKEEZIqYHe2pVM8BL0Dmud0cQlkpj3F/RAVWkwh9xJIMuMsiOffI0HzF/2 +CSgbVdJKeZgco3MEpfkvLMtYtkCDFyA4XvSKaDB88YjJtGXlMC0XB7wFeWu1y6OZ +RgLlS4Zir5/rKsSwuhzkIBjeHMwP/gxX2Y+rlrCAwjhuJ7hvev230+8VSKaEtY/Z +M1rUgf3RAgMBAAECggEBALEUBHQYJvdy1i65D8hLEIH2eTRaaQ4aMY/mdEh4e0hn +0nKdedzxxc656jkNUbvQ6SMYrOEyVWC1X0KJGHtvWIrdDfvAV2paG8E+61ib/WsR +/F/6envrlGvnPKuZ1QaIR4VP1evqvVcGoMb+T1j1wPAIC7h1F6uAR27fVxVdiOAq +pzw+gCqF1962aj/Tz7StU51WohtmMTJHouoGOqTFSoKW5Mya6Yf99A7W5I0hKCZr +ddyKhX6TkbosGS5CP6lMK6P9KfZzzxP8v7g2OJ6Mfpb7U+nKggHvUtUz5UzuzSY5 +Iorf8fjLQtMRWGbDzLrtll/jPv1Hdh2YwdBOx2mAOAECgYEA/qv3oPuGn2Bt8qg0 +e8gyT+U4b8ssWIjcPBWgdsJo79RdF1xrHY3T0cC6EbMCO8ZDgwv+aMl9FBfJWRYM +ObXITh5R5xMIBFpZHkBIEMNxfnQaH7RBNuYwiCSzmSptzNk+6z2TkZFFW6JfILTe +rAiea0DeCKJakp2/N2UincUOc40CgYEA8b8B3PFZoxxDbRPhNwlvdSWNvLslBNU2 +/OhW19MnXwY7MMZVPt7YSVDaAcZEX1mU/eUTSzIvm6Sow0EXocS7nzmuoLnsnv9f +vhtAkp3AEpvF0zNbD8MlT8z4El8JK+tW2XEtkE5HgjQt5YQrk4nc0eJIlHOTJw7y +cbMg6OZUIFUCgYBxQlV8mKAEXURIeJnuutf1REHXJgpwzVz0s8GLT2aP0mgcLZPN +rveW/xlBKdVCdCguLbVVMNaZiwKWxgFl4PxWEZHnLEWSegPMOlZSbjkZPdUoaGfg +XHsU8Q2WfpIaWjtrLxVj1bF80TdxOj8VTzf1BwI34MxbDCCwKCA+/hYxOQKBgGId +mvz4e+AGrZsM0YCL9M/AASnTbu/qNZoqFm0cR0N6/PUL2jddLL188i58MO3eJulx +WwZPBSGPj+tHdPb0KQ4z1Btpuo7BqTM4TlnzaqxiysSweEoKcw9Tam/SYJ+Rsbsp +A0wpaT6APQyFO0ZzUstgowKVcekNWPsqr7W3HffNAoGAJXZlTvCq/m1Zc176a0cN +NayUjgWTeXIk+8RqB3/UeA5DLD5XfOUJPL/XvIZ2n5mlUrbMuHuvsXo0TnQm7sT2 +dt3jl0yo2JLtKXKuR5Q9KC7ehOUozIc9vsi0YzSL1zRHL35ZNx4wx/QNCfMuwUIT +5EL80FokYrFEWZ5qRuyAT6M= +-----END PRIVATE KEY----- diff --git a/tests/auto/security/pki/trusted/certs/ca.der b/tests/auto/security/pki/trusted/certs/ca.der Binary files differnew file mode 100644 index 0000000..3f31274 --- /dev/null +++ b/tests/auto/security/pki/trusted/certs/ca.der diff --git a/tests/auto/security/pki/trusted/certs/open62541-testserver.der b/tests/auto/security/pki/trusted/certs/open62541-testserver.der Binary files differnew file mode 100644 index 0000000..7aac4cd --- /dev/null +++ b/tests/auto/security/pki/trusted/certs/open62541-testserver.der diff --git a/tests/auto/security/pki/trusted/crl/ca.crl.pem b/tests/auto/security/pki/trusted/crl/ca.crl.pem new file mode 100644 index 0000000..5adff29 --- /dev/null +++ b/tests/auto/security/pki/trusted/crl/ca.crl.pem @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIBtDCBnQIBATANBgkqhkiG9w0BAQsFADA5MQswCQYDVQQGEwJERTESMBAGA1UE +CgwJb3BlbjYyNTQxMRYwFAYDVQQDDA1vcGVuNjI1NDEub3JnFw0xOTAyMDUwOTIx +MjZaFw0xOTAzMDcwOTIxMjZaoDAwLjAfBgNVHSMEGDAWgBT2JxGCWDhuet+WnrDX +1SXW4O8HNTALBgNVHRQEBAICEAAwDQYJKoZIhvcNAQELBQADggEBAF31DhT8Im+Z +QkpkFtUzmM9FfjqD4vrkrCAAA6U382mwn5KDcm4LA+FjFePHgSyk+ytLD03z7hSp +wgyxbldg4yC4GynUp7XcbB4zus6Lym/ayDgGVQEw81c/c7YAelc8u055TBJUcmuX +/XV/JXfSO2ZWM5MZZCev79w8Oj4hmUPp9ZpAJkxt/GYictGSkDpBTMTAWRRenPUe +F4qvkvYMkKMrURLFMfYcqa2ePszxnyjdfi6KXHllsHl0iHduSq1acAdx1G/itoDq +ML7QLS/M/VBYYxSGghLPetanQ+6f+OYztgTuzw2nG4DXxfMhfijoi6LbfFDvy0K+ +mHqhbQW1Go8= +-----END X509 CRL----- diff --git a/tests/auto/security/security.pro b/tests/auto/security/security.pro new file mode 100644 index 0000000..d3d203e --- /dev/null +++ b/tests/auto/security/security.pro @@ -0,0 +1,9 @@ +TARGET = tst_security + +QT += testlib opcua network +QT -= gui +CONFIG += testcase +RESOURCES += certs.qrc + +SOURCES += \ + tst_security.cpp diff --git a/tests/auto/security/tst_security.cpp b/tests/auto/security/tst_security.cpp new file mode 100644 index 0000000..01ad718 --- /dev/null +++ b/tests/auto/security/tst_security.cpp @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtOpcUa/QOpcUaAuthenticationInformation> +#include <QtOpcUa/QOpcUaClient> +#include <QtOpcUa/QOpcUaEndpointDescription> +#include <QtOpcUa/QOpcUaProvider> + +#include <QtCore/QCoreApplication> +#include <QtCore/QProcess> +#include <QtCore/QScopedPointer> + +#include <QtTest/QSignalSpy> +#include <QtTest/QtTest> +#include <QTcpSocket> +#include <QTcpServer> + +class OpcuaConnector +{ +public: + OpcuaConnector(QOpcUaClient *client, const QOpcUaEndpointDescription &endPoint) + : opcuaClient(client) + { + QVERIFY(opcuaClient != nullptr); + QSignalSpy connectedSpy(opcuaClient, &QOpcUaClient::connected); + QSignalSpy disconnectedSpy(opcuaClient, &QOpcUaClient::disconnected); + QSignalSpy stateSpy(opcuaClient, &QOpcUaClient::stateChanged); + + QTest::qWait(500); + + opcuaClient->connectToEndpoint(endPoint); + QTRY_VERIFY2(opcuaClient->state() == QOpcUaClient::Connected, "Could not connect to server"); + + QCOMPARE(connectedSpy.count(), 1); // one connected signal fired + QCOMPARE(disconnectedSpy.count(), 0); // zero disconnected signals fired + QCOMPARE(stateSpy.count(), 2); + + QCOMPARE(stateSpy.at(0).at(0).value<QOpcUaClient::ClientState>(), + QOpcUaClient::ClientState::Connecting); + QCOMPARE(stateSpy.at(1).at(0).value<QOpcUaClient::ClientState>(), + QOpcUaClient::ClientState::Connected); + + stateSpy.clear(); + connectedSpy.clear(); + disconnectedSpy.clear(); + + QVERIFY(opcuaClient->endpoint() == endPoint); + } + + ~OpcuaConnector() + { + QSignalSpy connectedSpy(opcuaClient, &QOpcUaClient::connected); + QSignalSpy disconnectedSpy(opcuaClient, &QOpcUaClient::disconnected); + QSignalSpy stateSpy(opcuaClient, &QOpcUaClient::stateChanged); + QSignalSpy errorSpy(opcuaClient, &QOpcUaClient::errorChanged); + + QVERIFY(opcuaClient != nullptr); + if (opcuaClient->state() == QOpcUaClient::Connected) { + + opcuaClient->disconnectFromEndpoint(); + + QTRY_VERIFY(opcuaClient->state() == QOpcUaClient::Disconnected); + + QCOMPARE(connectedSpy.count(), 0); + QCOMPARE(disconnectedSpy.count(), 1); + QCOMPARE(stateSpy.count(), 2); + QCOMPARE(stateSpy.at(0).at(0).value<QOpcUaClient::ClientState>(), + QOpcUaClient::ClientState::Closing); + QCOMPARE(stateSpy.at(1).at(0).value<QOpcUaClient::ClientState>(), + QOpcUaClient::ClientState::Disconnected); + QCOMPARE(errorSpy.size(), 0); + } + + opcuaClient = nullptr; + } + + QOpcUaClient *opcuaClient; +}; + +static QString messageSecurityModeToString(QOpcUaEndpointDescription::MessageSecurityMode msm) +{ + if (msm == QOpcUaEndpointDescription::None) + return "None"; + else if (msm == QOpcUaEndpointDescription::Sign) + return "Sign"; + else if (msm == QOpcUaEndpointDescription::SignAndEncrypt) + return "SignAndEncrypt"; + return "Invalid"; +} + +#define defineDataMethod(name) void name()\ +{\ + QTest::addColumn<QString>("backend");\ + QTest::addColumn<QOpcUaEndpointDescription>("endpoint");\ + for (auto backend : m_backends)\ + for (auto endpoint : m_endpoints) { \ + const QString rowName = QString("%1 using %2 %3").arg(backend).arg(endpoint.securityPolicyUri()).arg(messageSecurityModeToString(endpoint.securityMode())); \ + QTest::newRow(rowName.toLatin1().constData()) << backend << endpoint; \ + } \ +} + +class Tst_QOpcUaSecurity: public QObject +{ + Q_OBJECT + +public: + Tst_QOpcUaSecurity(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + defineDataMethod(connectAndDisconnectUsingCertificate_data) + void connectAndDisconnectUsingCertificate(); + +private: + QString envOrDefault(const char *env, QString def) + { + return qEnvironmentVariableIsSet(env) ? qgetenv(env).constData() : def; + } + + QString m_testServerPath; + QStringList m_backends; + QProcess m_serverProcess; + QVector<QOpcUaEndpointDescription> m_endpoints; + QString m_discoveryEndpoint; + QOpcUaProvider m_opcUa; + QSharedPointer<QTemporaryDir> m_pkiData; +}; + +Tst_QOpcUaSecurity::Tst_QOpcUaSecurity() +{ + m_backends = QOpcUaProvider::availableBackends(); +} + +void Tst_QOpcUaSecurity::initTestCase() +{ + const quint16 defaultPort = 43344; + const QHostAddress defaultHost(QHostAddress::LocalHost); + + m_pkiData = QTest::qExtractTestData("pki"); + qDebug() << "PKI data available at" << m_pkiData->path(); + + if (qEnvironmentVariableIsEmpty("OPCUA_HOST") && qEnvironmentVariableIsEmpty("OPCUA_PORT")) { + m_testServerPath = qApp->applicationDirPath() + +#if defined(Q_OS_MACOS) + + QLatin1String("/../../open62541-testserver/open62541-testserver.app/Contents/MacOS/open62541-testserver") +#else + +#ifdef Q_OS_WIN + + QLatin1String("/..") +#endif + + QLatin1String("/../../open62541-testserver/open62541-testserver") +#ifdef Q_OS_WIN + + QLatin1String(".exe") +#endif + +#endif + ; + if (!QFile::exists(m_testServerPath)) { + qDebug() << "Server Path:" << m_testServerPath; + QSKIP("all auto tests rely on an open62541-based test-server"); + } + + // In this case the test is supposed to open its own server. + // Unfortunately there is no way to check if the server has started up successfully + // because of missing error handling. + // This checks will detect other servers blocking the port. + + // Check for running server + QTcpSocket socket; + socket.connectToHost(defaultHost, defaultPort); + QVERIFY2(socket.waitForConnected(1500) == false, "Server is already running"); + + // Check for running server which does not respond + QTcpServer server; + QVERIFY2(server.listen(defaultHost, defaultPort) == true, "Port is occupied by another process. Check for defunct server."); + server.close(); + + m_serverProcess.start(m_testServerPath); + QVERIFY2(m_serverProcess.waitForStarted(), qPrintable(m_serverProcess.errorString())); + // Let the server come up + QTest::qSleep(2000); + } + QString host = envOrDefault("OPCUA_HOST", defaultHost.toString()); + QString port = envOrDefault("OPCUA_PORT", QString::number(defaultPort)); + m_discoveryEndpoint = QString("opc.tcp://%1:%2").arg(host).arg(port); + qDebug() << "Using endpoint:" << m_discoveryEndpoint; + + QScopedPointer<QOpcUaClient> client(m_opcUa.createClient(m_backends.first())); + QVERIFY2(client, "Loading backend failed"); + + if (client) { + QSignalSpy endpointSpy(client.data(), &QOpcUaClient::endpointsRequestFinished); + + client->requestEndpoints(m_discoveryEndpoint); + endpointSpy.wait(); + QCOMPARE(endpointSpy.size(), 1); + QCOMPARE(endpointSpy.at(0).at(2).value<QUrl>(), m_discoveryEndpoint); + + const QVector<QOpcUaEndpointDescription> desc = endpointSpy.at(0).at(0).value<QVector<QOpcUaEndpointDescription>>(); + QVERIFY(desc.size() > 0); + + m_endpoints.clear(); + + // Select first non-None security policy + for (const auto &endpoint : qAsConst(desc)) { + if (!endpoint.securityPolicyUri().endsWith("#None")) { + m_endpoints.append(endpoint); + qDebug() << endpoint.securityPolicyUri(); + } + } + + QVERIFY(m_endpoints.size() > 0); + } +} + +void Tst_QOpcUaSecurity::connectAndDisconnectUsingCertificate() +{ + QFETCH(QString, backend); + QFETCH(QOpcUaEndpointDescription, endpoint); + + QScopedPointer<QOpcUaClient> client(m_opcUa.createClient(backend)); + QVERIFY2(client, QString("Loading backend failed: %1").arg(backend).toLatin1().data()); + + if (!client->supportedUserTokenTypes().contains(QOpcUaUserTokenPolicy::TokenType::Certificate)) + QSKIP(QString("This test is skipped because backend %1 does not support certificate authentication").arg(client->backend()).toLatin1().constData()); + + const QString pkidir = m_pkiData->path(); + QOpcUaPkiConfiguration pkiConfig; + pkiConfig.setClientCertificateLocation(pkidir + "/own/certs/tst_security.der"); + pkiConfig.setPrivateKeyLocation(pkidir + "/own/private/privateKeyWithoutPassword.pem"); + pkiConfig.setTrustListLocation(pkidir + "/trusted/certs"); + pkiConfig.setRevocationListLocation(pkidir + "/trusted/crl"); + pkiConfig.setIssuerListLocation(pkidir + "/issuers/certs"); + pkiConfig.setIssuerRevocationListLocation(pkidir + "/issuers/crl"); + + const auto identity = pkiConfig.applicationIdentity(); + QOpcUaAuthenticationInformation authInfo; + authInfo.setCertificateAuthentication(); + + client->setAuthenticationInformation(authInfo); + client->setIdentity(identity); + client->setPkiConfiguration(pkiConfig); + + qDebug() << "Testing security policy" << endpoint.securityPolicyUri(); + QSignalSpy connectSpy(client.data(), &QOpcUaClient::stateChanged); + + client->connectToEndpoint(endpoint); + connectSpy.wait(); + if (client->state() == QOpcUaClient::Connecting) + connectSpy.wait(); + + QCOMPARE(connectSpy.count(), 2); + QCOMPARE(connectSpy.at(0).at(0), QOpcUaClient::Connecting); + connectSpy.wait(); + QCOMPARE(connectSpy.at(1).at(0), QOpcUaClient::Connected); + + QCOMPARE(client->endpoint(), endpoint); + QCOMPARE(client->error(), QOpcUaClient::NoError); + qDebug() << "connected"; + + connectSpy.clear(); + client->disconnectFromEndpoint(); + connectSpy.wait(); + QCOMPARE(connectSpy.count(), 2); + QCOMPARE(connectSpy.at(0).at(0), QOpcUaClient::Closing); + QCOMPARE(connectSpy.at(1).at(0), QOpcUaClient::Disconnected); +} + +void Tst_QOpcUaSecurity::cleanupTestCase() +{ + if (m_serverProcess.state() == QProcess::Running) { + m_serverProcess.kill(); + m_serverProcess.waitForFinished(2000); + } +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QTEST_SET_MAIN_SOURCE_PATH + + // run tests for all available backends + QStringList availableBackends = QOpcUaProvider::availableBackends(); + if (availableBackends.empty()) { + qDebug("No OPCUA backends found, skipping tests."); + return EXIT_SUCCESS; + } + + Tst_QOpcUaSecurity tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_security.moc" + diff --git a/tests/open62541-testserver/certs.qrc b/tests/open62541-testserver/certs.qrc index b152bf3..07bde08 100644 --- a/tests/open62541-testserver/certs.qrc +++ b/tests/open62541-testserver/certs.qrc @@ -3,5 +3,6 @@ <file>pki/own/private/open62541-testserver.der</file> <file>pki/own/certs/open62541-testserver.der</file> <file>pki/trusted/certs/opcuaviewer.der</file> + <file>pki/trusted/certs/tst_security.der</file> </qresource> </RCC> diff --git a/tests/open62541-testserver/pki/own/certs/open62541-testserver.der b/tests/open62541-testserver/pki/own/certs/open62541-testserver.der Binary files differindex c8bad3d..7aac4cd 100644 --- a/tests/open62541-testserver/pki/own/certs/open62541-testserver.der +++ b/tests/open62541-testserver/pki/own/certs/open62541-testserver.der diff --git a/tests/open62541-testserver/pki/trusted/certs/tst_security.der b/tests/open62541-testserver/pki/trusted/certs/tst_security.der Binary files differnew file mode 100644 index 0000000..f7527ba --- /dev/null +++ b/tests/open62541-testserver/pki/trusted/certs/tst_security.der |