nymea-plugins/nuki/nukiauthenticator.cpp

592 lines
26 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "nukiauthenticator.h"
#include "nukiutils.h"
#include "nymeasettings.h"
#include "extern-plugininfo.h"
extern "C" {
#include "sodium.h"
#include "sodium/crypto_box.h"
#include "sodium/crypto_secretbox.h"
}
#include <QtEndian>
#include <QSettings>
#include <QDataStream>
NukiAuthenticator::NukiAuthenticator(const QBluetoothHostInfo &hostInfo, BluetoothGattCharacteristic *pairingCharacteristic, QObject *parent) :
QObject(parent),
m_hostInfo(hostInfo),
m_pairingCharacteristic(pairingCharacteristic)
{
#ifdef QT_DEBUG
// Enable full debug messages containing sensible data for debug builds
m_debug = true;
#endif
// Check if we have authentication data for this thing and set initial state
loadData();
if (isValid()) {
qCDebug(dcNuki()) << "Found valid authroization data for" << hostInfo.address().toString();
setState(AuthenticationStateAuthenticated);
} else {
setState(AuthenticationStateUnauthenticated);
}
connect(m_pairingCharacteristic, &BluetoothGattCharacteristic::valueChanged, this, &NukiAuthenticator::onPairingDataCharacteristicChanged);
}
NukiUtils::ErrorCode NukiAuthenticator::error() const
{
return m_error;
}
NukiAuthenticator::AuthenticationState NukiAuthenticator::state() const
{
return m_state;
}
bool NukiAuthenticator::isValid() const
{
return !m_privateKey.isEmpty() &&
!m_publicKey.isEmpty() &&
!m_publicKeyNuki.isEmpty() &&
m_authorizationId != 0 &&
!m_authorizationIdRawData.isEmpty() &&
!m_uuid.isEmpty();
}
void NukiAuthenticator::clearSettings()
{
QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
setting.beginGroup(m_hostInfo.address().toString());
setting.remove("");
setting.endGroup();
qCDebug(dcNuki()) << "Settings cleared for" << m_hostInfo.address().toString() << "in" << setting.fileName();
}
void NukiAuthenticator::startAuthenticationProcess()
{
setState(AuthenticationStateRequestPublicKey);
}
quint32 NukiAuthenticator::authorizationId() const
{
return m_authorizationId;
}
QByteArray NukiAuthenticator::authorizationIdRawData() const
{
return m_authorizationIdRawData;
}
QByteArray NukiAuthenticator::encryptData(const QByteArray &data, const QByteArray &nonce)
{
// Calculate shared key
qCDebug(dcNuki()) << "Authenticator: Encrypt data";
Q_ASSERT_X(nonce.length() == crypto_box_NONCEBYTES, "data length", "The nonce does not have the correct length.");
/* Note: https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html
* unsigned char *c The encrypted message (length of the data + crypto_box_MACBYTES)
* const unsigned char *m The message to encrypt
* unsigned long long mlen The length of the message to encrypt
* const unsigned char *n The nonce (must also sent unencrypted)
* const unsigned char *pk The public key of the Nuki
* const unsigned char *sk The private key of nymea for this Nuki
*/
unsigned char *encrypted = NULL;
std::vector<unsigned char> realBuffer(crypto_box_MACBYTES + data.length());
encrypted = &realBuffer[0];
int result = crypto_box_easy(encrypted,
reinterpret_cast<const unsigned char *>(data.data()),
static_cast<unsigned long long>(data.length()),
reinterpret_cast<const unsigned char *>(nonce.data()),
reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()),
reinterpret_cast<const unsigned char *>(m_privateKey.data()));
if (result < 0) {
qCWarning(dcNuki()) << "Could not encrypt data. Something went wrong";
return QByteArray();
}
QByteArray encryptedData = QByteArray(reinterpret_cast<const char*>(encrypted), crypto_box_MACBYTES + data.length());
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
if (m_debug) qCDebug(dcNuki()) << " Unencrypted data:" << NukiUtils::convertByteArrayToHexStringCompact(data);
if (m_debug) qCDebug(dcNuki()) << " Encrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(encryptedData);
return encryptedData;
}
QByteArray NukiAuthenticator::decryptData(const QByteArray &data, const QByteArray &nonce)
{
qCDebug(dcNuki()) << "Authenticator: Decrypt data";
Q_ASSERT_X(nonce.length() == crypto_box_NONCEBYTES, "data length", "The nonce does not have the correct length.");
Q_ASSERT_X(static_cast<uint>(data.length()) >= crypto_box_MACBYTES, "data length", "The encrypted data is to short.");
/* Note: https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html
* unsigned char *m The decrypted message result
* const unsigned char *c The message to decrypt / cyphertext (length of the encrypted data + crypto_box_MACBYTES)
* unsigned long long clen The length of the message to decrypt
* const unsigned char *n The nonce used while encryption (received in the unencrypted ADATA)
* const unsigned char *pk The public key of the Nuki
* const unsigned char *sk The private key of nymea for this Nuki
*/
unsigned char *decrypted = NULL;
std::vector<unsigned char> realBuffer(data.length() - crypto_box_MACBYTES);
decrypted = &realBuffer[0];
int result = crypto_box_open_easy(decrypted,
reinterpret_cast<const unsigned char *>(data.data()),
static_cast<unsigned long long>(data.length()),
reinterpret_cast<const unsigned char *>(nonce.data()),
reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()),
reinterpret_cast<const unsigned char *>(m_privateKey.data()));
if (result < 0) {
qCWarning(dcNuki()) << "Could not decrypt data. Something went wrong";
return QByteArray();
}
QByteArray decryptedData = QByteArray(reinterpret_cast<const char*>(decrypted), data.length() - crypto_box_MACBYTES);
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
if (m_debug) qCDebug(dcNuki()) << " Encrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(data);
if (m_debug) qCDebug(dcNuki()) << " Decrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(decryptedData);
return decryptedData;
}
QByteArray NukiAuthenticator::generateNonce(int length) const
{
unsigned char *nounce = NULL;
std::vector<unsigned char> realBuffer(length);
nounce = &realBuffer[0];
randombytes_buf(nounce, length);
return QByteArray(reinterpret_cast<const char *>(nounce), length);
}
void NukiAuthenticator::setState(NukiAuthenticator::AuthenticationState state)
{
if (m_state == state)
return;
m_state = state;
emit stateChanged(m_state);
qCDebug(dcNuki()) << m_state;
switch (m_state) {
case AuthenticationStateUnauthenticated:
break;
case AuthenticationStateAuthenticated:
qCDebug(dcNuki()) << "Device" << m_hostInfo.address().toString() << "authenticated.";
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
break;
case AuthenticationStateRequestPublicKey:
requestPublicKey();
break;
case AuthenticationStateGenerateKeyPair:
generateKeyPair();
setState(AuthenticationStateSendPublicKey);
break;
case AuthenticationStateSendPublicKey:
sendPublicKey();
setState(AuthenticationStateReadChallenge);
break;
case AuthenticationStateReadChallenge:
break;
case AuthenticationStateAutorization:
sendAuthorizationAuthenticator();
setState(AuthenticationStateReadSecondChallenge);
break;
case AuthenticationStateReadSecondChallenge:
break;
case AuthenticationStateAuthenticateData:
sendAuthenticateData();
setState(AuthenticationStateAuthorizationId);
break;
case AuthenticationStateAuthorizationId:
break;
case AuthenticationStateAuthorizationIdConfirm:
sendAuthoizationIdConfirm();
setState(AuthenticationStateStatus);
break;
case AuthenticationStateStatus:
break;
case AuthenticationStateError:
emit errorOccured(m_error);
emit authenticationProcessFinished(false);
break;
default:
qCWarning(dcNuki()) << "Authenticator: Unknown state.";
break;
}
}
bool NukiAuthenticator::createAuthenticator(const QByteArray content)
{
// Create shared key
qCDebug(dcNuki()) << "Authenticator: Calculate shared key";
unsigned char sharedKey[crypto_box_BEFORENMBYTES];
int result = crypto_box_beforenm(sharedKey, reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()), reinterpret_cast<const unsigned char *>(m_privateKey.data()));
if (result < 0) {
qCWarning(dcNuki()) << "Could not create shared key for autorization authenticator.";
return false;
}
m_sharedKey = QByteArray(reinterpret_cast<const char*>(sharedKey), crypto_box_BEFORENMBYTES);
Q_ASSERT_X(m_sharedKey.length() == 32, "data length", "The shared key does not have the correct length.");
if (m_debug) qCDebug(dcNuki()) << "Authenticator: Calculate authenticator hash HMAC-SHA-256";
if (m_debug) qCDebug(dcNuki()) << " Shared key :" << NukiUtils::convertByteArrayToHexStringCompact(m_sharedKey);
if (m_debug) qCDebug(dcNuki()) << " Nuki nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
// Calculate authenticator hash input for HMAC-SHA-256
qCDebug(dcNuki()) << "Authenticator: Calculate authenticator data";
unsigned char authenticator[crypto_auth_hmacsha256_BYTES];
result = crypto_auth_hmacsha256(authenticator, reinterpret_cast<const unsigned char *>(content.data()), content.length(), reinterpret_cast<const unsigned char *>(m_sharedKey.data()));
if (result < 0) {
qCWarning(dcNuki()) << "Could not create authenticator hash for autorization authenticator.";
return false;
}
m_authenticator = QByteArray(reinterpret_cast<const char*>(authenticator), crypto_auth_hmacsha256_BYTES);
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(m_authenticator);
return true;
}
void NukiAuthenticator::requestPublicKey()
{
qCDebug(dcNuki()) << "Authenticator: Request public key fom Nuki";
QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::WriteOnly);
#else
QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(NukiUtils::CommandPublicKey);
QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandRequestData, payload);
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(data);
m_pairingCharacteristic->writeCharacteristic(data);
}
void NukiAuthenticator::sendPublicKey()
{
qCDebug(dcNuki()) << "Authenticator: Send public key to Nuki";
QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandPublicKey, m_publicKey);
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(data);
m_pairingCharacteristic->writeCharacteristic(data);
}
void NukiAuthenticator::generateKeyPair()
{
qCDebug(dcNuki()) << "Generate key pair";
unsigned char publicKey[crypto_box_PUBLICKEYBYTES];
unsigned char secretKey[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(publicKey, secretKey);
m_publicKey = QByteArray(reinterpret_cast<const char *>(publicKey), crypto_box_PUBLICKEYBYTES);
m_privateKey = QByteArray(reinterpret_cast<const char *>(secretKey), crypto_box_SECRETKEYBYTES);
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
}
void NukiAuthenticator::sendAuthorizationAuthenticator()
{
QByteArray valueR;
valueR.append(m_publicKey);
valueR.append(m_publicKeyNuki);
valueR.append(m_nonceNuki);
// Create authenticator and store it into m_authenticator
if (!createAuthenticator(valueR)) {
qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
setState(AuthenticationStateError);
}
// Send the authenticator
qCDebug(dcNuki()) << "Authenticator: Send authorization authenticator to Nuki";
QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationAuthenticator, m_authenticator);
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
m_pairingCharacteristic->writeCharacteristic(message);
}
void NukiAuthenticator::sendAuthenticateData()
{
// Calculate new nounce
m_nonce = generateNonce();
QByteArray content;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&content, QDataStream::WriteOnly);
#else
QDataStream stream(&content, QIODevice::WriteOnly);
#endif
// Note: 0x00 = App, 0x01 = Bridge, 0x02 = Fob
stream << static_cast<quint8>(0x01);
// Note: app id (42)
stream << static_cast<quint32>(0x002A);
// Note: the name of the bridge in 32 bytes [ 0 0 0 ... n y m e a ]
QByteArray name = QByteArray(27, '\0').append(QByteArray("nymea"));
Q_ASSERT_X(name.length() == 32, "data length", "Name has not the correct length.");
QByteArray valueR = content;
valueR.append(name);
valueR.append(m_nonce);
valueR.append(m_nonceNuki);
if (m_debug) qCDebug(dcNuki()) << " Name :" << qUtf8Printable(name) << NukiUtils::convertByteArrayToHexStringCompact(name);
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonce);
// Create authenticator and store it into m_authenticator
if (!createAuthenticator(valueR)) {
qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
setState(AuthenticationStateError);
}
// Prepare message to send to Nuki
QByteArray data;
data.append(m_authenticator);
data.append(content);
data.append(name);
data.append(m_nonce);
qCDebug(dcNuki()) << "Authenticator: Send authentication data to Nuki";
QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationData, data);
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
m_pairingCharacteristic->writeCharacteristic(message);
}
void NukiAuthenticator::sendAuthoizationIdConfirm()
{
qCDebug(dcNuki()) << "Authenticator: Create data for authentication ID confirm";
QByteArray valueR;
valueR.append(m_authorizationIdRawData);
valueR.append(m_nonceNuki);
// Create authenticator and store it into m_authenticator
if (!createAuthenticator(valueR)) {
qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
setState(AuthenticationStateError);
}
// Calculate new nounce
m_nonce = generateNonce();
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonce);
if (m_debug) qCDebug(dcNuki()) << " Nuki Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
// Prepare message to send to Nuki
QByteArray data;
data.append(m_authenticator);
data.append(m_authorizationIdRawData);
qCDebug(dcNuki()) << "Authenticator: Send authentication ID confirm to Nuki";
QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationIdConfirmation, data);
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
m_pairingCharacteristic->writeCharacteristic(message);
}
void NukiAuthenticator::saveData()
{
QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
setting.beginGroup(m_hostInfo.address().toString());
setting.setValue("privateKey", m_privateKey);
setting.setValue("publicKey", m_publicKey);
setting.setValue("publicKeyNuki", m_publicKeyNuki);
setting.setValue("authenticationIdRawData", m_authorizationIdRawData);
setting.setValue("authenticationId", m_authorizationId);
setting.setValue("uuid", m_uuid);
setting.endGroup();
qCDebug(dcNuki()) << "Authenticator: Settings saved to" << setting.fileName();
}
void NukiAuthenticator::loadData()
{
QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
setting.beginGroup(m_hostInfo.address().toString());
m_privateKey = setting.value("privateKey", QByteArray()).toByteArray();
m_publicKey = setting.value("publicKey", QByteArray()).toByteArray();
m_publicKeyNuki = setting.value("publicKeyNuki", QByteArray()).toByteArray();
m_authorizationIdRawData = setting.value("authenticationIdRawData", QByteArray()).toByteArray();
m_authorizationId = static_cast<quint32>(setting.value("authenticationId", 0).toInt());
m_uuid = setting.value("uuid", QByteArray()).toByteArray();
setting.endGroup();
qCDebug(dcNuki()) << "Authenticator: Settings loaded from" << setting.fileName();
}
void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &value)
{
// Process pairing characteristic data
QByteArray data = QByteArray(value);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::ReadOnly);
#else
QDataStream stream(&data, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
quint16 command;
stream >> command;
m_currentReceivingData = value;
if (m_debug) qCDebug(dcNuki()) << "Authenticator data received: <--" << static_cast<NukiUtils::Command>(command) << "|" << NukiUtils::convertByteArrayToHexStringCompact(value);
switch (command) {
case NukiUtils::CommandErrorReport:
quint8 error;
quint16 commandIdentifier;
quint16 crc;
stream >> error >> commandIdentifier >> crc;
if (!NukiUtils::validateMessageCrc(data)) {
qCWarning(dcNuki()) << "Invalid message";
// FIXME: check what to do if crc is invalid
}
m_error = static_cast<NukiUtils::ErrorCode>(error);
qCWarning(dcNuki()) << "Authenticator: Error for command" << static_cast<NukiUtils::Command>(commandIdentifier) << m_error;
setState(AuthenticationStateError);
break;
case NukiUtils::CommandPublicKey:
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for public key message.";
// FIXME: check what to do if crc is invalid
}
qCDebug(dcNuki()) << "Authenticator: Nuki public key message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
m_publicKeyNuki = m_currentReceivingData.mid(2, 32);
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki public key:" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
setState(AuthenticationStateGenerateKeyPair);
break;
case NukiUtils::CommandChallenge:
qCDebug(dcNuki()) << "Authenticator: Nuki challenge message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
// FIXME: check what to do if crc is invalid
}
m_nonceNuki = m_currentReceivingData.mid(2, 32);
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki nonce:" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
// Check if this was from the first challenge read or the second
if (m_state == AuthenticationStateReadChallenge) {
setState(AuthenticationStateAutorization);
} else if (m_state == AuthenticationStateReadSecondChallenge) {
setState(AuthenticationStateAuthenticateData);
} else {
qCWarning(dcNuki()) << "Received a challenge without expecting one.";
setState(AuthenticationStateError);
}
break;
case NukiUtils::CommandAuthorizationId: {
qCDebug(dcNuki()) << "Authenticator: Nuki authorization ID message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
// FIXME: check what to do if crc is invalid
}
// Parse data
QByteArray message = m_currentReceivingData.mid(2, m_currentReceivingData.length() - 4);
QByteArray authenticator = message.left(32);
Q_ASSERT_X(authenticator.length() == 32, "data length", "Nuki nonce has not the correct length.");
// Read authorization ID
m_authorizationIdRawData = message.mid(32, 4);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&m_authorizationIdRawData, QDataStream::ReadOnly);
#else
QDataStream stream(&m_authorizationIdRawData, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_authorizationId;
m_uuid = message.mid(36, 16);
Q_ASSERT_X(m_uuid.length() == 16, "data length", "UUID has not the correct length.");
m_nonceNuki = message.mid(52, 32);
Q_ASSERT_X(m_nonceNuki.length() == 32, "data length", "Nuki nonce has not the correct length.");
if (m_debug) qCDebug(dcNuki()) << " Full message :" << NukiUtils::convertByteArrayToHexStringCompact(message);
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(authenticator);
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
if (m_debug) qCDebug(dcNuki()) << " UUID data :" << NukiUtils::convertByteArrayToHexStringCompact(m_uuid);
if (m_debug) qCDebug(dcNuki()) << " Nuki nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
setState(AuthenticationStateAuthorizationIdConfirm);
break;
}
case NukiUtils::CommandStatus: {
quint8 status;
stream >> status;
if (!NukiUtils::validateMessageCrc(data)) {
qCWarning(dcNuki()) << "Invalid message";
// FIXME: check what to do if crc is invalid
}
NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>(status);
if (m_debug) qCDebug(dcNuki()) << statusCode;
switch (statusCode) {
case NukiUtils::StatusCodeAccepted:
qCWarning(dcNuki()) << "The command was accepted, but not completed.";
setState(AuthenticationStateError);
break;
case NukiUtils::StatusCodeCompeted:
qCDebug(dcNuki()) << "Nuki authentication process finished successfully!";
saveData();
setState(AuthenticationStateAuthenticated);
emit authenticationProcessFinished(true);
break;
default:
break;
}
break;
}
default:
qCWarning(dcNuki()) << "Authenticator: Unhandled command identifier for parining charateristic" << NukiUtils::convertUint16ToHexString(command);
break;
}
}