improve certificate dialog
This commit is contained in:
parent
6f1a847f15
commit
1742834cdb
@ -66,7 +66,6 @@ bool TcpSocketTransport::isEncrypted() const
|
|||||||
|
|
||||||
QSslCertificate TcpSocketTransport::serverCertificate() const
|
QSslCertificate TcpSocketTransport::serverCertificate() const
|
||||||
{
|
{
|
||||||
qDebug() << "******" << m_socket.peerCertificate();
|
|
||||||
return m_socket.peerCertificate();
|
return m_socket.peerCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -226,6 +226,27 @@ Connection *JsonRpcClient::currentConnection() const
|
|||||||
return m_connection->currentConnection();
|
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
|
bool JsonRpcClient::initialSetupRequired() const
|
||||||
{
|
{
|
||||||
return m_initialSetupRequired;
|
return m_initialSetupRequired;
|
||||||
@ -587,31 +608,16 @@ void JsonRpcClient::helloReply(const QVariantMap ¶ms)
|
|||||||
} else {
|
} else {
|
||||||
// We have a certificate pinned already. Check if it's the same
|
// We have a certificate pinned already. Check if it's the same
|
||||||
if (m_connection->sslCertificate().toPem() != pem) {
|
if (m_connection->sslCertificate().toPem() != pem) {
|
||||||
|
|
||||||
// Uh oh, the certificate has changed
|
// Uh oh, the certificate has changed
|
||||||
|
qWarning() << "This connections certificate has changed!";
|
||||||
|
|
||||||
QSslCertificate certificate = m_connection->sslCertificate();
|
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...
|
// Reject the connection until the UI explicitly accepts this...
|
||||||
m_connection->disconnectFromHost();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "This connections certificate is trusted.";
|
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 serverUuid READ serverUuid NOTIFY handshakeReceived)
|
||||||
Q_PROPERTY(QString serverQtVersion READ serverQtVersion NOTIFY serverQtVersionChanged)
|
Q_PROPERTY(QString serverQtVersion READ serverQtVersion NOTIFY serverQtVersionChanged)
|
||||||
Q_PROPERTY(QString serverQtBuildVersion READ serverQtBuildVersion NOTIFY serverQtVersionChanged)
|
Q_PROPERTY(QString serverQtBuildVersion READ serverQtBuildVersion NOTIFY serverQtVersionChanged)
|
||||||
|
Q_PROPERTY(QVariantMap certificateIssuerInfo READ certificateIssuerInfo NOTIFY currentConnectionChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum CloudConnectionState {
|
enum CloudConnectionState {
|
||||||
@ -86,6 +87,7 @@ public:
|
|||||||
bool connected() const;
|
bool connected() const;
|
||||||
NymeaHost* currentHost() const;
|
NymeaHost* currentHost() const;
|
||||||
Connection* currentConnection() const;
|
Connection* currentConnection() const;
|
||||||
|
QVariantMap certificateIssuerInfo() const;
|
||||||
bool initialSetupRequired() const;
|
bool initialSetupRequired() const;
|
||||||
bool authenticationRequired() const;
|
bool authenticationRequired() const;
|
||||||
bool pushButtonAuthAvailable() const;
|
bool pushButtonAuthAvailable() const;
|
||||||
@ -120,7 +122,7 @@ signals:
|
|||||||
void currentConnectionChanged();
|
void currentConnectionChanged();
|
||||||
void handshakeReceived();
|
void handshakeReceived();
|
||||||
void newSslCertificate();
|
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 initialSetupRequiredChanged();
|
||||||
void authenticationRequiredChanged();
|
void authenticationRequiredChanged();
|
||||||
void pushButtonAuthAvailableChanged();
|
void pushButtonAuthAvailableChanged();
|
||||||
|
|||||||
@ -62,7 +62,7 @@ Page {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
onLeftButtonClicked: {
|
onLeftButtonClicked: {
|
||||||
var dialog = connectionDialogComponent.createObject(root, {headerIcon: leftButtonImageSource})
|
var dialog = connectionDialogComponent.createObject(root)
|
||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,6 +406,23 @@ Page {
|
|||||||
id: connectionDialog
|
id: connectionDialog
|
||||||
title: engine.jsonRpcClient.currentHost.name
|
title: engine.jsonRpcClient.currentHost.name
|
||||||
standardButtons: Dialog.NoButton
|
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 {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -422,21 +439,44 @@ Page {
|
|||||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
Label {
|
|
||||||
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: engine.jsonRpcClient.currentHost.uuid
|
Layout.preferredHeight: app.margins
|
||||||
font.pixelSize: app.smallFont
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: Material.color(Material.Grey)
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
}
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
RowLayout {
|
||||||
text: engine.jsonRpcClient.currentConnection.url
|
ColumnLayout {
|
||||||
font.pixelSize: app.smallFont
|
Label {
|
||||||
elide: Text.ElideRight
|
Layout.fillWidth: true
|
||||||
color: Material.color(Material.Grey)
|
text: engine.jsonRpcClient.currentHost.uuid
|
||||||
horizontalAlignment: Text.AlignHCenter
|
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 {
|
Item {
|
||||||
@ -446,16 +486,6 @@ Page {
|
|||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
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 {
|
Button {
|
||||||
id: disconnectButton
|
id: disconnectButton
|
||||||
@ -466,6 +496,15 @@ Page {
|
|||||||
engine.jsonRpcClient.disconnectFromHost();
|
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();
|
init();
|
||||||
}
|
}
|
||||||
onVerifyConnectionCertificate: {
|
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 certDialogComponent = Qt.createComponent(Qt.resolvedUrl("connection/CertificateErrorDialog.qml"));
|
||||||
var popup = certDialogComponent.createObject(root);
|
var popup = certDialogComponent.createObject(root);
|
||||||
popup.accepted.connect(function(){
|
popup.accepted.connect(function(){
|
||||||
|
|||||||
@ -40,14 +40,10 @@ Dialog {
|
|||||||
width: Math.min(parent.width * .9, 400)
|
width: Math.min(parent.width * .9, 400)
|
||||||
x: (parent.width - width) / 2
|
x: (parent.width - width) / 2
|
||||||
y: (parent.height - height) / 2
|
y: (parent.height - height) / 2
|
||||||
standardButtons: Dialog.Yes | Dialog.No
|
standardButtons: Dialog.Ok
|
||||||
|
|
||||||
property string serverUuid
|
property string serverUuid
|
||||||
property var fingerprint
|
|
||||||
property var issuerInfo
|
property var issuerInfo
|
||||||
property var pem
|
|
||||||
|
|
||||||
readonly property bool hasOldFingerprint: engine.connection.isTrusted(url)
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: certLayout
|
id: certLayout
|
||||||
@ -60,84 +56,68 @@ Dialog {
|
|||||||
ColorIcon {
|
ColorIcon {
|
||||||
Layout.preferredHeight: app.iconSize * 2
|
Layout.preferredHeight: app.iconSize * 2
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
name: certDialog.hasOldFingerprint ? "../images/lock-broken.svg" : "../images/info.svg"
|
name: "../images/lock-closed.svg"
|
||||||
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
|
color: app.accentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: titleLabel
|
id: titleLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
text: certDialog.hasOldFingerprint ? qsTr("Warning") : qsTr("Hi there!")
|
text: qsTr("Certificate information")
|
||||||
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
|
color: app.accentColor
|
||||||
font.pixelSize: app.largeFont
|
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 {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.preferredHeight: app.margins
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ThinDivider {}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
text: qsTr("nymea UUID:")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
wrapMode: Text.WordWrap
|
}
|
||||||
text: certDialog.hasOldFingerprint ? qsTr("Do you want to connect nevertheless?") : qsTr("Do you want to trust this device?")
|
Label {
|
||||||
font.bold: true
|
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
|
prominentSubText: false
|
||||||
progressive: false
|
progressive: false
|
||||||
property bool isSecure: nymeaHost.connections.get(defaultConnectionIndex).secure
|
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
|
property bool isOnline: nymeaHost.connections.get(defaultConnectionIndex).bearerType !== Connection.BearerTypeWan ? nymeaHost.connections.get(defaultConnectionIndex).online : true
|
||||||
tertiaryIconName: isSecure ? "../images/network-secure.svg" : ""
|
tertiaryIconName: isSecure ? "../images/network-secure.svg" : ""
|
||||||
tertiaryIconColor: isTrusted ? app.accentColor : Material.foreground
|
|
||||||
secondaryIconName: !isOnline ? "../images/cloud-error.svg" : ""
|
secondaryIconName: !isOnline ? "../images/cloud-error.svg" : ""
|
||||||
secondaryIconColor: "red"
|
secondaryIconColor: "red"
|
||||||
|
|
||||||
@ -387,13 +385,13 @@ Page {
|
|||||||
return "../images/bluetooth.svg";
|
return "../images/bluetooth.svg";
|
||||||
case Connection.BearerTypeCloud:
|
case Connection.BearerTypeCloud:
|
||||||
return "../images/cloud.svg"
|
return "../images/cloud.svg"
|
||||||
|
case Connection.BearerTypeLoopback:
|
||||||
|
return "../images/network-wired.svg"
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
tertiaryIconName: model.secure ? "../images/network-secure.svg" : ""
|
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" : ""
|
secondaryIconName: !model.online ? "../images/cloud-error.svg" : ""
|
||||||
secondaryIconColor: "red"
|
secondaryIconColor: "red"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user