improve certificate dialog

pull/368/head
Michael Zanetti 2020-04-19 15:12:21 +02:00
parent 6f1a847f15
commit 1742834cdb
7 changed files with 141 additions and 117 deletions

View File

@ -66,7 +66,6 @@ bool TcpSocketTransport::isEncrypted() const
QSslCertificate TcpSocketTransport::serverCertificate() const
{
qDebug() << "******" << m_socket.peerCertificate();
return m_socket.peerCertificate();
}

View File

@ -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 &params)
} 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.";

View File

@ -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();

View File

@ -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()
}
}
}
}

View File

@ -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(){

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 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();
}
}

View File

@ -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"