diff --git a/src/PairingManager/PairingManager.cc b/src/PairingManager/PairingManager.cc new file mode 100644 index 0000000..9846226 --- /dev/null +++ b/src/PairingManager/PairingManager.cc @@ -0,0 +1,575 @@ +/**************************************************************************** + * + * (c) 2019 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "PairingManager.h" +#include "SettingsManager.h" +#include "MicrohardManager.h" +#include "QGCApplication.h" +#include "QGCCorePlugin.h" + +#include +#include +#include +#include + +QGC_LOGGING_CATEGORY(PairingManagerLog, "PairingManagerLog") + +static const char* jsonFileName = "pairing.json"; + +//----------------------------------------------------------------------------- +static QString +random_string(uint length) +{ + auto randchar = []() -> char + { + const char charset[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const uint max_index = (sizeof(charset) - 1); + return charset[static_cast(rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return QString::fromStdString(str); +} + +//----------------------------------------------------------------------------- +PairingManager::PairingManager(QGCApplication* app, QGCToolbox* toolbox) + : QGCTool(app, toolbox) + , _aes("J6+KuWh9K2!hG(F'", 0x368de30e8ec063ce) +{ + _jsonFileName = QDir::temp().filePath(jsonFileName); + connect(this, &PairingManager::parsePairingJson, this, &PairingManager::_parsePairingJson); + connect(this, &PairingManager::setPairingStatus, this, &PairingManager::_setPairingStatus); + connect(this, &PairingManager::startUpload, this, &PairingManager::_startUpload); +} + +//----------------------------------------------------------------------------- +PairingManager::~PairingManager() +{ +} + +//----------------------------------------------------------------------------- + +void +PairingManager::setToolbox(QGCToolbox *toolbox) +{ + QGCTool::setToolbox(toolbox); + _updatePairedDeviceNameList(); + emit pairedListChanged(); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_pairingCompleted(QString name) +{ + _writeJson(_jsonDoc, _pairingCacheFile(name)); + _remotePairingMap["NM"] = name; + _lastPaired = name; + _updatePairedDeviceNameList(); + emit pairedListChanged(); + emit pairedVehicleChanged(); + //_app->informationMessageBoxOnMainThread("", tr("Paired with %1").arg(name)); + setPairingStatus(PairingSuccess, tr("Pairing Successfull")); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_connectionCompleted(QString /*name*/) +{ + //QString pwd = _remotePairingMap["PWD"].toString(); + //_toolbox->microhardManager()->switchToConnectionEncryptionKey(pwd); + //_app->informationMessageBoxOnMainThread("", tr("Connected to %1").arg(name)); + setPairingStatus(PairingConnected, tr("Connection Successfull")); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_startUpload(QString pairURL, QJsonDocument jsonDoc) +{ + QMutexLocker lock(&_uploadMutex); + if (_uploadManager != nullptr) { + return; + } + _uploadManager = new QNetworkAccessManager(this); + + QString str = jsonDoc.toJson(QJsonDocument::JsonFormat::Compact); + qCDebug(PairingManagerLog) << "Starting upload to: " << pairURL << " " << str; + _uploadData = QString::fromStdString(_aes.encrypt(str.toStdString())); + _uploadURL = pairURL; + _startUploadRequest(); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_startUploadRequest() +{ + QNetworkRequest req; + req.setUrl(QUrl(_uploadURL)); + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QNetworkReply *reply = _uploadManager->post(req, _uploadData.toUtf8()); + connect(reply, &QNetworkReply::finished, this, &PairingManager::_uploadFinished); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_stopUpload() +{ + QMutexLocker lock(&_uploadMutex); + if (_uploadManager != nullptr) { + delete _uploadManager; + _uploadManager = nullptr; + } +} + +//----------------------------------------------------------------------------- +void +PairingManager::_uploadFinished() +{ + QMutexLocker lock(&_uploadMutex); + QNetworkReply* reply = qobject_cast(QObject::sender()); + if (reply) { + if (_uploadManager != nullptr) { + if (reply->error() == QNetworkReply::NoError) { + qCDebug(PairingManagerLog) << "Upload finished."; + QByteArray bytes = reply->readAll(); + QString str = QString::fromUtf8(bytes.data(), bytes.size()); + qCDebug(PairingManagerLog) << "Reply: " << str; + auto a = str.split(QRegExp("\\s+")); + if (a[0] == "Accepted" && a.length() > 1) { + _pairingCompleted(a[1]); + } else if (a[0] == "Connected" && a.length() > 1) { + _connectionCompleted(a[1]); + } else if (a[0] == "Connection" && a.length() > 1) { + setPairingStatus(PairingConnectionRejected, tr("Connection Rejected")); + qCDebug(PairingManagerLog) << "Connection error: " << str; + } else { + setPairingStatus(PairingRejected, tr("Pairing Rejected")); + qCDebug(PairingManagerLog) << "Pairing error: " << str; + } + _uploadManager->deleteLater(); + _uploadManager = nullptr; + } else { + if(++_pairRetryCount > 3) { + qCDebug(PairingManagerLog) << "Giving up"; + setPairingStatus(PairingError, tr("No Response From Vehicle")); + _uploadManager->deleteLater(); + _uploadManager = nullptr; + } else { + qCDebug(PairingManagerLog) << "Upload error: " + reply->errorString(); + _startUploadRequest(); + } + } + } + } +} + +//----------------------------------------------------------------------------- +void +PairingManager::_parsePairingJsonFile() +{ + QFile file(_jsonFileName); + file.open(QIODevice::ReadOnly | QIODevice::Text); + QString json = file.readAll(); + file.remove(); + file.close(); + + jsonReceived(json); +} + +//----------------------------------------------------------------------------- +void +PairingManager::connectToPairedDevice(QString name) +{ + setPairingStatus(PairingConnecting, tr("Connecting to %1").arg(name)); + QFile file(_pairingCacheFile(name)); + file.open(QIODevice::ReadOnly | QIODevice::Text); + QString json = file.readAll(); + jsonReceived(json); +} + +//----------------------------------------------------------------------------- +void +PairingManager::removePairedDevice(QString name) +{ + QFile file(_pairingCacheFile(name)); + file.remove(); + _updatePairedDeviceNameList(); + emit pairedListChanged(); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_updatePairedDeviceNameList() +{ + _deviceList.clear(); + QDirIterator it(_pairingCacheDir().absolutePath(), QDir::Files); + while (it.hasNext()) { + QFileInfo fileInfo(it.next()); + _deviceList.append(fileInfo.fileName()); + qCDebug(PairingManagerLog) << "Listing: " << fileInfo.fileName(); + } +} + +//----------------------------------------------------------------------------- +QString +PairingManager::_assumeMicrohardPairingJson() +{ + QJsonDocument json; + QJsonObject jsonObject; + + jsonObject.insert("LT", "MH"); + jsonObject.insert("IP", "192.168.168.10"); + jsonObject.insert("AIP", _toolbox->microhardManager()->remoteIPAddr()); + jsonObject.insert("CU", _toolbox->microhardManager()->configUserName()); + jsonObject.insert("CP", _toolbox->microhardManager()->configPassword()); + jsonObject.insert("EK", _toolbox->microhardManager()->encryptionKey()); + json.setObject(jsonObject); + + return QString(json.toJson(QJsonDocument::Compact)); +} + +//----------------------------------------------------------------------------- +void +PairingManager::_parsePairingJson(QString jsonEnc) +{ + QString json = QString::fromStdString(_aes.decrypt(jsonEnc.toStdString())); + if (json == "") { + json = jsonEnc; + } + qCDebug(PairingManagerLog) << "Parsing JSON: " << json; + + _jsonDoc = QJsonDocument::fromJson(json.toUtf8()); + + if (_jsonDoc.isNull()) { + setPairingStatus(PairingError, tr("Invalid Pairing File")); + qCDebug(PairingManagerLog) << "Failed to create Pairing JSON doc."; + return; + } + if (!_jsonDoc.isObject()) { + setPairingStatus(PairingError, tr("Error Parsing Pairing File")); + qCDebug(PairingManagerLog) << "Pairing JSON is not an object."; + return; + } + + QJsonObject jsonObj = _jsonDoc.object(); + + if (jsonObj.isEmpty()) { + setPairingStatus(PairingError, tr("Error Parsing Pairing File")); + qCDebug(PairingManagerLog) << "Pairing JSON object is empty."; + return; + } + + _remotePairingMap = jsonObj.toVariantMap(); + QString linkType = _remotePairingMap["LT"].toString(); + QString pport = _remotePairingMap["PP"].toString(); + if (pport.length()==0) { + pport = "29351"; + } + + if (linkType.length()==0) { + setPairingStatus(PairingError, tr("Error Parsing Pairing File")); + qCDebug(PairingManagerLog) << "Pairing JSON is malformed."; + return; + } + + _toolbox->microhardManager()->switchToPairingEncryptionKey(); + + QString pairURL = "http://" + _remotePairingMap["IP"].toString() + ":" + pport; + bool connecting = jsonObj.contains("PWD"); + QJsonDocument jsonDoc; + + if (!connecting) { + pairURL += + "/pair"; + QString pwd = random_string(8); + // TODO generate certificates + QString cert1 = ""; + QString cert2 = ""; + jsonObj.insert("PWD", pwd); + jsonObj.insert("CERT1", cert1); + jsonObj.insert("CERT2", cert2); + _jsonDoc.setObject(jsonObj); + if (linkType == "ZT") { + jsonDoc = _createZeroTierPairingJson(cert1); + } else if (linkType == "MH") { + jsonDoc = _createMicrohardPairingJson(pwd, cert1); + } + } else { + pairURL += + "/connect"; + QString cert2 = _remotePairingMap["CERT2"].toString(); + if (linkType == "ZT") { + jsonDoc = _createZeroTierConnectJson(cert2); + } else if (linkType == "MH") { + jsonDoc = _createMicrohardConnectJson(cert2); + } + } + + if (linkType == "ZT") { + _toolbox->settingsManager()->appSettings()->enableMicrohard()->setRawValue(false); + _toolbox->settingsManager()->appSettings()->enableTaisync()->setRawValue(false); + emit startUpload(pairURL, jsonDoc); + } else if (linkType == "MH") { + _toolbox->settingsManager()->appSettings()->enableMicrohard()->setRawValue(true); + _toolbox->settingsManager()->appSettings()->enableTaisync()->setRawValue(false); + if (_remotePairingMap.contains("AIP")) { + _toolbox->microhardManager()->setRemoteIPAddr(_remotePairingMap["AIP"].toString()); + } + if (_remotePairingMap.contains("CU")) { + _toolbox->microhardManager()->setConfigUserName(_remotePairingMap["CU"].toString()); + } + if (_remotePairingMap.contains("CP")) { + _toolbox->microhardManager()->setConfigPassword(_remotePairingMap["CP"].toString()); + } + if (_remotePairingMap.contains("EK") && !connecting) { + _toolbox->microhardManager()->setEncryptionKey(_remotePairingMap["EK"].toString()); + } + _toolbox->microhardManager()->updateSettings(); + emit startUpload(pairURL, jsonDoc); + } +} + +//----------------------------------------------------------------------------- +QString +PairingManager::_getLocalIPInNetwork(QString remoteIP, int num) +{ + QStringList pieces = remoteIP.split("."); + QString ipPrefix = ""; + for (int i = 0; iGetStringUTFChars(messageA, nullptr); + QString ndef = QString::fromUtf8(stringL); + envA->ReleaseStringUTFChars(messageA, stringL); + if (envA->ExceptionCheck()) + envA->ExceptionClear(); + qCDebug(PairingManagerLog) << "NDEF Tag Received: " << ndef; + qgcApp()->toolbox()->pairingManager()->jsonReceived(ndef); +} + +//----------------------------------------------------------------------------- +void PairingManager::setNativeMethods(void) +{ + // REGISTER THE C++ FUNCTION WITH JNI + JNINativeMethod javaMethods[] { + {"nativeNFCTagReceived", "(Ljava/lang/String;)V", reinterpret_cast(jniNFCTagReceived)} + }; + + QAndroidJniEnvironment jniEnv; + if (jniEnv->ExceptionCheck()) { + jniEnv->ExceptionDescribe(); + jniEnv->ExceptionClear(); + } + + jclass objectClass = jniEnv->FindClass(kJniClassName); + if(!objectClass) { + qWarning() << "Couldn't find class:" << kJniClassName; + return; + } + + jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0])); + + if (val < 0) { + qWarning() << "Error registering methods: " << val; + } else { + qCDebug(PairingManagerLog) << "Native Functions Registered"; + } + + if (jniEnv->ExceptionCheck()) { + jniEnv->ExceptionDescribe(); + jniEnv->ExceptionClear(); + } +} +#endif +//----------------------------------------------------------------------------- diff --git a/src/PairingManager/PairingManager.h b/src/PairingManager/PairingManager.h new file mode 100644 index 0000000..5f9ef80 --- /dev/null +++ b/src/PairingManager/PairingManager.h @@ -0,0 +1,153 @@ +/**************************************************************************** + * + * (c) 2019 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "aes.h" +#include "QGCToolbox.h" +#include "QGCLoggingCategory.h" +#include "Fact.h" +#if defined QGC_ENABLE_QTNFC +#include "QtNFC.h" +#endif +#ifdef __android__ +#include +#include +#include +#endif + +Q_DECLARE_LOGGING_CATEGORY(PairingManagerLog) + +class AppSettings; +class QGCApplication; + +//----------------------------------------------------------------------------- +class PairingManager : public QGCTool +{ + Q_OBJECT +public: + explicit PairingManager (QGCApplication* app, QGCToolbox* toolbox); + ~PairingManager () override; + + // Override from QGCTool + virtual void setToolbox(QGCToolbox *toolbox) override; + + enum PairingStatus { + PairingIdle, + PairingActive, + PairingSuccess, + PairingConnecting, + PairingConnected, + PairingRejected, + PairingConnectionRejected, + PairingError + }; + + Q_ENUM(PairingStatus) + + QStringList pairingLinkTypeStrings (); + QString pairingStatusStr () const; + QStringList pairedDeviceNameList () { return _deviceList; } + PairingStatus pairingStatus () { return _status; } + QString pairedVehicle () { return _lastPaired; } + int nfcIndex () { return _nfcIndex; } + int microhardIndex () { return _microhardIndex; } + bool firstBoot () { return _firstBoot; } + bool errorState () { return _status == PairingRejected || _status == PairingConnectionRejected || _status == PairingError; } + void setStatusMessage (PairingStatus status, QString statusStr) { emit setPairingStatus(status, statusStr); } + void jsonReceived (QString json) { emit parsePairingJson(json); } + void setFirstBoot (bool set) { _firstBoot = set; emit firstBootChanged(); } +#ifdef __android__ + static void setNativeMethods (void); +#endif + Q_INVOKABLE void connectToPairedDevice (QString name); + Q_INVOKABLE void removePairedDevice (QString name); + +#if defined defined QGC_ENABLE_QTNFC + Q_INVOKABLE void startNFCScan(); +#endif +#if QGC_GST_MICROHARD_ENABLED + Q_INVOKABLE void startMicrohardPairing(); +#endif + Q_INVOKABLE void stopPairing(); + + Q_PROPERTY(QString pairingStatusStr READ pairingStatusStr NOTIFY pairingStatusChanged) + Q_PROPERTY(PairingStatus pairingStatus READ pairingStatus NOTIFY pairingStatusChanged) + Q_PROPERTY(QStringList pairedDeviceNameList READ pairedDeviceNameList NOTIFY pairedListChanged) + Q_PROPERTY(QStringList pairingLinkTypeStrings READ pairingLinkTypeStrings CONSTANT) + Q_PROPERTY(QString pairedVehicle READ pairedVehicle NOTIFY pairedVehicleChanged) + Q_PROPERTY(bool errorState READ errorState NOTIFY pairingStatusChanged) + Q_PROPERTY(int nfcIndex READ nfcIndex CONSTANT) + Q_PROPERTY(int microhardIndex READ microhardIndex CONSTANT) + Q_PROPERTY(bool firstBoot READ firstBoot WRITE setFirstBoot NOTIFY firstBootChanged) + +signals: + void startUpload (QString pairURL, QJsonDocument); + void closeConnection (); + void pairingConfigurationsChanged (); + void nameListChanged (); + void pairingStatusChanged (); + void parsePairingJson (QString json); + void setPairingStatus (PairingStatus status, QString pairingStatus); + void pairedListChanged (); + void pairedVehicleChanged (); + void firstBootChanged (); + +private slots: + void _startUpload (QString pairURL, QJsonDocument); + void _stopUpload (); + void _startUploadRequest (); + void _parsePairingJson (QString jsonEnc); + void _setPairingStatus (PairingStatus status, QString pairingStatus); + +private: + QString _statusString; + QString _jsonFileName; + QString _lastPaired; + QVariantMap _remotePairingMap; + int _nfcIndex = -1; + int _microhardIndex = -1; + int _pairRetryCount = 0; + PairingStatus _status = PairingIdle; + AES _aes; + QJsonDocument _jsonDoc{}; + QMutex _uploadMutex{}; + QNetworkAccessManager* _uploadManager = nullptr; + QString _uploadURL{}; + QString _uploadData{}; + bool _firstBoot = true; + QStringList _deviceList; + + void _parsePairingJsonFile (); + QJsonDocument _createZeroTierConnectJson (QString cert2); + QJsonDocument _createMicrohardConnectJson (QString cert2); + QJsonDocument _createZeroTierPairingJson (QString cert1); + QJsonDocument _createMicrohardPairingJson (QString pwd, QString cert1); + QString _assumeMicrohardPairingJson (); + void _writeJson (QJsonDocument &jsonDoc, QString fileName); + QString _getLocalIPInNetwork (QString remoteIP, int num); + void _uploadFinished (); + void _uploadError (QNetworkReply::NetworkError code); + void _pairingCompleted (QString name); + void _connectionCompleted (QString name); + QDir _pairingCacheDir (); + QString _pairingCacheFile (QString uavName); + void _updatePairedDeviceNameList (); + +#if defined QGC_ENABLE_QTNFC + PairingNFC pairingNFC; +#endif +}; diff --git a/src/PairingManager/QtNFC.cc b/src/PairingManager/QtNFC.cc new file mode 100644 index 0000000..5921bc7 --- /dev/null +++ b/src/PairingManager/QtNFC.cc @@ -0,0 +1,135 @@ +/**************************************************************************** + * + * (c) 2019 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ +#include "PairingManager.h" +#include "QtNFC.h" +#include "QGCApplication.h" +#include + +QGC_LOGGING_CATEGORY(PairingNFCLog, "PairingNFCLog") + +#include + +//----------------------------------------------------------------------------- +PairingNFC::PairingNFC() +{ +} + +//----------------------------------------------------------------------------- +void +PairingNFC::start() +{ + if (manager != nullptr) { + return; + } + qgcApp()->toolbox()->pairingManager()->setStatusMessage(tr("Waiting for NFC connection")); + qCDebug(PairingNFCLog) << "Waiting for NFC connection"; + + manager = new QNearFieldManager(this); + if (!manager->isAvailable()) { + qWarning() << "NFC not available"; + delete manager; + manager = nullptr; + return; + } + + QNdefFilter filter; + filter.setOrderMatch(false); + filter.appendRecord(1, UINT_MAX); + // type parameter cannot specify substring so filter for "image/" below + filter.appendRecord(QNdefRecord::Mime, QByteArray(), 0, 1); + + int result = manager->registerNdefMessageHandler(filter, this, SLOT(handleMessage(QNdefMessage, QNearFieldTarget*))); + + if (result < 0) + qWarning() << "Platform does not support NDEF message handler registration"; + + manager->startTargetDetection(); + connect(manager, &QNearFieldManager::targetDetected, this, &PairingNFC::targetDetected); + connect(manager, &QNearFieldManager::targetLost, this, &PairingNFC::targetLost); +} + +//----------------------------------------------------------------------------- +void +PairingNFC::stop() +{ + if (manager != nullptr) { + qgcApp()->toolbox()->pairingManager()->setStatusMessage(""); + qCDebug(PairingNFCLog) << "NFC: Stop"; + manager->stopTargetDetection(); + delete manager; + manager = nullptr; + } +} + +//----------------------------------------------------------------------------- +void +PairingNFC::targetDetected(QNearFieldTarget *target) +{ + if (!target) { + return; + } + + qgcApp()->toolbox()->pairingManager()->setStatusMessage(tr("Device detected")); + qCDebug(PairingNFCLog) << "NFC: Device detected"; + connect(target, &QNearFieldTarget::ndefMessageRead, this, &PairingNFC::handlePolledNdefMessage); + connect(target, SIGNAL(error(QNearFieldTarget::Error,QNearFieldTarget::RequestId)), + this, SLOT(targetError(QNearFieldTarget::Error,QNearFieldTarget::RequestId))); + connect(target, &QNearFieldTarget::requestCompleted, this, &PairingNFC::handleRequestCompleted); + + manager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess); + QNearFieldTarget::RequestId id = target->readNdefMessages(); + if (target->waitForRequestCompleted(id)) { + qCDebug(PairingNFCLog) << "requestCompleted "; + QVariant res = target->requestResponse(id); + qCDebug(PairingNFCLog) << "Response: " << res.toString(); + } +} + +//----------------------------------------------------------------------------- +void +PairingNFC::handleRequestCompleted(const QNearFieldTarget::RequestId& id) +{ + Q_UNUSED(id); + qCDebug(PairingNFCLog) << "handleRequestCompleted "; +} + +//----------------------------------------------------------------------------- +void +PairingNFC::targetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId& id) +{ + Q_UNUSED(id); + qCDebug(PairingNFCLog) << "Error: " << error; +} + +//----------------------------------------------------------------------------- +void +PairingNFC::targetLost(QNearFieldTarget *target) +{ + qgcApp()->toolbox()->pairingManager()->setStatusMessage(tr("Device removed")); + qCDebug(PairingNFCLog) << "NFC: Device removed"; + if (target) { + target->deleteLater(); + } +} + +//----------------------------------------------------------------------------- +void +PairingNFC::handlePolledNdefMessage(QNdefMessage message) +{ + qCDebug(PairingNFCLog) << "NFC: Handle NDEF message"; +// QNearFieldTarget *target = qobject_cast(sender()); + for (const QNdefRecord &record : message) { + if (record.isRecordType()) { + QNdefNfcTextRecord textRecord(record); + qgcApp()->toolbox()->pairingManager()->jsonReceived(textRecord.text()); + } + } +} + +//----------------------------------------------------------------------------- diff --git a/src/PairingManager/QtNFC.h b/src/PairingManager/QtNFC.h new file mode 100644 index 0000000..d81c19a --- /dev/null +++ b/src/PairingManager/QtNFC.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * + * (c) 2019 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include "QGCLoggingCategory.h" + +Q_DECLARE_LOGGING_CATEGORY(PairingNFCLog) + +class PairingNFC : public QObject +{ + Q_OBJECT + +public: + PairingNFC(); + + void start(); + + void stop(); + +signals: + void parsePairingJson(QString json); + +public slots: + void targetDetected(QNearFieldTarget *target); + void targetLost(QNearFieldTarget *target); + void handlePolledNdefMessage(QNdefMessage message); + void targetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId& id); + void handleRequestCompleted(const QNearFieldTarget::RequestId& id); + +private: + bool _exitThread = false; ///< true: signal thread to exit + QNearFieldManager *manager = nullptr; +}; diff --git a/src/PairingManager/aes.cpp b/src/PairingManager/aes.cpp new file mode 100644 index 0000000..57c88ac --- /dev/null +++ b/src/PairingManager/aes.cpp @@ -0,0 +1,154 @@ +#include "aes.h" + +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +AES::AES(std::string password, unsigned long long salt) +{ + int nrounds = 5; + unsigned char key[32], iv[32]; + + /* + * Gen key & IV for AES 256 CBC mode. A SHA1 digest is used to hash the supplied key material. + * nrounds is the number of times the we hash the material. More rounds are more secure but + * slower. + */ + EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), + reinterpret_cast(&salt), + reinterpret_cast(password.c_str()), + static_cast(password.length()), + nrounds, key, iv); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + encCipherContext = EVP_CIPHER_CTX_new(); + decCipherContext = EVP_CIPHER_CTX_new(); + + EVP_CIPHER_CTX_init(encCipherContext); + EVP_EncryptInit_ex(encCipherContext, EVP_aes_256_cbc(), nullptr, key, iv); + EVP_CIPHER_CTX_init(decCipherContext); + EVP_DecryptInit_ex(decCipherContext, EVP_aes_256_cbc(), nullptr, key, iv); +#else + EVP_CIPHER_CTX_init(&encCipherContext); + EVP_EncryptInit_ex(&encCipherContext, EVP_aes_256_cbc(), nullptr, key, iv); + EVP_CIPHER_CTX_init(&decCipherContext); + EVP_DecryptInit_ex(&decCipherContext, EVP_aes_256_cbc(), nullptr, key, iv); +#endif +} + +//----------------------------------------------------------------------------- +AES::~AES() +{ +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + EVP_CIPHER_CTX_free(encCipherContext); + EVP_CIPHER_CTX_free(decCipherContext); +#else + EVP_CIPHER_CTX_cleanup(&encCipherContext); + EVP_CIPHER_CTX_cleanup(&decCipherContext); +#endif +} + +//----------------------------------------------------------------------------- +std::string +AES::encrypt(std::string plainText) +{ + unsigned long sourceLen = static_cast(plainText.length() + 1); + unsigned long destLen = sourceLen * 2; + unsigned char* compressed = new unsigned char[destLen]; + int err = compress2(compressed, &destLen, + reinterpret_cast(plainText.c_str()), + sourceLen, 9); + if (err != Z_OK) { + return {}; + } + + int pLen = static_cast(destLen); + int cLen = pLen + AES_BLOCK_SIZE; + int fLen = 0; + unsigned char* cipherText = new unsigned char[cLen]; + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + EVP_EncryptInit_ex(encCipherContext, nullptr, nullptr, nullptr, nullptr); + EVP_EncryptUpdate(encCipherContext, cipherText, &cLen, compressed, pLen); + EVP_EncryptFinal_ex(encCipherContext, cipherText + cLen, &fLen); +#else + EVP_EncryptInit_ex(&encCipherContext, nullptr, nullptr, nullptr, nullptr); + EVP_EncryptUpdate(&encCipherContext, cipherText, &cLen, compressed, pLen); + EVP_EncryptFinal_ex(&encCipherContext, cipherText + cLen, &fLen); +#endif + + std::vector data(cipherText, cipherText + cLen + fLen); + std::string res = base64Encode(data); + delete[] cipherText; + delete[] compressed; + + return res; +} +//----------------------------------------------------------------------------- +std::string +AES::decrypt(std::string cipherText) +{ + int fLen = 0; + std::vector text = base64Decode(cipherText); + int pLen = static_cast(text.size()); + unsigned char* plainText = new unsigned char[pLen]; + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + EVP_DecryptInit_ex(decCipherContext, nullptr, nullptr, nullptr, nullptr); + EVP_DecryptUpdate(decCipherContext, plainText, &pLen, text.data(), pLen); + EVP_DecryptFinal_ex(decCipherContext, plainText + pLen, &fLen); +#else + EVP_DecryptInit_ex(&decCipherContext, nullptr, nullptr, nullptr, nullptr); + EVP_DecryptUpdate(&decCipherContext, plainText, &pLen, text.data(), pLen); + EVP_DecryptFinal_ex(&decCipherContext, plainText + pLen, &fLen); +#endif + + unsigned long destLen = static_cast((pLen + fLen) * 2); + unsigned char* uncompressed = new unsigned char[destLen]; + int err = uncompress(uncompressed, &destLen, plainText, static_cast(pLen + fLen)); + if (err != Z_OK) { + return {}; + } + + std::string res(reinterpret_cast(uncompressed)); + delete[] uncompressed; + + return res; +} + +//----------------------------------------------------------------------------- +struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } }; + +std::string +AES::base64Encode(const std::vector& binary) +{ + std::unique_ptr b64(BIO_new(BIO_f_base64())); + BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL); + BIO* sink = BIO_new(BIO_s_mem()); + BIO_push(b64.get(), sink); + BIO_write(b64.get(), binary.data(), static_cast(binary.size())); + BIO_ctrl(b64.get(), BIO_CTRL_FLUSH, 0, nullptr); + const char* encoded; + const unsigned long len = static_cast(BIO_ctrl(sink, BIO_CTRL_INFO, 0, &encoded)); + + return std::string(encoded, len); +} + +//----------------------------------------------------------------------------- +std::vector +AES::base64Decode(std::string encoded) +{ + std::unique_ptr b64(BIO_new(BIO_f_base64())); + BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL); + BIO* source = BIO_new_mem_buf(encoded.c_str(), -1); // read-only source + BIO_push(b64.get(), source); + const unsigned long maxlen = encoded.length() / 4 * 3 + 1; + std::vector decoded(maxlen); + const unsigned long len = static_cast(BIO_read(b64.get(), decoded.data(), static_cast(maxlen))); + decoded.resize(len); + return decoded; +} + +//----------------------------------------------------------------------------- diff --git a/src/PairingManager/aes.h b/src/PairingManager/aes.h new file mode 100644 index 0000000..3b09dbe --- /dev/null +++ b/src/PairingManager/aes.h @@ -0,0 +1,35 @@ +#ifndef AES_H +#define AES_H + +#pragma once + +#include +#include +#include + +class AES +{ +public: + AES(std::string password, unsigned long long salt); + + ~AES(); + + std::string encrypt(std::string plainText); + + std::string decrypt(std::string cipherText); + +private: +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + EVP_CIPHER_CTX *encCipherContext = nullptr; + EVP_CIPHER_CTX *decCipherContext = nullptr; +#else + EVP_CIPHER_CTX encCipherContext; + EVP_CIPHER_CTX decCipherContext; +#endif + + std::string base64Encode(const std::vector& binary); + + std::vector base64Decode(std::string encoded); +}; + +#endif // AES_H