diff --git a/libnymea-app/connection/tcpsockettransport.cpp b/libnymea-app/connection/tcpsockettransport.cpp index d06b2fba..0a93c7e4 100644 --- a/libnymea-app/connection/tcpsockettransport.cpp +++ b/libnymea-app/connection/tcpsockettransport.cpp @@ -66,7 +66,6 @@ bool TcpSocketTransport::isEncrypted() const QSslCertificate TcpSocketTransport::serverCertificate() const { - qDebug() << "******" << m_socket.peerCertificate(); return m_socket.peerCertificate(); } diff --git a/libnymea-app/jsonrpc/jsonrpcclient.cpp b/libnymea-app/jsonrpc/jsonrpcclient.cpp index 1f88faaa..6df4d6ae 100644 --- a/libnymea-app/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app/jsonrpc/jsonrpcclient.cpp @@ -226,6 +226,27 @@ 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; @@ -587,31 +608,16 @@ void JsonRpcClient::helloReply(const QVariantMap ¶ms) } 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(); - qWarning() << "This connections certificate has changed!" << certificate; + QVariantMap issuerInfo = certificateIssuerInfo(); + emit verifyConnectionCertificate(m_serverUuid, issuerInfo, certificate.toPem()); // Reject the connection until the UI explicitly accepts this... m_connection->disconnectFromHost(); - QStringList info; - info << tr("Common Name:") << certificate.issuerInfo(QSslCertificate::CommonName); - info << tr("Oragnisation:") << certificate.issuerInfo(QSslCertificate::Organization); - info << tr("Locality:") << certificate.issuerInfo(QSslCertificate::LocalityName); - info << tr("Oragnisational Unit:")<< certificate.issuerInfo(QSslCertificate::OrganizationalUnitName); - info << tr("Country:")<< certificate.issuerInfo(QSslCertificate::CountryName); - - QByteArray certificateFingerprint; - QByteArray digest = 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()); - } - - emit verifyConnectionCertificate(m_serverUuid, info, certificateFingerprint, certificate.toPem()); return; } qDebug() << "This connections certificate is trusted."; diff --git a/libnymea-app/jsonrpc/jsonrpcclient.h b/libnymea-app/jsonrpc/jsonrpcclient.h index 889f495b..85854c23 100644 --- a/libnymea-app/jsonrpc/jsonrpcclient.h +++ b/libnymea-app/jsonrpc/jsonrpcclient.h @@ -61,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 { @@ -86,6 +87,7 @@ public: bool connected() const; NymeaHost* currentHost() const; Connection* currentConnection() const; + QVariantMap certificateIssuerInfo() const; bool initialSetupRequired() const; bool authenticationRequired() const; bool pushButtonAuthAvailable() const; @@ -120,7 +122,7 @@ signals: void currentConnectionChanged(); void handshakeReceived(); void newSslCertificate(); - void verifyConnectionCertificate(const QString &serverUuid, const QStringList &issuerInfo, const QByteArray &fingerprint, const QByteArray &pem); + void verifyConnectionCertificate(const QString &serverUuid, const QVariantMap &issuerInfo, const QByteArray &pem); void initialSetupRequiredChanged(); void authenticationRequiredChanged(); void pushButtonAuthAvailableChanged(); diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 07896e3d..0bb0096c 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -62,7 +62,7 @@ Page { return "" } onLeftButtonClicked: { - var dialog = connectionDialogComponent.createObject(root, {headerIcon: leftButtonImageSource}) + var dialog = connectionDialogComponent.createObject(root) dialog.open(); } @@ -406,6 +406,23 @@ Page { id: connectionDialog 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 @@ -422,21 +439,44 @@ Page { wrapMode: Text.WrapAtWordBoundaryOrAnywhere horizontalAlignment: Text.AlignHCenter } - Label { + + Item { Layout.fillWidth: true - text: engine.jsonRpcClient.currentHost.uuid - font.pixelSize: app.smallFont - elide: Text.ElideRight - color: Material.color(Material.Grey) - horizontalAlignment: Text.AlignHCenter + Layout.preferredHeight: app.margins } - Label { - Layout.fillWidth: true - text: engine.jsonRpcClient.currentConnection.url - font.pixelSize: app.smallFont - elide: Text.ElideRight - color: Material.color(Material.Grey) - horizontalAlignment: Text.AlignHCenter + + RowLayout { + 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 { @@ -446,16 +486,6 @@ Page { RowLayout { Layout.fillWidth: true - Button { - id: cancelButton - text: qsTr("OK") - Layout.preferredWidth: Math.max(cancelButton.implicitWidth, disconnectButton.implicitWidth) - onClicked: connectionDialog.close() - } - - Item { - Layout.fillWidth: true - } Button { id: disconnectButton @@ -466,6 +496,15 @@ Page { engine.jsonRpcClient.disconnectFromHost(); } } + Item { + Layout.fillWidth: true + } + Button { + id: cancelButton + text: qsTr("OK") + Layout.preferredWidth: Math.max(cancelButton.implicitWidth, disconnectButton.implicitWidth) + onClicked: connectionDialog.close() + } } } } diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 5df8568b..09bb1b29 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -247,7 +247,7 @@ Item { init(); } onVerifyConnectionCertificate: { - print("Asking user to verify certificate:", serverUuid, issuerInfo, fingerprint, 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(){ diff --git a/nymea-app/ui/connection/CertificateDialog.qml b/nymea-app/ui/connection/CertificateDialog.qml index b832d937..85fcf792 100644 --- a/nymea-app/ui/connection/CertificateDialog.qml +++ b/nymea-app/ui/connection/CertificateDialog.qml @@ -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 serverUuid - property var fingerprint 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.jsonRpcClient.acceptCertificate(certDialog.serverUuid, certDialog.pem) - } - onRejected: { - engine.connection.disconnect(); - } } diff --git a/nymea-app/ui/connection/ConnectPage.qml b/nymea-app/ui/connection/ConnectPage.qml index 539abb2f..2e8a5ff8 100644 --- a/nymea-app/ui/connection/ConnectPage.qml +++ b/nymea-app/ui/connection/ConnectPage.qml @@ -185,10 +185,8 @@ Page { prominentSubText: false progressive: false property bool isSecure: nymeaHost.connections.get(defaultConnectionIndex).secure - property bool isTrusted: engine.jsonRpcClient.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" @@ -387,13 +385,13 @@ 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.jsonRpcClient.isTrusted(url) secondaryIconName: !model.online ? "../images/cloud-error.svg" : "" secondaryIconColor: "red"