improve certificate dialog
parent
6f1a847f15
commit
1742834cdb
|
|
@ -66,7 +66,6 @@ bool TcpSocketTransport::isEncrypted() const
|
|||
|
||||
QSslCertificate TcpSocketTransport::serverCertificate() const
|
||||
{
|
||||
qDebug() << "******" << m_socket.peerCertificate();
|
||||
return m_socket.peerCertificate();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue