feat: use sha256
based-on:a428b61c7dbased-on:b7757fbd68based-on:a238b27879
This commit is contained in:
parent
743c3feef3
commit
fbaa0a8433
8 changed files with 280 additions and 39 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
"╚═════════════════╝"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue