diff --git a/src/apps/deskflow-gui/MainWindow.cpp b/src/apps/deskflow-gui/MainWindow.cpp index 959d6f758..b4f8bacea 100644 --- a/src/apps/deskflow-gui/MainWindow.cpp +++ b/src/apps/deskflow-gui/MainWindow.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -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("

You are connecting to a server.

" - "

Here is it's TLS fingerprint:

" - "

%1

" + "

Here is it's TLS fingerprint:

\n\n" + "

SHA256:%1\n" + "

%2\n\n" + "

SHA1(Obsolete): Compare for old versions only: %3

" "

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).

" "

Do you want to trust this fingerprint for future " "connections? If you don't, a connection cannot be made.

") - .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; +} diff --git a/src/apps/deskflow-gui/MainWindow.h b/src/apps/deskflow-gui/MainWindow.h index d6f07d0ab..cf1676297 100644 --- a/src/apps/deskflow-gui/MainWindow.h +++ b/src/apps/deskflow-gui/MainWindow.h @@ -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; diff --git a/src/lib/gui/tls/TlsCertificate.cpp b/src/lib/gui/tls/TlsCertificate.cpp index 705ea443b..882c71ce5 100644 --- a/src/lib/gui/tls/TlsCertificate.cpp +++ b/src/lib/gui/tls/TlsCertificate.cpp @@ -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 #include +#include +#include +#include +#include + 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; +} diff --git a/src/lib/gui/tls/TlsCertificate.h b/src/lib/gui/tls/TlsCertificate.h index 51857f774..cf711457e 100644 --- a/src/lib/gui/tls/TlsCertificate.h +++ b/src/lib/gui/tls/TlsCertificate.h @@ -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; }; diff --git a/src/lib/net/SecureSocket.cpp b/src/lib/net/SecureSocket.cpp index 1496d1796..841b327f0 100644 --- a/src/lib/net/SecureSocket.cpp +++ b/src/lib/net/SecureSocket.cpp @@ -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; } diff --git a/src/lib/net/SecureUtils.cpp b/src/lib/net/SecureUtils.cpp index c5cd0e8fb..d7ff1ff99 100644 --- a/src/lib/net/SecureUtils.cpp +++ b/src/lib/net/SecureUtils.cpp @@ -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 #include #include + +#include #include namespace deskflow { @@ -154,4 +156,107 @@ int getCertLength(const std::string &path) return size; } +std::string formatSSLFingerprintColumns(const std::vector &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 &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(field[x][y], len), 1)); + result.append("║\n"); + } + + result.append("╚═════════════════╝"); + return result; +} + } // namespace deskflow diff --git a/src/lib/net/SecureUtils.h b/src/lib/net/SecureUtils.h index 7ff311aef..6d28994ae 100644 --- a/src/lib/net/SecureUtils.h +++ b/src/lib/net/SecureUtils.h @@ -24,6 +24,8 @@ namespace deskflow { */ std::string formatSSLFingerprint(const std::vector &fingerprint, bool enableSeparators = true); +std::string formatSSLFingerprintColumns(const std::vector &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 &rawDigest); } // namespace deskflow diff --git a/src/test/unittests/net/SecureUtilsTests.cpp b/src/test/unittests/net/SecureUtilsTests.cpp index 027503087..1e9fde493 100644 --- a/src/test/unittests/net/SecureUtilsTests.cpp +++ b/src/test/unittests/net/SecureUtilsTests.cpp @@ -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 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" + "╚═════════════════╝" + ); +}