feat: use sha256

based-on: a428b61c7d
 based-on: b7757fbd68
 based-on: a238b27879
This commit is contained in:
sithlord48 2025-02-08 21:42:19 -05:00 committed by Nick Bolton
parent 743c3feef3
commit fbaa0a8433
8 changed files with 280 additions and 39 deletions

View file

@ -36,6 +36,7 @@
#include <QApplication>
#include <QDesktopServices>
#include <QFileDialog>
#include <QFont>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMenu>
@ -471,27 +472,44 @@ void MainWindow::showMyFingerprint()
{
auto localPath = QStringLiteral("%1/%2").arg(getTlsPath(), kFingerprintLocalFilename).toStdString();
if (!QFile::exists(QString::fromStdString(localPath))) {
QMessageBox::information(
this, tr("TLS fingerprint Error"),
tr("Unable to read localfinger print: %1\n You may want to regenerate your keys.")
.arg(QString::fromStdString(localPath))
);
if (regenerateLocalFingerprints())
showMyFingerprint();
return;
}
deskflow::FingerprintDatabase db;
db.read(localPath);
if (db.fingerprints().empty()) {
QMessageBox::information(
this, tr("TLS fingerprint Error"),
tr("Unable to read localDatabase\n You may want to regenerate your keys.")
.arg(QString::fromStdString(localPath))
);
if (db.fingerprints().size() != 2) {
if (regenerateLocalFingerprints())
showMyFingerprint();
return;
}
const auto fingerprint = QString::fromStdString(deskflow::formatSSLFingerprint(db.fingerprints().front().data));
QMessageBox::information(this, "TLS fingerprint", fingerprint);
QString message = QStringLiteral("\n");
for (const auto &fingerprint : db.fingerprints()) {
if (fingerprint.algorithm == "sha1") {
message.append(
QStringLiteral("\nSHA1\n%1\n").arg(QString::fromStdString(deskflow::formatSSLFingerprint(fingerprint.data)))
);
}
if (fingerprint.algorithm == "sha256") {
message.append(QStringLiteral("\nSHA256\n%1\n%2\n")
.arg(
QString::fromStdString(deskflow::formatSSLFingerprintColumns(fingerprint.data)),
QString::fromStdString(deskflow::generateFingerprintArt(fingerprint.data))
));
}
}
// TODO This dialog better in the future
QMessageBox mbox(this);
mbox.setWindowTitle(tr("Your Fingerprints"));
auto font = new QFont("Monospace");
font->setStyleHint(QFont::TypeWriter);
mbox.setFont(*font);
mbox.setText(message);
mbox.exec();
}
void MainWindow::setModeServer()
@ -688,19 +706,28 @@ void MainWindow::checkConnected(const QString &line)
void MainWindow::checkFingerprint(const QString &line)
{
static const QRegularExpression re(".*server fingerprint: ([A-F0-9:]+)");
static const QRegularExpression re(R"(.*server fingerprint: \(SHA1\) ([A-F0-9:]+) \(SHA256\) ([A-F0-9:]+))");
auto match = re.match(line);
if (!match.hasMatch()) {
return;
}
auto localPath = QStringLiteral("%1/%2").arg(getTlsPath(), kFingerprintTrustedServersFilename).toStdString();
const deskflow::FingerprintData sha1 = {
deskflow::fingerprintTypeToString(deskflow::FingerprintType::SHA1),
deskflow::string::fromHex(match.captured(1).toStdString())
};
const deskflow::FingerprintData sha256 = {
deskflow::fingerprintTypeToString(deskflow::FingerprintType::SHA256),
deskflow::string::fromHex(match.captured(2).toStdString())
};
// Only Save the sha256
auto localPath = QStringLiteral("%1/%2").arg(getTlsPath(), kFingerprintTrustedServersFilename).toStdString();
deskflow::FingerprintDatabase db;
db.read(localPath);
const deskflow::FingerprintData fingerprint{"sha1", deskflow::string::fromHex(match.captured(1).toStdString())};
if (db.isTrusted(fingerprint)) {
if (db.isTrusted(sha256)) {
return;
}
@ -713,19 +740,25 @@ void MainWindow::checkFingerprint(const QString &line)
QMessageBox::StandardButton fingerprintReply = QMessageBox::information(
this, tr("Security question"),
tr("<p>You are connecting to a server.</p>"
"<p>Here is it's TLS fingerprint:</p>"
"<p>%1</p>"
"<p>Here is it's TLS fingerprint:</p>\n\n"
"<p>SHA256:%1\n"
"<p>%2\n\n"
"<p>SHA1(Obsolete): Compare for old versions only: %3</p>"
"<p>Compare this fingerprint to the one on your server's screen. "
"If the two don't match exactly, then it's probably not the server "
"you're expecting (it could be a malicious user).</p>"
"<p>Do you want to trust this fingerprint for future "
"connections? If you don't, a connection cannot be made.</p>")
.arg(QString::fromStdString(deskflow::formatSSLFingerprint(fingerprint.data))),
.arg(
QString::fromStdString(deskflow::formatSSLFingerprint(sha256.data)),
QString::fromStdString(deskflow::generateFingerprintArt(sha256.data)),
QString::fromStdString(deskflow::formatSSLFingerprint(sha1.data))
),
QMessageBox::Yes | QMessageBox::No
);
if (fingerprintReply == QMessageBox::Yes) {
db.addTrusted(fingerprint);
db.addTrusted(sha256);
db.write(localPath);
m_coreProcess.start();
}
@ -1063,3 +1096,13 @@ QString MainWindow::getTlsPath()
CoreTool coreTool;
return QStringLiteral("%1/%2").arg(coreTool.getProfileDir(), kSslDir);
}
bool MainWindow::regenerateLocalFingerprints()
{
TlsCertificate tls;
if (!tls.generateFingerprint(m_appConfig.tlsCertPath())) {
QMessageBox::critical(this, tr("TLS Fingerprint Error"), tr("Failed to calculate keys"));
return false;
}
return true;
}

View file

@ -166,6 +166,10 @@ private:
QString getTlsPath();
// Generate prints if they are missing
// Returns true if successful
bool regenerateLocalFingerprints();
VersionChecker m_versionChecker;
bool m_secureSocket = false;
deskflow::gui::config::ServerConfigDialogState m_serverConfigDialogState;

View file

@ -7,6 +7,7 @@
#include "TlsCertificate.h"
#include "base/finally.h"
#include "common/constants.h"
#include "gui/core/CoreTool.h"
#include "net/FingerprintData.h"
@ -17,8 +18,17 @@
#include <QDir>
#include <QProcess>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent)
{
CoreTool coreTool;
m_profileDir = coreTool.getProfileDir();
if (m_profileDir.isEmpty())
qCritical() << "unable to get profile dir";
}
bool TlsCertificate::generateCertificate(const QString &path, int keyLength)
@ -45,18 +55,12 @@ bool TlsCertificate::generateCertificate(const QString &path, int keyLength)
bool TlsCertificate::generateFingerprint(const QString &certificateFilename)
{
qDebug("generating tls fingerprint");
const std::string certPath = certificateFilename.toStdString();
try {
auto fingerprint =
deskflow::pemFileCertFingerprint(certificateFilename.toStdString(), deskflow::FingerprintType::SHA1);
CoreTool coreTool;
QString profileDir = coreTool.getProfileDir();
auto localPath = QStringLiteral("%1/%2/%3").arg(profileDir, kSslDir, kFingerprintLocalFilename).toStdString();
deskflow::FingerprintDatabase db;
db.addTrusted(fingerprint);
db.write(localPath);
db.addTrusted(deskflow::pemFileCertFingerprint(certPath, deskflow::FingerprintType::SHA1));
db.addTrusted(deskflow::pemFileCertFingerprint(certPath, deskflow::FingerprintType::SHA256));
db.write(QStringLiteral("%1/%2").arg(getTlsDir(), kFingerprintLocalFilename).toStdString());
qDebug("tls fingerprint generated");
return true;
@ -70,3 +74,55 @@ int TlsCertificate::getCertKeyLength(const QString &path)
{
return deskflow::getCertLength(path.toStdString());
}
QString TlsCertificate::getCertificatePath() const
{
return QStringLiteral("%1/%2/%3").arg(m_profileDir, kSslDir, kCertificateFilename);
}
QString TlsCertificate::getTlsDir() const
{
return QStringLiteral("%1/%2").arg(m_profileDir, kSslDir);
}
bool TlsCertificate::isCertificateValid(const QString &path)
{
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
auto fp = deskflow::fopenUtf8Path(path.toStdString(), "r");
if (!fp) {
qWarning() << tr("could not read from default certificate file");
return false;
}
auto fileClose = deskflow::finally([fp]() { std::fclose(fp); });
auto *cert = PEM_read_X509(fp, nullptr, nullptr, nullptr);
if (!cert) {
qWarning() << tr("could not load default certificate file to memory");
return false;
}
auto certFree = deskflow::finally([cert]() { X509_free(cert); });
auto *pubkey = X509_get_pubkey(cert);
if (!pubkey) {
qWarning() << tr("default certificate key file does not contain valid public key");
return false;
}
auto pubkeyFree = deskflow::finally([pubkey]() { EVP_PKEY_free(pubkey); });
auto type = EVP_PKEY_type(EVP_PKEY_id(pubkey));
if (type != EVP_PKEY_RSA && type != EVP_PKEY_DSA) {
qWarning() << tr("public key in default certificate key file is not RSA or DSA");
return false;
}
auto bits = EVP_PKEY_bits(pubkey);
if (bits < 2048) {
// We could have small keys in old barrier installations
qWarning() << tr("public key in default certificate key file is too small");
return false;
}
return true;
}

View file

@ -16,9 +16,13 @@ class TlsCertificate : public QObject
public:
explicit TlsCertificate(QObject *parent = nullptr);
bool isCertificateValid(const QString &path);
bool generateCertificate(const QString &path, int keyLength);
bool generateFingerprint(const QString &certificateFilename);
int getCertKeyLength(const QString &path);
QString getCertificatePath() const;
QString getTlsDir() const;
private:
bool generateFingerprint(const QString &certificateFilename);
QString m_profileDir;
};

View file

@ -612,16 +612,22 @@ void SecureSocket::disconnect()
bool SecureSocket::verifyCertFingerprint()
{
// calculate received certificate fingerprint
deskflow::FingerprintData fingerprint;
deskflow::FingerprintData sha1;
deskflow::FingerprintData sha256;
try {
fingerprint = deskflow::sslCertFingerprint(SSL_get_peer_certificate(m_ssl->m_ssl), deskflow::FingerprintType::SHA1);
auto cert = SSL_get_peer_certificate(m_ssl->m_ssl);
sha1 = deskflow::sslCertFingerprint(cert, deskflow::FingerprintType::SHA1);
sha256 = deskflow::sslCertFingerprint(cert, deskflow::FingerprintType::SHA256);
} catch (const std::exception &e) {
LOG((CLOG_ERR "%s", e.what()));
return false;
}
LOG((CLOG_NOTE "server fingerprint: %s", deskflow::formatSSLFingerprint(fingerprint.data).c_str()));
// Gui Must Parse these two lines, DO NOT CHANGE
LOG(
(CLOG_NOTE "server fingerprint: (SHA1) %s (SHA256) %s", deskflow::formatSSLFingerprint(sha1.data).c_str(),
deskflow::formatSSLFingerprint(sha256.data).c_str())
);
std::string trustedServersFilename = deskflow::string::sprintf(
"%s/%s/%s", ARCH->getProfileDirectory().c_str(), kSslDir, kFingerprintTrustedServersFilename
@ -641,7 +647,7 @@ bool SecureSocket::verifyCertFingerprint()
return false;
}
if (!db.isTrusted(fingerprint)) {
if (!db.isTrusted(sha256)) {
LOG((CLOG_WARN "fingerprint does not match trusted fingerprint"));
return false;
}

View file

@ -6,7 +6,7 @@
*/
#include "SecureUtils.h"
#include "FingerprintDatabase.h"
#include "base/String.h"
#include "base/finally.h"
#include "io/filesystem.h"
@ -15,6 +15,8 @@
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <algorithm>
#include <stdexcept>
namespace deskflow {
@ -154,4 +156,107 @@ int getCertLength(const std::string &path)
return size;
}
std::string formatSSLFingerprintColumns(const std::vector<uint8_t> &fingerprint)
{
auto kmaxColumns = 8;
std::string hex = deskflow::string::toHex(fingerprint, 2);
deskflow::string::uppercase(hex);
if (hex.empty() || hex.size() % 2 != 0) {
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');
}
separated.pop_back(); // we don't need last newline character
return separated;
}
/*
Draw an ASCII-Art representing the fingerprint so human brain can
profit from its built-in pattern recognition ability.
This technique is called "random art" and can be found in some
scientific publications like this original paper:
"Hash Visualization: a New Technique to improve Real-World Security",
Perrig A. and Song D., 1999, International Workshop on Cryptographic
Techniques and E-Commerce (CrypTEC '99)
sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf
The subject came up in a talk by Dan Kaminsky, too.
If you see the picture is different, the key is different.
If the picture looks the same, you still know nothing.
The algorithm used here is a worm crawling over a discrete plane,
leaving a trace (augmenting the field) everywhere it goes.
Movement is taken from rawDigest 2bit-wise. Bumping into walls
makes the respective movement vector be ignored for this turn.
Graphs are not unambiguous, because circles in graphs can be
walked in either direction.
*/
/*
Field sizes for the random art. Have to be odd, so the starting point
can be in the exact middle of the picture, and `baseSize` should be >=8 .
Else pictures would be too dense, and drawing the frame would
fail, too, because the key type would not fit in anymore.
*/
std::string generateFingerprintArt(const std::vector<std::uint8_t> &rawDigest)
{
const auto baseSize = 8;
const auto rows = (baseSize + 1);
const auto columns = (baseSize * 2 + 1);
const std::string characterPool = " .o+=*BOX@%&#/^SE";
const std::size_t len = characterPool.length() - 1;
std::uint8_t field[columns][rows];
memset(field, 0, columns * rows * sizeof(char));
int x = columns / 2;
int y = rows / 2;
/* process raw key */
for (size_t i = 0; i < rawDigest.size(); i++) {
/* each byte conveys four 2-bit move commands */
int input = rawDigest[i];
for (uint32_t b = 0; b < 4; b++) {
/* evaluate 2 bit, rest is shifted later */
x += (input & 0x1) ? 1 : -1;
y += (input & 0x2) ? 1 : -1;
/* assure we are still in bounds */
x = std::clamp(x, 0, columns - 1);
y = std::clamp(y, 0, rows - 1);
/* augment the field */
if (field[x][y] < len - 2)
field[x][y]++;
input = input >> 2;
}
}
/* mark starting point and end point*/
field[columns / 2][rows / 2] = len - 1;
field[x][y] = len;
std::string result;
result.reserve((columns + 3) * (rows + 2));
result.append("╔═════════════════╗\n");
/* output content */
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("║\n");
}
result.append("╚═════════════════╝");
return result;
}
} // namespace deskflow

View file

@ -24,6 +24,8 @@ namespace deskflow {
*/
std::string formatSSLFingerprint(const std::vector<uint8_t> &fingerprint, bool enableSeparators = true);
std::string formatSSLFingerprintColumns(const std::vector<uint8_t> &fingerprint);
FingerprintData sslCertFingerprint(X509 *cert, FingerprintType type);
FingerprintData pemFileCertFingerprint(const std::string &path, FingerprintType type);
@ -31,4 +33,6 @@ FingerprintData pemFileCertFingerprint(const std::string &path, FingerprintType
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);
} // namespace deskflow

View file

@ -19,3 +19,22 @@ TEST(SecureUtilsTest, formatSSLFingerprints_fromHex_withSeperators)
"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"
);
}
TEST(SecureUtilsTest, createFingerprintArt)
{
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};
ASSERT_EQ(
deskflow::generateFingerprintArt(fingerprint), "╔═════════════════╗\n"
"║*X+. . ║\n"
"║*oo + ║\n"
"║ + = ║\n"
"║ B . . ║\n"
"║.+... o S ║\n"
"║E+ ++. . ║\n"
"║B*++.. . ║\n"
"║+o*o o . ║\n"
"║+o*Bo . ║\n"
"╚═════════════════╝"
);
}