diff --git a/src/lib/gui/MainWindow.cpp b/src/lib/gui/MainWindow.cpp index 654741fbf..7612672f8 100644 --- a/src/lib/gui/MainWindow.cpp +++ b/src/lib/gui/MainWindow.cpp @@ -151,8 +151,8 @@ MainWindow::MainWindow() return; } - deskflow::FingerprintDatabase db; - db.read(Settings::tlsLocalDb().toStdString()); + FingerprintDatabase db; + db.read(Settings::tlsLocalDb()); if (db.fingerprints().size() != kTlsDbSize) { regenerateLocalFingerprints(); } @@ -541,19 +541,15 @@ void MainWindow::showMyFingerprint() return; } - deskflow::FingerprintDatabase db; - db.read(Settings::tlsLocalDb().toStdString()); + FingerprintDatabase db; + db.read(Settings::tlsLocalDb()); if (db.fingerprints().size() != kTlsDbSize) { if (regenerateLocalFingerprints()) showMyFingerprint(); return; } - QList fingerprints; - fingerprints.reserve(db.fingerprints().size()); - std::copy(db.fingerprints().begin(), db.fingerprints().end(), std::back_inserter(fingerprints)); - - FingerprintDialog fingerprintDialog(this, fingerprints); + FingerprintDialog fingerprintDialog(this, db.fingerprints()); fingerprintDialog.exec(); } @@ -772,17 +768,11 @@ void MainWindow::checkFingerprint(const QString &line) return; } - const deskflow::FingerprintData sha1 = { - deskflow::fingerprintTypeToString(deskflow::FingerprintType::SHA1), - deskflow::string::fromHex(match.captured(1).toStdString()) - }; + const Fingerprint sha1 = {Fingerprint::Type::SHA1, QByteArray::fromHex(match.captured(1).remove(":").toLatin1())}; const auto sha256Text = match.captured(2); - const deskflow::FingerprintData sha256 = { - deskflow::fingerprintTypeToString(deskflow::FingerprintType::SHA256), - deskflow::string::fromHex(sha256Text.toStdString()) - }; + const Fingerprint sha256 = {Fingerprint::Type::SHA256, QByteArray::fromHex(match.captured(2).remove(":").toLatin1())}; const bool isClient = m_coreProcess.mode() == CoreMode::Client; @@ -791,8 +781,8 @@ void MainWindow::checkFingerprint(const QString &line) return; } - deskflow::FingerprintDatabase db; - db.read(trustedFingerprintDb().toStdString()); + FingerprintDatabase db; + db.read(trustedFingerprintDatabase()); if (db.isTrusted(sha256)) { qDebug("fingerprint is trusted"); @@ -813,7 +803,7 @@ void MainWindow::checkFingerprint(const QString &line) if (fingerprintDialog.exec() == QDialog::Accepted) { db.addTrusted(sha256); - db.write(trustedFingerprintDb().toStdString()); + db.write(trustedFingerprintDatabase()); if (isClient) { m_checkedServers.removeAll(sha256Text); m_coreProcess.start(); @@ -1116,7 +1106,7 @@ void MainWindow::setHostName() applyConfig(); } -QString MainWindow::trustedFingerprintDb() +QString MainWindow::trustedFingerprintDatabase() { const bool isClient = m_coreProcess.mode() == CoreMode::Client; return isClient ? Settings::tlsTrustedServersDb() : Settings::tlsTrustedClientsDb(); diff --git a/src/lib/gui/MainWindow.h b/src/lib/gui/MainWindow.h index cb2e8ded7..741c34c70 100644 --- a/src/lib/gui/MainWindow.h +++ b/src/lib/gui/MainWindow.h @@ -156,10 +156,10 @@ private: void remoteHostChanged(const QString &newRemoteHost); /** - * @brief trustedFingerprintDb get the fingerprintDb for the trusted clients or trusted servers. + * @brief trustedFingerprintDatabase get the FingerprintDatabase for the trusted clients or trusted servers. * @return The path to the trusted fingerprint file */ - QString trustedFingerprintDb(); + QString trustedFingerprintDatabase(); // Generate prints if they are missing // Returns true if successful diff --git a/src/lib/gui/dialogs/FingerprintDialog.cpp b/src/lib/gui/dialogs/FingerprintDialog.cpp index f4668f875..21feecac2 100644 --- a/src/lib/gui/dialogs/FingerprintDialog.cpp +++ b/src/lib/gui/dialogs/FingerprintDialog.cpp @@ -14,7 +14,7 @@ #include FingerprintDialog::FingerprintDialog( - QWidget *parent, const QList &fingerprints, FingerprintDialogMode mode + QWidget *parent, const QList &fingerprints, FingerprintDialogMode mode ) : QDialog(parent), m_lblHeader{new QLabel(this)}, diff --git a/src/lib/gui/dialogs/FingerprintDialog.h b/src/lib/gui/dialogs/FingerprintDialog.h index 0a3d0da32..9dd44001a 100644 --- a/src/lib/gui/dialogs/FingerprintDialog.h +++ b/src/lib/gui/dialogs/FingerprintDialog.h @@ -6,7 +6,7 @@ #pragma once -#include "net/FingerprintData.h" +#include "net/Fingerprint.h" #include #include @@ -27,7 +27,7 @@ class FingerprintDialog : public QDialog public: explicit FingerprintDialog( - QWidget *parent = nullptr, const QList &fingerprints = {}, + QWidget *parent = nullptr, const QList &fingerprints = {}, FingerprintDialogMode mode = FingerprintDialogMode::Local ); ~FingerprintDialog() override = default; diff --git a/src/lib/gui/tls/TlsCertificate.cpp b/src/lib/gui/tls/TlsCertificate.cpp index bcb7e9e94..1de7036d8 100644 --- a/src/lib/gui/tls/TlsCertificate.cpp +++ b/src/lib/gui/tls/TlsCertificate.cpp @@ -9,7 +9,8 @@ #include "base/FinalAction.h" #include "common/Settings.h" -#include "net/FingerprintData.h" +#include "io/Filesystem.h" +#include "net/Fingerprint.h" #include "net/FingerprintDatabase.h" #include "net/SecureUtils.h" @@ -52,10 +53,10 @@ bool TlsCertificate::generateFingerprint(const QString &certificateFilename) qDebug("generating tls fingerprint"); const std::string certPath = certificateFilename.toStdString(); try { - deskflow::FingerprintDatabase db; - db.addTrusted(deskflow::pemFileCertFingerprint(certPath, deskflow::FingerprintType::SHA1)); - db.addTrusted(deskflow::pemFileCertFingerprint(certPath, deskflow::FingerprintType::SHA256)); - db.write(Settings::tlsLocalDb().toStdString()); + FingerprintDatabase db; + db.addTrusted(deskflow::pemFileCertFingerprint(certPath, Fingerprint::Type::SHA1)); + db.addTrusted(deskflow::pemFileCertFingerprint(certPath, Fingerprint::Type::SHA256)); + db.write(Settings::tlsLocalDb()); qDebug("tls fingerprint generated"); return true; diff --git a/src/lib/gui/widgets/FingerprintPreview.cpp b/src/lib/gui/widgets/FingerprintPreview.cpp index 8e8fdceb0..9728603e9 100644 --- a/src/lib/gui/widgets/FingerprintPreview.cpp +++ b/src/lib/gui/widgets/FingerprintPreview.cpp @@ -12,8 +12,7 @@ #include -FingerprintPreview::FingerprintPreview(QWidget *parent, const QList &fingerprints) - : QFrame(parent) +FingerprintPreview::FingerprintPreview(QWidget *parent, const QList &fingerprints) : QFrame(parent) { setFrameShape(QFrame::StyledPanel); setFrameStyle(QFrame::Sunken); @@ -24,13 +23,18 @@ FingerprintPreview::FingerprintPreview(QWidget *parent, const QList -#include +#include class FingerprintPreview : public QFrame { Q_OBJECT public: - explicit FingerprintPreview(QWidget *parent, const QList &fingerprints = {}); + explicit FingerprintPreview(QWidget *parent, const QList &fingerprints = {}); ~FingerprintPreview() override = default; }; diff --git a/src/lib/net/CMakeLists.txt b/src/lib/net/CMakeLists.txt index a7a7d95ea..ce6d3e8d5 100644 --- a/src/lib/net/CMakeLists.txt +++ b/src/lib/net/CMakeLists.txt @@ -13,8 +13,8 @@ endif() find_package(OpenSSL ${REQUIRED_OPENSSL_VERSION} REQUIRED COMPONENTS SSL Crypto) add_library(net STATIC - FingerprintData.cpp - FingerprintData.h + Fingerprint.cpp + Fingerprint.h FingerprintDatabase.cpp FingerprintDatabase.h IDataSocket.cpp diff --git a/src/lib/net/Fingerprint.cpp b/src/lib/net/Fingerprint.cpp new file mode 100644 index 000000000..bed4757db --- /dev/null +++ b/src/lib/net/Fingerprint.cpp @@ -0,0 +1,88 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers + * SPDX-FileCopyrightText: (C) 2021 Barrier Contributors + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "Fingerprint.h" + +#include + +bool Fingerprint::isValid() const +{ + switch (type) { + case Fingerprint::Type::Invalid: + return false; + case Fingerprint::Type::SHA1: + return data.length() == 20; + case Fingerprint::Type::SHA256: + return data.length() == 32; + default: + return false; + } +} + +bool Fingerprint::operator==(const Fingerprint &other) const +{ + return type == other.type && data == other.data; +} + +QString Fingerprint::toDbLine() const +{ + if (!isValid()) { + qInfo() << "unable to parse invalid db line"; + return {}; + } + return QStringLiteral("v2:%1:%2").arg(typeToString(type), data.toHex().toLower()); +} + +Fingerprint Fingerprint::fromDbLine(const QString &line) +{ + Fingerprint result; + + if (line.isEmpty()) + return result; + + if (line.startsWith("v2:")) { + const auto sLine = line.split(':'); + if (sLine.count() != 3) + return result; + result.type = typeFromString(sLine.at(1)); + result.data = QByteArray::fromHex(sLine.at(2).toLower().toLatin1()); + } else { + // v1 fallback + const auto kSha1ColonCount = 19; + const auto kSha1HexCharCount = 40; + const auto kSha1ExpectedSize = kSha1HexCharCount + kSha1ColonCount; + if (line.size() != kSha1ExpectedSize || line.count(':') != kSha1ColonCount) + return result; + result.type = Fingerprint::Type::SHA1; + auto l2 = line; + result.data = QByteArray::fromHex(l2.remove(':').toLatin1()); + } + + return result; +} + +Fingerprint::Type Fingerprint::typeFromString(const QString &type) +{ + const auto t = type.toLower(); + if (t == m_type_sha1) + return Type::SHA1; + if (t == m_type_sha256) + return Type::SHA256; + return Type::Invalid; +} + +QString Fingerprint::typeToString(Fingerprint::Type type) +{ + switch (type) { + case Type::SHA1: + return m_type_sha1; + case Type::SHA256: + return m_type_sha256; + default: + return m_type_invalid; + } +} diff --git a/src/lib/net/Fingerprint.h b/src/lib/net/Fingerprint.h new file mode 100644 index 000000000..ab328da5c --- /dev/null +++ b/src/lib/net/Fingerprint.h @@ -0,0 +1,38 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include +#include + +struct Fingerprint +{ +private: + Q_GADGET + inline static QString m_type_sha1 = QStringLiteral("sha1"); + inline static QString m_type_sha256 = QStringLiteral("sha256"); + inline static QString m_type_invalid = QStringLiteral("invalid"); + +public: + enum class Type + { + Invalid, + SHA1, + SHA256 + }; + Q_ENUM(Type) + Type type = Type::Invalid; + QByteArray data; + + bool isValid() const; + + bool operator==(const Fingerprint &other) const; + QString toDbLine() const; + static Fingerprint fromDbLine(const QString &line); + static QString typeToString(Fingerprint::Type type); + static Fingerprint::Type typeFromString(const QString &type); +}; diff --git a/src/lib/net/FingerprintData.cpp b/src/lib/net/FingerprintData.cpp deleted file mode 100644 index 150a011e8..000000000 --- a/src/lib/net/FingerprintData.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers - * SPDX-FileCopyrightText: (C) 2021 Barrier Contributors - * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception - */ - -#include "FingerprintDatabase.h" - -#include "io/Filesystem.h" - -#include -#include - -namespace deskflow { - -bool FingerprintData::operator==(const FingerprintData &other) const -{ - return algorithm == other.algorithm && data == other.data; -} - -const char *fingerprintTypeToString(FingerprintType type) -{ - switch (type) { - case FingerprintType::Invalid: - return "invalid"; - case FingerprintType::SHA1: - return "sha1"; - case FingerprintType::SHA256: - return "sha256"; - default: - break; - } - return "invalid"; -} - -FingerprintType fingerprintTypeFromString(const std::string &type) -{ - if (type == "sha1") - return FingerprintType::SHA1; - - if (type == "sha256") - return FingerprintType::SHA256; - - return FingerprintType::Invalid; -} - -} // namespace deskflow diff --git a/src/lib/net/FingerprintData.h b/src/lib/net/FingerprintData.h deleted file mode 100644 index d45f34e86..000000000 --- a/src/lib/net/FingerprintData.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers - * SPDX-FileCopyrightText: (C) 2021 Barrier Contributors - * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception - */ - -#pragma once - -#include -#include -#include - -namespace deskflow { - -enum FingerprintType -{ - Invalid, - SHA1, - SHA256 -}; - -struct FingerprintData -{ - std::string algorithm; - std::vector data; - - bool valid() const - { - return !algorithm.empty(); - } - - bool operator==(const FingerprintData &other) const; -}; - -const char *fingerprintTypeToString(FingerprintType type); -FingerprintType fingerprintTypeFromString(const std::string &type); - -} // namespace deskflow diff --git a/src/lib/net/FingerprintDatabase.cpp b/src/lib/net/FingerprintDatabase.cpp index 552fe126c..6e8023f94 100644 --- a/src/lib/net/FingerprintDatabase.cpp +++ b/src/lib/net/FingerprintDatabase.cpp @@ -7,57 +7,69 @@ #include "FingerprintDatabase.h" -#include "base/String.h" -#include "io/Filesystem.h" +#include +#include -#include -#include - -namespace deskflow { - -void FingerprintDatabase::read(const fs::path &path) +void FingerprintDatabase::read(const QString &path) { - std::ifstream file; - openUtf8Path(file, path, std::ios_base::in); - readStream(file); -} - -void FingerprintDatabase::write(const fs::path &path) -{ - std::ofstream file; - openUtf8Path(file, path, std::ios_base::out); - writeStream(file); -} - -void FingerprintDatabase::readStream(std::istream &stream) -{ - if (!stream.good()) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) return; + QTextStream in(&file); + readStream(in); +} + +void FingerprintDatabase::readStream(QTextStream &in) +{ + // Make sure the stream has something to read + if (!in.device() && !in.string()) + return; + + if (in.device()) { + if (!in.device()->isReadable()) + return; } - std::string line; - while (std::getline(stream, line)) { - if (line.empty()) { + if (in.string()) { + if (in.string()->isEmpty()) + return; + } + + QString line; + while (!in.atEnd()) { + line = in.readLine(); + if (line.isEmpty()) + continue; + auto fingerprint = Fingerprint::fromDbLine(line); + if (!fingerprint.isValid()) { continue; } - - auto fingerprint = parseDbLine(line); - if (!fingerprint.valid()) { - continue; - } - - m_fingerprints.push_back(fingerprint); + m_fingerprints.append(fingerprint); } } -void FingerprintDatabase::writeStream(std::ostream &stream) +void FingerprintDatabase::write(const QString &path) { - if (!stream.good()) { + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) return; + QTextStream out(&file); + writeStream(out); +} + +void FingerprintDatabase::writeStream(QTextStream &out) +{ + // Make sure the stream has somewhere to write + if (!out.device() && !out.string()) + return; + + if (out.device()) { + if (!out.device()->isWritable()) + return; } - for (const auto &fingerprint : m_fingerprints) { - stream << toDbLine(fingerprint) << "\n"; + for (const auto &fingerprint : std::as_const(m_fingerprints)) { + out << fingerprint.toDbLine() << "\n"; } } @@ -66,68 +78,15 @@ void FingerprintDatabase::clear() m_fingerprints.clear(); } -void FingerprintDatabase::addTrusted(const FingerprintData &fingerprint) +void FingerprintDatabase::addTrusted(const Fingerprint &fingerprint) { if (isTrusted(fingerprint)) { return; } - m_fingerprints.push_back(fingerprint); + m_fingerprints.append(fingerprint); } -bool FingerprintDatabase::isTrusted(const FingerprintData &fingerprint) +bool FingerprintDatabase::isTrusted(const Fingerprint &fingerprint) { - auto found = std::find(m_fingerprints.begin(), m_fingerprints.end(), fingerprint); - return found != m_fingerprints.end(); + return m_fingerprints.contains(fingerprint); } - -FingerprintData FingerprintDatabase::parseDbLine(const std::string &line) -{ - - const auto kSha1ColonCount = 19; - const auto kSha1HexCharCount = 40; - const auto kSha1ExpectedSize = kSha1HexCharCount + kSha1ColonCount; - - FingerprintData result; - - // legacy v1 certificate handling - if (std::count(line.begin(), line.end(), ':') == kSha1ColonCount && line.size() == kSha1ExpectedSize) { - auto data = string::fromHex(line); - if (data.empty()) { - return result; - } - result.algorithm = fingerprintTypeToString(FingerprintType::SHA1); - result.data = data; - return result; - } - - auto versionEndPos = line.find(':'); - if (versionEndPos == std::string::npos) { - return result; - } - if (line.substr(0, versionEndPos) != "v2") { - return result; - } - auto algoStartPos = versionEndPos + 1; - auto algoEndPos = line.find(':', algoStartPos); - if (algoEndPos == std::string::npos) { - return result; - } - auto algorithm = line.substr(algoStartPos, algoEndPos - algoStartPos); - auto data = string::fromHex(line.substr(algoEndPos + 1)); - - if (data.empty()) { - return result; - } - - result.algorithm = algorithm; - result.data = data; - return result; -} - -std::string FingerprintDatabase::toDbLine(const FingerprintData &fingerprint) -{ - std::string fingerprintStr(fingerprint.data.begin(), fingerprint.data.end()); - return "v2:" + fingerprint.algorithm + ":" + string::toHex(fingerprintStr, 2); -} - -} // namespace deskflow diff --git a/src/lib/net/FingerprintDatabase.h b/src/lib/net/FingerprintDatabase.h index fdd22d7eb..f6be8e1d6 100644 --- a/src/lib/net/FingerprintDatabase.h +++ b/src/lib/net/FingerprintDatabase.h @@ -7,39 +7,28 @@ #pragma once -#include "FingerprintData.h" +#include "Fingerprint.h" -#include "io/Filesystem.h" - -#include -#include -#include - -namespace deskflow { +#include class FingerprintDatabase { public: - void read(const fs::path &path); - void write(const fs::path &path); + void read(const QString &path); + void write(const QString &path); - void readStream(std::istream &stream); - void writeStream(std::ostream &stream); + void readStream(QTextStream &in); + void writeStream(QTextStream &out); void clear(); - void addTrusted(const FingerprintData &fingerprint); - bool isTrusted(const FingerprintData &fingerprint); + void addTrusted(const Fingerprint &fingerprint); + bool isTrusted(const Fingerprint &fingerprint); - const std::vector &fingerprints() const + const QList &fingerprints() const { return m_fingerprints; } - static FingerprintData parseDbLine(const std::string &line); - static std::string toDbLine(const FingerprintData &fingerprint); - private: - std::vector m_fingerprints; + QList m_fingerprints; }; - -} // namespace deskflow diff --git a/src/lib/net/SecureSocket.cpp b/src/lib/net/SecureSocket.cpp index d60c942e0..a4050ffab 100644 --- a/src/lib/net/SecureSocket.cpp +++ b/src/lib/net/SecureSocket.cpp @@ -448,8 +448,7 @@ int SecureSocket::secureAccept(int socket) // If not fatal and no retry, state is good if (retry == 0) { if (m_securityLevel == SecurityLevel::PeerAuth) { - std::string dbDir = Settings::tlsTrustedClientsDb().toStdString().c_str(); - if (!verifyCertFingerprint(dbDir)) { + if (!verifyCertFingerprint(Settings::tlsTrustedClientsDb())) { retry = 0; disconnect(); return -1; // Fail @@ -521,8 +520,7 @@ int SecureSocket::secureConnect(int socket) retry = 0; // No error, set ready, process and return ok m_secureReady = true; - std::string dbDir = Settings::tlsTrustedServersDb().toStdString().c_str(); - if (verifyCertFingerprint(dbDir)) { + if (verifyCertFingerprint(Settings::tlsTrustedServersDb())) { LOG((CLOG_INFO "connected to secure socket")); if (!showCertificate()) { disconnect(); @@ -645,14 +643,14 @@ void SecureSocket::disconnect() sendEvent(EventTypes::StreamInputShutdown); } -bool SecureSocket::verifyCertFingerprint(const deskflow::fs::path &fingerprintDbPath) +bool SecureSocket::verifyCertFingerprint(const QString &FingerprintDatabasePath) { - deskflow::FingerprintData sha1; - deskflow::FingerprintData sha256; + Fingerprint sha1; + Fingerprint sha256; try { auto cert = SSL_get_peer_certificate(m_ssl->m_ssl); - sha1 = deskflow::sslCertFingerprint(cert, deskflow::FingerprintType::SHA1); - sha256 = deskflow::sslCertFingerprint(cert, deskflow::FingerprintType::SHA256); + sha1 = deskflow::sslCertFingerprint(cert, Fingerprint::Type::SHA1); + sha256 = deskflow::sslCertFingerprint(cert, Fingerprint::Type::SHA256); } catch (const std::exception &e) { LOG((CLOG_ERR "%s", e.what())); return false; @@ -660,26 +658,25 @@ bool SecureSocket::verifyCertFingerprint(const deskflow::fs::path &fingerprintDb // Gui Must Parse these two lines, DO NOT CHANGE LOG( - (CLOG_NOTE "peer fingerprint: (SHA1) %s (SHA256) %s", deskflow::formatSSLFingerprint(sha1.data).c_str(), - deskflow::formatSSLFingerprint(sha256.data).c_str()) + (CLOG_NOTE "peer fingerprint: (SHA1) %s (SHA256) %s", + deskflow::formatSSLFingerprint(sha1.data).toStdString().c_str(), + deskflow::formatSSLFingerprint(sha256.data).toStdString().c_str()) ); - // check if this fingerprint exist - std::ifstream file; + QFile file(FingerprintDatabasePath); - deskflow::openUtf8Path(file, fingerprintDbPath); - deskflow::FingerprintDatabase db; - db.read(fingerprintDbPath); + FingerprintDatabase db; + db.read(FingerprintDatabasePath); const bool emptyDB = db.fingerprints().empty(); - const auto &path = fingerprintDbPath.string(); - if (file.good() && emptyDB) { - LOG((CLOG_ERR "failed to open trusted fingerprints file: %s", path.c_str())); + const auto &path = FingerprintDatabasePath; + if (file.exists() && emptyDB) { + LOG((CLOG_ERR "failed to open trusted fingerprints file: %s", path.toStdString().c_str())); return false; } if (!emptyDB) { - LOG((CLOG_DEBUG "read %d fingerprint(s) from file: %s", db.fingerprints().size(), path.c_str())); + LOG((CLOG_DEBUG "read %d fingerprint(s) from file: %s", db.fingerprints().size(), path.toStdString().c_str())); } if (!db.isTrusted(sha256)) { diff --git a/src/lib/net/SecureSocket.h b/src/lib/net/SecureSocket.h index c9257b609..cddc7c577 100644 --- a/src/lib/net/SecureSocket.h +++ b/src/lib/net/SecureSocket.h @@ -16,6 +16,7 @@ class IEventQueue; class SocketMultiplexer; class ISocketMultiplexerJob; +class QString; struct Ssl; @@ -76,7 +77,7 @@ private: bool showCertificate() const; void checkResult(int n, int &retry); void disconnect(); - bool verifyCertFingerprint(const deskflow::fs::path &fingerprintDbPath); + bool verifyCertFingerprint(const QString &FingerprintDatabasePath); ISocketMultiplexerJob *serviceConnect(ISocketMultiplexerJob *, bool, bool, bool); diff --git a/src/lib/net/SecureUtils.cpp b/src/lib/net/SecureUtils.cpp index 590b569f4..749633a4d 100644 --- a/src/lib/net/SecureUtils.cpp +++ b/src/lib/net/SecureUtils.cpp @@ -23,12 +23,12 @@ namespace deskflow { namespace { -const EVP_MD *digestForType(FingerprintType type) +const EVP_MD *digestForType(Fingerprint::Type type) { switch (type) { - case FingerprintType::SHA1: + case Fingerprint::Type::SHA1: return EVP_sha1(); - case FingerprintType::SHA256: + case Fingerprint::Type::SHA256: return EVP_sha256(); default: break; @@ -38,22 +38,15 @@ const EVP_MD *digestForType(FingerprintType type) } // namespace -std::string formatSSLFingerprint(const std::vector &fingerprint, bool enableSeparators) +QString formatSSLFingerprint(const QByteArray &fingerprint, bool enableSeparators) { - std::string result = deskflow::string::toHex(fingerprint, 2); - - deskflow::string::uppercase(result); - - if (enableSeparators) { - const auto usedSpaces = 3; - size_t separators = result.size() / 2; - for (size_t i = 1; i < separators; i++) - result.insert(i * usedSpaces - 1, ":"); - } - return result; + if (enableSeparators) + return fingerprint.toHex(':').toUpper(); + else + return fingerprint.toHex().toUpper(); } -FingerprintData sslCertFingerprint(X509 *cert, FingerprintType type) +Fingerprint sslCertFingerprint(X509 *cert, Fingerprint::Type type) { if (!cert) { throw std::runtime_error("certificate is null"); @@ -67,12 +60,11 @@ FingerprintData sslCertFingerprint(X509 *cert, FingerprintType type) throw std::runtime_error("failed to calculate fingerprint, digest result: " + std::to_string(result)); } - std::vector digestVec; - digestVec.assign(reinterpret_cast(digest), reinterpret_cast(digest) + digestLength); - return {fingerprintTypeToString(type), digestVec}; + QByteArray digestArray(reinterpret_cast(digest), digestLength); + return {type, digestArray}; } -FingerprintData pemFileCertFingerprint(const std::string &path, FingerprintType type) +Fingerprint pemFileCertFingerprint(const std::string &path, Fingerprint::Type type) { auto fp = fopenUtf8Path(path, "r"); if (!fp) { @@ -156,27 +148,25 @@ int getCertLength(const std::string &path) return size; } -std::string formatSSLFingerprintColumns(const std::vector &fingerprint) +QString formatSSLFingerprintColumns(const QByteArray &fingerprint) { - auto kmaxColumns = 8; + auto kmaxColumns = 24; - std::string hex = deskflow::string::toHex(fingerprint, 2); - deskflow::string::uppercase(hex); - if (hex.empty() || hex.size() % 2 != 0) { + QString hex = fingerprint.toHex(':').toUpper(); + if (hex.isEmpty()) { return hex; } - std::string separated; - for (std::size_t i = 0; i < hex.size(); i += kmaxColumns * 2) { - for (std::size_t j = i; j < i + 16 && j < hex.size() - 1; j += 2) { - separated.push_back(hex[j]); - separated.push_back(hex[j + 1]); - separated.push_back(':'); - } - separated.push_back('\n'); + QString final; + while (!hex.isEmpty()) { + final.append(hex.mid(0, kmaxColumns)); + hex.remove(0, kmaxColumns); + if (final.endsWith(':')) + final.removeLast(); + final.append('\n'); } - separated.pop_back(); // we don't need last newline character - return separated; + final.removeLast(); + return final; } /* @@ -206,12 +196,12 @@ walked in either direction. fail, too, because the key type would not fit in anymore. */ -std::string generateFingerprintArt(const std::vector &rawDigest) +QString generateFingerprintArt(const QByteArray &rawDigest) { const auto baseSize = 8; const auto rows = (baseSize + 1); const auto columns = (baseSize * 2 + 1); - const std::string characterPool = " .o+=*BOX@%&#/^SE"; + const QString characterPool = " .o+=*BOX@%&#/^SE"; const std::size_t len = characterPool.length() - 1; std::uint8_t field[columns][rows]; @@ -243,7 +233,7 @@ std::string generateFingerprintArt(const std::vector &rawDigest) field[columns / 2][rows / 2] = len - 1; field[x][y] = len; - std::string result; + QString result; result.reserve((columns + 3) * (rows + 2)); result.append("╔═════════════════╗\n"); @@ -251,7 +241,7 @@ std::string generateFingerprintArt(const std::vector &rawDigest) for (y = 0; y < rows; y++) { result.append("║"); for (x = 0; x < columns; x++) - result.append(characterPool.substr(std::min(field[x][y], len), 1)); + result.append(characterPool.at(std::min(field[x][y], len))); result.append("║\n"); } diff --git a/src/lib/net/SecureUtils.h b/src/lib/net/SecureUtils.h index 6d28994ae..a448d45d9 100644 --- a/src/lib/net/SecureUtils.h +++ b/src/lib/net/SecureUtils.h @@ -7,7 +7,7 @@ #pragma once -#include "FingerprintData.h" +#include "Fingerprint.h" #include #include @@ -22,17 +22,17 @@ namespace deskflow { * @param enableSeparators insert : seperator every byte when true * @return a Formated Fingerprint String */ -std::string formatSSLFingerprint(const std::vector &fingerprint, bool enableSeparators = true); +QString formatSSLFingerprint(const QByteArray &fingerprint, bool enableSeparators = true); -std::string formatSSLFingerprintColumns(const std::vector &fingerprint); +QString formatSSLFingerprintColumns(const QByteArray &fingerprint); -FingerprintData sslCertFingerprint(X509 *cert, FingerprintType type); +Fingerprint sslCertFingerprint(X509 *cert, Fingerprint::Type type); -FingerprintData pemFileCertFingerprint(const std::string &path, FingerprintType type); +Fingerprint pemFileCertFingerprint(const std::string &path, Fingerprint::Type type); void generatePemSelfSignedCert(const std::string &path, int keyLength = 2048); int getCertLength(const std::string &path); -std::string generateFingerprintArt(const std::vector &rawDigest); +QString generateFingerprintArt(const QByteArray &rawDigest); } // namespace deskflow diff --git a/src/unittests/net/CMakeLists.txt b/src/unittests/net/CMakeLists.txt index 720dd4c50..429a310f3 100644 --- a/src/unittests/net/CMakeLists.txt +++ b/src/unittests/net/CMakeLists.txt @@ -13,6 +13,14 @@ create_test( WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/net" ) +create_test( + NAME FingerprintTests + DEPENDS net + LIBS base arch mt io ${extra_libs} + SOURCE FingerprintTests.cpp + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/net" +) + create_test( NAME FingerprintDatabaseTests DEPENDS net diff --git a/src/unittests/net/FingerprintDatabaseTests.cpp b/src/unittests/net/FingerprintDatabaseTests.cpp index 7506581f1..b60686c6b 100644 --- a/src/unittests/net/FingerprintDatabaseTests.cpp +++ b/src/unittests/net/FingerprintDatabaseTests.cpp @@ -7,39 +7,28 @@ #include "FingerprintDatabaseTests.h" -#include "net/FingerprintData.h" +#include "net/Fingerprint.h" #include "net/FingerprintDatabase.h" #include -using namespace deskflow; - -void FingerprintDatabaseTests::parseDBLine() -{ - QVERIFY(!FingerprintDatabase::parseDbLine("").valid()); - QVERIFY(!FingerprintDatabase::parseDbLine("abcd").valid()); - QVERIFY(!FingerprintDatabase::parseDbLine("v1:algo:something").valid()); - QVERIFY(!FingerprintDatabase::parseDbLine("v2:algo:something").valid()); - QVERIFY(!FingerprintDatabase::parseDbLine("v2:algo:01020304abc").valid()); - QVERIFY(!FingerprintDatabase::parseDbLine("v2:algo:01020304ZZ").valid()); - QCOMPARE(FingerprintDatabase::parseDbLine("v2:algo:01020304ab"), (FingerprintData{"algo", {1, 2, 3, 4, 0xab}})); -} - void FingerprintDatabaseTests::readFile() { - std::istringstream stream; - stream.str(R"( -v2:algo1:01020304ab -v2:algo2:03040506ab + QString data = R"( +v2:sha1:01020304ab +v2:sha1:03040506ab AB:CD:EF:00:01:02:03:04:05:06:07:08:09:10:11:12:13:14:15:16 -)"); +)"; + + QTextStream stream(&data); + FingerprintDatabase db; db.readStream(stream); - std::vector expected = { - {"algo1", {1, 2, 3, 4, 0xab}}, - {"algo2", {3, 4, 5, 6, 0xab}}, - {"sha1", {0xab, 0xcd, 0xef, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}}, + // Only one will be in our list as only one is valid + QList expected = { + {Fingerprint::Type::SHA1, + QByteArray::fromRawData("\xAB\xCD\xEF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16", 20)} }; QCOMPARE(db.fingerprints(), expected); @@ -47,22 +36,27 @@ AB:CD:EF:00:01:02:03:04:05:06:07:08:09:10:11:12:13:14:15:16 void FingerprintDatabaseTests::writeFile() { - std::ostringstream stream; + QString out; + QTextStream stream(&out); FingerprintDatabase db; - db.addTrusted({"algo1", {1, 2, 3, 4, 0xab}}); - db.addTrusted({"algo2", {3, 4, 5, 6, 0xab}}); + db.addTrusted( + {Fingerprint::Type::SHA1, QByteArray::fromHex(QString("ABCDEF0001020304050607080910111213141516").toLatin1())} + ); + db.addTrusted( + {Fingerprint::Type::SHA1, QByteArray::fromHex(QString("0001020304050607080910111213141516ABCDEF").toLatin1())} + ); db.writeStream(stream); - QCOMPARE(stream.str(), R"(v2:algo1:01020304ab -v2:algo2:03040506ab + QCOMPARE(stream.readAll(), R"(v2:sha1:abcdef0001020304050607080910111213141516 +v2:sha1:0001020304050607080910111213141516abcdef )"); } void FingerprintDatabaseTests::clear() { FingerprintDatabase db; - db.addTrusted({"algo1", {1, 2, 3, 4, 0xab}}); + db.addTrusted({Fingerprint::Type::SHA1, QByteArray::fromHex(QString("01020304ab").toLatin1())}); db.clear(); QVERIFY(db.fingerprints().empty()); @@ -70,17 +64,22 @@ void FingerprintDatabaseTests::clear() void FingerprintDatabaseTests::trusted() { + Fingerprint trusted1 = {Fingerprint::Type::SHA1, QByteArray::fromHex(QString("01020304ab").toLatin1())}; + Fingerprint trusted2 = {Fingerprint::Type::SHA1, QByteArray::fromHex(QString("03040506ab").toLatin1())}; + Fingerprint untrusted = {Fingerprint::Type::SHA1, QByteArray::fromHex(QString("01020304ac").toLatin1())}; + FingerprintDatabase db; - db.addTrusted({"algo1", {1, 2, 3, 4, 0xab}}); - QVERIFY(db.isTrusted({"algo1", {1, 2, 3, 4, 0xab}})); - QVERIFY(!db.isTrusted({"algo2", {1, 2, 3, 4, 0xab}})); - QVERIFY(!db.isTrusted({"algo1", {1, 2, 3, 4, 0xac}})); + db.addTrusted(trusted1); + QVERIFY(db.isTrusted(trusted1)); + QVERIFY(!db.isTrusted(trusted2)); + QVERIFY(!db.isTrusted(untrusted)); - db.addTrusted({"algo2", {3, 4, 5, 6, 0xab}}); - QVERIFY(db.isTrusted({"algo2", {3, 4, 5, 6, 0xab}})); + db.addTrusted(trusted2); + QVERIFY(db.isTrusted(trusted1)); + QVERIFY(db.isTrusted(trusted2)); + QVERIFY(!db.isTrusted(untrusted)); - db.addTrusted({"algo1", {1, 2, 3, 4, 0xab}}); QCOMPARE(db.fingerprints().size(), 2); } diff --git a/src/unittests/net/FingerprintDatabaseTests.h b/src/unittests/net/FingerprintDatabaseTests.h index b411836ac..d7bfd4e19 100644 --- a/src/unittests/net/FingerprintDatabaseTests.h +++ b/src/unittests/net/FingerprintDatabaseTests.h @@ -10,7 +10,6 @@ class FingerprintDatabaseTests : public QObject { Q_OBJECT private slots: - void parseDBLine(); void readFile(); void writeFile(); void clear(); diff --git a/src/unittests/net/FingerprintTests.cpp b/src/unittests/net/FingerprintTests.cpp new file mode 100644 index 000000000..c10aad67e --- /dev/null +++ b/src/unittests/net/FingerprintTests.cpp @@ -0,0 +1,188 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "FingerprintTests.h" + +#include "net/Fingerprint.h" + +void FingerprintTests::test_isValid() +{ + Fingerprint f; + + QVERIFY(!f.isValid()); + + // Always invalid without a type + f.data = f.data.fill('\x23', 1); + QVERIFY(!f.isValid()); + + // SHA1 Tests + f.type = Fingerprint::Type::SHA1; + + // Invalid SHA1, no Data + f.data.clear(); + QVERIFY(!f.isValid()); + + // Invalid SHA1, 5 bytes + f.data = f.data.fill('\x23', 5); + QVERIFY(!f.isValid()); + + // Valid SHA1, 20 bytes + f.data = f.data.fill('\x23', 20); + QVERIFY(f.isValid()); + + // Invalid SHA1, 25 bytes + f.data = f.data.fill('\x23', 25); + QVERIFY(!f.isValid()); + + // SHA256 Tests + f.type = Fingerprint::Type::SHA256; + + // Invalid SHA256, no Data + f.data.clear(); + QVERIFY(!f.isValid()); + + // Invalid SHA256, 16 bytes + f.data = f.data.fill('\x23', 16); + QVERIFY(!f.isValid()); + + // Valid SHA256, 32 bytes + f.data = f.data.fill('\x23', 32); + QVERIFY(f.isValid()); + + // Invalid SHA256, 50 bytes + f.data = f.data.fill('\x23', 50); + QVERIFY(!f.isValid()); +} + +void FingerprintTests::test_toDbLine() +{ + Fingerprint f; + + // Invalid Fingerprints return empty string + QVERIFY(f.toDbLine().isEmpty()); + + // Always invalid without a type + f.data = f.data.fill('\x23', 20); + QVERIFY(f.toDbLine().isEmpty()); + + // Invalid SHA1, type w/o data + f.type = Fingerprint::Type::SHA1; + f.data.clear(); + QVERIFY(f.toDbLine().isEmpty()); + + // Valid Sha1 + f.data = f.data.fill('\x23', 20); + auto expectedString = QStringLiteral("v2:sha1:2323232323232323232323232323232323232323"); + QCOMPARE(f.toDbLine(), expectedString); + + // Valid Sha256 + f.type = Fingerprint::Type::SHA256; + f.data = f.data.fill('\x23', 32); + expectedString = QStringLiteral("v2:sha256:2323232323232323232323232323232323232323232323232323232323232323"); + QCOMPARE(f.toDbLine(), expectedString); +} + +void FingerprintTests::test_fromDbLine() +{ + Fingerprint expected; + + // The Following are all invalid + auto actual = Fingerprint::fromDbLine(""); + QCOMPARE(actual, expected); + + actual = Fingerprint::fromDbLine("abcd"); + QCOMPARE(actual, expected); + + actual = Fingerprint::fromDbLine("v1:algo:something"); + QCOMPARE(actual, expected); + + actual = Fingerprint::fromDbLine("v2:algo:something"); + expected.data = QByteArray::fromHex(QString("something").toLatin1()); + QCOMPARE(actual, expected); + + actual = Fingerprint::fromDbLine("v2:algo:01020304abc"); + expected.data = QByteArray::fromHex(QString("01020304abc").toLatin1()); + QCOMPARE(actual, expected); + + actual = Fingerprint::fromDbLine("v2:algo:01020304ZZ"); + expected.data = QByteArray::fromHex(QString("01020304ZZ").toLatin1()); + QCOMPARE(actual, expected); + + // Test V1 Only support Sha1 + expected.type = Fingerprint::Type::SHA1; + expected.data = + QByteArray::fromRawData("\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23", 20); + actual = Fingerprint::fromDbLine("23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23"); + QCOMPARE(actual, expected); + + // V1 does not support SHA256 + expected.type = Fingerprint::Type::SHA256; + expected.data = QByteArray::fromRawData( + "\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23" + "\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23", + 32 + ); + actual = Fingerprint::fromDbLine( + "23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23:23" + ); + QCOMPARE_NE(actual, expected); + + // V2 SHA1 Test + expected.type = Fingerprint::Type::SHA1; + expected.data = + QByteArray::fromRawData("\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23", 20); + actual = Fingerprint::fromDbLine("v2:sha1:2323232323232323232323232323232323232323"); + QCOMPARE(actual, expected); + + // V2 SHA1 Invalid Input + actual = Fingerprint::fromDbLine("v2:sha1:23232323232323232323232323232323232323"); + QCOMPARE_NE(actual, expected); + + // V2 SHA256 Test + expected.type = Fingerprint::Type::SHA256; + expected.data = QByteArray::fromRawData( + "\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23" + "\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23\x23", + 32 + ); + actual = Fingerprint::fromDbLine("v2:sha256:2323232323232323232323232323232323232323232323232323232323232323"); + QCOMPARE(actual, expected); + + // V2 SHA256 Invalid Input + actual = Fingerprint::fromDbLine("v2:sha256:232323232323232323232323232323232323232323232323232323"); + QCOMPARE_NE(actual, expected); +} + +void FingerprintTests::test_typeToString() +{ + Fingerprint expected; + + expected.type = Fingerprint::Type::Invalid; + QCOMPARE(expected.type, Fingerprint::Type::Invalid); + QCOMPARE(Fingerprint::typeToString(expected.type), QStringLiteral("invalid")); + + expected.type = Fingerprint::Type::SHA1; + QCOMPARE(expected.type, Fingerprint::Type::SHA1); + QCOMPARE(Fingerprint::typeToString(expected.type), QStringLiteral("sha1")); + + expected.type = Fingerprint::Type::SHA256; + QCOMPARE(expected.type, Fingerprint::Type::SHA256); + QCOMPARE(Fingerprint::typeToString(expected.type), QStringLiteral("sha256")); +} + +void FingerprintTests::test_typeFromString() +{ + QCOMPARE(Fingerprint::Type::SHA1, Fingerprint::typeFromString("sha1")); + QCOMPARE(Fingerprint::Type::SHA1, Fingerprint::typeFromString("SHA1")); + QCOMPARE(Fingerprint::Type::SHA256, Fingerprint::typeFromString("sha256")); + QCOMPARE(Fingerprint::Type::SHA256, Fingerprint::typeFromString("SHA256")); + + QCOMPARE(Fingerprint::Type::Invalid, Fingerprint::typeFromString("invalid")); + QCOMPARE(Fingerprint::Type::Invalid, Fingerprint::typeFromString("")); + QCOMPARE(Fingerprint::Type::Invalid, Fingerprint::typeFromString("230p89jivon345")); +} + +QTEST_MAIN(FingerprintTests) diff --git a/src/unittests/net/FingerprintTests.h b/src/unittests/net/FingerprintTests.h new file mode 100644 index 000000000..3c72f4f09 --- /dev/null +++ b/src/unittests/net/FingerprintTests.h @@ -0,0 +1,18 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include + +class FingerprintTests : public QObject +{ + Q_OBJECT +private slots: + void test_isValid(); + void test_toDbLine(); + void test_fromDbLine(); + void test_typeToString(); + void test_typeFromString(); +}; diff --git a/src/unittests/net/SecureUtilsTests.cpp b/src/unittests/net/SecureUtilsTests.cpp index cc6fce2a6..bc5ad413e 100644 --- a/src/unittests/net/SecureUtilsTests.cpp +++ b/src/unittests/net/SecureUtilsTests.cpp @@ -13,9 +13,11 @@ using namespace deskflow; void SecureUtilsTests::checkHex() { - std::vector fingerprint = {40, 253, 10, 152, 138, 14, 161, 108, 215, 232, 108, 167, 238, 88, 65, 113, - 202, 178, 142, 73, 37, 148, 144, 37, 38, 5, 141, 175, 99, 237, 46, 48}; - + QByteArray fingerprint( + "\x28\xFD\x0A\x98\x8A\x0E\xA1\x6C\xD7\xE8\x6C\xA7\xEE\x58\x41\x71\xCA\xB2\x8E\x49\x25\x94\x90\x25\x26\x05\x8D\xAF" + "\x63\xED\x2E\x30", + 32 + ); QCOMPARE( deskflow::formatSSLFingerprint(fingerprint, true), "28:FD:0A:98:8A:0E:A1:6C:D7:E8:6C:A7:EE:58:41:71:CA:B2:8E:49:25:94:90:25:26:05:8D:AF:63:ED:2E:30" @@ -24,8 +26,11 @@ void SecureUtilsTests::checkHex() void SecureUtilsTests::checkArt() { - std::vector fingerprint = {40, 253, 10, 152, 138, 14, 161, 108, 215, 232, 108, 167, 238, 88, 65, 113, - 202, 178, 142, 73, 37, 148, 144, 37, 38, 5, 141, 175, 99, 237, 46, 48}; + QByteArray fingerprint( + "\x28\xFD\x0A\x98\x8A\x0E\xA1\x6C\xD7\xE8\x6C\xA7\xEE\x58\x41\x71\xCA\xB2\x8E\x49\x25\x94\x90\x25\x26\x05\x8D\xAF" + "\x63\xED\x2E\x30", + 32 + ); QCOMPARE( deskflow::generateFingerprintArt(fingerprint), "╔═════════════════╗\n" "║*X+. . ║\n"