diff options
author | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2016-01-19 18:06:24 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2016-02-03 11:30:29 +0000 |
commit | c516f6157a35fbabcd204dd628a301734fc76f1a (patch) | |
tree | 1533a040e7e99e38d60ef050febef54e47761573 /src/bluetooth/qlowenergycontroller_bluez.cpp | |
parent | 92c7a1c5716d47b1e33e5dfbbe3ea89f0557aca5 (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.cpp | 115 |
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 ¶ms, 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 |