summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_bluez.cpp
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@theqtcompany.com>2016-01-19 18:06:24 +0100
committerChristian Kandeler <christian.kandeler@theqtcompany.com>2016-02-03 11:30:29 +0000
commitc516f6157a35fbabcd204dd628a301734fc76f1a (patch)
tree1533a040e7e99e38d60ef050febef54e47761573 /src/bluetooth/qlowenergycontroller_bluez.cpp
parent92c7a1c5716d47b1e33e5dfbbe3ea89f0557aca5 (diff)
Bluetooth LE: Add support for Signed Write command.
- This is how we get at the signature resolving key: 1) On connection from a client, we read the key from the respective BlueZ settings file (BlueZ 5 only, as I did not manage to find out where BlueZ 4 keeps this information). 2) Also monitor the HCI traffic for key updates (due to re-pairing). - While there is an autotest for the actual hashing procedure, the overall feature cannot be easily tested for various reasons (there is no signed write support in our client API, for one). However, to help with manual testing, the server part of our autotest now exposes a characteristic that supports signed writes. - This feature requires a Linux kernel >= 3.7. Change-Id: I7ede9b430de167fe1f4519eedf8670d88d79aa25 Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_bluez.cpp')
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp115
1 files changed, 110 insertions, 5 deletions
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index 1562c3f9..06586192 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -38,13 +38,16 @@
**
****************************************************************************/
+#include "lecmacverifier_p.h"
#include "qlowenergycontroller_p.h"
#include "qbluetoothsocket_p.h"
#include "qleadvertiser_p.h"
#include "bluez/bluez_data_p.h"
#include "bluez/hcimanager_p.h"
+#include <QtCore/QFileInfo>
#include <QtCore/QLoggingCategory>
+#include <QtCore/QSettings>
#include <QtBluetooth/QBluetoothLocalDevice>
#include <QtBluetooth/QBluetoothSocket>
#include <QtBluetooth/QLowEnergyCharacteristicData>
@@ -60,8 +63,6 @@
#include <sys/socket.h>
#include <unistd.h>
-#define ATTRIBUTE_CHANNEL_ID 4
-
#define ATT_DEFAULT_LE_MTU 23
#define ATT_MAX_LE_MTU 0x200
@@ -271,6 +272,7 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
connect(hciManager, SIGNAL(encryptionChangedEvent(QBluetoothAddress,bool)),
this, SLOT(encryptionChangedEvent(QBluetoothAddress,bool)));
hciManager->monitorEvent(HciManager::LeMetaEvent);
+ hciManager->monitorAclPackets();
connect(hciManager, &HciManager::connectionComplete, [this](quint16 handle) {
connectionHandle = handle;
qCDebug(QT_BT_BLUEZ) << "received connection complete event, handle:" << handle;
@@ -281,11 +283,22 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
emit q_ptr->connectionUpdated(params);
}
);
+ connect(hciManager, &HciManager::signatureResolvingKeyReceived,
+ [this](quint16 handle, const quint128 &csrk) {
+ if (handle == connectionHandle) {
+ qCDebug(QT_BT_BLUEZ) << "received new signature resolving key"
+ << QByteArray(reinterpret_cast<const char *>(csrk.data),
+ sizeof csrk).toHex();
+ signingData.insert(remoteDevice.toUInt64(), SigningData(csrk));
+ }
+ }
+ );
}
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
{
closeServerSocket();
+ delete cmacVerifier;
}
class ServerSocket
@@ -337,6 +350,7 @@ private:
int m_socket = -1;
};
+
void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &params,
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
@@ -2462,9 +2476,32 @@ void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &
qCWarning(QT_BT_BLUEZ) << "Ignoring signed write on encrypted link.";
return;
}
- // const QByteArray signature = packet.right(12);
- qCWarning(QT_BT_BLUEZ) << "signed write not implemented, ignoring.";
- return; // TODO: Check signature and continue if it's valid. Check and update sign counter.
+ const auto signingDataIt = signingData.find(remoteDevice.toUInt64());
+ if (signingDataIt == signingData.constEnd()) {
+ qCWarning(QT_BT_BLUEZ) << "No CSRK found for peer device, ignoring signed write";
+ return;
+ }
+
+ const quint32 signCounter = getBtData<quint32>(packet.data() + packet.count() - 12);
+ if (signCounter < signingDataIt.value().counter + 1) {
+ qCWarning(QT_BT_BLUEZ) << "Client's' sign counter" << signCounter
+ << "not greater than local sign counter"
+ << signingDataIt.value().counter
+ << "; ignoring signed write command.";
+ return;
+ }
+
+ const quint64 macFromClient = getBtData<quint64>(packet.data() + packet.count() - 8);
+ const bool signatureCorrect = verifyMac(packet.left(packet.count() - 12),
+ signingDataIt.value().key, signCounter, macFromClient);
+ if (!signatureCorrect) {
+ qCWarning(QT_BT_BLUEZ) << "Signed Write packet has wrong signature, disconnecting";
+ disconnectFromDevice(); // Recommended by spec v4.2, Vol 3, part C, 10.4.2
+ return;
+ }
+
+ signingDataIt.value().counter = signCounter;
+ storeSignCounter();
valueLength = packet.count() - 15;
} else {
valueLength = packet.count() - 3;
@@ -2678,6 +2715,7 @@ void QLowEnergyControllerPrivate::handleConnectionRequest()
l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol,
QBluetoothSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered);
restoreClientConfigurations();
+ loadSigningDataIfNecessary();
setState(QLowEnergyController::ConnectedState);
}
@@ -2773,6 +2811,64 @@ void QLowEnergyControllerPrivate::restoreClientConfigurations()
sendNextIndication();
}
+void QLowEnergyControllerPrivate::loadSigningDataIfNecessary()
+{
+ const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64());
+ if (signingDataIt != signingData.constEnd())
+ return; // We are up to date for this device.
+ const QString settingsFilePath = keySettingsFilePath();
+ if (!QFileInfo(settingsFilePath).exists()) {
+ qCDebug(QT_BT_BLUEZ) << "No settings found for peer device.";
+ return;
+ }
+ QSettings settings(settingsFilePath, QSettings::IniFormat);
+ settings.beginGroup(QLatin1String("RemoteSignatureKey"));
+ const QByteArray keyString = settings.value(QLatin1String("Key")).toByteArray();
+ if (keyString.isEmpty()) {
+ qCDebug(QT_BT_BLUEZ) << "No remote signature key found in settings file";
+ return;
+ }
+ const QByteArray keyData = QByteArray::fromHex(keyString);
+ if (keyData.count() != int(sizeof(quint128))) {
+ qCWarning(QT_BT_BLUEZ) << "Remote signature key in settings file has invalid size"
+ << keyString.count();
+ return;
+ }
+ qCDebug(QT_BT_BLUEZ) << "CSRK of peer device is" << keyString;
+ const quint32 counter = settings.value(QLatin1String("Counter"), 0).toUInt();
+ quint128 csrk;
+ using namespace std;
+ memcpy(csrk.data, keyData.constData(), keyData.count());
+ signingData.insert(remoteDevice.toUInt64(), SigningData(csrk, counter - 1));
+}
+
+void QLowEnergyControllerPrivate::storeSignCounter()
+{
+ const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64());
+ if (signingDataIt == signingData.constEnd())
+ return;
+ const QString settingsFilePath = keySettingsFilePath();
+ if (!QFileInfo(settingsFilePath).exists())
+ return;
+ QSettings settings(settingsFilePath, QSettings::IniFormat);
+ if (!settings.isWritable())
+ return;
+ settings.beginGroup(QLatin1String("RemoteSignatureKey"));
+ const QString counterKey = QLatin1String("Counter");
+ if (!settings.allKeys().contains(counterKey))
+ return;
+ const quint32 counterValue = signingDataIt.value().counter + 1;
+ if (counterValue == settings.value(counterKey).toUInt())
+ return;
+ settings.setValue(counterKey, counterValue);
+}
+
+QString QLowEnergyControllerPrivate::keySettingsFilePath() const
+{
+ return QString::fromLatin1("/var/lib/bluetooth/%1/%2/info")
+ .arg(localAdapter.toString(), remoteDevice.toString());
+}
+
static QByteArray uuidToByteArray(const QBluetoothUuid &uuid)
{
QByteArray ba;
@@ -2999,4 +3095,13 @@ int QLowEnergyControllerPrivate::checkReadPermissions(QVector<Attribute> &attrib
return 0;
}
+bool QLowEnergyControllerPrivate::verifyMac(const QByteArray &message, const quint128 &csrk,
+ quint32 signCounter, quint64 expectedMac)
+{
+ if (!cmacVerifier)
+ cmacVerifier = new LeCmacVerifier;
+ return cmacVerifier->verify(LeCmacVerifier::createFullMessage(message, signCounter), csrk,
+ expectedMac);
+}
+
QT_END_NAMESPACE