Add a verification key that's displayed when pairing

The key is a sha256 of both devices' certificates. Both should generate the
same key, so hey user can check they are pairing against the right device.

Thanks Matthias Gerstner <mgerstner@suse.de> for reporting this.
This commit is contained in:
Albert Vaca Cintora 2020-11-26 11:28:32 +01:00
parent b272ab86b3
commit e7518493df
9 changed files with 131 additions and 46 deletions

View file

@ -15,6 +15,7 @@ class PairingHandler;
class NetworkPacket; class NetworkPacket;
class LinkProvider; class LinkProvider;
class Device; class Device;
class QSslCertificate;
class DeviceLink class DeviceLink
: public QObject : public QObject
@ -44,6 +45,8 @@ public:
//The daemon will periodically destroy unpaired links if this returns false //The daemon will periodically destroy unpaired links if this returns false
virtual bool linkShouldBeKeptAlive() { return false; } virtual bool linkShouldBeKeptAlive() { return false; }
virtual QSslCertificate certificate() const = 0;
Q_SIGNALS: Q_SIGNALS:
void pairingRequest(PairingHandler* handler); void pairingRequest(PairingHandler* handler);
void pairingRequestExpired(PairingHandler* handler); void pairingRequestExpired(PairingHandler* handler);

View file

@ -164,8 +164,7 @@ void LanDeviceLink::setPairStatus(PairStatus status)
DeviceLink::setPairStatus(status); DeviceLink::setPairStatus(status);
if (status == Paired) { if (status == Paired) {
Q_ASSERT(KdeConnectConfig::instance().trustedDevices().contains(deviceId())); Q_ASSERT(KdeConnectConfig::instance().trustedDevices().contains(deviceId()));
Q_ASSERT(!m_socketLineReader->peerCertificate().isNull()); KdeConnectConfig::instance().setDeviceProperty(deviceId(), QStringLiteral("certificate"), QString::fromLatin1(certificate().toPem()));
KdeConnectConfig::instance().setDeviceProperty(deviceId(), QStringLiteral("certificate"), QString::fromLatin1(m_socketLineReader->peerCertificate().toPem().data()));
} }
} }

View file

@ -42,7 +42,7 @@ public:
bool linkShouldBeKeptAlive() override; bool linkShouldBeKeptAlive() override;
QHostAddress hostAddress() const; QHostAddress hostAddress() const;
QSslCertificate certificate() const; QSslCertificate certificate() const override;
private Q_SLOTS: private Q_SLOTS:
void dataReceived(); void dataReceived();

View file

@ -8,6 +8,7 @@
#define LOOPBACKDEVICELINK_H #define LOOPBACKDEVICELINK_H
#include "../devicelink.h" #include "../devicelink.h"
#include <QSslCertificate>
class LoopbackLinkProvider; class LoopbackLinkProvider;
@ -23,6 +24,8 @@ public:
void userRequestsPair() override { setPairStatus(Paired); } void userRequestsPair() override { setPairStatus(Paired); }
void userRequestsUnpair() override { setPairStatus(NotPaired); } void userRequestsUnpair() override { setPairStatus(NotPaired); }
QSslCertificate certificate() const override { return QSslCertificate(); }
}; };
#endif #endif

View file

@ -9,6 +9,7 @@
#include <QVector> #include <QVector>
#include <QSet> #include <QSet>
#include <QSslCertificate> #include <QSslCertificate>
#include <QSslKey>
#include <KSharedConfig> #include <KSharedConfig>
#include <KConfigGroup> #include <KConfigGroup>
@ -537,25 +538,47 @@ bool Device::isPluginEnabled(const QString& pluginName) const
QString Device::encryptionInfo() const QString Device::encryptionInfo() const
{ {
QString result; QString result;
QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha1; const QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha256;
QString localSha1 = QString::fromLatin1(KdeConnectConfig::instance().certificate().digest(digestAlgorithm).toHex()); QString localChecksum = QString::fromLatin1(KdeConnectConfig::instance().certificate().digest(digestAlgorithm).toHex());
for (int i = 2; i<localSha1.size(); i += 3) { for (int i = 2; i<localChecksum.size(); i += 3) {
localSha1.insert(i, QStringLiteral(":")); // Improve readability localChecksum.insert(i, QStringLiteral(":")); // Improve readability
} }
result += i18n("SHA1 fingerprint of your device certificate is: %1\n", localSha1); result += i18n("SHA256 fingerprint of your device certificate is: %1\n", localChecksum);
std::string remotePem = KdeConnectConfig::instance().getDeviceProperty(id(), QStringLiteral("certificate")).toStdString(); std::string remotePem = KdeConnectConfig::instance().getDeviceProperty(id(), QStringLiteral("certificate")).toStdString();
QSslCertificate remoteCertificate = QSslCertificate(QByteArray(remotePem.c_str(), (int)remotePem.size())); QSslCertificate remoteCertificate = QSslCertificate(QByteArray(remotePem.c_str(), (int)remotePem.size()));
QString remoteSha1 = QString::fromLatin1(remoteCertificate.digest(digestAlgorithm).toHex()); QString remoteChecksum = QString::fromLatin1(remoteCertificate.digest(digestAlgorithm).toHex());
for (int i = 2; i < remoteSha1.size(); i += 3) { for (int i = 2; i < remoteChecksum.size(); i += 3) {
remoteSha1.insert(i, QStringLiteral(":")); // Improve readability remoteChecksum.insert(i, QStringLiteral(":")); // Improve readability
} }
result += i18n("SHA1 fingerprint of remote device certificate is: %1\n", remoteSha1); result += i18n("SHA256 fingerprint of remote device certificate is: %1\n", remoteChecksum);
return result; return result;
} }
QSslCertificate Device::certificate() const
{
if (!d->m_deviceLinks.isEmpty()) {
return d->m_deviceLinks[0]->certificate();
}
return QSslCertificate();
}
QByteArray Device::verificationKey() const
{
auto a = KdeConnectConfig::instance().certificate().publicKey().toDer();
auto b = certificate().publicKey().toDer();
if (a < b) {
std::swap(a, b);
}
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(a);
hash.addData(b);
return hash.result().toHex();
}
QString Device::pluginIconName(const QString& pluginName) QString Device::pluginIconName(const QString& pluginName)
{ {
if (hasPlugin(pluginName)) { if (hasPlugin(pluginName)) {

View file

@ -64,6 +64,7 @@ public:
QString type() const; QString type() const;
QString iconName() const; QString iconName() const;
QString statusIconName() const; QString statusIconName() const;
Q_SCRIPTABLE QByteArray verificationKey() const;
Q_SCRIPTABLE QString encryptionInfo() const; Q_SCRIPTABLE QString encryptionInfo() const;
//Add and remove links //Add and remove links
@ -132,6 +133,7 @@ private: //Methods
void setName(const QString& name); void setName(const QString& name);
void setType(const QString& strtype); void setType(const QString& strtype);
QString iconForStatus(bool reachable, bool paired) const; QString iconForStatus(bool reachable, bool paired) const;
QSslCertificate certificate() const;
private: private:
class DevicePrivate; class DevicePrivate;

View file

@ -49,14 +49,17 @@ public:
notification->setIconName(QStringLiteral("dialog-information")); notification->setIconName(QStringLiteral("dialog-information"));
notification->setComponentName(QStringLiteral("kdeconnect")); notification->setComponentName(QStringLiteral("kdeconnect"));
notification->setTitle(QStringLiteral("KDE Connect")); notification->setTitle(QStringLiteral("KDE Connect"));
notification->setText(i18n("Pairing request from %1", device->name().toHtmlEscaped())); notification->setText(i18n("Pairing request from %1\n<br/>Key: %2...", device->name().toHtmlEscaped(), QString::fromUtf8(device->verificationKey().left(8))));
notification->setDefaultAction(i18n("Open")); notification->setDefaultAction(i18n("Open"));
notification->setActions(QStringList() << i18n("Accept") << i18n("Reject")); notification->setActions(QStringList() << i18n("Accept") << i18n("Reject") << i18n("View key"));
connect(notification, &KNotification::action1Activated, device, &Device::acceptPairing); connect(notification, &KNotification::action1Activated, device, &Device::acceptPairing);
connect(notification, &KNotification::action2Activated, device, &Device::rejectPairing); connect(notification, &KNotification::action2Activated, device, &Device::rejectPairing);
connect(notification, QOverload<>::of(&KNotification::activated), this, [this, device] { QString deviceId = device->id();
openConfiguration(device->id()); auto openSettings = [this, deviceId] {
}); openConfiguration(deviceId);
};
connect(notification, &KNotification::action3Activated, openSettings);
connect(notification, QOverload<>::of(&KNotification::activated), openSettings);
notification->sendEvent(); notification->sendEvent();
} }

View file

@ -221,6 +221,7 @@ void KdeConnectKcm::resetDeviceView()
delete kcmUi->pluginSelector; delete kcmUi->pluginSelector;
kcmUi->pluginSelector = new KPluginSelector(this); kcmUi->pluginSelector = new KPluginSelector(this);
kcmUi->deviceInfo_layout->addWidget(kcmUi->pluginSelector); kcmUi->deviceInfo_layout->addWidget(kcmUi->pluginSelector);
kcmUi->verificationKey->setText(i18n("Key: %1", QString::fromUtf8(currentDevice->verificationKey())));
kcmUi->pluginSelector->setConfigurationArguments(QStringList(currentDevice->id())); kcmUi->pluginSelector->setConfigurationArguments(QStringList(currentDevice->id()));

View file

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>965</width> <width>1198</width>
<height>528</height> <height>528</height>
</rect> </rect>
</property> </property>
@ -143,34 +143,85 @@
<enum>QLayout::SetMaximumSize</enum> <enum>QLayout::SetMaximumSize</enum>
</property> </property>
<item> <item>
<widget class="QLabel" name="name_label"> <layout class="QVBoxLayout" name="verticalLayout_2">
<property name="font"> <item>
<font> <widget class="QWidget" name="deviceAndStatus" native="true">
<pointsize>10</pointsize> <property name="sizePolicy">
<weight>75</weight> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<bold>true</bold> <horstretch>0</horstretch>
</font> <verstretch>0</verstretch>
</property> </sizepolicy>
<property name="text"> </property>
<string>Device</string> <layout class="QHBoxLayout" name="deviceAndStatus_layout">
</property> <property name="spacing">
<property name="textFormat"> <number>6</number>
<enum>Qt::PlainText</enum> </property>
</property> <property name="leftMargin">
</widget> <number>0</number>
</item> </property>
<item> <property name="topMargin">
<widget class="QLabel" name="status_label"> <number>0</number>
<property name="sizePolicy"> </property>
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <property name="rightMargin">
<horstretch>0</horstretch> <number>0</number>
<verstretch>0</verstretch> </property>
</sizepolicy> <property name="bottomMargin">
</property> <number>0</number>
<property name="text"> </property>
<string>(status)</string> <item>
</property> <widget class="QLabel" name="name_label">
</widget> <property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Device</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="status_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>(status)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="verificationKey">
<property name="text">
<string>🔑 abababab</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">