Update client and implement ssl server improvements

This commit is contained in:
Simon Stürz 2018-08-16 17:33:07 +02:00
parent 3625e6e46c
commit d4d34a86c2
21 changed files with 402 additions and 191 deletions

View File

@ -328,6 +328,56 @@ The client allowes you to test the proxy server and create a dummy client for te
-p, --port <port> The proxy server port. Default 1212
# Testing a local server
## Start the server
In order to test a connection and play with the server API, you can install the proxy server on your machine and try to connect to it.
$ sudo apt update
$ sudo apt install nymea-remoteproxy nymea-remoteproxy-client
Once installed, the `nymea-remoteproxy` will start automatically using the default configurations shipped in `/etc/nymea/nymea-remoteproxy.conf`.
Using this file allowes you to configure the server for your test purposes.
The only thing you need is a certificate, which can be loaded from the server. The server does not support insecure connection for now. If you don't have any certificate, you can create one for testing.
> *Note:* you can enter whatever you like for the certificate.
$ cd /tmp/
$ openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -keyout test-proxy-certificate.key -out test-proxy-certificate.crt
Now place the certificate and the key where they belong:
$ sudo cp /tmp/test-proxy-certificate.crt /etc/ssl/certs/
$ sudo cp /tmp/test-proxy-certificate.key /etc/ssl/private/
Change following configuration in the `/etc/nymea/nymea-remoteproxy.conf`:
...
certificate=/etc/ssl/certs/test-proxy-certificate.crt
certificateKey=/etc/ssl/private/test-proxy-certificate.key
...
Now stop the proxy server and start it manually:
$ sudo systemctl stop nymea-remoteproxy.conf
> *Note*: the `-m` starts the proxy with a dummy authenticator, which allowes to use any token, it will always be authenticated and should be used only on localhost running servers.
$ sudo nymea-remoteproxy -c /etc/nymea/nymea-remoteproxy -m
## Connect two clients
Once the server is up and running with the dummy authenticator, you can try to connect to the service using the `nymea-remoteproxy-client` in a different terminal.
> *Note:* assuming you are starting the client on the same system as the server:
$ nymea-remoteproxy-client -a 127.0.0.1 -p 443 -t "dummytoken1"
Open a second terminal and start the same command again.
# License

View File

@ -1,4 +1,5 @@
#include <QUrl>
#include <QHash>
#include <QCoreApplication>
#include <QLoggingCategory>
#include <QCommandLineParser>
@ -6,51 +7,133 @@
#include "proxyclient.h"
static QHash<QString, bool> s_loggingFilters;
static const char *const normal = "\033[0m";
static const char *const warning = "\e[33m";
static const char *const error = "\e[31m";
static void loggingCategoryFilter(QLoggingCategory *category)
{
if (s_loggingFilters.contains(category->categoryName())) {
bool debugEnabled = s_loggingFilters.value(category->categoryName());
category->setEnabled(QtDebugMsg, debugEnabled);
category->setEnabled(QtWarningMsg, debugEnabled || s_loggingFilters.value("Warnings"));
} else {
// Enable default debug output
category->setEnabled(QtDebugMsg, true);
category->setEnabled(QtWarningMsg, s_loggingFilters.value("qml") || s_loggingFilters.value("Warnings"));
}
}
static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
switch (type) {
case QtInfoMsg:
fprintf(stdout, "%s: %s\n", context.category, message.toUtf8().data());
break;
case QtDebugMsg:
fprintf(stdout, "%s: %s\n", context.category, message.toUtf8().data());
break;
case QtWarningMsg:
fprintf(stdout, "%s%s: %s%s\n", warning, context.category, message.toUtf8().data(), normal);
break;
case QtCriticalMsg:
fprintf(stdout, "%s%s: %s%s\n", error, context.category, message.toUtf8().data(), normal);
break;
case QtFatalMsg:
fprintf(stdout, "%s%s: %s%s\n", error, context.category, message.toUtf8().data(), normal);
break;
}
fflush(stdout);
}
int main(int argc, char *argv[])
{
qInstallMessageHandler(consoleLogHandler);
QCoreApplication application(argc, argv);
application.setApplicationName(SERVER_NAME_STRING);
application.setOrganizationName("guh");
application.setOrganizationName("nymea");
application.setApplicationVersion(SERVER_VERSION_STRING);
// Default debug categories
s_loggingFilters.insert("ProxyClient", true);
s_loggingFilters.insert("RemoteProxyClientJsonRpc", false);
s_loggingFilters.insert("RemoteProxyClientWebSocket", false);
s_loggingFilters.insert("RemoteProxyClientConnection", false);
s_loggingFilters.insert("RemoteProxyClientJsonRpcTraffic", false);
s_loggingFilters.insert("RemoteProxyClientConnectionTraffic", false);
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
parser.setApplicationDescription(QString("\nThe nymea remote proxy server client application. This client allowes to test "
parser.setApplicationDescription(QString("\nThe nymea remote proxy client application. This client allowes to test "
"a server application as client perspective.\n\n"
"Server version: %1\n"
"Version: %1\n"
"API version: %2\n\n"
"Copyright %3 2018 Simon Stürz <simon.stuerz@guh.io>\n")
.arg(SERVER_VERSION_STRING)
.arg(API_VERSION_STRING)
.arg(QChar(0xA9)));
QCommandLineOption urlOption(QStringList() << "u" << "url", "The proxy server url. Default wss://dev-remoteproxy.nymea.io:443", "url");
urlOption.setDefaultValue("wss://dev-remoteproxy.nymea.io:443");
parser.addOption(urlOption);
QCommandLineOption tokenOption(QStringList() << "t" << "token", "The AWS token for authentication.", "token");
parser.addOption(tokenOption);
QCommandLineOption addressOption(QStringList() << "a" << "address", "The proxy server host address. Default 127.0.0.1", "address");
addressOption.setDefaultValue("127.0.0.1");
parser.addOption(addressOption);
QCommandLineOption insecureOption(QStringList() << "i" << "igore-ssl", "Ignore SSL certificate errors.");
parser.addOption(insecureOption);
QCommandLineOption portOption(QStringList() << "p" << "port", "The proxy server port. Default 1212", "port");
portOption.setDefaultValue("1212");
parser.addOption(portOption);
QCommandLineOption nameOption(QStringList() << "name", "The name of the client. Default nymea-remoteproxyclient", "name");
nameOption.setDefaultValue("nymea-remoteproxyclient");
parser.addOption(nameOption);
QCommandLineOption uuidOption(QStringList() << "uuid", "The uuid of the client. If not specified, a new one will be created", "uuid");
parser.addOption(uuidOption);
QCommandLineOption verboseOption(QStringList() << "verbose", "Print more information about the connection.");
parser.addOption(verboseOption);
QCommandLineOption veryVerboseOption(QStringList() << "very-verbose", "Print the complete traffic information from the connection.");
parser.addOption(veryVerboseOption);
parser.process(application);
// Enable verbose
if (parser.isSet(verboseOption) || parser.isSet(veryVerboseOption)) {
s_loggingFilters["RemoteProxyClientJsonRpc"] = true;
s_loggingFilters["RemoteProxyClientWebSocket"] = true;
s_loggingFilters["RemoteProxyClientConnection"] = true;
}
// Enable very verbose
if (parser.isSet(veryVerboseOption)) {
s_loggingFilters["RemoteProxyClientJsonRpcTraffic"] = true;
s_loggingFilters["RemoteProxyClientConnectionTraffic"] = true;
}
QLoggingCategory::installFilter(loggingCategoryFilter);
if (!parser.isSet(tokenOption)) {
qWarning() << "Please specify the token for authentication." << endl;
qCCritical(dcProxyClient()) << "Please specify the token for authentication using -t <token> or --token <token>." << endl << endl;
parser.showHelp(-1);
}
QUrl serverUrl(parser.value(urlOption));
if (!serverUrl.isValid()) {
qCCritical(dcProxyClient()) << "Invalid proxy server url passed." << parser.value(urlOption);
exit(-1);
}
ProxyClient client;
client.setHostAddress(QHostAddress(parser.value(addressOption)));
client.setPort(parser.value(portOption).toInt());
client.start(parser.value(tokenOption));
QUuid uuid(parser.value(uuidOption));
if (uuid.isNull()) {
uuid = QUuid::createUuid();
}
ProxyClient client(parser.value(nameOption), uuid);
client.setInsecure(parser.isSet(insecureOption));
client.start(serverUrl, parser.value(tokenOption));
return application.exec();
}

View File

@ -1,51 +1,73 @@
#include "proxyclient.h"
Q_LOGGING_CATEGORY(dcProxyClient, "ProxyClient")
ProxyClient::ProxyClient(QObject *parent) :
QObject(parent)
ProxyClient::ProxyClient(const QString &name, const QUuid &uuid, QObject *parent) :
QObject(parent),
m_name(name),
m_uuid(uuid)
{
m_connection = new RemoteProxyConnection(QUuid::createUuid(), "nymea-remoteproxy-client", this);
m_connection->setInsecureConnection(true);
m_connection = new RemoteProxyConnection(m_uuid, m_name, this);
qCDebug(dcProxyClient()) << "Creating remote proxy connection" << m_name << m_uuid.toString();
connect(m_connection, &RemoteProxyConnection::ready, this, &ProxyClient::onClientReady);
connect(m_connection, &RemoteProxyConnection::authenticated, this, &ProxyClient::onAuthenticationFinished);
connect(m_connection, &RemoteProxyConnection::remoteConnectionEstablished, this, &ProxyClient::onRemoteConnectionEstablished);
connect(m_connection, &RemoteProxyConnection::errorOccured, this, &ProxyClient::onErrorOccured);
connect(m_connection, &RemoteProxyConnection::disconnected, this, &ProxyClient::onClientDisconnected);
connect(m_connection, &RemoteProxyConnection::sslErrors, this, &ProxyClient::onSslErrors);
}
void ProxyClient::setHostAddress(const QHostAddress &hostAddress)
void ProxyClient::setInsecure(bool insecure)
{
m_hostAddress = hostAddress;
}
void ProxyClient::setPort(int port)
{
m_port = port;
m_insecure = insecure;
}
void ProxyClient::onErrorOccured(RemoteProxyConnection::Error error)
{
qDebug() << "Error occured" << error << m_connection->errorString();
qCWarning(dcProxyClient()) << "Error occured" << error << m_connection->errorString();
exit(-1);
}
void ProxyClient::onClientReady()
{
qCDebug(dcProxyClient()) << "Connected to proxy server" << m_connection->serverUrl().toString();
qCDebug(dcProxyClient()) << "Start authentication";
m_connection->authenticate(m_token);
}
void ProxyClient::onAuthenticationFinished()
{
qDebug() << "Authentication finished.";
qCDebug(dcProxyClient()) << "Authentication finished successfully.";
}
void ProxyClient::onRemoteConnectionEstablished()
{
qCDebug(dcProxyClient()) << "----------------------------------------------------------------------------------";
qCDebug(dcProxyClient()) << "Remote connection established with" << m_connection->tunnelPartnerName() << m_connection->tunnelPartnerUuid();
qCDebug(dcProxyClient()) << "----------------------------------------------------------------------------------";
}
void ProxyClient::onClientDisconnected()
{
qDebug() << "Disconnected from" << m_connection;
qCDebug(dcProxyClient()) << "Disconnected from" << m_connection->serverUrl().toString();
exit(1);
}
void ProxyClient::start(const QString &token)
void ProxyClient::onSslErrors(const QList<QSslError> errors)
{
if (m_insecure) {
qCDebug(dcProxyClient()) << "SSL errors occured. Ignoring because explicit specified.";
m_connection->ignoreSslErrors();
} else {
qCWarning(dcProxyClient()) << "SSL errors occured:";
foreach (const QSslError &sslError, errors) {
qCWarning(dcProxyClient()) << " --> " << sslError.errorString();
}
}
}
void ProxyClient::start(const QUrl &url, const QString &token)
{
m_token = token;
m_connection->connectServer(m_hostAddress, static_cast<quint16>(m_port));
m_connection->connectServer(url);
}

View File

@ -2,38 +2,40 @@
#define PROXYCLIENT_H
#include <QObject>
#include <QLoggingCategory>
#include "remoteproxyconnection.h"
using namespace remoteproxyclient;
Q_DECLARE_LOGGING_CATEGORY(dcProxyClient)
class ProxyClient : public QObject
{
Q_OBJECT
public:
explicit ProxyClient(QObject *parent = nullptr);
explicit ProxyClient(const QString &name, const QUuid &uuid, QObject *parent = nullptr);
void setHostAddress(const QHostAddress &hostAddress);
void setPort(int port);
void setInsecure(bool insecure);
private:
QString m_name;
QUuid m_uuid;
QString m_token;
QHostAddress m_hostAddress = QHostAddress::LocalHost;
int m_port = 1212;
bool m_insecure = false;
RemoteProxyConnection *m_connection = nullptr;
signals:
private slots:
void onErrorOccured(RemoteProxyConnection::Error error);
void onClientReady();
void onAuthenticationFinished();
void onRemoteConnectionEstablished();
void onClientDisconnected();
void onSslErrors(const QList<QSslError> errors);
public slots:
void start(const QString &token);
void start(const QUrl &url, const QString &token);
};

View File

@ -166,7 +166,8 @@ void JsonRpcServer::asyncReplyFinished()
if (!reply->timedOut()) {
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first
,"validating return value", formatAssertion(reply->handler()->name(), reply->method(), reply->handler(), reply->data()).toLatin1().data());
,"validating return value", formatAssertion(reply->handler()->name(),
reply->method(), reply->handler(), reply->data()).toLatin1().data());
sendResponse(proxyClient, reply->commandId(), reply->data());
if (!reply->success()) {
// Disconnect this client since the request was not successfully

View File

@ -34,6 +34,7 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName)
setLogFileName(settings.value("logFile", "/var/log/nymea-remoteproxy.log").toString());
setSslCertificateFileName(settings.value("certificate", "/etc/ssl/certs/ssl-cert-snakeoil.pem").toString());
setSslCertificateKeyFileName(settings.value("certificateKey", "/etc/ssl/private/ssl-cert-snakeoil.key").toString());
setSslCertificateChainFileName(settings.value("certificateChain", "").toString());
settings.beginGroup("WebSocketServer");
setWebSocketServerHost(QHostAddress(settings.value("host", "127.0.0.1").toString()));
@ -45,35 +46,44 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName)
setTcpServerPort(static_cast<quint16>(settings.value("port", 1213).toInt()));
settings.endGroup();
// Load ssl configuration
// Load SSL configuration
QSslConfiguration sslConfiguration;
// Load certificate
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater);
// SSL certificate
QFile certFile(sslCertificateFileName());
if (!certFile.open(QIODevice::ReadOnly)) {
qCWarning(dcApplication()) << "Could not open certificate file" << sslCertificateFileName() << certFile.errorString();
return false;
}
QSslCertificate certificate(&certFile, QSsl::Pem);
qCDebug(dcApplication()) << "Loaded successfully certificate" << sslCertificateFileName();
certFile.close();
// Create SSL configuration
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.setLocalCertificate(certificate);
sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater);
// SSL key
// SSL certificate key
QFile certKeyFile(sslCertificateKeyFileName());
if (!certKeyFile.open(QIODevice::ReadOnly)) {
qCWarning(dcApplication()) << "Could not open certificate key file:" << sslCertificateKeyFileName() << certKeyFile.errorString();
return false;
}
QSslKey sslKey(&certKeyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
qCDebug(dcApplication()) << "Loaded successfully certificate key" << sslCertificateKeyFileName();
certKeyFile.close();
sslConfiguration.setPrivateKey(sslKey);
// SSL certificate chain
if (!sslCertificateChainFileName().isEmpty()) {
QFile certChainFile(sslCertificateChainFileName());
if (!certChainFile.open(QIODevice::ReadOnly)) {
qCWarning(dcApplication()) << "Could not open certificate chain file:" << sslCertificateChainFileName() << certChainFile.errorString();
return false;
}
QSslCertificate certificate(&certKeyFile, QSsl::Pem);
sslConfiguration.setLocalCertificateChain( { certificate } );
}
m_sslConfiguration = sslConfiguration;
return true;
@ -129,6 +139,16 @@ void ProxyConfiguration::setSslCertificateKeyFileName(const QString &fileName)
m_sslCertificateKeyFileName = fileName;
}
QString ProxyConfiguration::sslCertificateChainFileName() const
{
return m_sslCertificateChainFileName;
}
void ProxyConfiguration::setSslCertificateChainFileName(const QString &fileName)
{
m_sslCertificateChainFileName = fileName;
}
QSslConfiguration ProxyConfiguration::sslConfiguration() const
{
return m_sslConfiguration;
@ -183,6 +203,7 @@ QDebug operator<<(QDebug debug, ProxyConfiguration *configuration)
debug.nospace() << " - logfile:" << configuration->logFileName() << endl;
debug.nospace() << " - certificate:" << configuration->sslCertificateFileName() << endl;
debug.nospace() << " - certificate key:" << configuration->sslCertificateKeyFileName() << endl;
debug.nospace() << " - certificate chain:" << configuration->sslCertificateChainFileName() << endl;
debug.nospace() << " - SSL certificate information:";
debug.nospace() << " Common name:" << configuration->sslConfiguration().localCertificate().issuerInfo(QSslCertificate::CommonName) << endl;
debug.nospace() << " Organisation:" << configuration->sslConfiguration().localCertificate().issuerInfo(QSslCertificate::Organization) << endl;

View File

@ -32,6 +32,9 @@ public:
QString sslCertificateKeyFileName() const;
void setSslCertificateKeyFileName(const QString &fileName);
QString sslCertificateChainFileName() const;
void setSslCertificateChainFileName(const QString &fileName);
QSslConfiguration sslConfiguration() const;
// WebSocketServer
@ -55,6 +58,7 @@ private:
QString m_logFileName = "/var/log/nymea-remoteproxy.log";
QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem";
QString m_sslCertificateKeyFileName = "/etc/ssl/private/ssl-cert-snakeoil.key";
QString m_sslCertificateChainFileName;
QSslConfiguration m_sslConfiguration;
// WebSocketServer

View File

@ -35,10 +35,10 @@ QVariantMap JsonReply::params() const
QVariantMap JsonReply::requestMap()
{
QVariantMap request;
request.insert("id", m_commandId);
request.insert("method", m_nameSpace + "." + m_method);
request.insert("id", commandId());
request.insert("method", nameSpace() + "." + method());
if (!m_params.isEmpty())
request.insert("params", m_params);
request.insert("params", params());
m_commandId++;
return request;

View File

@ -7,19 +7,23 @@ ProxyConnection::ProxyConnection(QObject *parent) : QObject(parent)
}
bool ProxyConnection::connected()
{
return m_connected;
}
void ProxyConnection::setConnected(bool connected)
{
if (m_connected == connected)
return;
m_connected = connected;
emit connectedChanged(m_connected);
}
ProxyConnection::~ProxyConnection()
{
}
bool ProxyConnection::allowSslErrors() const
{
return m_allowSslErrors;
}
void ProxyConnection::setAllowSslErrors(bool allowSslErrors)
{
m_allowSslErrors = allowSslErrors;
}
}

View File

@ -2,6 +2,7 @@
#define SOCKETCONNECTOR_H
#include <QObject>
#include <QSslError>
#include <QHostAddress>
namespace remoteproxyclient {
@ -14,24 +15,28 @@ public:
virtual ~ProxyConnection() = 0;
virtual void sendData(const QByteArray &data) = 0;
virtual bool isConnected() = 0;
virtual QUrl serverUrl() const = 0;
bool allowSslErrors() const;
void setAllowSslErrors(bool allowSslErrors);
virtual void ignoreSslErrors() = 0;
virtual void ignoreSslErrors(const QList<QSslError> &errors) = 0;
bool connected();
private:
bool m_allowSslErrors = false;
bool m_connected = false;
protected:
void setConnected(bool connected);
signals:
void connectedChanged(bool connected);
void dataReceived(const QByteArray &data);
void errorOccured();
void sslErrorOccured();
void sslErrors(const QList<QSslError> &errors);
public slots:
virtual void connectServer(const QHostAddress &address, quint16 port) = 0;
virtual void connectServer(const QUrl &serverUrl) = 0;
virtual void disconnectServer() = 0;
};

View File

@ -32,7 +32,7 @@ JsonReply *JsonRpcClient::callAuthenticate(const QUuid &clientUuid, const QStrin
params.insert("token", token);
JsonReply *reply = new JsonReply(m_commandId, "Authentication", "Authenticate", params, this);
qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method()) << reply->params();
qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
sendRequest(reply->requestMap());
m_replies.insert(m_commandId, reply);
return reply;
@ -66,6 +66,7 @@ void JsonRpcClient::processData(const QByteArray &data)
if (dataMap.value("status").toString() == "error") {
qCWarning(dcRemoteProxyClientJsonRpc()) << "Api error happend" << dataMap.value("error").toString();
// FIMXME: handle json layer errors
}
reply->setResponse(dataMap);

View File

@ -38,6 +38,9 @@ QString RemoteProxyConnection::errorString() const
case ErrorNoError:
errorString = "";
break;
case ErrorHostNotFound:
errorString = "The proxy host url could not be resolved.";
break;
case ErrorSocketError:
errorString = "Socket connection error occured.";
break;
@ -55,6 +58,16 @@ QString RemoteProxyConnection::errorString() const
return errorString;
}
void RemoteProxyConnection::ignoreSslErrors()
{
m_connection->ignoreSslErrors();
}
void RemoteProxyConnection::ignoreSslErrors(const QList<QSslError> &errors)
{
m_connection->ignoreSslErrors(errors);
}
bool RemoteProxyConnection::isConnected() const
{
return m_state == StateConnected
@ -67,8 +80,7 @@ bool RemoteProxyConnection::isConnected() const
bool RemoteProxyConnection::isAuthenticated() const
{
return m_state == StateWaitTunnel
|| m_state == StateRemoteConnected;
return m_state == StateWaitTunnel || m_state == StateRemoteConnected;
}
bool RemoteProxyConnection::isRemoteConnected() const
@ -81,19 +93,9 @@ RemoteProxyConnection::ConnectionType RemoteProxyConnection::connectionType() co
return m_connectionType;
}
void RemoteProxyConnection::setConnectionType(RemoteProxyConnection::ConnectionType connectionType)
QUrl RemoteProxyConnection::serverUrl() const
{
m_connectionType = connectionType;
}
QHostAddress RemoteProxyConnection::serverAddress() const
{
return m_serverAddress;
}
quint16 RemoteProxyConnection::serverPort() const
{
return m_serverPort;
return m_serverUrl;
}
QString RemoteProxyConnection::serverName() const
@ -116,14 +118,14 @@ QString RemoteProxyConnection::proxyServerApiVersion() const
return m_proxyServerApiVersion;
}
bool RemoteProxyConnection::insecureConnection() const
QString RemoteProxyConnection::tunnelPartnerName() const
{
return m_insecureConnection;
return m_tunnelPartnerName;
}
void RemoteProxyConnection::setInsecureConnection(bool insecureConnection)
QString RemoteProxyConnection::tunnelPartnerUuid() const
{
m_insecureConnection = insecureConnection;
return m_tunnelPartnerUuid;
}
bool RemoteProxyConnection::sendData(const QByteArray &data)
@ -170,6 +172,24 @@ void RemoteProxyConnection::setState(RemoteProxyConnection::State state)
m_state = state;
qCDebug(dcRemoteProxyClientConnection()) << "State changed" << m_state;
emit stateChanged(m_state);
switch (m_state) {
case StateConnected:
emit connected();
break;
case StateDisconnected:
emit disconnected();
break;
case StateReady:
emit ready();
break;
case StateRemoteConnected:
emit remoteConnectionEstablished();
break;
default:
break;
}
}
void RemoteProxyConnection::setError(RemoteProxyConnection::Error error)
@ -185,16 +205,14 @@ void RemoteProxyConnection::setError(RemoteProxyConnection::Error error)
void RemoteProxyConnection::onConnectionChanged(bool isConnected)
{
if (isConnected) {
qCDebug(dcRemoteProxyClientConnection()) << "Connected from proxy server.";
qCDebug(dcRemoteProxyClientConnection()) << "Connected to proxy server.";
setState(StateConnected);
emit connected();
setState(StateInitializing);
JsonReply *reply = m_jsonClient->callHello();
connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onHelloFinished);
} else {
qCDebug(dcRemoteProxyClientConnection()) << "Disconnected from proxy server.";
emit disconnected();
setState(StateDisconnected);
cleanUp();
}
@ -226,11 +244,6 @@ void RemoteProxyConnection::onConnectionSocketError()
setError(ErrorSocketError);
}
void RemoteProxyConnection::onConnectionSslError()
{
setError(ErrorSslError);
}
void RemoteProxyConnection::onHelloFinished()
{
JsonReply *reply = static_cast<JsonReply *>(sender());
@ -255,7 +268,6 @@ void RemoteProxyConnection::onHelloFinished()
m_proxyServerApiVersion = responseParams.value("apiVersion").toString();
setState(StateReady);
emit ready();
}
void RemoteProxyConnection::onAuthenticateFinished()
@ -280,15 +292,23 @@ void RemoteProxyConnection::onAuthenticateFinished()
void RemoteProxyConnection::onTunnelEstablished(const QString &clientName, const QString &clientUuid)
{
qCDebug(dcRemoteProxyClientConnection()) << "####### Remote connection established with" << clientName << clientUuid;
qCDebug(dcRemoteProxyClientConnection()) << "Remote connection established successfully with" << clientName << clientUuid;
m_tunnelPartnerName = clientName;
m_tunnelPartnerUuid = clientUuid;
setState(StateRemoteConnected);
emit remoteConnectionEstablished();
}
bool RemoteProxyConnection::connectServer(const QHostAddress &serverAddress, quint16 port)
{
m_serverAddress = serverAddress;
m_serverPort = port;
bool RemoteProxyConnection::connectServer(const QUrl &url)
{
// Verify url
// FIXME: support also tcp
if (url.scheme() != "wss") {
qCWarning(dcRemoteProxyClientConnection()) << "Unsupported connection type" << url.scheme() << "Default to wss";
m_serverUrl.setScheme("wss");
}
m_serverUrl = url;
m_connectionType = ConnectionTypeWebSocket;
m_error = ErrorNoError;
cleanUp();
@ -296,22 +316,23 @@ bool RemoteProxyConnection::connectServer(const QHostAddress &serverAddress, qui
switch (m_connectionType) {
case ConnectionTypeWebSocket:
m_connection = qobject_cast<ProxyConnection *>(new WebSocketConnection(this));
break;
case ConnectionTypeTcpSocket:
// FIXME:
m_connection = qobject_cast<ProxyConnection *>(new WebSocketConnection(this));
break;
}
m_connection->setAllowSslErrors(m_insecureConnection);
connect(m_connection, &ProxyConnection::connectedChanged, this, &RemoteProxyConnection::onConnectionChanged);
connect(m_connection, &ProxyConnection::dataReceived, this, &RemoteProxyConnection::onConnectionDataAvailable);
connect(m_connection, &ProxyConnection::errorOccured, this, &RemoteProxyConnection::onConnectionSocketError);
connect(m_connection, &ProxyConnection::sslErrorOccured, this, &RemoteProxyConnection::onConnectionSslError);
connect(m_connection, &ProxyConnection::sslErrors, this, &RemoteProxyConnection::sslErrors);
m_jsonClient = new JsonRpcClient(m_connection, this);
connect(m_jsonClient, &JsonRpcClient::tunnelEstablished, this, &RemoteProxyConnection::onTunnelEstablished);
qCDebug(dcRemoteProxyClientConnection()) << "Connecting to" << QString("%1:%2").arg(serverAddress.toString()).arg(port);
m_connection->connectServer(serverAddress, port);
setState(StateConnecting);
qCDebug(dcRemoteProxyClientConnection()) << "Connecting to" << m_serverUrl.toString();
m_connection->connectServer(m_serverUrl);
return true;
}

View File

@ -4,6 +4,7 @@
#include <QUuid>
#include <QDebug>
#include <QObject>
#include <QHostInfo>
#include <QWebSocket>
#include <QHostAddress>
#include <QLoggingCategory>
@ -21,11 +22,13 @@ class RemoteProxyConnection : public QObject
Q_OBJECT
public:
enum ConnectionType {
ConnectionTypeWebSocket
ConnectionTypeWebSocket,
ConnectionTypeTcpSocket
};
Q_ENUM(ConnectionType)
enum State {
StateHostLookup,
StateConnecting,
StateConnected,
StateInitializing,
@ -39,6 +42,7 @@ public:
enum Error {
ErrorNoError,
ErrorHostNotFound,
ErrorSocketError,
ErrorSslError,
ErrorProxyNotResponding,
@ -53,23 +57,24 @@ public:
RemoteProxyConnection::Error error() const;
QString errorString() const;
void ignoreSslErrors();
void ignoreSslErrors(const QList<QSslError> &errors);
bool isConnected() const;
bool isAuthenticated() const;
bool isRemoteConnected() const;
RemoteProxyConnection::ConnectionType connectionType() const;
void setConnectionType(RemoteProxyConnection::ConnectionType connectionType);
QHostAddress serverAddress() const;
quint16 serverPort() const;
QUrl serverUrl() const;
QString serverName() const;
QString proxyServerName() const;
QString proxyServerVersion() const;
QString proxyServerApiVersion() const;
bool insecureConnection() const;
void setInsecureConnection(bool insecureConnection);
QString tunnelPartnerName() const;
QString tunnelPartnerUuid() const;
bool sendData(const QByteArray &data);
@ -78,8 +83,7 @@ private:
QUuid m_clientUuid;
QString m_clientName;
QHostAddress m_serverAddress;
quint16 m_serverPort = 443;
QUrl m_serverUrl;
State m_state = StateDisconnected;
Error m_error = ErrorNoError;
@ -96,6 +100,10 @@ private:
QString m_proxyServerVersion;
QString m_proxyServerApiVersion;
// Tunnel
QString m_tunnelPartnerName;
QString m_tunnelPartnerUuid;
void cleanUp();
void setState(State state);
@ -110,6 +118,7 @@ signals:
void stateChanged(RemoteProxyConnection::State state);
void errorOccured(RemoteProxyConnection::Error error);
void sslErrors(const QList<QSslError> &errors);
void dataReady(const QByteArray &data);
@ -117,14 +126,13 @@ private slots:
void onConnectionChanged(bool isConnected);
void onConnectionDataAvailable(const QByteArray &data);
void onConnectionSocketError();
void onConnectionSslError();
void onHelloFinished();
void onAuthenticateFinished();
void onTunnelEstablished(const QString &clientName, const QString &clientUuid);
public slots:
bool connectServer(const QHostAddress &serverAddress, quint16 port);
bool connectServer(const QUrl &url);
bool authenticate(const QString &token);
void disconnectServer();

View File

@ -9,14 +9,13 @@ WebSocketConnection::WebSocketConnection(QObject *parent) :
{
m_webSocket = new QWebSocket("libnymea-remoteproxyclient", QWebSocketProtocol::Version13, this);
connect(m_webSocket, &QWebSocket::connected, this, &WebSocketConnection::onConnected);
connect(m_webSocket, &QWebSocket::disconnected, this, &WebSocketConnection::onDisconnected);
connect(m_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketConnection::onTextMessageReceived);
connect(m_webSocket, &QWebSocket::binaryMessageReceived, this, &WebSocketConnection::onBinaryMessageReceived);
connect(m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
connect(m_webSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(onSslError(QList<QSslError>)));
connect(m_webSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
connect(m_webSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SIGNAL(sslErrors(QList<QSslError>)));
}
WebSocketConnection::~WebSocketConnection()
@ -34,15 +33,14 @@ void WebSocketConnection::sendData(const QByteArray &data)
m_webSocket->sendTextMessage(QString::fromUtf8(data));
}
bool WebSocketConnection::isConnected()
void WebSocketConnection::ignoreSslErrors()
{
return m_webSocket->state() == QAbstractSocket::ConnectedState;
m_webSocket->ignoreSslErrors();
}
void WebSocketConnection::onConnected()
void WebSocketConnection::ignoreSslErrors(const QList<QSslError> &errors)
{
qCDebug(dcRemoteProxyClientWebSocket()) << "Connected with" << m_webSocket->requestUrl().toString();
emit connectedChanged(true);
m_webSocket->ignoreSslErrors(errors);
}
void WebSocketConnection::onDisconnected()
@ -57,23 +55,19 @@ void WebSocketConnection::onError(QAbstractSocket::SocketError error)
emit errorOccured();
}
void WebSocketConnection::onSslError(const QList<QSslError> &errors)
{
if (allowSslErrors()) {
qCDebug(dcRemoteProxyClientWebSocket()) << "Ignore ssl errors because the developer explicitly allowed to use an insecure connection.";
m_webSocket->ignoreSslErrors();
} else {
qCWarning(dcRemoteProxyClientWebSocket()) << "SSL errors occured:";
foreach (const QSslError sslError, errors) {
qCWarning(dcRemoteProxyClientWebSocket()) << " -->" << static_cast<int>(sslError.error()) << sslError.errorString();
}
emit sslErrorOccured();
}
}
void WebSocketConnection::onStateChanged(QAbstractSocket::SocketState state)
{
qCDebug(dcRemoteProxyClientWebSocket()) << "Socket state changed" << state;
switch (state) {
case QAbstractSocket::ConnectedState:
qCDebug(dcRemoteProxyClientWebSocket()) << "Connected with" << m_webSocket->requestUrl().toString();
setConnected(true);
break;
default:
setConnected(false);
break;
}
}
void WebSocketConnection::onTextMessageReceived(const QString &message)
@ -86,28 +80,19 @@ void WebSocketConnection::onBinaryMessageReceived(const QByteArray &message)
Q_UNUSED(message)
}
void WebSocketConnection::connectServer(const QHostAddress &address, quint16 port)
void WebSocketConnection::connectServer(const QUrl &serverUrl)
{
if (isConnected()) {
if (connected()) {
m_webSocket->close();
}
QUrl url;
url.setScheme("wss");
url.setHost(address.toString());
url.setPort(port);
m_serverUrl = url;
qCDebug(dcRemoteProxyClientWebSocket()) << "Connecting to" << serverUrl().toString();
m_serverUrl = serverUrl;
qCDebug(dcRemoteProxyClientWebSocket()) << "Connecting to" << m_serverUrl.toString();
m_webSocket->open(m_serverUrl);
}
void WebSocketConnection::disconnectServer()
{
if (!isConnected())
return;
qCDebug(dcRemoteProxyClientWebSocket()) << "Disconnecting from" << serverUrl().toString();
m_webSocket->close();
}

View File

@ -22,23 +22,23 @@ public:
QUrl serverUrl() const override;
void sendData(const QByteArray &data) override;
bool isConnected() override;
void ignoreSslErrors() override;
void ignoreSslErrors(const QList<QSslError> &errors) override;
private:
QUrl m_serverUrl;
QWebSocket *m_webSocket = nullptr;
private slots:
void onConnected();
void onDisconnected();
void onError(QAbstractSocket::SocketError error);
void onSslError(const QList<QSslError> &errors);
void onStateChanged(QAbstractSocket::SocketState state);
void onTextMessageReceived(const QString &message);
void onBinaryMessageReceived(const QByteArray &message);
public slots:
void connectServer(const QHostAddress &address, quint16 port) override;
void connectServer(const QUrl &serverUrl) override;
void disconnectServer() override;
};

View File

@ -3,11 +3,12 @@ writeLogs=false
logFile=/var/log/nymea-remoteproxy.log
certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem
certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key
certificateChain=
[WebSocketServer]
host=0.0.0.0
host=127.0.0.1
port=443
[TcpServer]
host=0.0.0.0
host=127.0.0.1
port=80

View File

@ -114,7 +114,7 @@ int main(int argc, char *argv[])
parser.addVersionOption();
parser.setApplicationDescription(QString("\nThe nymea remote proxy server. This server allowes nymea-cloud users and "
"registered nymea deamons to establish a tunnel connection.\n\n"
"Server version: %1\n"
"Version: %1\n"
"API version: %2\n\n"
"Copyright %3 2018 Simon Stürz <simon.stuerz@guh.io>\n")
.arg(SERVER_VERSION_STRING)
@ -202,7 +202,6 @@ int main(int argc, char *argv[])
qCDebug(dcApplication()) << "==========================================================";
qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion();
qCDebug(dcApplication()) << "==========================================================";
if (parser.isSet(developmentOption)) {
qCWarning(dcApplication()) << "##########################################################";
qCWarning(dcApplication()) << "# DEVELOPMENT MODE #";
@ -212,6 +211,8 @@ int main(int argc, char *argv[])
if (s_loggingEnabled)
qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName();
qCDebug(dcApplication()) << "Using ssl version:" << QSslSocket::sslLibraryVersionString();
Authenticator *authenticator = nullptr;
if (parser.isSet(mockAuthenticatorOption)) {
authenticator = qobject_cast<Authenticator *>(new DummyAuthenticator(nullptr));

View File

@ -57,7 +57,7 @@ void RemoteProxyOfflineTests::webserverConnectionBlocked()
{
// Create a dummy server which blocks the port
QWebSocketServer dummyServer("dummy-server", QWebSocketServer::NonSecureMode);
dummyServer.listen(QHostAddress::LocalHost, m_port);
dummyServer.listen(QHostAddress::LocalHost, 1212);
// Start proxy webserver
QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged);
@ -208,21 +208,19 @@ void RemoteProxyOfflineTests::clientConnection()
// Connect to the server (insecure disabled)
RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this);
connection->setInsecureConnection(true);
connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
// Connect to server (insecue enabled for testing)
QSignalSpy readySpy(connection, &RemoteProxyConnection::ready);
QVERIFY(connection->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connection->connectServer(m_serverUrl));
readySpy.wait();
QVERIFY(readySpy.count() == 1);
QVERIFY(connection->isConnected());
QVERIFY(!connection->isRemoteConnected());
QVERIFY(connection->state() == RemoteProxyConnection::StateReady);
QVERIFY(connection->error() == RemoteProxyConnection::ErrorNoError);
QVERIFY(connection->serverAddress() == QHostAddress::LocalHost);
QVERIFY(connection->serverPort() == m_port);
QVERIFY(connection->serverUrl() == m_serverUrl);
QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeWebSocket);
QVERIFY(connection->insecureConnection() == true);
QVERIFY(connection->serverName() == SERVER_NAME_STRING);
QVERIFY(connection->proxyServerName() == Engine::instance()->serverName());
QVERIFY(connection->proxyServerVersion() == SERVER_VERSION_STRING);
@ -241,6 +239,7 @@ void RemoteProxyOfflineTests::clientConnection()
connection->disconnectServer();
// FIXME: check why it waits the full time here
spyDisconnected.wait(100);
QVERIFY(spyDisconnected.count() == 1);
QVERIFY(!connection->isConnected());
@ -259,21 +258,21 @@ void RemoteProxyOfflineTests::remoteConnection()
// Create two connection
RemoteProxyConnection *connectionOne = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this);
connectionOne->setInsecureConnection(true);
connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(QUuid::createUuid(), "Test client two", this);
connectionTwo->setInsecureConnection(true);
connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
// Connect one
QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready);
QVERIFY(connectionOne->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connectionOne->connectServer(m_serverUrl));
connectionOneReadySpy.wait();
QVERIFY(connectionOneReadySpy.count() == 1);
QVERIFY(connectionOne->isConnected());
// Connect two
QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready);
QVERIFY(connectionTwo->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connectionTwo->connectServer(m_serverUrl));
connectionTwoReadySpy.wait();
QVERIFY(connectionTwoReadySpy.count() == 1);
QVERIFY(connectionTwo->isConnected());
@ -345,31 +344,31 @@ void RemoteProxyOfflineTests::trippleConnection()
// Create two connection
RemoteProxyConnection *connectionOne = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this);
connectionOne->setInsecureConnection(true);
connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(QUuid::createUuid(), "Test client two", this);
connectionTwo->setInsecureConnection(true);
connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
RemoteProxyConnection *connectionThree = new RemoteProxyConnection(QUuid::createUuid(), "Token thief ^w^", this);
connectionThree->setInsecureConnection(true);
connect(connectionThree, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
// Connect one
QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready);
QVERIFY(connectionOne->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connectionOne->connectServer(m_serverUrl));
connectionOneReadySpy.wait();
QVERIFY(connectionOneReadySpy.count() == 1);
QVERIFY(connectionOne->isConnected());
// Connect two
QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready);
QVERIFY(connectionTwo->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connectionTwo->connectServer(m_serverUrl));
connectionTwoReadySpy.wait();
QVERIFY(connectionTwoReadySpy.count() == 1);
QVERIFY(connectionTwo->isConnected());
// Connect two
QSignalSpy connectionThreeReadySpy(connectionThree, &RemoteProxyConnection::ready);
QVERIFY(connectionThree->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connectionThree->connectServer(m_serverUrl));
connectionThreeReadySpy.wait();
QVERIFY(connectionThreeReadySpy.count() == 1);
QVERIFY(connectionThree->isConnected());
@ -422,16 +421,16 @@ void RemoteProxyOfflineTests::sslConfigurations()
// Connect to the server (insecure disabled)
RemoteProxyConnection *connector = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this);
QSignalSpy spyError(connector, &RemoteProxyConnection::errorOccured);
QVERIFY(connector->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connector->connectServer(m_serverUrl));
spyError.wait();
QVERIFY(spyError.count() == 2);
QVERIFY(spyError.count() == 1);
QVERIFY(connector->error() == RemoteProxyConnection::ErrorSocketError);
QVERIFY(connector->state() == RemoteProxyConnection::StateDisconnected);
// Connect to server (insecue enabled)
QSignalSpy spyConnected(connector, &RemoteProxyConnection::connected);
connector->setInsecureConnection(true);
connector->connectServer(QHostAddress::LocalHost, m_port);
connect(connector, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
connector->connectServer(m_serverUrl);
spyConnected.wait();
QVERIFY(connector->isConnected());

View File

@ -22,21 +22,19 @@ void RemoteProxyOnlineTests::awsStaticCredentials()
// Connect to the server (insecure disabled)
RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this);
connection->setInsecureConnection(true);
connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
// Connect to server (insecue enabled for testing)
QSignalSpy readySpy(connection, &RemoteProxyConnection::ready);
QVERIFY(connection->connectServer(QHostAddress::LocalHost, m_port));
QVERIFY(connection->connectServer(m_serverUrl));
readySpy.wait();
QVERIFY(readySpy.count() == 1);
QVERIFY(connection->isConnected());
QVERIFY(!connection->isRemoteConnected());
QVERIFY(connection->state() == RemoteProxyConnection::StateReady);
QVERIFY(connection->error() == RemoteProxyConnection::ErrorNoError);
QVERIFY(connection->serverAddress() == QHostAddress::LocalHost);
QVERIFY(connection->serverPort() == m_port);
QVERIFY(connection->serverUrl() == m_serverUrl);
QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeWebSocket);
QVERIFY(connection->insecureConnection() == true);
QVERIFY(connection->serverName() == SERVER_NAME_STRING);
QVERIFY(connection->proxyServerName() == Engine::instance()->serverName());
QVERIFY(connection->proxyServerVersion() == SERVER_VERSION_STRING);

View File

@ -146,6 +146,7 @@ QVariant BaseTest::injectSocketData(const QByteArray &data)
{
QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13);
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
QSignalSpy spyConnection(socket, SIGNAL(connected()));
socket->open(Engine::instance()->webSocketServer()->serverUrl());
spyConnection.wait();

View File

@ -29,8 +29,7 @@ public:
protected:
ProxyConfiguration *m_configuration = nullptr;
quint16 m_port = 1212;
QHostAddress m_serverAddress = QHostAddress::LocalHost;
QUrl m_serverUrl = QUrl("wss://127.0.0.1:1212");
QSslConfiguration m_sslConfiguration;
@ -65,6 +64,11 @@ public slots:
socket->ignoreSslErrors();
}
void ignoreConnectionSslError(const QList<QSslError> &) {
RemoteProxyConnection *connection = static_cast<RemoteProxyConnection *>(sender());
connection->ignoreSslErrors();
}
inline void verifyError(const QVariant &response, const QString &fieldName, const QString &error)
{
QJsonDocument jsonDoc = QJsonDocument::fromVariant(response);