feat: port fingerprint info into Qt

mv FingerprintData -> Fingerprint
This commit is contained in:
sithlord48
2025-03-27 23:43:04 -04:00
committed by Chris Rizzitello
parent c4704649ea
commit 0309d35aef
24 changed files with 540 additions and 353 deletions

View File

@ -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<deskflow::FingerprintData> 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();

View File

@ -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

View File

@ -14,7 +14,7 @@
#include <QVBoxLayout>
FingerprintDialog::FingerprintDialog(
QWidget *parent, const QList<deskflow::FingerprintData> &fingerprints, FingerprintDialogMode mode
QWidget *parent, const QList<Fingerprint> &fingerprints, FingerprintDialogMode mode
)
: QDialog(parent),
m_lblHeader{new QLabel(this)},

View File

@ -6,7 +6,7 @@
#pragma once
#include "net/FingerprintData.h"
#include "net/Fingerprint.h"
#include <QDialog>
#include <QDialogButtonBox>
@ -27,7 +27,7 @@ class FingerprintDialog : public QDialog
public:
explicit FingerprintDialog(
QWidget *parent = nullptr, const QList<deskflow::FingerprintData> &fingerprints = {},
QWidget *parent = nullptr, const QList<Fingerprint> &fingerprints = {},
FingerprintDialogMode mode = FingerprintDialogMode::Local
);
~FingerprintDialog() override = default;

View File

@ -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;

View File

@ -12,8 +12,7 @@
#include <net/SecureUtils.h>
FingerprintPreview::FingerprintPreview(QWidget *parent, const QList<deskflow::FingerprintData> &fingerprints)
: QFrame(parent)
FingerprintPreview::FingerprintPreview(QWidget *parent, const QList<Fingerprint> &fingerprints) : QFrame(parent)
{
setFrameShape(QFrame::StyledPanel);
setFrameStyle(QFrame::Sunken);
@ -24,13 +23,18 @@ FingerprintPreview::FingerprintPreview(QWidget *parent, const QList<deskflow::Fi
QString sha256Art;
for (const auto &fingerprint : fingerprints) {
if (fingerprint.algorithm == "sha1") {
sha1String = QString::fromStdString(deskflow::formatSSLFingerprint(fingerprint.data));
}
switch (fingerprint.type) {
case Fingerprint::Type::SHA1:
sha1String = deskflow::formatSSLFingerprint(fingerprint.data);
break;
if (fingerprint.algorithm == "sha256") {
sha256String = QString::fromStdString(deskflow::formatSSLFingerprintColumns(fingerprint.data));
sha256Art = QString::fromStdString(deskflow::generateFingerprintArt(fingerprint.data));
case Fingerprint::Type::SHA256:
sha256String = deskflow::formatSSLFingerprintColumns(fingerprint.data);
sha256Art = deskflow::generateFingerprintArt(fingerprint.data);
break;
default:
qWarning() << "FingerprintPreview: Unknown fingerprint type: " << fingerprint.type;
break;
}
}

View File

@ -7,12 +7,12 @@
#pragma once
#include <QFrame>
#include <net/FingerprintData.h>
#include <net/Fingerprint.h>
class FingerprintPreview : public QFrame
{
Q_OBJECT
public:
explicit FingerprintPreview(QWidget *parent, const QList<deskflow::FingerprintData> &fingerprints = {});
explicit FingerprintPreview(QWidget *parent, const QList<Fingerprint> &fingerprints = {});
~FingerprintPreview() override = default;
};

View File

@ -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

View File

@ -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 <QDebug>
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;
}
}

38
src/lib/net/Fingerprint.h Normal file
View File

@ -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 <QByteArray>
#include <QObject>
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);
};

View File

@ -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 <algorithm>
#include <fstream>
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

View File

@ -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 <cstdint>
#include <string>
#include <vector>
namespace deskflow {
enum FingerprintType
{
Invalid,
SHA1,
SHA256
};
struct FingerprintData
{
std::string algorithm;
std::vector<std::uint8_t> 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

View File

@ -7,57 +7,69 @@
#include "FingerprintDatabase.h"
#include "base/String.h"
#include "io/Filesystem.h"
#include <QFile>
#include <QTextStream>
#include <algorithm>
#include <fstream>
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

View File

@ -7,39 +7,28 @@
#pragma once
#include "FingerprintData.h"
#include "Fingerprint.h"
#include "io/Filesystem.h"
#include <iosfwd>
#include <string>
#include <vector>
namespace deskflow {
#include <QList>
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<FingerprintData> &fingerprints() const
const QList<Fingerprint> &fingerprints() const
{
return m_fingerprints;
}
static FingerprintData parseDbLine(const std::string &line);
static std::string toDbLine(const FingerprintData &fingerprint);
private:
std::vector<FingerprintData> m_fingerprints;
QList<Fingerprint> m_fingerprints;
};
} // namespace deskflow

View File

@ -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)) {

View File

@ -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);

View File

@ -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<uint8_t> &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<std::uint8_t> digestVec;
digestVec.assign(reinterpret_cast<std::uint8_t *>(digest), reinterpret_cast<std::uint8_t *>(digest) + digestLength);
return {fingerprintTypeToString(type), digestVec};
QByteArray digestArray(reinterpret_cast<const char *>(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<uint8_t> &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<std::uint8_t> &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<std::uint8_t> &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<std::uint8_t> &rawDigest)
for (y = 0; y < rows; y++) {
result.append("");
for (x = 0; x < columns; x++)
result.append(characterPool.substr(std::min<int>(field[x][y], len), 1));
result.append(characterPool.at(std::min<int>(field[x][y], len)));
result.append("\n");
}

View File

@ -7,7 +7,7 @@
#pragma once
#include "FingerprintData.h"
#include "Fingerprint.h"
#include <cstdint>
#include <openssl/ossl_typ.h>
@ -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<uint8_t> &fingerprint, bool enableSeparators = true);
QString formatSSLFingerprint(const QByteArray &fingerprint, bool enableSeparators = true);
std::string formatSSLFingerprintColumns(const std::vector<uint8_t> &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<std::uint8_t> &rawDigest);
QString generateFingerprintArt(const QByteArray &rawDigest);
} // namespace deskflow

View File

@ -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

View File

@ -7,39 +7,28 @@
#include "FingerprintDatabaseTests.h"
#include "net/FingerprintData.h"
#include "net/Fingerprint.h"
#include "net/FingerprintDatabase.h"
#include <sstream>
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<FingerprintData> 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<Fingerprint> 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);
}

View File

@ -10,7 +10,6 @@ class FingerprintDatabaseTests : public QObject
{
Q_OBJECT
private slots:
void parseDBLine();
void readFile();
void writeFile();
void clear();

View File

@ -0,0 +1,188 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* 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)

View File

@ -0,0 +1,18 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include <QTest>
class FingerprintTests : public QObject
{
Q_OBJECT
private slots:
void test_isValid();
void test_toDbLine();
void test_fromDbLine();
void test_typeToString();
void test_typeFromString();
};

View File

@ -13,9 +13,11 @@ using namespace deskflow;
void SecureUtilsTests::checkHex()
{
std::vector<uint8_t> 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<uint8_t> 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"