summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java4
-rw-r--r--src/bluetooth/bluetooth.pro4
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp7
-rw-r--r--src/bluetooth/bluez/hcimanager_p.h2
-rw-r--r--src/bluetooth/lecmaccalculator.cpp (renamed from src/bluetooth/lecmacverifier.cpp)40
-rw-r--r--src/bluetooth/lecmaccalculator_p.h (renamed from src/bluetooth/lecmacverifier_p.h)13
-rw-r--r--src/bluetooth/qlowenergycontroller_android.cpp8
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp129
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm4
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h2
-rw-r--r--src/bluetooth/qlowenergycontroller_p.cpp2
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h17
-rw-r--r--src/bluetooth/qlowenergyservice.cpp26
-rw-r--r--src/bluetooth/qlowenergyservice.h3
-rw-r--r--src/bluetooth/qlowenergyservice_osx.mm8
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp8
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp50
17 files changed, 212 insertions, 115 deletions
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
index 5fe5d03d..5150a083 100644
--- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
+++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
@@ -835,11 +835,13 @@ public class QtBluetoothLE {
newJob.jobType = IoJobType.Write;
// writeMode must be in sync with QLowEnergyService::WriteMode
- // For now we ignore SignedWriteType as Qt doesn't support it yet.
switch (writeMode) {
case 1: //WriteWithoutResponse
newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
break;
+ case 2: //WriteSigned
+ newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED;
+ break;
default:
newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
break;
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index 99c9fe07..604f4de8 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -51,7 +51,7 @@ PRIVATE_HEADERS += \
qlowenergycontroller_p.h \
qlowenergyserviceprivate_p.h \
qleadvertiser_p.h \
- lecmacverifier_p.h
+ lecmaccalculator_p.h
SOURCES += \
qbluetoothaddress.cpp\
@@ -104,7 +104,7 @@ config_bluez:qtHaveModule(dbus) {
SOURCES += \
qleadvertiser_bluez.cpp \
qlowenergycontroller_bluez.cpp \
- lecmacverifier.cpp
+ lecmaccalculator.cpp
config_linux_crypto_api:DEFINES += CONFIG_LINUX_CRYPTO_API
else:message("Linux crypto API not present, signed writes will not work.")
} else {
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp
index cf2f2a0f..dfb99d5a 100644
--- a/src/bluetooth/bluez/hcimanager.cpp
+++ b/src/bluetooth/bluez/hcimanager.cpp
@@ -486,8 +486,8 @@ void HciManager::handleHciAclPacket(const quint8 *data, int size)
// qCDebug(QT_BT_BLUEZ) << "handle:" << aclData->handle << "PB:" << aclData->pbFlag
// << "BC:" << aclData->bcFlag << "data len:" << aclData->dataLen;
- // Consider only directed, complete messages from controller to host (i.e. incoming packets).
- if (aclData->pbFlag != 2 || aclData->bcFlag != 0)
+ // Consider only directed, complete messages.
+ if ((aclData->pbFlag != 0 && aclData->pbFlag != 2) || aclData->bcFlag != 0)
return;
if (size < int(sizeof(L2CapHeader))) {
@@ -516,7 +516,8 @@ void HciManager::handleHciAclPacket(const quint8 *data, int size)
}
quint128 csrk;
memcpy(&csrk, data + 1, sizeof csrk);
- emit signatureResolvingKeyReceived(aclData->handle, csrk);
+ const bool isRemoteKey = aclData->pbFlag == 2;
+ emit signatureResolvingKeyReceived(aclData->handle, isRemoteKey, csrk);
}
void HciManager::handleLeMetaEvent(const quint8 *data)
diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h
index eb899c79..3bae92e5 100644
--- a/src/bluetooth/bluez/hcimanager_p.h
+++ b/src/bluetooth/bluez/hcimanager_p.h
@@ -91,7 +91,7 @@ signals:
void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data);
void connectionComplete(quint16 handle);
void connectionUpdate(quint16 handle, const QLowEnergyConnectionParameters &parameters);
- void signatureResolvingKeyReceived(quint16 connHandle, const quint128 &csrk);
+ void signatureResolvingKeyReceived(quint16 connHandle, bool remoteKey, const quint128 &csrk);
private slots:
void _q_readNotify();
diff --git a/src/bluetooth/lecmacverifier.cpp b/src/bluetooth/lecmaccalculator.cpp
index 23db951c..1cda9576 100644
--- a/src/bluetooth/lecmacverifier.cpp
+++ b/src/bluetooth/lecmaccalculator.cpp
@@ -36,7 +36,7 @@
** $QT_END_LICENSE$
**
****************************************************************************/
-#include "lecmacverifier_p.h"
+#include "lecmaccalculator_p.h"
#include "bluez/bluez_data_p.h"
@@ -57,7 +57,7 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
-LeCmacVerifier::LeCmacVerifier()
+LeCmacCalculator::LeCmacCalculator()
{
#ifdef CONFIG_LINUX_CRYPTO_API
m_baseSocket = socket(AF_ALG, SOCK_SEQPACKET, 0);
@@ -81,13 +81,13 @@ LeCmacVerifier::LeCmacVerifier()
#endif
}
-LeCmacVerifier::~LeCmacVerifier()
+LeCmacCalculator::~LeCmacCalculator()
{
if (m_baseSocket != -1)
close(m_baseSocket);
}
-QByteArray LeCmacVerifier::createFullMessage(const QByteArray &message, quint32 signCounter)
+QByteArray LeCmacCalculator::createFullMessage(const QByteArray &message, quint32 signCounter)
{
// Spec v4.2, Vol 3, Part H, 2.4.5
QByteArray fullMessage = message;
@@ -96,8 +96,7 @@ QByteArray LeCmacVerifier::createFullMessage(const QByteArray &message, quint32
return fullMessage;
}
-bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk,
- quint64 expectedMac) const
+quint64 LeCmacCalculator::calculateMac(const QByteArray &message, const quint128 &csrk) const
{
#ifdef CONFIG_LINUX_CRYPTO_API
if (m_baseSocket == -1)
@@ -108,7 +107,7 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk,
sizeof csrkMsb).toHex();
if (setsockopt(m_baseSocket, 279 /* SOL_ALG */, ALG_SET_KEY, csrkMsb.data, sizeof csrkMsb) == -1) {
qCWarning(QT_BT_BLUEZ) << "setsockopt() failed for crypto socket:" << strerror(errno);
- return false;
+ return 0;
}
class SocketWrapper
@@ -127,7 +126,7 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk,
SocketWrapper cryptoSocket(accept(m_baseSocket, nullptr, 0));
if (cryptoSocket.value() == -1) {
qCWarning(QT_BT_BLUEZ) << "accept() failed for crypto socket:" << strerror(errno);
- return false;
+ return 0;
}
QByteArray messageSwapped(message.count(), Qt::Uninitialized);
@@ -139,7 +138,7 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk,
messageSwapped.count() - totalBytesWritten);
if (bytesWritten == -1) {
qCWarning(QT_BT_BLUEZ) << "writing to crypto socket failed:" << strerror(errno);
- return false;
+ return 0;
}
totalBytesWritten += bytesWritten;
} while (totalBytesWritten < messageSwapped.count());
@@ -151,14 +150,27 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk,
sizeof mac - totalBytesRead);
if (bytesRead == -1) {
qCWarning(QT_BT_BLUEZ) << "reading from crypto socket failed:" << strerror(errno);
- return false;
+ return 0;
}
totalBytesRead += bytesRead;
} while (totalBytesRead < qint64(sizeof mac));
- mac = qFromBigEndian(mac);
- if (mac != expectedMac) {
- qCWarning(QT_BT_BLUEZ) << hex << "signature verification failed: calculated mac:" << mac
- << "expected mac:" << expectedMac;
+ return qFromBigEndian(mac);
+#else // CONFIG_LINUX_CRYPTO_API
+ Q_UNUSED(message);
+ Q_UNUSED(csrk);
+ qCWarning(QT_BT_BLUEZ) << "CMAC calculation failed due to missing Linux crypto API.";
+ return 0;
+#endif
+}
+
+bool LeCmacCalculator::verify(const QByteArray &message, const quint128 &csrk,
+ quint64 expectedMac) const
+{
+#ifdef CONFIG_LINUX_CRYPTO_API
+ const quint64 actualMac = calculateMac(message, csrk);
+ if (actualMac != expectedMac) {
+ qCWarning(QT_BT_BLUEZ) << hex << "signature verification failed: calculated mac:"
+ << actualMac << "expected mac:" << expectedMac;
return false;
}
return true;
diff --git a/src/bluetooth/lecmacverifier_p.h b/src/bluetooth/lecmaccalculator_p.h
index e09b6013..1777f637 100644
--- a/src/bluetooth/lecmacverifier_p.h
+++ b/src/bluetooth/lecmaccalculator_p.h
@@ -36,8 +36,8 @@
** $QT_END_LICENSE$
**
****************************************************************************/
-#ifndef LECMACVERIFIER_H
-#define LECMACVERIFIER_H
+#ifndef LECMACCALCULATOR_H
+#define LECMACCALCULATOR_H
//
// W A R N I N G
@@ -56,14 +56,17 @@ QT_BEGIN_NAMESPACE
struct quint128;
-class Q_AUTOTEST_EXPORT LeCmacVerifier
+class Q_AUTOTEST_EXPORT LeCmacCalculator
{
public:
- LeCmacVerifier();
- ~LeCmacVerifier();
+ LeCmacCalculator();
+ ~LeCmacCalculator();
static QByteArray createFullMessage(const QByteArray &message, quint32 signCounter);
+ quint64 calculateMac(const QByteArray &message, const quint128 &csrk) const;
+
+ // Convenience function.
bool verify(const QByteArray &message, const quint128 &csrk, quint64 expectedMac) const;
private:
diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp
index f740abc7..cd6603a3 100644
--- a/src/bluetooth/qlowenergycontroller_android.cpp
+++ b/src/bluetooth/qlowenergycontroller_android.cpp
@@ -178,7 +178,7 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QByteArray &newValue,
- bool writeWithResponse)
+ QLowEnergyService::WriteMode mode)
{
//TODO don't ignore WriteWithResponse, right now we assume responses
Q_ASSERT(!service.isNull());
@@ -196,10 +196,10 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
if (hub) {
qCDebug(QT_BT_ANDROID) << "Write characteristic with handle " << charHandle
<< newValue.toHex() << "(service:" << service->uuid
- << ", writeWithResponse:" << writeWithResponse << ")";
+ << ", writeWithResponse:" << (mode == QLowEnergyService::WriteWithResponse)
+ << ", signed:" << (mode == QLowEnergyService::WriteSigned) << ")";
result = hub->javaObject().callMethod<jboolean>("writeCharacteristic", "(I[BI)Z",
- charHandle, payload,
- writeWithResponse ? QLowEnergyService::WriteWithResponse : QLowEnergyService::WriteWithoutResponse);
+ charHandle, payload, mode);
}
if (env->ExceptionOccurred()) {
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index 729847f9..adac55fc 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -38,7 +38,7 @@
**
****************************************************************************/
-#include "lecmacverifier_p.h"
+#include "lecmaccalculator_p.h"
#include "qlowenergycontroller_p.h"
#include "qbluetoothsocket_p.h"
#include "qleadvertiser_p.h"
@@ -287,21 +287,25 @@ void QLowEnergyControllerPrivate::init()
}
);
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));
+ [this](quint16 handle, bool remoteKey, const quint128 &csrk) {
+ if (handle != connectionHandle)
+ return;
+ if ((remoteKey && role == QLowEnergyController::CentralRole)
+ || (!remoteKey && role == QLowEnergyController::PeripheralRole)) {
+ return;
}
- }
+ 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;
+ delete cmacCalculator;
}
class ServerSocket
@@ -456,6 +460,7 @@ void QLowEnergyControllerPrivate::connectToDevice()
// Unbuffered mode required to separate each GATT packet
l2cpSocket->connectToService(remoteDevice, ATTRIBUTE_CHANNEL_ID,
QIODevice::ReadWrite | QIODevice::Unbuffered);
+ loadSigningDataIfNecessary(LocalSigningKey);
}
void QLowEnergyControllerPrivate::l2cpConnected()
@@ -1779,7 +1784,7 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QByteArray &newValue,
- bool writeWithResponse)
+ QLowEnergyService::WriteMode mode)
{
Q_ASSERT(!service.isNull());
@@ -1787,12 +1792,10 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
return;
QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle];
- if (role == QLowEnergyController::PeripheralRole) {
+ if (role == QLowEnergyController::PeripheralRole)
writeCharacteristicForPeripheral(charData, newValue);
- } else {
- writeCharacteristicForCentral(charHandle, charData.valueHandle, newValue,
- writeWithResponse);
- }
+ else
+ writeCharacteristicForCentral(service, charHandle, charData.valueHandle, newValue, mode);
}
void QLowEnergyControllerPrivate::writeDescriptor(
@@ -2356,48 +2359,72 @@ void QLowEnergyControllerPrivate::writeCharacteristicForPeripheral(
}
}
-void QLowEnergyControllerPrivate::writeCharacteristicForCentral(
+void QLowEnergyControllerPrivate::writeCharacteristicForCentral(const QSharedPointer<QLowEnergyServicePrivate> &service,
QLowEnergyHandle charHandle,
QLowEnergyHandle valueHandle,
const QByteArray &newValue,
- bool writeWithResponse)
-{
- const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
-
- quint8 packet[WRITE_REQUEST_HEADER_SIZE];
- if (writeWithResponse) {
+ QLowEnergyService::WriteMode mode)
+{
+ QByteArray packet(WRITE_REQUEST_HEADER_SIZE + newValue.count(), Qt::Uninitialized);
+ putBtData(valueHandle, packet.data() + 1);
+ memcpy(packet.data() + 3, newValue.constData(), newValue.count());
+ bool writeWithResponse = false;
+ switch (mode) {
+ case QLowEnergyService::WriteWithResponse:
if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
sendNextPrepareWriteRequest(charHandle, newValue, 0);
sendNextPendingRequest();
return;
- } else {
- // write value fits into single package
- packet[0] = ATT_OP_WRITE_REQUEST;
}
- } else {
- // write without response
+ // write value fits into single package
+ packet[0] = ATT_OP_WRITE_REQUEST;
+ writeWithResponse = true;
+ break;
+ case QLowEnergyService::WriteWithoutResponse:
packet[0] = ATT_OP_WRITE_COMMAND;
+ break;
+ case QLowEnergyService::WriteSigned:
+ packet[0] = ATT_OP_SIGNED_WRITE_COMMAND;
+ if (!isBonded()) {
+ qCWarning(QT_BT_BLUEZ) << "signed write not possible: requires bond between devices";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+ if (securityLevel() >= BT_SECURITY_MEDIUM) {
+ qCWarning(QT_BT_BLUEZ) << "signed write not possible: not allowed on encrypted link";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+ const auto signingDataIt = signingData.find(remoteDevice.toUInt64());
+ if (signingDataIt == signingData.end()) {
+ qCWarning(QT_BT_BLUEZ) << "signed write not possible: no signature key found";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+ ++signingDataIt.value().counter;
+ packet = LeCmacCalculator::createFullMessage(packet, signingDataIt.value().counter);
+ const quint64 mac = LeCmacCalculator().calculateMac(packet, signingDataIt.value().key);
+ packet.resize(packet.count() + sizeof mac);
+ putBtData(mac, packet.data() + packet.count() - sizeof mac);
+ storeSignCounter(LocalSigningKey);
+ break;
}
- putBtData(valueHandle, &packet[1]);
-
- QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
-
qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle
- << "(size:" << size << "with response:" << writeWithResponse << ")";
+ << "(size:" << packet.count() << "with response:"
+ << (mode == QLowEnergyService::WriteWithResponse)
+ << "signed:" << (mode == QLowEnergyService::WriteSigned) << ")";
// Advantage of write without response is the quick turnaround.
- // It can be send at any time and does not produce responses.
+ // It can be sent at any time and does not produce responses.
// Therefore we will not put them into the openRequest queue at all.
if (!writeWithResponse) {
- sendPacket(data);
+ sendPacket(packet);
return;
}
Request request;
- request.payload = data;
+ request.payload = packet;
request.command = ATT_OP_WRITE_REQUEST;
request.reference = charHandle;
request.reference2 = newValue;
@@ -2516,7 +2543,7 @@ void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &
}
signingDataIt.value().counter = signCounter;
- storeSignCounter();
+ storeSignCounter(RemoteSigningKey);
valueLength = packet.count() - 15;
} else {
valueLength = packet.count() - 3;
@@ -2730,7 +2757,7 @@ void QLowEnergyControllerPrivate::handleConnectionRequest()
l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol,
QBluetoothSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered);
restoreClientConfigurations();
- loadSigningDataIfNecessary();
+ loadSigningDataIfNecessary(RemoteSigningKey);
setState(QLowEnergyController::ConnectedState);
}
@@ -2826,7 +2853,7 @@ void QLowEnergyControllerPrivate::restoreClientConfigurations()
sendNextIndication();
}
-void QLowEnergyControllerPrivate::loadSigningDataIfNecessary()
+void QLowEnergyControllerPrivate::loadSigningDataIfNecessary(SigningKeyType keyType)
{
const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64());
if (signingDataIt != signingData.constEnd())
@@ -2837,15 +2864,16 @@ void QLowEnergyControllerPrivate::loadSigningDataIfNecessary()
return;
}
QSettings settings(settingsFilePath, QSettings::IniFormat);
- settings.beginGroup(QLatin1String("RemoteSignatureKey"));
+ const QString group = signingKeySettingsGroup(keyType);
+ settings.beginGroup(group);
const QByteArray keyString = settings.value(QLatin1String("Key")).toByteArray();
if (keyString.isEmpty()) {
- qCDebug(QT_BT_BLUEZ) << "No remote signature key found in settings file";
+ qCDebug(QT_BT_BLUEZ) << "Group" << group << "not 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"
+ qCWarning(QT_BT_BLUEZ) << "Signing key in settings file has invalid size"
<< keyString.count();
return;
}
@@ -2857,7 +2885,7 @@ void QLowEnergyControllerPrivate::loadSigningDataIfNecessary()
signingData.insert(remoteDevice.toUInt64(), SigningData(csrk, counter - 1));
}
-void QLowEnergyControllerPrivate::storeSignCounter()
+void QLowEnergyControllerPrivate::storeSignCounter(SigningKeyType keyType) const
{
const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64());
if (signingDataIt == signingData.constEnd())
@@ -2868,7 +2896,7 @@ void QLowEnergyControllerPrivate::storeSignCounter()
QSettings settings(settingsFilePath, QSettings::IniFormat);
if (!settings.isWritable())
return;
- settings.beginGroup(QLatin1String("RemoteSignatureKey"));
+ settings.beginGroup(signingKeySettingsGroup(keyType));
const QString counterKey = QLatin1String("Counter");
if (!settings.allKeys().contains(counterKey))
return;
@@ -2878,6 +2906,11 @@ void QLowEnergyControllerPrivate::storeSignCounter()
settings.setValue(counterKey, counterValue);
}
+QString QLowEnergyControllerPrivate::signingKeySettingsGroup(SigningKeyType keyType) const
+{
+ return QLatin1String(keyType == LocalSigningKey ? "LocalSignatureKey" : "RemoteSignatureKey");
+}
+
QString QLowEnergyControllerPrivate::keySettingsFilePath() const
{
return QString::fromLatin1("/var/lib/bluetooth/%1/%2/info")
@@ -3113,9 +3146,9 @@ int QLowEnergyControllerPrivate::checkReadPermissions(QVector<Attribute> &attrib
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,
+ if (!cmacCalculator)
+ cmacCalculator = new LeCmacCalculator;
+ return cmacCalculator->verify(LeCmacCalculator::createFullMessage(message, signCounter), csrk,
expectedMac);
}
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index 62958c11..47f65965 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -664,7 +664,7 @@ void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnerg
void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service,
QLowEnergyHandle charHandle, const QByteArray &newValue,
- bool writeWithResponse)
+ QLowEnergyService::WriteMode mode)
{
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
@@ -697,7 +697,7 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner
[manager write:newValueCopy
charHandle:charHandle
onService:serviceUuid
- withResponse:writeWithResponse];
+ withResponse:mode == QLowEnergyService::WriteWithResponse];
});
}
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 6dc27aac..853e1f9b 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -124,7 +124,7 @@ private:
QLowEnergyHandle charHandle);
void writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service,
QLowEnergyHandle charHandle, const QByteArray &newValue,
- bool writeWithResponse);
+ QLowEnergyService::WriteMode mode);
quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle,
const QByteArray &value,
diff --git a/src/bluetooth/qlowenergycontroller_p.cpp b/src/bluetooth/qlowenergycontroller_p.cpp
index b4bd9a0f..6d42984e 100644
--- a/src/bluetooth/qlowenergycontroller_p.cpp
+++ b/src/bluetooth/qlowenergycontroller_p.cpp
@@ -102,7 +102,7 @@ void QLowEnergyControllerPrivate::readDescriptor(const QSharedPointer<QLowEnergy
void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> /*service*/,
const QLowEnergyHandle /*charHandle*/,
const QByteArray &/*newValue*/,
- bool /*writeWithResponse*/)
+ QLowEnergyService::WriteMode /*writeMode*/)
{
}
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 6e231dd1..2256025c 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -92,7 +92,7 @@ class QLowEnergyServiceData;
#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE)
class HciManager;
-class LeCmacVerifier;
+class LeCmacCalculator;
class QSocketNotifier;
#elif defined(QT_ANDROID_BLUETOOTH)
class LowEnergyNotificationHub;
@@ -159,7 +159,7 @@ public:
// write data
void writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
- const QByteArray &newValue, bool writeWithResponse = true);
+ const QByteArray &newValue, QLowEnergyService::WriteMode mode);
void writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descriptorHandle,
@@ -260,7 +260,7 @@ private:
quint32 counter = quint32(-1);
};
QHash<quint64, SigningData> signingData;
- LeCmacVerifier *cmacVerifier = nullptr;
+ LeCmacCalculator *cmacCalculator = nullptr;
bool requestPending;
quint16 mtuSize;
@@ -279,8 +279,11 @@ private:
QVector<TempClientConfigurationData> gatherClientConfigData();
void storeClientConfigurations();
void restoreClientConfigurations();
- void loadSigningDataIfNecessary();
- void storeSignCounter();
+
+ enum SigningKeyType { LocalSigningKey, RemoteSigningKey };
+ void loadSigningDataIfNecessary(SigningKeyType keyType);
+ void storeSignCounter(SigningKeyType keyType) const;
+ QString signingKeySettingsGroup(SigningKeyType keyType) const;
QString keySettingsFilePath() const;
void sendPacket(const QByteArray &packet);
@@ -368,11 +371,11 @@ private:
void writeCharacteristicForPeripheral(
QLowEnergyServicePrivate::CharData &charData,
const QByteArray &newValue);
- void writeCharacteristicForCentral(
+ void writeCharacteristicForCentral(const QSharedPointer<QLowEnergyServicePrivate> &service,
QLowEnergyHandle charHandle,
QLowEnergyHandle valueHandle,
const QByteArray &newValue,
- bool writeWithResponse);
+ QLowEnergyService::WriteMode mode);
void writeDescriptorForPeripheral(
const QSharedPointer<QLowEnergyServicePrivate> &service,
diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp
index c9f21c70..1e5c363d 100644
--- a/src/bluetooth/qlowenergyservice.cpp
+++ b/src/bluetooth/qlowenergyservice.cpp
@@ -255,6 +255,18 @@ QT_BEGIN_NAMESPACE
write mode. Its adavantage is a quicker
write operation as it may happen in between other
device interactions.
+
+ \value WriteSigned If a characteristic is written using this mode, the remote peripheral
+ shall not send a write confirmation. The operation's success
+ cannot be determined and the payload must not be longer than 8 bytes.
+ A bond must exist between the two devices and the link must not be
+ encrypted.
+ A characteristic must have set the
+ \l QLowEnergyCharacteristic::WriteSigned property to support this
+ write mode.
+ This value was introduced in Qt 5.7 and is currently only
+ supported on Android and on Linux with BlueZ 5 and a kernel version
+ 3.7 or newer.
*/
/*!
@@ -696,20 +708,10 @@ void QLowEnergyService::writeCharacteristic(
}
// don't write if properties don't permit it
- if (mode == WriteWithResponse)
- {
- d->controller->writeCharacteristic(characteristic.d_ptr,
+ d->controller->writeCharacteristic(characteristic.d_ptr,
characteristic.attributeHandle(),
newValue,
- true);
- } else if (mode == WriteWithoutResponse) {
- d->controller->writeCharacteristic(characteristic.d_ptr,
- characteristic.attributeHandle(),
- newValue,
- false);
- } else {
- d->setError(QLowEnergyService::OperationError);
- }
+ mode);
}
/*!
diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h
index 496c9dde..0449f4c9 100644
--- a/src/bluetooth/qlowenergyservice.h
+++ b/src/bluetooth/qlowenergyservice.h
@@ -82,7 +82,8 @@ public:
enum WriteMode {
WriteWithResponse = 0,
- WriteWithoutResponse
+ WriteWithoutResponse,
+ WriteSigned
};
Q_ENUM(WriteMode)
diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm
index 5a78b814..97d64040 100644
--- a/src/bluetooth/qlowenergyservice_osx.mm
+++ b/src/bluetooth/qlowenergyservice_osx.mm
@@ -213,13 +213,7 @@ void QLowEnergyService::writeCharacteristic(const QLowEnergyCharacteristic &ch,
return;
}
- // Don't write if properties don't permit it
- if (mode == WriteWithResponse)
- controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, true);
- else if (mode == WriteWithoutResponse)
- controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, false);
- else
- d_ptr->setError(QLowEnergyService::OperationError);
+ controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, mode);
}
bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const
diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
index b7c95816..6fa9b023 100644
--- a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
+++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
@@ -121,7 +121,7 @@ void addCustomService()
serviceData.addCharacteristic(charData);
charData.setUuid(QBluetoothUuid(quint16(0x5001)));
- charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned);
+ charData.setProperties(QLowEnergyCharacteristic::Read);
charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure.
serviceData.addCharacteristic(charData);
charData.setValue("something");
@@ -138,6 +138,12 @@ void addCustomService()
charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
serviceData.addCharacteristic(charData);
+ charData.setUuid(QBluetoothUuid(quint16(0x5004)));
+ charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned);
+ charData.setDescriptors(QList<QLowEnergyDescriptorData>());
+ charData.setValue("initial");
+ serviceData.addCharacteristic(charData);
+
addService(serviceData);
}
diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
index f487db00..ac2060e9 100644
--- a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
+++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
@@ -43,7 +43,7 @@
#include <QtTest/QtTest>
#ifdef Q_OS_LINUX
-#include <QtBluetooth/private/lecmacverifier_p.h>
+#include <QtBluetooth/private/lecmaccalculator_p.h>
#endif
#include <algorithm>
@@ -167,7 +167,7 @@ void TestQLowEnergyControllerGattServer::cmacVerifier()
};
QFETCH(QByteArray, message);
QFETCH(quint64, expectedMac);
- const bool success = LeCmacVerifier().verify(message, csrk, expectedMac);
+ const bool success = LeCmacCalculator().verify(message, csrk, expectedMac);
QVERIFY(success);
#else // CONFIG_LINUX_CRYPTO_API
QSKIP("CMAC verification test only applicable for developer builds on Linux "
@@ -333,7 +333,7 @@ void TestQLowEnergyControllerGattServer::serverCommunication()
QVERIFY(spy->wait(5000));
}
QCOMPARE(customService->includedServices().count(), 0);
- QCOMPARE(customService->characteristics().count(), 4);
+ QCOMPARE(customService->characteristics().count(), 5);
QLowEnergyCharacteristic customChar
= customService->characteristic(QBluetoothUuid(quint16(0x5000)));
QVERIFY(customChar.isValid());
@@ -366,12 +366,43 @@ void TestQLowEnergyControllerGattServer::serverCommunication()
= customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(cc4ClientConfig.isValid());
+ QLowEnergyCharacteristic customChar5
+ = customService->characteristic(QBluetoothUuid(quint16(0x5004)));
+ QVERIFY(customChar5.isValid());
+ QCOMPARE(customChar5.properties(),
+ QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned);
+ QCOMPARE(customChar5.descriptors().count(), 0);
+ QCOMPARE(customChar5.value(), QByteArray("initial"));
+
customService->writeCharacteristic(customChar, "whatever");
spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
(QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
QVERIFY(spy->wait(3000));
QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
+ const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress)
+ != QBluetoothLocalDevice::Unpaired;
+
+ customService->writeCharacteristic(customChar5, "1", QLowEnergyService::WriteSigned);
+ if (isBonded) {
+ // Signed write is done twice to test the sign counter stuff.
+ // Note: We assume here that the link is not encrypted, as this information is not exported.
+ customService->readCharacteristic(customChar5);
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
+ QVERIFY(spy->wait(3000));
+ QCOMPARE(customChar5.value(), QByteArray("1"));
+ customService->writeCharacteristic(customChar5, "2", QLowEnergyService::WriteSigned);
+ customService->readCharacteristic(customChar5);
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
+ QVERIFY(spy->wait(3000));
+ QCOMPARE(customChar5.value(), QByteArray("2"));
+ } else {
+ spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
+ (QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
+ QVERIFY(spy->wait(3000));
+ QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
+ }
+
QByteArray indicateValue(2, 0);
qToLittleEndian<quint16>(2, reinterpret_cast<uchar *>(indicateValue.data()));
customService->writeDescriptor(cc3ClientConfig, indicateValue);
@@ -396,8 +427,6 @@ void TestQLowEnergyControllerGattServer::serverCommunication()
spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connectionUpdated));
QVERIFY(spy->wait(5000));
- const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress)
- != QBluetoothLocalDevice::Unpaired;
m_leController->disconnectFromDevice();
if (m_leController->state() != QLowEnergyController::UnconnectedState) {
@@ -438,6 +467,17 @@ void TestQLowEnergyControllerGattServer::serverCommunication()
if (isBonded) {
QCOMPARE(cc3ClientConfig.value(), indicateValue);
QCOMPARE(cc4ClientConfig.value(), notifyValue);
+
+ // Do a third signed write to test sign counter persistence.
+ customChar5 = customService->characteristic(QBluetoothUuid(quint16(0x5004)));
+ QVERIFY(customChar5.isValid());
+ QCOMPARE(customChar5.value(), QByteArray("2"));
+ customService->writeCharacteristic(customChar5, "3", QLowEnergyService::WriteSigned);
+ customService->readCharacteristic(customChar5);
+ spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
+ QVERIFY(spy->wait(3000));
+ QCOMPARE(customChar5.value(), QByteArray("3"));
+
} else {
QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0));
QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0));