Merge PR #368: Improve certificate pinning mechanism

This commit is contained in:
Jenkins nymea 2020-04-19 15:24:05 +02:00
commit 6f3e4fae32
34 changed files with 465 additions and 355 deletions

View File

@ -69,32 +69,6 @@ NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
updateActiveBearers();
}
void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &pem)
{
storePem(url, pem);
if (m_currentHost) {
connectInternal(m_currentHost);
}
}
bool NymeaConnection::isTrusted(const QString &url)
{
// Do we have a legacy fingerprint
QSettings settings;
settings.beginGroup("acceptedCertificates");
if (settings.contains(QUrl(url).host())) {
return true;
}
// Do we have a PEM file?
QByteArray pem;
if (loadPem(url, pem)) {
return true;
}
return false;
}
NymeaConnection::BearerTypes NymeaConnection::availableBearerTypes() const
{
return m_availableBearerTypes;
@ -161,7 +135,6 @@ Connection *NymeaConnection::currentConnection() const
if (!m_currentHost || !m_currentTransport) {
return nullptr;
}
qDebug() << "secure:" << m_transportCandidates.value(m_currentTransport)->secure();
return m_transportCandidates.value(m_currentTransport);
}
@ -187,63 +160,8 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
qDebug() << "Ignoring host mismatch on certificate.";
ignoredErrors.append(error);
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
// Check our cert DB
QByteArray pem;
// Keep this for compatibility with old versions for a bit...
// New code will look up the PEM instead and set it before the connection attempt
// However, we want to emit verifyConnectionCertificate in any case here.
QSettings settings;
settings.beginGroup("acceptedCertificates");
QByteArray storedFingerPrint = settings.value(transport->url().host()).toByteArray();
settings.endGroup();
QByteArray certificateFingerprint;
QByteArray digest = error.certificate().digest(QCryptographicHash::Sha256);
for (int i = 0; i < digest.length(); i++) {
if (certificateFingerprint.length() > 0) {
certificateFingerprint.append(":");
}
certificateFingerprint.append(digest.mid(i,1).toHex().toUpper());
}
// Ignore self signed certs for connections to localhost
if (QHostAddress(transport->url().host()) == QHostAddress::LocalHost || QHostAddress(transport->url().host()) == QHostAddress::LocalHostIPv6) {
ignoredErrors.append(error);
// Check old style fingerprint storage
} else if (storedFingerPrint == certificateFingerprint) {
qDebug() << "This fingerprint is known to us.";
ignoredErrors.append(error);
// Update the config to use the new system:
storePem(transport->url(), error.certificate().toPem());
// Check new style PEM storage
} else if (loadPem(transport->url(), pem) && pem == error.certificate().toPem()) {
qDebug() << "Found a SSL certificate for this host. Ignoring error.";
ignoredErrors.append(error);
// Ok... nothing found... Pop up the message
} else {
qDebug() << "Host presents an unknown self signed certificate:" << error.certificate();
qDebug() << "Asking user for confirmation.";
QStringList info;
info << tr("Common Name:") << error.certificate().issuerInfo(QSslCertificate::CommonName);
info << tr("Oragnisation:") <<error.certificate().issuerInfo(QSslCertificate::Organization);
info << tr("Locality:") << error.certificate().issuerInfo(QSslCertificate::LocalityName);
info << tr("Oragnisational Unit:")<< error.certificate().issuerInfo(QSslCertificate::OrganizationalUnitName);
info << tr("Country:")<< error.certificate().issuerInfo(QSslCertificate::CountryName);
// info << tr("State:")<< error.certificate().issuerInfo(QSslCertificate::StateOrProvinceName);
// info << tr("Name Qualifier:")<< error.certificate().issuerInfo(QSslCertificate::DistinguishedNameQualifier);
// info << tr("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress);
m_connectionStatus = ConnectionStatusSslUntrusted;
emit connectionStatusChanged();
emit verifyConnectionCertificate(transport->url().toString(), info, certificateFingerprint, error.certificate().toPem());
}
qDebug() << "Ignoring self signed certificate.";
ignoredErrors.append(error);
} else {
// Reject the connection on all other errors...
qDebug() << "SSL Error:" << error.errorString() << error.certificate();
@ -475,35 +393,6 @@ void NymeaConnection::updateActiveBearers()
}
}
bool NymeaConnection::storePem(const QUrl &host, const QByteArray &pem)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
if (!dir.exists()) {
dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
}
QFile certFile(dir.absoluteFilePath(host.host() + ".pem"));
if (!certFile.open(QFile::WriteOnly)) {
return false;
}
certFile.write(pem);
certFile.close();
return true;
}
bool NymeaConnection::loadPem(const QUrl &host, QByteArray &pem)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
// qDebug() << "Loading certificates from:" << dir.absoluteFilePath(host.host() + ".pem");
QFile certFile(dir.absoluteFilePath(host.host() + ".pem"));
if (!certFile.open(QFile::ReadOnly)) {
return false;
}
pem.clear();
pem.append(certFile.readAll());
return true;
}
void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory)
{
foreach (const QString &scheme, transportFactory->supportedSchemes()) {
@ -511,7 +400,7 @@ void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transpor
}
}
void NymeaConnection::connect(NymeaHost *nymeaHost, Connection *connection)
void NymeaConnection::connectToHost(NymeaHost *nymeaHost, Connection *connection)
{
if (!nymeaHost) {
return;
@ -602,15 +491,15 @@ bool NymeaConnection::connectInternal(Connection *connection)
QObject::connect(newTransport, &NymeaTransportInterface::disconnected, this, &NymeaConnection::onDisconnected);
QObject::connect(newTransport, &NymeaTransportInterface::dataReady, this, &NymeaConnection::dataAvailable);
// Load any certificate we might have for this url
QByteArray pem;
if (loadPem(connection->url(), pem)) {
qDebug() << "Loaded SSL certificate for" << connection->url().host();
QList<QSslError> expectedSslErrors;
expectedSslErrors.append(QSslError::HostNameMismatch);
expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, QSslCertificate(pem)));
newTransport->ignoreSslErrors(expectedSslErrors);
}
// // Load any certificate we might have for this url
// QByteArray pem;
// if (loadPem(connection->url(), pem)) {
// qDebug() << "Loaded SSL certificate for" << connection->url().host();
// QList<QSslError> expectedSslErrors;
// expectedSslErrors.append(QSslError::HostNameMismatch);
// expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, QSslCertificate(pem)));
// newTransport->ignoreSslErrors(expectedSslErrors);
// }
m_transportCandidates.insert(newTransport, connection);
qDebug() << "Connecting to:" << connection->url() << newTransport << m_transportCandidates.value(newTransport);
@ -666,7 +555,20 @@ bool NymeaConnection::isConnectionBearerAvailable(Connection::BearerType connect
return false;
}
void NymeaConnection::disconnect()
void NymeaConnection::disconnectFromHost()
{
setCurrentHost(nullptr);
}
bool NymeaConnection::isEncrypted() const
{
return m_currentTransport && m_currentTransport->isEncrypted();
}
QSslCertificate NymeaConnection::sslCertificate() const
{
if (!m_currentTransport) {
return QSslCertificate();
}
return m_currentTransport->serverCertificate();
}

View File

@ -85,10 +85,11 @@ public:
void registerTransport(NymeaTransportInterfaceFactory *transportFactory);
Q_INVOKABLE void connect(NymeaHost* nymeaHost, Connection *connection = nullptr);
Q_INVOKABLE void disconnect();
Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &pem);
Q_INVOKABLE bool isTrusted(const QString &url);
Q_INVOKABLE void connectToHost(NymeaHost* nymeaHost, Connection *connection = nullptr);
Q_INVOKABLE void disconnectFromHost();
bool isEncrypted() const;
QSslCertificate sslCertificate() const;
NymeaConnection::BearerTypes availableBearerTypes() const;
@ -120,9 +121,6 @@ private slots:
void updateActiveBearers();
private:
bool storePem(const QUrl &host, const QByteArray &pem);
bool loadPem(const QUrl &host, QByteArray &pem);
void connectInternal(NymeaHost *host);
bool connectInternal(Connection *connection);

View File

@ -243,6 +243,11 @@ QString Connection::hostAddress() const
return m_url.host();
}
int Connection::port() const
{
return m_url.port();
}
Connection::BearerType Connection::bearerType() const
{
return m_bearerType;

View File

@ -43,6 +43,7 @@ class Connection: public QObject {
Q_OBJECT
Q_PROPERTY(QUrl url READ url CONSTANT)
Q_PROPERTY(QString hostAddress READ hostAddress CONSTANT)
Q_PROPERTY(int port READ port CONSTANT)
Q_PROPERTY(BearerType bearerType READ bearerType CONSTANT)
Q_PROPERTY(bool secure READ secure CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
@ -68,6 +69,7 @@ public:
QUrl url() const;
QString hostAddress() const;
int port() const;
BearerType bearerType() const;
bool secure() const;
QString displayName() const;

View File

@ -31,7 +31,7 @@
#include "nymeahosts.h"
#include "connection/discovery/nymeadiscovery.h"
#include "nymeahost.h"
#include "connection/nymeaconnection.h"
#include "jsonrpc/jsonrpcclient.h"
#include <QUuid>
NymeaHosts::NymeaHosts(QObject *parent) :
@ -186,18 +186,18 @@ void NymeaHostsFilterModel::setDiscovery(NymeaDiscovery *discovery)
}
}
NymeaConnection *NymeaHostsFilterModel::nymeaConnection() const
JsonRpcClient *NymeaHostsFilterModel::jsonRpcClient() const
{
return m_nymeaConnection;
return m_jsonRpcClient;
}
void NymeaHostsFilterModel::setNymeaConnection(NymeaConnection *nymeaConnection)
void NymeaHostsFilterModel::setJsonRpcClient(JsonRpcClient *jsonRpcClient)
{
if (m_nymeaConnection != nymeaConnection) {
m_nymeaConnection = nymeaConnection;
emit nymeaConnectionChanged();
if (m_jsonRpcClient != jsonRpcClient) {
m_jsonRpcClient = jsonRpcClient;
emit jsonRpcClientChanged();
connect(m_nymeaConnection, &NymeaConnection::availableBearerTypesChanged, this, [this](){
connect(m_jsonRpcClient, &JsonRpcClient::availableBearerTypesChanged, this, [this](){
// qDebug() << "Bearer Types Changed!";
invalidateFilter();
emit countChanged();
@ -247,24 +247,24 @@ bool NymeaHostsFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &s
{
Q_UNUSED(sourceParent)
NymeaHost *host = m_nymeaDiscovery->nymeaHosts()->get(sourceRow);
if (m_nymeaConnection && !m_showUneachableBearers) {
if (m_jsonRpcClient && !m_showUneachableBearers) {
bool hasReachableConnection = false;
for (int i = 0; i < host->connections()->rowCount(); i++) {
// qDebug() << "checking host for available bearer" << host->name() << host->connections()->get(i)->url() << "available bearer types:" << m_nymeaConnection->availableBearerTypes() << "hosts bearer types" << host->connections()->get(i)->bearerType();
// Either enable a connection when the Bearer type is directly available
switch (host->connections()->get(i)->bearerType()) {
case Connection::BearerTypeLan:
hasReachableConnection |= m_nymeaConnection->availableBearerTypes().testFlag(NymeaConnection::BearerTypeEthernet);
hasReachableConnection |= m_nymeaConnection->availableBearerTypes().testFlag(NymeaConnection::BearerTypeWiFi);
hasReachableConnection |= m_jsonRpcClient->availableBearerTypes().testFlag(NymeaConnection::BearerTypeEthernet);
hasReachableConnection |= m_jsonRpcClient->availableBearerTypes().testFlag(NymeaConnection::BearerTypeWiFi);
break;
case Connection::BearerTypeWan:
case Connection::BearerTypeCloud:
hasReachableConnection |= m_nymeaConnection->availableBearerTypes().testFlag(NymeaConnection::BearerTypeEthernet);
hasReachableConnection |= m_nymeaConnection->availableBearerTypes().testFlag(NymeaConnection::BearerTypeWiFi);
hasReachableConnection |= m_nymeaConnection->availableBearerTypes().testFlag(NymeaConnection::BearerTypeMobileData);
hasReachableConnection |= m_jsonRpcClient->availableBearerTypes().testFlag(NymeaConnection::BearerTypeEthernet);
hasReachableConnection |= m_jsonRpcClient->availableBearerTypes().testFlag(NymeaConnection::BearerTypeWiFi);
hasReachableConnection |= m_jsonRpcClient->availableBearerTypes().testFlag(NymeaConnection::BearerTypeMobileData);
break;
case Connection::BearerTypeBluetooth:
hasReachableConnection |= m_nymeaConnection->availableBearerTypes().testFlag(NymeaConnection::BearerTypeBluetooth);
hasReachableConnection |= m_jsonRpcClient->availableBearerTypes().testFlag(NymeaConnection::BearerTypeBluetooth);
break;
case Connection::BearerTypeUnknown:
hasReachableConnection = true;

View File

@ -38,7 +38,7 @@
#include "nymeahost.h"
class NymeaDiscovery;
class NymeaConnection;
class JsonRpcClient;
class NymeaHosts : public QAbstractListModel
{
@ -87,7 +87,7 @@ class NymeaHostsFilterModel: public QSortFilterProxyModel
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(NymeaDiscovery* discovery READ discovery WRITE setDiscovery NOTIFY discoveryChanged)
Q_PROPERTY(NymeaConnection* nymeaConnection READ nymeaConnection WRITE setNymeaConnection NOTIFY nymeaConnectionChanged)
Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient WRITE setJsonRpcClient NOTIFY jsonRpcClientChanged)
Q_PROPERTY(bool showUnreachableBearers READ showUnreachableBearers WRITE setShowUnreachableBearers NOTIFY showUnreachableBearersChanged)
Q_PROPERTY(bool showUnreachableHosts READ showUnreachableHosts WRITE setShowUnreachableHosts NOTIFY showUnreachableHostsChanged)
@ -97,8 +97,8 @@ public:
NymeaDiscovery* discovery() const;
void setDiscovery(NymeaDiscovery *discovery);
NymeaConnection* nymeaConnection() const;
void setNymeaConnection(NymeaConnection* nymeaConnection);
JsonRpcClient* jsonRpcClient() const;
void setJsonRpcClient(JsonRpcClient* jsonRpcClient);
bool showUnreachableBearers() const;
void setShowUnreachableBearers(bool showUnreachableBearers);
@ -111,7 +111,7 @@ public:
signals:
void countChanged();
void discoveryChanged();
void nymeaConnectionChanged();
void jsonRpcClientChanged();
void showUnreachableBearersChanged();
void showUnreachableHostsChanged();
@ -120,7 +120,7 @@ protected:
private:
NymeaDiscovery *m_nymeaDiscovery = nullptr;
NymeaConnection *m_nymeaConnection = nullptr;
JsonRpcClient *m_jsonRpcClient = nullptr;
bool m_showUneachableBearers = false;
bool m_showUneachableHosts = false;

View File

@ -66,6 +66,8 @@ public:
virtual ConnectionState connectionState() const = 0;
virtual void sendData(const QByteArray &data) = 0;
virtual void ignoreSslErrors(const QList<QSslError> &errors) { Q_UNUSED(errors) }
virtual bool isEncrypted() const { return false; }
virtual QSslCertificate serverCertificate() const { return QSslCertificate(); }
signals:
void connected();

View File

@ -31,6 +31,7 @@
#include "tcpsockettransport.h"
#include <QUrl>
#include <QSslConfiguration>
TcpSocketTransport::TcpSocketTransport(QObject *parent) : NymeaTransportInterface(parent)
{
@ -58,6 +59,16 @@ void TcpSocketTransport::ignoreSslErrors(const QList<QSslError> &errors)
m_socket.ignoreSslErrors(errors);
}
bool TcpSocketTransport::isEncrypted() const
{
return m_socket.isEncrypted();
}
QSslCertificate TcpSocketTransport::serverCertificate() const
{
return m_socket.peerCertificate();
}
void TcpSocketTransport::onConnected()
{
if (m_url.scheme() == "nymea") {

View File

@ -56,6 +56,8 @@ public:
void disconnect() override;
void sendData(const QByteArray &data) override;
void ignoreSslErrors(const QList<QSslError> &errors) override;
bool isEncrypted() const override;
QSslCertificate serverCertificate() const override;
private slots:
void onConnected();

View File

@ -99,6 +99,16 @@ void WebsocketTransport::ignoreSslErrors(const QList<QSslError> &errors)
m_socket->ignoreSslErrors();
}
bool WebsocketTransport::isEncrypted() const
{
return !m_socket->sslConfiguration().isNull();
}
QSslCertificate WebsocketTransport::serverCertificate() const
{
return m_socket->sslConfiguration().peerCertificate();
}
void WebsocketTransport::onTextMessageReceived(const QString &data)
{
emit dataReady(data.toUtf8());

View File

@ -56,6 +56,9 @@ public:
void sendData(const QByteArray &data) override;
void ignoreSslErrors(const QList<QSslError> &errors) override;
bool isEncrypted() const override;
QSslCertificate serverCertificate() const override;
private:
QUrl m_url;
QWebSocket *m_socket;

View File

@ -108,7 +108,6 @@ void DeviceClasses::addDeviceClass(DeviceClass *deviceClass)
void DeviceClasses::clearModel()
{
beginResetModel();
qDebug() << "Devices: delete all deviceClasses";
qDeleteAll(m_deviceClasses);
m_deviceClasses.clear();
endResetModel();

View File

@ -134,7 +134,6 @@ void Devices::removeDevice(Device *device)
void Devices::clearModel()
{
beginResetModel();
qDebug() << "Devices: delete all devices";
qDeleteAll(m_devices);
m_devices.clear();
endResetModel();

View File

@ -39,15 +39,9 @@
#include "system/systemcontroller.h"
#include "configuration/networkmanager.h"
#include "connection/tcpsockettransport.h"
#include "connection/websockettransport.h"
#include "connection/bluetoothtransport.h"
#include "connection/cloudtransport.h"
Engine::Engine(QObject *parent) :
QObject(parent),
m_connection(new NymeaConnection(this)),
m_jsonRpcClient(new JsonRpcClient(m_connection, this)),
m_jsonRpcClient(new JsonRpcClient(this)),
m_deviceManager(new DeviceManager(m_jsonRpcClient, this)),
m_ruleManager(new RuleManager(m_jsonRpcClient, this)),
m_scriptManager(new ScriptManager(m_jsonRpcClient, this)),
@ -56,10 +50,6 @@ Engine::Engine(QObject *parent) :
m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)),
m_systemController(new SystemController(m_jsonRpcClient, this))
{
m_connection->registerTransport(new TcpSocketTransportFactory());
m_connection->registerTransport(new WebsocketTransportFactory());
m_connection->registerTransport(new BluetoothTransportFactoy());
m_connection->registerTransport(new CloudTransportFactory());
connect(m_jsonRpcClient, &JsonRpcClient::connectedChanged, this, &Engine::onConnectedChanged);
@ -138,11 +128,6 @@ void Engine::deployCertificate()
});
}
NymeaConnection *Engine::connection() const
{
return m_connection;
}
void Engine::onConnectedChanged()
{
qDebug() << "Engine: connected changed:" << m_jsonRpcClient->connected();

View File

@ -49,7 +49,6 @@ class NetworkManager;
class Engine : public QObject
{
Q_OBJECT
Q_PROPERTY(NymeaConnection* connection READ connection CONSTANT)
Q_PROPERTY(DeviceManager* deviceManager READ deviceManager CONSTANT)
Q_PROPERTY(RuleManager* ruleManager READ ruleManager CONSTANT)
Q_PROPERTY(ScriptManager* scriptManager READ scriptManager CONSTANT)
@ -64,7 +63,6 @@ public:
bool connected() const;
QString connectedHost() const;
NymeaConnection *connection() const;
DeviceManager *deviceManager() const;
RuleManager *ruleManager() const;
ScriptManager *scriptManager() const;
@ -77,7 +75,6 @@ public:
Q_INVOKABLE void deployCertificate();
private:
NymeaConnection *m_connection;
JsonRpcClient *m_jsonRpcClient;
DeviceManager *m_deviceManager;
RuleManager *m_ruleManager;

View File

@ -33,6 +33,11 @@
#include "types/param.h"
#include "types/params.h"
#include "connection/tcpsockettransport.h"
#include "connection/websockettransport.h"
#include "connection/bluetoothtransport.h"
#include "connection/cloudtransport.h"
#include <QJsonDocument>
#include <QVariantMap>
#include <QDebug>
@ -41,13 +46,24 @@
#include <QVersionNumber>
#include <QMetaEnum>
#include <QLocale>
#include <QDir>
#include <QStandardPaths>
JsonRpcClient::JsonRpcClient(NymeaConnection *connection, QObject *parent) :
JsonRpcClient::JsonRpcClient(QObject *parent) :
JsonHandler(parent),
m_id(0),
m_connection(connection)
m_id(0)
{
m_connection = new NymeaConnection(this);
m_connection->registerTransport(new TcpSocketTransportFactory());
m_connection->registerTransport(new WebsocketTransportFactory());
m_connection->registerTransport(new BluetoothTransportFactoy());
m_connection->registerTransport(new CloudTransportFactory());
connect(m_connection, &NymeaConnection::availableBearerTypesChanged, this, &JsonRpcClient::availableBearerTypesChanged);
connect(m_connection, &NymeaConnection::connectionStatusChanged, this, &JsonRpcClient::connectionStatusChanged);
connect(m_connection, &NymeaConnection::connectedChanged, this, &JsonRpcClient::onInterfaceConnectedChanged);
connect(m_connection, &NymeaConnection::currentHostChanged, this, &JsonRpcClient:: currentHostChanged);
connect(m_connection, &NymeaConnection::currentConnectionChanged, this, &JsonRpcClient:: currentConnectionChanged);
connect(m_connection, &NymeaConnection::dataAvailable, this, &JsonRpcClient::dataReceived);
registerNotificationHandler(this, "notificationReceived");
@ -89,6 +105,32 @@ int JsonRpcClient::sendCommand(const QString &method, QObject *caller, const QSt
return sendCommand(method, QVariantMap(), caller, callbackMethod);
}
NymeaConnection::BearerTypes JsonRpcClient::availableBearerTypes() const
{
return m_connection->availableBearerTypes();
}
NymeaConnection::ConnectionStatus JsonRpcClient::connectionStatus() const
{
return m_connection->connectionStatus();
}
void JsonRpcClient::connectToHost(NymeaHost *host, Connection *connection)
{
m_connection->connectToHost(host, connection);
}
void JsonRpcClient::disconnectFromHost()
{
m_connection->disconnectFromHost();
}
void JsonRpcClient::acceptCertificate(const QString &serverUuid, const QByteArray &pem)
{
qDebug() << "Pinning new certificate for" << serverUuid;
storePem(serverUuid, pem);
}
void JsonRpcClient::getCloudConnectionStatus()
{
JsonRpcReply *reply = createReply("JSONRPC.IsCloudConnected", QVariantMap(), this, "isCloudConnectedReply");
@ -174,6 +216,37 @@ bool JsonRpcClient::connected() const
return m_connected;
}
NymeaHost *JsonRpcClient::currentHost() const
{
return m_connection->currentHost();
}
Connection *JsonRpcClient::currentConnection() const
{
return m_connection->currentConnection();
}
QVariantMap JsonRpcClient::certificateIssuerInfo() const
{
QVariantMap issuerInfo;
foreach (const QByteArray &attr, m_connection->sslCertificate().issuerInfoAttributes()) {
issuerInfo.insert(attr, m_connection->sslCertificate().issuerInfo(attr));
}
QByteArray certificateFingerprint;
QByteArray digest = m_connection->sslCertificate().digest(QCryptographicHash::Sha256);
for (int i = 0; i < digest.length(); i++) {
if (certificateFingerprint.length() > 0) {
certificateFingerprint.append(":");
}
certificateFingerprint.append(digest.mid(i,1).toHex().toUpper());
}
issuerInfo.insert("fingerprint", certificateFingerprint);
return issuerInfo;
}
bool JsonRpcClient::initialSetupRequired() const
{
return m_initialSetupRequired;
@ -377,6 +450,33 @@ void JsonRpcClient::sendRequest(const QVariantMap &request)
m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n");
}
bool JsonRpcClient::loadPem(const QUuid &serverUud, QByteArray &pem)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
QFile certFile(dir.absoluteFilePath(serverUud.toString().remove(QRegExp("[{}]")) + ".pem"));
if (!certFile.open(QFile::ReadOnly)) {
return false;
}
pem.clear();
pem.append(certFile.readAll());
return true;
}
bool JsonRpcClient::storePem(const QUuid &serverUuid, const QByteArray &pem)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
if (!dir.exists()) {
dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
}
QFile certFile(dir.absoluteFilePath(serverUuid.toString().remove(QRegExp("[{}]")) + ".pem"));
if (!certFile.open(QFile::WriteOnly | QFile::Truncate)) {
return false;
}
certFile.write(pem);
certFile.close();
return true;
}
void JsonRpcClient::onInterfaceConnectedChanged(bool connected)
{
@ -497,6 +597,34 @@ void JsonRpcClient::helloReply(const QVariantMap &params)
return;
}
// Verify SSL certificate
if (m_connection->isEncrypted()) {
QByteArray pem;
if (!loadPem(m_serverUuid, pem)) {
qDebug() << "No SSL certificate for this host stored. Accepting and pinning new certificate.";
// No certificate yet! Inform ui about it.
emit newSslCertificate();
storePem(m_serverUuid, m_connection->sslCertificate().toPem());
} else {
// We have a certificate pinned already. Check if it's the same
if (m_connection->sslCertificate().toPem() != pem) {
// Uh oh, the certificate has changed
qWarning() << "This connections certificate has changed!";
QSslCertificate certificate = m_connection->sslCertificate();
QVariantMap issuerInfo = certificateIssuerInfo();
emit verifyConnectionCertificate(m_serverUuid, issuerInfo, certificate.toPem());
// Reject the connection until the UI explicitly accepts this...
m_connection->disconnectFromHost();
return;
}
qDebug() << "This connections certificate is trusted.";
}
}
emit handshakeReceived();
if (m_connection->currentHost()->uuid().isNull()) {

View File

@ -46,7 +46,11 @@ class Params;
class JsonRpcClient : public JsonHandler
{
Q_OBJECT
Q_PROPERTY(NymeaConnection::BearerTypes availableBearerTypes READ availableBearerTypes NOTIFY availableBearerTypesChanged)
Q_PROPERTY(NymeaConnection::ConnectionStatus connectionStatus READ connectionStatus NOTIFY connectionStatusChanged)
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
Q_PROPERTY(NymeaHost* currentHost READ currentHost NOTIFY currentHostChanged)
Q_PROPERTY(Connection* currentConnection READ currentConnection NOTIFY currentConnectionChanged)
Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged)
Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged)
Q_PROPERTY(bool pushButtonAuthAvailable READ pushButtonAuthAvailable NOTIFY pushButtonAuthAvailableChanged)
@ -57,6 +61,7 @@ class JsonRpcClient : public JsonHandler
Q_PROPERTY(QString serverUuid READ serverUuid NOTIFY handshakeReceived)
Q_PROPERTY(QString serverQtVersion READ serverQtVersion NOTIFY serverQtVersionChanged)
Q_PROPERTY(QString serverQtBuildVersion READ serverQtBuildVersion NOTIFY serverQtVersionChanged)
Q_PROPERTY(QVariantMap certificateIssuerInfo READ certificateIssuerInfo NOTIFY currentConnectionChanged)
public:
enum CloudConnectionState {
@ -67,7 +72,7 @@ public:
};
Q_ENUM(CloudConnectionState)
explicit JsonRpcClient(NymeaConnection *connection, QObject *parent = nullptr);
explicit JsonRpcClient(QObject *parent = nullptr);
QString nameSpace() const override;
@ -77,8 +82,12 @@ public:
int sendCommand(const QString &method, const QVariantMap &params, QObject *caller = nullptr, const QString &callbackMethod = QString());
int sendCommand(const QString &method, QObject *caller = nullptr, const QString &callbackMethod = QString());
void setConnection(NymeaConnection *connection);
NymeaConnection::BearerTypes availableBearerTypes() const;
NymeaConnection::ConnectionStatus connectionStatus() const;
bool connected() const;
NymeaHost* currentHost() const;
Connection* currentConnection() const;
QVariantMap certificateIssuerInfo() const;
bool initialSetupRequired() const;
bool authenticationRequired() const;
bool pushButtonAuthAvailable() const;
@ -93,20 +102,31 @@ public:
QString serverQtBuildVersion();
// ui methods
Q_INVOKABLE void connectToHost(NymeaHost *host, Connection *connection = nullptr);
Q_INVOKABLE void disconnectFromHost();
Q_INVOKABLE void acceptCertificate(const QString &serverUuid, const QByteArray &pem);
Q_INVOKABLE bool ensureServerVersion(const QString &jsonRpcVersion);
Q_INVOKABLE int createUser(const QString &username, const QString &password);
Q_INVOKABLE int authenticate(const QString &username, const QString &password, const QString &deviceName);
Q_INVOKABLE int requestPushButtonAuth(const QString &deviceName);
Q_INVOKABLE void setupRemoteAccess(const QString &idToken, const QString &userId);
Q_INVOKABLE bool ensureServerVersion(const QString &jsonRpcVersion);
signals:
void availableBearerTypesChanged();
void connectionStatusChanged();
void connectedChanged(bool connected);
void currentHostChanged();
void currentConnectionChanged();
void handshakeReceived();
void newSslCertificate();
void verifyConnectionCertificate(const QString &serverUuid, const QVariantMap &issuerInfo, const QByteArray &pem);
void initialSetupRequiredChanged();
void authenticationRequiredChanged();
void pushButtonAuthAvailableChanged();
void authenticatedChanged();
void connectedChanged(bool connected);
void tokenChanged();
void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion);
void authenticationFailed();
@ -166,6 +186,9 @@ private:
void sendRequest(const QVariantMap &request);
bool loadPem(const QUuid &serverUud, QByteArray &pem);
bool storePem(const QUuid &serverUuid, const QByteArray &pem);
};

View File

@ -124,7 +124,6 @@ void TagListModel::update()
}
}
qDebug() << "Model populated" << m_list.count() << this;
endResetModel();
emit countChanged();
}

View File

@ -200,7 +200,6 @@ void SystemController::setAutomaticTime(bool automaticTime)
void SystemController::getCapabilitiesResponse(const QVariantMap &data)
{
qDebug() << "capabilities received" << data;
m_powerManagementAvailable = data.value("params").toMap().value("powerManagement").toBool();
emit powerManagementAvailableChanged();
@ -219,6 +218,8 @@ void SystemController::getCapabilitiesResponse(const QVariantMap &data)
if (m_jsonRpcClient->ensureServerVersion("4.1")) {
m_jsonRpcClient->sendCommand("System.GetTime", this, "getServerTimeResponse");
}
qDebug() << "nymea:core capabilities: Power management:" << m_powerManagementAvailable << "Update management:" << m_updateManagementAvailable << "Time management:" << m_timeManagementAvailable;
}
void SystemController::getUpdateStatusResponse(const QVariantMap &data)

View File

@ -97,7 +97,6 @@ void Plugins::addPlugin(Plugin *plugin)
void Plugins::clearModel()
{
beginResetModel();
qDebug() << "Plugins: delete all plugins";
qDeleteAll(m_plugins);
m_plugins.clear();
endResetModel();

View File

@ -212,5 +212,6 @@
<file>ui/components/SettingsPageBase.qml</file>
<file>ui/components/SettingsPageSectionHeader.qml</file>
<file>ui/grouping/GroupInterfacesPage.qml</file>
<file>ui/connection/CertificateErrorDialog.qml</file>
</qresource>
</RCC>

View File

@ -45,10 +45,10 @@ Page {
title: swipeView.currentItem.title
leftButtonVisible: true
leftButtonImageSource: {
switch (engine.connection.currentConnection.bearerType) {
switch (engine.jsonRpcClient.currentConnection.bearerType) {
case Connection.BearerTypeLan:
case Connection.BearerTypeWan:
if (engine.connection.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
if (engine.jsonRpcClient.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
return "../images/network-wired.svg"
}
return "../images/network-wifi.svg";
@ -62,7 +62,7 @@ Page {
return ""
}
onLeftButtonClicked: {
var dialog = connectionDialogComponent.createObject(root, {headerIcon: leftButtonImageSource})
var dialog = connectionDialogComponent.createObject(root)
dialog.open();
}
@ -404,8 +404,25 @@ Page {
id: connectionDialogComponent
MeaDialog {
id: connectionDialog
title: engine.connection.currentHost.name
title: engine.jsonRpcClient.currentHost.name
standardButtons: Dialog.NoButton
headerIcon: {
switch (engine.jsonRpcClient.currentConnection.bearerType) {
case Connection.BearerTypeLan:
case Connection.BearerTypeWan:
if (engine.jsonRpcClient.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
return "../images/network-wired.svg"
}
return "../images/network-wifi.svg";
case Connection.BearerTypeBluetooth:
return "../images/network-wifi.svg";
case Connection.BearerTypeCloud:
return "../images/cloud.svg"
case Connection.BearerTypeLoopback:
return "../images/network-wired.svg"
}
return ""
}
Label {
Layout.fillWidth: true
@ -417,27 +434,11 @@ Page {
}
Label {
Layout.fillWidth: true
text: engine.connection.currentHost.name
text: engine.jsonRpcClient.currentHost.name
elide: Text.ElideRight
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.fillWidth: true
text: engine.connection.currentHost.uuid
font.pixelSize: app.smallFont
elide: Text.ElideRight
color: Material.color(Material.Grey)
horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.fillWidth: true
text: engine.connection.currentConnection.url
font.pixelSize: app.smallFont
elide: Text.ElideRight
color: Material.color(Material.Grey)
horizontalAlignment: Text.AlignHCenter
}
Item {
Layout.fillWidth: true
@ -445,17 +446,46 @@ Page {
}
RowLayout {
Layout.fillWidth: true
Button {
id: cancelButton
text: qsTr("OK")
Layout.preferredWidth: Math.max(cancelButton.implicitWidth, disconnectButton.implicitWidth)
onClicked: connectionDialog.close()
ColumnLayout {
Label {
Layout.fillWidth: true
text: engine.jsonRpcClient.currentHost.uuid
font.pixelSize: app.smallFont
elide: Text.ElideRight
color: Material.color(Material.Grey)
// horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.fillWidth: true
text: engine.jsonRpcClient.currentConnection.url
font.pixelSize: app.smallFont
elide: Text.ElideRight
color: Material.color(Material.Grey)
// horizontalAlignment: Text.AlignHCenter
}
}
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: engine.jsonRpcClient.currentConnection.secure ? "../images/lock-closed.svg" : "../images/lock-open.svg"
MouseArea {
anchors.fill: parent
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("connection/CertificateDialog.qml"));
var popup = component.createObject(app, {serverUuid: engine.jsonRpcClient.serverUuid, issuerInfo: engine.jsonRpcClient.certificateIssuerInfo});
popup.open();
}
}
}
}
Item {
Layout.fillWidth: true
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: app.margins
}
RowLayout {
Layout.fillWidth: true
Button {
id: disconnectButton
@ -463,9 +493,18 @@ Page {
Layout.preferredWidth: Math.max(cancelButton.implicitWidth, disconnectButton.implicitWidth)
onClicked: {
tabSettings.lastConnectedHost = "";
engine.connection.disconnect();
engine.jsonRpcClient.disconnectFromHost();
}
}
Item {
Layout.fillWidth: true
}
Button {
id: cancelButton
text: qsTr("OK")
Layout.preferredWidth: Math.max(cancelButton.implicitWidth, disconnectButton.implicitWidth)
onClicked: connectionDialog.close()
}
}
}
}

View File

@ -111,7 +111,7 @@ Item {
readonly property Engine engine: engineObject
readonly property Engine _engine: engineObject // In case a child cannot use "engine"
property int connectionTabIndex: index
// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url
// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.jsonRpcClient.url
Binding {
target: AWSClient
@ -122,7 +122,7 @@ Item {
Binding {
target: _discovery
property: "discovering"
value: engine.connection.currentHost === null
value: engine.jsonRpcClient.currentHost === null
}
StackView {
@ -136,12 +136,12 @@ Item {
setupPushNotifications();
if (autoConnectHost.length > 0) {
var host = discovery.nymeaHosts.createLanHost("Manual connection", autoConnectHost);
engine.connection.connect(host)
engine.jsonRpcClient.connectToHost(host)
} else if (tabSettings.lastConnectedHost.length > 0) {
print("Last connected host was", tabSettings.lastConnectedHost)
var cachedHost = discovery.nymeaHosts.find(tabSettings.lastConnectedHost);
if (cachedHost) {
engine.connection.connect(cachedHost)
engine.jsonRpcClient.connectToHost(cachedHost)
return;
}
print("Warning: There is a last connected host but UUID is unknown to discovery...")
@ -153,9 +153,9 @@ Item {
Timer { running: true; repeat: false; interval: 3000; onTriggered: PlatformHelper.hideSplashScreen(); }
function init() {
print("calling init. Auth required:", engine.jsonRpcClient.authenticationRequired, "initial setup required:", engine.jsonRpcClient.initialSetupRequired, "jsonrpc connected:", engine.jsonRpcClient.connected, "Current host:", engine.connection.currentHost)
print("calling init. Auth required:", engine.jsonRpcClient.authenticationRequired, "initial setup required:", engine.jsonRpcClient.initialSetupRequired, "jsonrpc connected:", engine.jsonRpcClient.connected, "Current host:", engine.jsonRpcClient.currentHost)
pageStack.clear()
if (!engine.connection.currentHost) {
if (!engine.jsonRpcClient.currentHost) {
print("pushing ConnectPage")
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
PlatformHelper.hideSplashScreen();
@ -169,7 +169,7 @@ Item {
var page = pageStack.push(Qt.resolvedUrl("PushButtonAuthPage.qml"))
page.backPressed.connect(function() {
tabSettings.lastConnectedHost = "";
engine.connection.disconnect();
engine.jsonRpcClient.disconnectFromHost();
init();
})
return;
@ -178,7 +178,7 @@ Item {
var page = pageStack.push(Qt.resolvedUrl("connection/SetupWizard.qml"));
page.backPressed.connect(function() {
tabSettings.lastConnectedHost = "";
engine.connection.disconnect()
engine.jsonRpcClient.disconnectFromHost()
init();
})
return;
@ -187,7 +187,7 @@ Item {
var page = pageStack.push(Qt.resolvedUrl("connection/LoginPage.qml"));
page.backPressed.connect(function() {
tabSettings.lastConnectedHost = "";
engine.connection.disconnect()
engine.jsonRpcClient.disconnectFromHost()
init();
})
return;
@ -200,10 +200,9 @@ Item {
return;
}
print("pushing ConnectingPage")
var page = pageStack.push(Qt.resolvedUrl("connection/ConnectingPage.qml"));
page.cancel.connect(function(){
engine.connection.disconnect();
engine.jsonRpcClient.disconnectFromHost();
})
}
@ -243,25 +242,24 @@ Item {
}
Connections {
target: engine.connection
target: engine.jsonRpcClient
onCurrentHostChanged: {
init();
}
onVerifyConnectionCertificate: {
print("verify cert!")
var certDialogComponent = Qt.createComponent(Qt.resolvedUrl("connection/CertificateDialog.qml"));
var popup = certDialogComponent.createObject(root, {url: url, issuerInfo: issuerInfo, fingerprint: fingerprint, pem: pem});
print("Asking user to verify certificate:", serverUuid, issuerInfo, pem)
var certDialogComponent = Qt.createComponent(Qt.resolvedUrl("connection/CertificateErrorDialog.qml"));
var popup = certDialogComponent.createObject(root);
popup.accepted.connect(function(){
engine.jsonRpcClient.acceptCertificate(serverUuid, pem);
engine.jsonRpcClient.connectToHost(discovery.nymeaHosts.find(serverUuid));
})
popup.open();
}
}
Connections {
target: engine.jsonRpcClient
onConnectedChanged: {
print("json client connected changed", engine.jsonRpcClient.connected)
if (engine.jsonRpcClient.connected) {
discovery.cacheHost(engine.connection.currentHost)
discovery.cacheHost(engine.jsonRpcClient.currentHost)
tabSettings.lastConnectedHost = engine.jsonRpcClient.serverUuid
}
init();
@ -348,7 +346,7 @@ Item {
Layout.fillWidth: true
text: qsTr("OK")
onClicked: {
engine.connection.disconnect();
engine.jsonRpcClient.disconnectFromHost();
popup.close()
}
}

View File

@ -124,10 +124,10 @@ SettingsPageBase {
secondaryIconName: !model.online ? "../images/cloud-error.svg" : ""
onClicked: {
print("clicked, connected:", engine.connection.connected, model.id)
if (!engine.connection.connected) {
print("clicked, connected:", engine.jsonRpcClient.connected, model.id)
if (!engine.jsonRpcClient.connected) {
var host = discovery.nymeaHosts.find(model.id)
engine.connection.connect(host);
engine.jsonRpcClient.connectToHost(host);
}
}

View File

@ -40,14 +40,10 @@ Dialog {
width: Math.min(parent.width * .9, 400)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
standardButtons: Dialog.Yes | Dialog.No
standardButtons: Dialog.Ok
property string url
property var fingerprint
property string serverUuid
property var issuerInfo
property var pem
readonly property bool hasOldFingerprint: engine.connection.isTrusted(url)
ColumnLayout {
id: certLayout
@ -60,84 +56,68 @@ Dialog {
ColorIcon {
Layout.preferredHeight: app.iconSize * 2
Layout.preferredWidth: height
name: certDialog.hasOldFingerprint ? "../images/lock-broken.svg" : "../images/info.svg"
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
name: "../images/lock-closed.svg"
color: app.accentColor
}
Label {
id: titleLabel
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: certDialog.hasOldFingerprint ? qsTr("Warning") : qsTr("Hi there!")
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
text: qsTr("Certificate information")
color: app.accentColor
font.pixelSize: app.largeFont
}
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: certDialog.hasOldFingerprint ? qsTr("The certificate of this %1:core has changed!").arg(app.systemName) : qsTr("It seems this is the first time you connect to this %1:core.").arg(app.systemName)
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: certDialog.hasOldFingerprint ? qsTr("Did you change the system's configuration? Verify if this information is correct.") : qsTr("This is the certificate for this %1:core. Once you trust it, an encrypted connection will be established.").arg(app.systemName)
}
ThinDivider {}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: certGridLayout.implicitHeight
Flickable {
anchors.fill: parent
contentHeight: certGridLayout.implicitHeight
clip: true
ScrollBar.vertical: ScrollBar {
policy: contentHeight > height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded
}
GridLayout {
id: certGridLayout
columns: 2
width: parent.width
Repeater {
model: certDialog.issuerInfo
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: modelData
}
}
Label {
Layout.fillWidth: true
Layout.columnSpan: 2
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: qsTr("Fingerprint: ") + certDialog.fingerprint
}
}
}
Layout.preferredHeight: app.margins
}
ThinDivider {}
Label {
text: qsTr("nymea UUID:")
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: certDialog.hasOldFingerprint ? qsTr("Do you want to connect nevertheless?") : qsTr("Do you want to trust this device?")
font.bold: true
}
Label {
text: certDialog.serverUuid
Layout.fillWidth: true
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: app.margins
}
GridLayout {
columns: 2
Label {
text: qsTr("Organisation:")
Layout.fillWidth: true
}
Label {
text: certDialog.issuerInfo["O"]
Layout.fillWidth: true
}
Label {
text: qsTr("Common name:")
Layout.fillWidth: true
}
Label {
text: certDialog.issuerInfo["CN"]
Layout.fillWidth: true
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: app.margins
}
Label {
text: qsTr("Fingerprint:")
Layout.fillWidth: true
}
Label {
text: certDialog.issuerInfo["fingerprint"]
Layout.fillWidth: true
wrapMode: Text.WrapAnywhere
}
}
onAccepted: {
engine.connection.acceptCertificate(certDialog.url, certDialog.pem)
}
onRejected: {
engine.connection.disconnect();
}
}

View File

@ -0,0 +1,45 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
MeaDialog {
id: root
title: qsTr("Insecure connection")
headerIcon: "../images/lock-broken.svg"
text: qsTr("The certificate for this %1 system has changed. This could be because the configuration has been changed, but could also mean the system has been compromised. Do you want to accept the new certificate?").arg(app.systemName)
standardButtons: Dialog.Ok | Dialog.Cancel
}

View File

@ -45,33 +45,22 @@ Page {
pageStack.push(discoveryPage, StackView.Immediate)
}
function connectToHost(url, noAnimations) {
function connectToHost(host, noAnimations) {
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"), noAnimations ? StackView.Immediate : StackView.PushTransition)
page.cancel.connect(function() {
engine.connection.disconnect()
pageStack.pop(root, StackView.Immediate);
pageStack.push(discoveryPage)
})
engine.connection.connect(url)
}
function connectToHost2(host, noAnimations) {
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"), noAnimations ? StackView.Immediate : StackView.PushTransition)
page.cancel.connect(function() {
engine.connection.disconnect()
engine.jsonRpcClient.disconnectFromHost()
pageStack.pop(root, StackView.Immediate);
pageStack.push(discoveryPage)
})
print("Connecting to host", host)
engine.connection.connect(host)
engine.jsonRpcClient.connectToHost(host)
}
NymeaHostsFilterModel {
id: hostsProxy
discovery: _discovery
showUnreachableBearers: false
nymeaConnection: engine.connection
jsonRpcClient: engine.jsonRpcClient
showUnreachableHosts: false
}
@ -91,7 +80,7 @@ Page {
onClicked: {
if (index === 2) {
var host = discovery.nymeaHosts.createWanHost("Demo server", "nymea://nymea.nymea.io:2222")
engine.connection.connect(host)
engine.jsonRpcClient.connectToHost(host)
} else {
pageStack.push(model.get(index).page, {nymeaDiscovery: discovery});
}
@ -176,7 +165,7 @@ Page {
switch (nymeaHost.connections.get(defaultConnectionIndex).bearerType) {
case Connection.BearerTypeLan:
case Connection.BearerTypeWan:
if (engine.connection.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
if (engine.jsonRpcClient.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
return "../images/network-wired.svg"
}
return "../images/network-wifi.svg";
@ -196,15 +185,13 @@ Page {
prominentSubText: false
progressive: false
property bool isSecure: nymeaHost.connections.get(defaultConnectionIndex).secure
property bool isTrusted: engine.connection.isTrusted(nymeaHostDelegate.nymeaHost.connections.get(defaultConnectionIndex).url)
property bool isOnline: nymeaHost.connections.get(defaultConnectionIndex).bearerType !== Connection.BearerTypeWan ? nymeaHost.connections.get(defaultConnectionIndex).online : true
tertiaryIconName: isSecure ? "../images/network-secure.svg" : ""
tertiaryIconColor: isTrusted ? app.accentColor : Material.foreground
secondaryIconName: !isOnline ? "../images/cloud-error.svg" : ""
secondaryIconColor: "red"
onClicked: {
root.connectToHost2(nymeaHostDelegate.nymeaHost)
root.connectToHost(nymeaHostDelegate.nymeaHost)
}
contextOptions: [
@ -272,7 +259,7 @@ Page {
text: qsTr("Demo mode (online)")
onClicked: {
var host = discovery.nymeaHosts.createWanHost("Demo server", "nymea://nymea.nymea.io:2222")
engine.connection.connect(host)
engine.jsonRpcClient.connectToHost(host)
}
}
@ -390,7 +377,7 @@ Page {
switch (model.bearerType) {
case Connection.BearerTypeLan:
case Connection.BearerTypeWan:
if (engine.connection.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
if (engine.jsonRpcClient.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) {
return "../images/network-wired.svg"
}
return "../images/network-wifi.svg";
@ -398,19 +385,19 @@ Page {
return "../images/bluetooth.svg";
case Connection.BearerTypeCloud:
return "../images/cloud.svg"
case Connection.BearerTypeLoopback:
return "../images/network-wired.svg"
}
return ""
}
tertiaryIconName: model.secure ? "../images/network-secure.svg" : ""
tertiaryIconColor: isTrusted ? app.accentColor : "gray"
readonly property bool isTrusted: engine.connection.isTrusted(url)
secondaryIconName: !model.online ? "../images/cloud-error.svg" : ""
secondaryIconColor: "red"
onClicked: {
dialog.close()
engine.connection.connect(dialog.nymeaHost, dialog.nymeaHost.connections.get(index))
engine.jsonRpcClient.connectToHost(dialog.nymeaHost, dialog.nymeaHost.connections.get(index))
}
}
}

View File

@ -72,7 +72,7 @@ Page {
}
Label {
Layout.fillWidth: true
text: engine.connection.currentHost.name.length > 0 ? engine.connection.currentHost.name : engine.connection.currentHost.uuid
text: engine.jsonRpcClient.currentHost.name.length > 0 ? engine.jsonRpcClient.currentHost.name : engine.jsonRpcClient.currentHost.uuid
font.pixelSize: app.smallFont
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
horizontalAlignment: Text.AlignHCenter
@ -82,7 +82,7 @@ Page {
Layout.preferredHeight: 150
text: {
var errorMessage;
switch (engine.connection.connectionStatus) {
switch (engine.jsonRpcClient.connectionStatus) {
case NymeaConnection.ConnectionStatusUnconnected:
case NymeaConnection.ConnectionStatusConnecting:
case NymeaConnection.ConnectionStatusConnected:
@ -115,7 +115,7 @@ Page {
break;
case NymeaConnection.ConnectionStatusUnknownError:
default:
errorMessage = qsTr("An unknown error happened. We're very sorry for that. (Error code: %1)").arg(engine.connection.connectionStatus);
errorMessage = qsTr("An unknown error happened. We're very sorry for that. (Error code: %1)").arg(engine.jsonRpcClient.connectionStatus);
}
return errorMessage;
}

View File

@ -127,7 +127,7 @@ Page {
print("Try to connect ", rpcUrl)
var host = discovery.nymeaHosts.createLanHost("Manual connection", rpcUrl);
engine.connection.connect(host)
engine.jsonRpcClient.connectToHost(host)
}
}
}

View File

@ -153,7 +153,7 @@ Page {
text: qsTr("Connect to %1:core").arg(app.systemName)
property string url
onClicked: {
engine.connection.connect(url)
engine.jsonRpcClient.connectToHost(url)
}
}

View File

@ -48,7 +48,7 @@ SettingsPageBase {
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Connection:")
subText: engine.connection.currentConnection.url
subText: engine.jsonRpcClient.currentConnection.url
progressive: false
prominentSubText: false
}

View File

@ -39,18 +39,8 @@ NymeaListItemDelegate {
subText: qsTr("Port: %1").arg(model.port)
iconName: "../images/network-vpn.svg"
progressive: false
iconColor: {
if ((engine.connection.hostAddress === model.address || model.address === "0.0.0.0")
&& engine.connection.port === model.port) {
return app.accentColor
}
return iconKeyColor
}
secondaryIconName: "../images/account.svg"
secondaryIconColor: model.authenticationEnabled ? app.accentColor : secondaryIconKeyColor
tertiaryIconName: "../images/network-secure.svg"
tertiaryIconColor: model.sslEnabled ? app.accentColor : tertiaryIconKeyColor
// canDelete: true
}

View File

@ -47,7 +47,10 @@ SettingsPageBase {
model: engine.nymeaConfiguration.tcpServerConfigurations
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
canDelete: true
iconColor: inUse ? app.accentColor : iconKeyColor
readonly property bool inUse: (engine.jsonRpcClient.currentConnection.hostAddress === model.address || model.address === "0.0.0.0")
&& engine.jsonRpcClient.currentConnection.port === model.port
canDelete: !inUse
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.tcpServerConfigurations.get(index).clone() });
@ -61,7 +64,6 @@ SettingsPageBase {
popup.open()
}
onDeleteClicked: {
print("should delete")
engine.nymeaConfiguration.deleteTcpServerConfiguration(model.id)
}
}
@ -93,7 +95,10 @@ SettingsPageBase {
model: engine.nymeaConfiguration.webSocketServerConfigurations
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
canDelete: true
iconColor: inUse ? app.accentColor : iconKeyColor
readonly property bool inUse: (engine.jsonRpcClient.currentConnection.hostAddress === model.address || model.address === "0.0.0.0")
&& engine.jsonRpcClient.currentConnection.port === model.port
canDelete: !inUse
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.webSocketServerConfigurations.get(index).clone() });

View File

@ -43,7 +43,7 @@ SettingsPageBase {
for (var i = 0; i < engine.nymeaConfiguration.webServerConfigurations.count; i++) {
var tmp = engine.nymeaConfiguration.webServerConfigurations.get(i)
print("checking config:", tmp.id, tmp.address, tmp.port, tmp.sslEnabled)
if (tmp.address === engine.connection.currentConnection.hostAddress || tmp.address === "0.0.0.0") {
if (tmp.address === engine.jsonRpcClient.currentConnection.hostAddress || tmp.address === "0.0.0.0") {
// This one prefers https over http...
// if (config === null || (!config.sslEnabled && tmp.sslEnabled)) {
@ -86,7 +86,7 @@ SettingsPageBase {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("The web server cannot be reached on %1.").arg(engine.connection.currentConnection.hostAddress)
text: qsTr("The web server cannot be reached on %1.").arg(engine.jsonRpcClient.currentConnection.hostAddress)
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
color: "red"
@ -111,10 +111,10 @@ SettingsPageBase {
enabled: root.usedConfig !== null && engine.nymeaConfiguration.webServerConfigurations.count > 0
text: qsTr("Open debug interface")
onClicked: {
print("opening:", engine.connection.currentConnection.url)
print("opening:", engine.jsonRpcClient.currentConnection.url)
var proto = "http" + (root.usedConfig.sslEnabled ? "s" : "") + "://"
var path = engine.connection.currentConnection.hostAddress + ":" + root.usedConfig.port + "/debug"
var path = engine.jsonRpcClient.currentConnection.hostAddress + ":" + root.usedConfig.port + "/debug"
print("opening:", proto + path)
Qt.openUrlExternally(proto + path)
}