Commit 1604309a authored by Albert Vaca Cintora's avatar Albert Vaca Cintora

Added symmetric pairing

A KNotification asks to accept the pairing when the other ends requests it
parent 4eb5dda9
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <QDBusConnection> #include <QDBusConnection>
#include <QNetworkSession> #include <QNetworkSession>
#include <QNetworkConfigurationManager> #include <QNetworkConfigurationManager>
#include <QSslKey>
#include <KIcon> #include <KIcon>
#include <KConfigGroup> #include <KConfigGroup>
...@@ -50,6 +51,58 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&) ...@@ -50,6 +51,58 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
qDebug() << "My id:" << uuid; qDebug() << "My id:" << uuid;
} }
if (!config->group("myself").hasKey("privateKey")) {
//TODO: Generate
QByteArray key = QByteArray(
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIICXAIBAAKBgQCnKxy6aZrABVvbxuWqMPbohH4KRDBGqyO/OwxvUD1qHpqZ9cJT\n"
"bgttiIaXzdQny5esf6brI6Di/ssIp9awdLBlMT+eR6zR7g446tbxaCFuUiL0QIei\n"
"izEveTDNRbson/8DPJrn8/81doTeXsuV7YbqmtUGwdZ5kiocAW92ZZukdQIDAQAB\n"
"AoGBAI18yuLoMQdnQblBne8vZDumsDsmPaoCfc4EP2ETi/d+kaHPxTryABAkJq7j\n"
"kjZgdi6VGIUacbjOqK/Zxrcw/H460EwOUzh97Z4t9CDtDhz6t3ddT8CfbG2TUgbx\n"
"Vv3mSYSUDBdNBV6YY4fyLtZl6oI2V+rBaFIT48+vAK9doKlhAkEA2ZKm9dc80IjU\n"
"c/Wwn8ij+6ALs4Mpa0dPYivgZ2QhXiX5TfMymal2dDufkOH4wIUO+8vV8CSmmTRU\n"
"8Lv/B3pY7QJBAMSxeJtTSFwBcGRaZKRMIqeuZ/yMMT4EqqIh1DjBpujCRKApVpkO\n"
"kVx3Yu7xyOfniXBwujiYNSL6LrWdKykEsKkCQEr2UDgbtIRU4H4jhHtI8dbcSavL\n"
"4RVpOFymqWZ2BVke1EqbJC/1Ry687DlK4h3Sulre3BMlTZEziqB25WN6L/ECQBJv\n"
"B3yXG4rz35KoHhJ/yCeq4rf6c4r6aPt07Cy9iWT6/+96sFD72oet8KmwI0IIowrU\n"
"pb80FJbIl6QRrL/VXrECQBDdeCAG6J3Cwm4ozQiDQyiNd1qJqWc4co9savJxLtEU\n"
"s5L4Qwfrexm16oCJimGmsa5q6Y0n4f5gY+MRh3n+nQo=\n"
"-----END RSA PRIVATE KEY-----\n"
);
//Test for validity
//QSslKey privateKey(key.toAscii(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
//qDebug() << "Valid private key:" << !privateKey.isNull();
config->group("myself").writeEntry("privateKey", key);
}
if (!config->group("myself").hasKey("publicKey")) {
//TODO: Generate
QByteArray key = QByteArray(
"-----BEGIN PUBLIC KEY-----\n"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnKxy6aZrABVvbxuWqMPbohH4K\n"
"RDBGqyO/OwxvUD1qHpqZ9cJTbgttiIaXzdQny5esf6brI6Di/ssIp9awdLBlMT+e\n"
"R6zR7g446tbxaCFuUiL0QIeiizEveTDNRbson/8DPJrn8/81doTeXsuV7YbqmtUG\n"
"wdZ5kiocAW92ZZukdQIDAQAB\n"
"-----END PUBLIC KEY-----\n"
);
//Test for validity
//QSslKey publicKey(key.toAscii(), QSsl::Rsa, QSsl::Pem, QSsl::PublicKey);
//qDebug() << "Valid public key:" << !publicKey.isNull();
config->group("myself").writeEntry("publicKey", key);
}
//Debugging //Debugging
qDebug() << "Starting KdeConnect daemon"; qDebug() << "Starting KdeConnect daemon";
...@@ -58,13 +111,10 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&) ...@@ -58,13 +111,10 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
mLinkProviders.insert(new LoopbackLinkProvider()); mLinkProviders.insert(new LoopbackLinkProvider());
//Read remebered paired devices //Read remebered paired devices
const KConfigGroup& known = config->group("devices").group("paired"); const KConfigGroup& known = config->group("devices");
const QStringList& list = known.groupList(); const QStringList& list = known.groupList();
const QString defaultName("unnamed");
Q_FOREACH(const QString& id, list) { Q_FOREACH(const QString& id, list) {
const KConfigGroup& data = known.group(id); Device* device = new Device(id);
const QString& name = data.readEntry<QString>("name", defaultName);
Device* device = new Device(id, name);
connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceReachableStatusChanged())); connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceReachableStatusChanged()));
mDevices[id] = device; mDevices[id] = device;
Q_EMIT deviceAdded(id); Q_EMIT deviceAdded(id);
...@@ -127,16 +177,12 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* ...@@ -127,16 +177,12 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*
if (mDevices.contains(id)) { if (mDevices.contains(id)) {
qDebug() << "It is a known device"; qDebug() << "It is a known device";
Device* device = mDevices[id]; Device* device = mDevices[id];
device->addLink(dl); device->addLink(dl);
} else { } else {
qDebug() << "It is a new device"; qDebug() << "It is a new device";
const QString& name = identityPackage.get<QString>("deviceName"); Device* device = new Device(identityPackage, dl);
Device* device = new Device(id, name, dl);
connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceReachableStatusChanged())); connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceReachableStatusChanged()));
mDevices[id] = device; mDevices[id] = device;
...@@ -157,7 +203,7 @@ void Daemon::onDeviceReachableStatusChanged() ...@@ -157,7 +203,7 @@ void Daemon::onDeviceReachableStatusChanged()
if (!device->reachable()) { if (!device->reachable()) {
if (!device->paired()) { if (!device->isPaired()) {
qDebug() << "Destroying device"; qDebug() << "Destroying device";
Q_EMIT deviceRemoved(id); Q_EMIT deviceRemoved(id);
mDevices.remove(id); mDevices.remove(id);
......
...@@ -41,11 +41,19 @@ struct default_arg<int> { ...@@ -41,11 +41,19 @@ struct default_arg<int> {
}; };
//Pointer types -> NULL (partial specialization) //Pointer types -> NULL (partial specialization)
//NOTE: Comented because it doesn't makeno sense to send a pointer over the network, but I just left it here for reference --albertvaka
/*template<class T*> /*template<class T*>
struct default_arg<T*> { struct default_arg<T*> {
static T* get() { NULL; } static T* get() { NULL; }
}; };
*/ */
//QByteArray-> empty qbytearray
template<>
struct default_arg<QByteArray> {
static QByteArray get() { return QByteArray(); }
};
//QStrings -> empty string //QStrings -> empty string
template<> template<>
struct default_arg<QString> { struct default_arg<QString> {
......
...@@ -7,7 +7,10 @@ ...@@ -7,7 +7,10 @@
#include <KPluginSelector> #include <KPluginSelector>
#include <KServiceTypeTrader> #include <KServiceTypeTrader>
#include <KPluginInfo> #include <KPluginInfo>
#include <KNotification>
#include <KIcon>
#include <QSslKey>
#include <QDebug> #include <QDebug>
#include "plugins/kdeconnectplugin.h" #include "plugins/kdeconnectplugin.h"
...@@ -16,36 +19,38 @@ ...@@ -16,36 +19,38 @@
#include "linkproviders/linkprovider.h" #include "linkproviders/linkprovider.h"
#include "networkpackage.h" #include "networkpackage.h"
Device::Device(const QString& id, const QString& name) Device::Device(const QString& id)
{ {
m_deviceId = id; m_deviceId = id;
KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc");
const KConfigGroup& data = config->group("devices").group(id);
const QString& name = data.readEntry<QString>("name", QString("unnamed"));
m_deviceName = name; m_deviceName = name;
m_paired = true;
m_knownIdentiy = true;
reloadPlugins(); const QByteArray& key = data.readEntry<QByteArray>("publicKey",QByteArray());
m_publicKey = QSslKey(key, QSsl::Rsa, QSsl::Pem, QSsl::PublicKey);
m_pairingRequested = false;
//Register in bus //Register in bus
QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
} }
Device::Device(const QString& id, const QString& name, DeviceLink* link) Device::Device(const NetworkPackage& identityPackage, DeviceLink* dl)
{ {
m_deviceId = identityPackage.get<QString>("deviceId");
m_deviceName = identityPackage.get<QString>("deviceName");
m_deviceId = id; addLink(dl);
m_deviceName = name;
m_paired = false;
m_knownIdentiy = true;
addLink(link); m_pairingRequested = false;
reloadPlugins();
//Register in bus //Register in bus
QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
} }
Device::~Device() Device::~Device()
...@@ -67,7 +72,7 @@ void Device::reloadPlugins() ...@@ -67,7 +72,7 @@ void Device::reloadPlugins()
{ {
QMap< QString, KdeConnectPlugin* > newPluginMap; QMap< QString, KdeConnectPlugin* > newPluginMap;
if (paired() && reachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices if (isPaired() && reachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices
QString path = KStandardDirs().resourceDirs("config").first()+"kdeconnect/"; QString path = KStandardDirs().resourceDirs("config").first()+"kdeconnect/";
QMap<QString,QString> pluginStates = KSharedConfig::openConfig(path + id())->group("Plugins").entryMap(); QMap<QString,QString> pluginStates = KSharedConfig::openConfig(path + id())->group("Plugins").entryMap();
...@@ -114,21 +119,75 @@ void Device::reloadPlugins() ...@@ -114,21 +119,75 @@ void Device::reloadPlugins()
} }
//TODO
QSslKey myPrivateKey() {
KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc");
const QString& key = config->group("myself").readEntry<QString>("privateKey",QString());
QSslKey privateKey(key.toAscii(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
qDebug() << "Valid public key:" << !privateKey.isNull();
void Device::setPair(bool b) return privateKey;
}
void Device::requestPair()
{ {
m_paired = b; if (isPaired() || m_pairingRequested) {
Q_EMIT pairingFailed(i18n("Already paired"));
return;
}
if (!reachable()) {
Q_EMIT pairingFailed(i18n("Device not reachable"));
return;
}
//Send our own public key
NetworkPackage np(PACKAGE_TYPE_PAIR);
np.set("pair", true);
KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc"); KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc");
if (b) { const QByteArray& key = config->group("myself").readEntry<QByteArray>("publicKey",QByteArray());
qDebug() << name() << "paired"; np.set("publicKey",key);
config->group("devices").group("paired").group(id()).writeEntry("name",name()); bool success = sendPackage(np);
Q_EMIT reachableStatusChanged();
} else { if (!success) {
qDebug() << name() << "unpaired"; Q_EMIT pairingFailed(i18n("Error contacting device"));
config->group("devices").group("paired").deleteGroup(id()); return;
//Do not Q_EMIT reachableStatusChanged() because we do not want it to suddenly disappear from device list
} }
reloadPlugins();
m_pairingRequested = true;
pairingTimer.start(20 * 1000);
connect(&pairingTimer, SIGNAL(timeout()),
this, SLOT(pairingTimeout()));
}
void Device::unpair()
{
if (!isPaired()) return;
m_publicKey = QSslKey();
m_pairingRequested = false;
pairingTimer.stop();
KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc");
config->group("devices").deleteGroup(id());
if (reachable()) {
NetworkPackage np(PACKAGE_TYPE_PAIR);
np.set("pair", false);
sendPackage(np);
}
reloadPlugins(); //Will unload the plugins
}
void Device::pairingTimeout()
{
m_pairingRequested = false;
Q_EMIT Q_EMIT pairingFailed("Timed out");
} }
static bool lessThan(DeviceLink* p1, DeviceLink* p2) static bool lessThan(DeviceLink* p1, DeviceLink* p2)
...@@ -182,9 +241,10 @@ void Device::removeLink(DeviceLink* link) ...@@ -182,9 +241,10 @@ void Device::removeLink(DeviceLink* link)
bool Device::sendPackage(const NetworkPackage& np) const bool Device::sendPackage(const NetworkPackage& np) const
{ {
if (!m_paired) { //Maybe we could block here any package that is not an identity or a pairing package
//qDebug() << "sendpackage disabled on untrusted device" << name();
return false; if (isPaired()) {
} }
Q_FOREACH(DeviceLink* dl, m_deviceLinks) { Q_FOREACH(DeviceLink* dl, m_deviceLinks) {
...@@ -198,14 +258,118 @@ bool Device::sendPackage(const NetworkPackage& np) const ...@@ -198,14 +258,118 @@ bool Device::sendPackage(const NetworkPackage& np) const
void Device::privateReceivedPackage(const NetworkPackage& np) void Device::privateReceivedPackage(const NetworkPackage& np)
{ {
if (np.type() == "kdeconnect.identity" && !m_knownIdentiy) {
m_deviceName = np.get<QString>("deviceName"); if (np.type() == PACKAGE_TYPE_PAIR) {
} else if (m_paired) {
//qDebug() << "package received from trusted device" << name(); qDebug() << "Pair package";
Q_EMIT receivedPackage(np);
bool pair = np.get<bool>("pair");
if (pair == isPaired()) {
qDebug() << "Already" << (pair? "paired":"unpaired");
return;
}
KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc");
if (pair) {
if (m_pairingRequested) { //We started pairing
qDebug() << "Pair answer";
m_pairingRequested = false;
pairingTimer.stop();
//Store as trusted device
const QByteArray& key = np.get<QByteArray>("publicKey");
config->group("devices").group(id()).writeEntry("publicKey",key);
config->group("devices").group(id()).writeEntry("name",name());
m_publicKey = QSslKey(key, QSsl::Rsa, QSsl::Pem, QSsl::PublicKey);
Q_EMIT pairingSuccesful();
} else {
qDebug() << "Pair request";
const QString& key = np.get<QString>("publicKey");
m_tempPublicKey = QSslKey(key.toAscii(), QSsl::Rsa, QSsl::Pem, QSsl::PublicKey);
KNotification* notification = new KNotification("pingReceived"); //KNotification::Persistent
notification->setPixmap(KIcon("dialog-information").pixmap(48, 48));
notification->setComponentData(KComponentData("kdeconnect", "kdeconnect"));
notification->setTitle("KDE Connect");
notification->setText(i18n("Pairing request from %1", m_deviceName));
notification->setActions(QStringList() << i18n("Accept") << i18n("Reject"));
connect(notification, SIGNAL(action1Activated()), this, SLOT(acceptPairing()));
connect(notification, SIGNAL(action2Activated()), this, SLOT(rejectPairing()));
notification->sendEvent();
}
} else {
qDebug() << "Unpair request";
if (m_pairingRequested) {
pairingTimer.stop();
Q_EMIT pairingFailed(i18n("Canceled by other peer"));
}
unpair();
}
} else if (!isPaired()) {
//TODO: Alert the other side that we don't trust them
qDebug() << "device" << name() << "not paired, ignoring package" << np.type();
} else { } else {
qDebug() << "device" << name() << "not trusted, ignoring package" << np.type();
//Forward signal
Q_EMIT receivedPackage(np);
} }
}
void Device::acceptPairing()
{
qDebug() << "Accepted pairing";
KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc");
//Send our own public key
NetworkPackage np(PACKAGE_TYPE_PAIR);
np.set("pair", true);
const QByteArray& key = config->group("myself").readEntry<QByteArray>("publicKey",QByteArray());
np.set("publicKey",key);
bool success = sendPackage(np);
if (!success) {
return;
}
//Store as trusted device
m_publicKey = m_tempPublicKey;
config->group("devices").group(id()).writeEntry("publicKey", m_tempPublicKey.toPem());
config->group("devices").group(id()).writeEntry("name", name());
reloadPlugins(); //This will load plugins
}
void Device::rejectPairing()
{
qDebug() << "Rejected pairing";
NetworkPackage np(PACKAGE_TYPE_PAIR);
np.set("pair", false);
sendPackage(np);
KNotification* notification = (KNotification*)sender();
notification->setActions(QStringList());
notification->setText(i18n("Pairing rejected"));
notification->update();
} }
QStringList Device::availableLinks() const QStringList Device::availableLinks() const
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
#include <QDBusConnection> #include <QDBusConnection>
#include <QString> #include <QString>
#include <QMap> #include <QMap>
#include <QSslKey>
#include <QTimer>
#include "networkpackage.h" #include "networkpackage.h"
...@@ -40,11 +42,11 @@ class Device ...@@ -40,11 +42,11 @@ class Device
Q_PROPERTY(QString name READ name) Q_PROPERTY(QString name READ name)
public: public:
//Device known from KConfig, we trust it but we need to wait for a incoming devicelink to communicate //Read device from KConfig, we already know it but we need to wait for a incoming devicelink to communicate
Device(const QString& id, const QString& name); Device(const QString& id);
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet //Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
Device(const QString& id, const QString& name, DeviceLink* dl); Device(const NetworkPackage& np, DeviceLink* dl);
virtual ~Device(); virtual ~Device();
...@@ -57,8 +59,8 @@ public: ...@@ -57,8 +59,8 @@ public:
void removeLink(DeviceLink*); void removeLink(DeviceLink*);
Q_SCRIPTABLE QStringList availableLinks() const; Q_SCRIPTABLE QStringList availableLinks() const;
Q_SCRIPTABLE bool trusted() const { return m_paired; } Q_SCRIPTABLE bool isPaired() const { return !m_publicKey.isNull(); }
Q_SCRIPTABLE bool paired() const { return m_paired; } Q_SCRIPTABLE bool pairRequested() const { return m_pairingRequested; }
Q_SCRIPTABLE bool reachable() const { return !m_deviceLinks.empty(); } Q_SCRIPTABLE bool reachable() const { return !m_deviceLinks.empty(); }
Q_SCRIPTABLE bool hasPlugin(const QString& name); Q_SCRIPTABLE bool hasPlugin(const QString& name);
Q_SCRIPTABLE QStringList loadedPlugins(); Q_SCRIPTABLE QStringList loadedPlugins();
...@@ -69,28 +71,37 @@ Q_SIGNALS: ...@@ -69,28 +71,37 @@ Q_SIGNALS:
public Q_SLOTS: public Q_SLOTS:
virtual bool sendPackage(const NetworkPackage& np) const; virtual bool sendPackage(const NetworkPackage& np) const;
//Dbus operations called from kcm //Dbus operations
public Q_SLOTS: public Q_SLOTS:
Q_SCRIPTABLE void setPair(bool b); Q_SCRIPTABLE void requestPair();
Q_SCRIPTABLE void reloadPlugins(); //From settings Q_SCRIPTABLE void unpair();
Q_SCRIPTABLE void reloadPlugins(); //From kconf
Q_SCRIPTABLE void sendPing(); Q_SCRIPTABLE void sendPing();
void acceptPairing();
void rejectPairing();
private Q_SLOTS: private Q_SLOTS:
void privateReceivedPackage(const NetworkPackage& np);
void linkDestroyed(QObject* o = 0); void linkDestroyed(QObject* o = 0);
virtual void privateReceivedPackage(const NetworkPackage& np); void pairingTimeout();
Q_SIGNALS: Q_SIGNALS:
Q_SCRIPTABLE void reachableStatusChanged(); Q_SCRIPTABLE void reachableStatusChanged();
Q_SCRIPTABLE void pluginsChanged(); Q_SCRIPTABLE void pluginsChanged();
Q_SCRIPTABLE void pairingSuccesful();
Q_SCRIPTABLE void pairingFailed(const QString& error);
private: private:
bool m_paired;
QString m_deviceId; QString m_deviceId;
QString m_deviceName; QString m_deviceName;
QSslKey m_publicKey;
QSslKey m_tempPublicKey;
bool m_pairingRequested;
QList<DeviceLink*> m_deviceLinks; QList<DeviceLink*> m_deviceLinks;
QMap<QString, KdeConnectPlugin*> m_plugins; QMap<QString, KdeConnectPlugin*> m_plugins;
bool m_knownIdentiy;
QTimer pairingTimer;
}; };
Q_DECLARE_METATYPE(Device*) Q_DECLARE_METATYPE(Device*)
......
...@@ -48,9 +48,6 @@ public: ...@@ -48,9 +48,6 @@ public:
static void unserialize(const QByteArray&, NetworkPackage*); static void unserialize(const QByteArray&, NetworkPackage*);
QByteArray serialize() const; QByteArray serialize() const;
static void rsaUnserialize(const QByteArray&, NetworkPackage*, Qssl );
QByteArray rsaSerialize() const;
static void createIdentityPackage(NetworkPackage*); static void createIdentityPackage(NetworkPackage*);
long id() const { return mId; } long id() const { return mId; }
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#define NETWORKPACKAGETYPES_H #define NETWORKPACKAGETYPES_H
#define PACKAGE_TYPE_IDENTITY QString("kdeconnect.identity") #define PACKAGE_TYPE_IDENTITY QString("kdeconnect.identity")
#define PACKAGE_TYPE_PAIR QString("kdeconnect.pair")
#define PACKAGE_TYPE_PING QString("kdeconnect.ping") #define PACKAGE_TYPE_PING QString("kdeconnect.ping")
#define PACKAGE_TYPE_NOTIFICATION QString("kdeconnect.notification") #define PACKAGE_TYPE_NOTIFICATION QString("kdeconnect.notification")
#define PACKAGE_TYPE_BATTERY QString("kdeconnect.battery") #define PACKAGE_TYPE_BATTERY QString("kdeconnect.battery")
......
...@@ -57,6 +57,7 @@ NotificationsPlugin::~NotificationsPlugin() ...@@ -57,6 +57,7 @@ NotificationsPlugin::~NotificationsPlugin()
bool NotificationsPlugin::receivePackage(const NetworkPackage& np) bool NotificationsPlugin::receivePackage(const NetworkPackage& np)
{