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 @@
#include <QDBusConnection>
#include <QNetworkSession>
#include <QNetworkConfigurationManager>
#include <QSslKey>
#include <KIcon>
#include <KConfigGroup>
......@@ -50,6 +51,58 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
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
qDebug() << "Starting KdeConnect daemon";
......@@ -58,13 +111,10 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
mLinkProviders.insert(new LoopbackLinkProvider());
//Read remebered paired devices
const KConfigGroup& known = config->group("devices").group("paired");
const KConfigGroup& known = config->group("devices");
const QStringList& list = known.groupList();
const QString defaultName("unnamed");
Q_FOREACH(const QString& id, list) {
const KConfigGroup& data = known.group(id);
const QString& name = data.readEntry<QString>("name", defaultName);
Device* device = new Device(id, name);
Device* device = new Device(id);
connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceReachableStatusChanged()));
mDevices[id] = device;
Q_EMIT deviceAdded(id);
......@@ -127,16 +177,12 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*
if (mDevices.contains(id)) {
qDebug() << "It is a known device";
Device* device = mDevices[id];
device->addLink(dl);
} else {
qDebug() << "It is a new device";
const QString& name = identityPackage.get<QString>("deviceName");
Device* device = new Device(id, name, dl);
Device* device = new Device(identityPackage, dl);
connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceReachableStatusChanged()));
mDevices[id] = device;
......@@ -157,7 +203,7 @@ void Daemon::onDeviceReachableStatusChanged()
if (!device->reachable()) {
if (!device->paired()) {
if (!device->isPaired()) {
qDebug() << "Destroying device";
Q_EMIT deviceRemoved(id);
mDevices.remove(id);
......
......@@ -41,11 +41,19 @@ struct default_arg<int> {
};
//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*>
struct default_arg<T*> {
static T* get() { NULL; }
};
*/
//QByteArray-> empty qbytearray
template<>
struct default_arg<QByteArray> {
static QByteArray get() { return QByteArray(); }
};
//QStrings -> empty string
template<>
struct default_arg<QString> {
......
......@@ -7,7 +7,10 @@
#include <KPluginSelector>
#include <KServiceTypeTrader>
#include <KPluginInfo>
#include <KNotification>
#include <KIcon>
#include <QSslKey>
#include <QDebug>
#include "plugins/kdeconnectplugin.h"
......@@ -16,36 +19,38 @@
#include "linkproviders/linkprovider.h"
#include "networkpackage.h"
Device::Device(const QString& id, const QString& name)
Device::Device(const QString& 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_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
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;
m_deviceName = name;
m_paired = false;
m_knownIdentiy = true;
addLink(dl);
addLink(link);
reloadPlugins();
m_pairingRequested = false;
//Register in bus
QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
}
Device::~Device()
......@@ -67,7 +72,7 @@ void Device::reloadPlugins()
{
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/";
QMap<QString,QString> pluginStates = KSharedConfig::openConfig(path + id())->group("Plugins").entryMap();
......@@ -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");
if (b) {
qDebug() << name() << "paired";
config->group("devices").group("paired").group(id()).writeEntry("name",name());
Q_EMIT reachableStatusChanged();
} else {
qDebug() << name() << "unpaired";
config->group("devices").group("paired").deleteGroup(id());
//Do not Q_EMIT reachableStatusChanged() because we do not want it to suddenly disappear from device list
const QByteArray& key = config->group("myself").readEntry<QByteArray>("publicKey",QByteArray());
np.set("publicKey",key);
bool success = sendPackage(np);
if (!success) {
Q_EMIT pairingFailed(i18n("Error contacting device"));
return;
}
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)
......@@ -182,9 +241,10 @@ void Device::removeLink(DeviceLink* link)
bool Device::sendPackage(const NetworkPackage& np) const
{
if (!m_paired) {
//qDebug() << "sendpackage disabled on untrusted device" << name();
return false;
//Maybe we could block here any package that is not an identity or a pairing package
if (isPaired()) {
}
Q_FOREACH(DeviceLink* dl, m_deviceLinks) {
......@@ -198,14 +258,118 @@ bool Device::sendPackage(const NetworkPackage& np) const
void Device::privateReceivedPackage(const NetworkPackage& np)
{
if (np.type() == "kdeconnect.identity" && !m_knownIdentiy) {
m_deviceName = np.get<QString>("deviceName");
} else if (m_paired) {
//qDebug() << "package received from trusted device" << name();
Q_EMIT receivedPackage(np);
if (np.type() == PACKAGE_TYPE_PAIR) {
qDebug() << "Pair package";
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 {
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
......
......@@ -25,6 +25,8 @@
#include <QDBusConnection>
#include <QString>
#include <QMap>
#include <QSslKey>
#include <QTimer>
#include "networkpackage.h"
......@@ -40,11 +42,11 @@ class Device
Q_PROPERTY(QString name READ name)
public:
//Device known from KConfig, we trust it but we need to wait for a incoming devicelink to communicate
Device(const QString& id, const QString& name);
//Read device from KConfig, we already know it but we need to wait for a incoming devicelink to communicate
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(const QString& id, const QString& name, DeviceLink* dl);
Device(const NetworkPackage& np, DeviceLink* dl);
virtual ~Device();
......@@ -57,8 +59,8 @@ public:
void removeLink(DeviceLink*);
Q_SCRIPTABLE QStringList availableLinks() const;
Q_SCRIPTABLE bool trusted() const { return m_paired; }
Q_SCRIPTABLE bool paired() const { return m_paired; }
Q_SCRIPTABLE bool isPaired() const { return !m_publicKey.isNull(); }
Q_SCRIPTABLE bool pairRequested() const { return m_pairingRequested; }
Q_SCRIPTABLE bool reachable() const { return !m_deviceLinks.empty(); }
Q_SCRIPTABLE bool hasPlugin(const QString& name);
Q_SCRIPTABLE QStringList loadedPlugins();
......@@ -69,28 +71,37 @@ Q_SIGNALS:
public Q_SLOTS:
virtual bool sendPackage(const NetworkPackage& np) const;
//Dbus operations called from kcm
//Dbus operations
public Q_SLOTS:
Q_SCRIPTABLE void setPair(bool b);
Q_SCRIPTABLE void reloadPlugins(); //From settings
Q_SCRIPTABLE void requestPair();
Q_SCRIPTABLE void unpair();
Q_SCRIPTABLE void reloadPlugins(); //From kconf
Q_SCRIPTABLE void sendPing();
void acceptPairing();
void rejectPairing();
private Q_SLOTS:
void privateReceivedPackage(const NetworkPackage& np);
void linkDestroyed(QObject* o = 0);
virtual void privateReceivedPackage(const NetworkPackage& np);
void pairingTimeout();
Q_SIGNALS:
Q_SCRIPTABLE void reachableStatusChanged();
Q_SCRIPTABLE void pluginsChanged();
Q_SCRIPTABLE void pairingSuccesful();
Q_SCRIPTABLE void pairingFailed(const QString& error);
private:
bool m_paired;
QString m_deviceId;
QString m_deviceName;
QSslKey m_publicKey;
QSslKey m_tempPublicKey;
bool m_pairingRequested;
QList<DeviceLink*> m_deviceLinks;
QMap<QString, KdeConnectPlugin*> m_plugins;
bool m_knownIdentiy;
QTimer pairingTimer;
};
Q_DECLARE_METATYPE(Device*)
......
......@@ -48,9 +48,6 @@ public:
static void unserialize(const QByteArray&, NetworkPackage*);
QByteArray serialize() const;
static void rsaUnserialize(const QByteArray&, NetworkPackage*, Qssl );
QByteArray rsaSerialize() const;
static void createIdentityPackage(NetworkPackage*);
long id() const { return mId; }
......
......@@ -22,6 +22,7 @@
#define NETWORKPACKAGETYPES_H
#define PACKAGE_TYPE_IDENTITY QString("kdeconnect.identity")
#define PACKAGE_TYPE_PAIR QString("kdeconnect.pair")
#define PACKAGE_TYPE_PING QString("kdeconnect.ping")
#define PACKAGE_TYPE_NOTIFICATION QString("kdeconnect.notification")
#define PACKAGE_TYPE_BATTERY QString("kdeconnect.battery")
......
......@@ -57,6 +57,7 @@ NotificationsPlugin::~NotificationsPlugin()
bool NotificationsPlugin::receivePackage(const NetworkPackage& np)
{
if (np.type() != PACKAGE_TYPE_NOTIFICATION) return false;
if (np.get<bool>("request")) return false;
notificationsDbusInterface->processPackage(np);
......
......@@ -59,6 +59,8 @@ KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&)
kcmUi->deviceList->setModel(sortProxyModel);
kcmUi->deviceInfo->setVisible(false);
kcmUi->progressBar->setVisible(false);
kcmUi->messages->setVisible(false);
setButtons(KCModule::NoAdditionalButton);
......@@ -66,12 +68,12 @@ KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&)
this, SLOT(resetSelection()));
connect(kcmUi->deviceList, SIGNAL(pressed(QModelIndex)),
this, SLOT(deviceSelected(QModelIndex)));
connect(kcmUi->pair_button, SIGNAL(pressed()),
this, SLOT(requestPair()));
connect(kcmUi->unpair_button, SIGNAL(pressed()),
this, SLOT(unpair()));
connect(kcmUi->ping_button, SIGNAL(pressed()),
this, SLOT(sendPing()));
connect(kcmUi->trust_checkbox, SIGNAL(toggled(bool)),
this, SLOT(trustedStateChanged(bool)));
}
......@@ -85,6 +87,7 @@ void KdeConnectKcm::resetSelection()
kcmUi->deviceList->selectionModel()->setCurrentIndex(sortProxyModel->mapFromSource(currentIndex), QItemSelectionModel::ClearAndSelect);
}
void KdeConnectKcm::deviceSelected(const QModelIndex& current)
{
......@@ -105,13 +108,33 @@ void KdeConnectKcm::deviceSelected(const QModelIndex& current)
return;
}
kcmUi->messages->setVisible(false);
if (currentDevice->pairRequested()) {
kcmUi->progressBar->setVisible(true);
kcmUi->unpair_button->setVisible(false);
kcmUi->pair_button->setVisible(false);
kcmUi->ping_button->setVisible(false);
} else {
kcmUi->progressBar->setVisible(false);
if (currentDevice->isPaired()) {
kcmUi->unpair_button->setVisible(true);
kcmUi->pair_button->setVisible(false);
kcmUi->ping_button->setVisible(true);
} else {
kcmUi->unpair_button->setVisible(false);
kcmUi->pair_button->setVisible(true);
kcmUi->ping_button->setVisible(false);
}
}
//FIXME: KPluginSelector has no way to remove a list of plugins and load another, so we need to destroy and recreate it each time
delete kcmUi->pluginSelector;
kcmUi->pluginSelector = new KPluginSelector(this);
kcmUi->verticalLayout_2->addWidget(kcmUi->pluginSelector);
kcmUi->deviceName->setText(currentDevice->name());
kcmUi->trust_checkbox->setChecked(currentDevice->paired());
kcmUi->name_label->setText(currentDevice->name());
kcmUi->status_label->setText(currentDevice->isPaired()? "paired" : "unpaired");
KService::List offers = KServiceTypeTrader::self()->query("KdeConnect/Plugin");
QList<KPluginInfo> scriptinfos = KPluginInfo::fromServices(offers);
......@@ -124,22 +147,58 @@ void KdeConnectKcm::deviceSelected(const QModelIndex& current)
this, SLOT(pluginsConfigChanged()));
}
void KdeConnectKcm::trustedStateChanged(bool b)
void KdeConnectKcm::requestPair()
{
if (!currentDevice) return;
QDBusPendingReply<void> pendingReply = currentDevice->setPair(b);
pendingReply.waitForFinished();
if (pendingReply.isValid()) {
//If dbus was down, calling this would make kcm crash
devicesModel->deviceStatusChanged(currentDevice->id());
} else {
//Revert checkbox
disconnect(kcmUi->trust_checkbox, SIGNAL(toggled(bool)),
this, SLOT(trustedStateChanged(bool)));
kcmUi->trust_checkbox->setCheckState(b? Qt::Unchecked : Qt::Checked);
connect(kcmUi->trust_checkbox, SIGNAL(toggled(bool)),
this, SLOT(trustedStateChanged(bool)));
}
kcmUi->messages->hide();
kcmUi->pair_button->setVisible(false);
kcmUi->progressBar->setVisible(true);
connect(currentDevice,SIGNAL(pairingSuccesful()),
this, SLOT(pairingSuccesful()));
connect(currentDevice,SIGNAL(pairingFailed(QString)),
this, SLOT(pairingFailed(QString)));
currentDevice->requestPair();
}
void KdeConnectKcm::unpair()
{
if (!currentDevice) return;
kcmUi->pair_button->setVisible(true);
kcmUi->unpair_button->setVisible(false);
kcmUi->progressBar->setVisible(false);
kcmUi->ping_button->setVisible(false);
currentDevice->unpair();
kcmUi->status_label->setText("(unpaired)");