refactor: generate Certs using secureUtils method

This commit is contained in:
sithlord48
2025-01-29 01:19:04 -05:00
committed by Nick Bolton
parent 5a71d63923
commit 2058519e57
5 changed files with 82 additions and 185 deletions

View File

@ -45,20 +45,6 @@ macro(configure_libs)
endif()
find_package(OpenSSL ${REQUIRED_OPENSSL_VERSION} REQUIRED COMPONENTS SSL Crypto)
if(WIN32) #Used for dev in TLS and WIX
cmake_path(SET OPENSSL_ROOT_DIR NORMALIZE "${OPENSSL_INCLUDE_DIR}/..")
message(VERBOSE "Set OPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR}")
set(OPENSSL_EXE_DIR "${OPENSSL_ROOT_DIR}/tools/openssl")
add_definitions(-DOPENSSL_EXE_DIR="${OPENSSL_EXE_DIR}")
# HACK Install a copy of openssl on windows
install(
FILES
${OPENSSL_EXE_DIR}/openssl.exe
${OPENSSL_EXE_DIR}/openssl.cnf
DESTINATION .
)
endif()
option(ENABLE_COVERAGE "Enable test coverage" OFF)
if(ENABLE_COVERAGE)

View File

@ -1,5 +1,6 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2015 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
@ -17,119 +18,11 @@
static const char *const kCertificateKeyLength = "rsa:";
static const char *const kCertificateHashAlgorithm = "-sha256";
static const char *const kCertificateLifetime = "365";
#if defined(Q_OS_WIN)
static const char *const kWinOpenSslBinary = "openssl.exe";
static const char *const kConfigFile = "openssl.cnf";
#elif defined(Q_OS_UNIX)
static const char *const kUnixOpenSslCommand = "openssl";
#endif
#if defined(Q_OS_WIN)
namespace deskflow::gui {
QString openSslWindowsDir()
{
auto openSslDir = QDir(QCoreApplication::applicationDirPath());
auto openSslBin = QFile(QStringLiteral("%1/%2").arg(openSslDir.absolutePath(), kWinOpenSslBinary));
// in production, openssl is deployed with the app.
// in development, we can use the openssl path available at compile-time.
if (!openSslBin.exists()) {
openSslDir = QDir(OPENSSL_EXE_DIR);
}
// if the path still isn't found, something is seriously wrong.
const auto path = openSslDir.absolutePath();
if (openSslDir.exists()) {
qDebug("openssl dir: %s", qUtf8Printable(path));
} else {
qFatal("openssl dir not found: %s", qUtf8Printable(path));
}
return QDir::cleanPath(path);
}
QString openSslWindowsBinary()
{
auto dir = QDir(openSslWindowsDir());
auto path = dir.filePath(kWinOpenSslBinary);
// when installed, there is no openssl bin dir; it's installed at the base.
// in development, we use the standard dir structure for openssl (bin dir).
if (!QFile::exists(path)) {
auto binDir = QDir(dir.filePath("bin"));
path = binDir.filePath(kWinOpenSslBinary);
}
// if the path still isn't found, something is seriously wrong.
if (!QFile::exists(path)) {
qFatal() << "openssl binary not found: " << path;
}
return path;
}
} // namespace deskflow::gui
using namespace deskflow::gui;
#endif
TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent)
{
}
bool TlsCertificate::runTool(const QStringList &args)
{
#if defined(Q_OS_WIN)
const auto program = openSslWindowsBinary();
#else
const auto program = kUnixOpenSslCommand;
#endif
QStringList environment;
// Windows is special! :)
// For OpenSSL, it's very common to bundle the openssl.exe and openssl.cnf files
// with the application. This is made a little more complex in the Windows dev
// env, because vcpkg can't find the openssl.cnf file by default, so we need to
// give it a bit of guidance by setting the `OPENSSL_CONF` env var.
#if defined(Q_OS_WIN)
const auto openSslDir = QDir(openSslWindowsDir());
const auto config = QDir::cleanPath(openSslDir.filePath(kConfigFile));
environment << QString("OPENSSL_CONF=%1").arg(config);
#endif
QProcess process;
process.setEnvironment(environment);
for (const auto &envVar : std::as_const(environment)) {
qDebug("set env var: %s", qUtf8Printable(envVar));
}
qDebug("running: %s %s", qUtf8Printable(program), qUtf8Printable(args.join(" ")));
process.start(program, args);
bool success = process.waitForStarted();
QString toolStderr;
if (success && process.waitForFinished()) {
m_toolStdout = process.readAllStandardOutput().trimmed();
toolStderr = process.readAllStandardError().trimmed();
}
if (int code = process.exitCode(); !success || code != 0) {
qDebug("openssl failed with code %d: %s", code, qUtf8Printable(toolStderr));
qCritical("failed to generate tls certificate:\n\n%s", qUtf8Printable(toolStderr));
return false;
}
return true;
}
bool TlsCertificate::generateCertificate(const QString &path, int keyLength)
{
qDebug("generating tls certificate: %s", qUtf8Printable(path));
@ -143,43 +36,14 @@ bool TlsCertificate::generateCertificate(const QString &path, int keyLength)
QString keySize = kCertificateKeyLength + QString::number(keyLength);
QStringList arguments;
// self signed certificate
arguments.append("req");
arguments.append("-x509");
arguments.append("-nodes");
// valid duration
arguments.append("-days");
arguments.append(kCertificateLifetime);
// subject information
arguments.append("-subj");
QString subInfo("/CN=%1");
arguments.append(subInfo.arg(kAppName));
// private key
arguments.append("-newkey");
arguments.append(keySize);
// key output filename
arguments.append("-keyout");
arguments.append(path);
// certificate output filename
arguments.append("-out");
arguments.append(path);
if (runTool(arguments)) {
qDebug("tls certificate generated");
return generateFingerprint(path);
} else {
qCritical("failed to generate tls certificate");
try {
deskflow::generatePemSelfSignedCert(path.toStdString(), keyLength);
} catch (const std::exception &e) {
qCritical() << "failed to generate self-signed pem cert: " << e.what();
return false;
}
qDebug("tls certificate generated");
return generateFingerprint(path);
}
bool TlsCertificate::generateFingerprint(const QString &certificateFilename)
@ -199,28 +63,5 @@ bool TlsCertificate::generateFingerprint(const QString &certificateFilename)
int TlsCertificate::getCertKeyLength(const QString &path)
{
QStringList arguments;
arguments.append("rsa");
arguments.append("-in");
arguments.append(path);
arguments.append("-text");
arguments.append("-noout");
if (!runTool(arguments)) {
qFatal("failed to get key length from certificate");
return 0;
}
const QString searchStart("Private-Key: (");
const QString searchEnd(" bit");
// Get the line that contains the key length from the output
const auto indexStart = m_toolStdout.indexOf(searchStart);
const auto indexEnd = m_toolStdout.indexOf(searchEnd, indexStart);
const auto start = indexStart + searchStart.length();
const auto end = indexEnd - (indexStart + searchStart.length());
auto keyLength = m_toolStdout.mid(start, end);
return keyLength.toInt();
return deskflow::getCertLength(path.toStdString());
}

View File

@ -1,5 +1,6 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2015 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
@ -19,9 +20,5 @@ public:
int getCertKeyLength(const QString &path);
private:
bool runTool(const QStringList &args);
bool generateFingerprint(const QString &certificateFilename);
private:
QString m_toolStdout;
};

View File

@ -10,6 +10,7 @@
#include "base/finally.h"
#include "io/filesystem.h"
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
@ -84,4 +85,72 @@ std::vector<std::uint8_t> pemFileCertFingerprint(const std::string &path, Finger
return SSLCertFingerprint(cert, type);
}
void generatePemSelfSignedCert(const std::string &path, int keyLength)
{
auto expirationDays = 365;
auto *privateKey = EVP_PKEY_new();
if (!privateKey) {
throw std::runtime_error("could not allocate private key for certificate");
}
auto privateKeyFree = finally([privateKey]() { EVP_PKEY_free(privateKey); });
privateKey = EVP_RSA_gen(keyLength);
auto *cert = X509_new();
if (!cert) {
throw std::runtime_error("could not allocate certificate");
}
auto certFree = finally([cert]() { X509_free(cert); });
ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), expirationDays * 24 * 3600);
X509_set_pubkey(cert, privateKey);
auto *name = X509_get_subject_name(cert);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, reinterpret_cast<const unsigned char *>("Deskflow"), -1, -1, 0);
X509_set_issuer_name(cert, name);
X509_sign(cert, privateKey, EVP_sha256());
auto fp = fopenUtf8Path(path.c_str(), "w");
if (!fp) {
throw std::runtime_error("could not open certificate output path");
}
auto fileClose = finally([fp]() { std::fclose(fp); });
PEM_write_PrivateKey(fp, privateKey, nullptr, nullptr, 0, nullptr, nullptr);
PEM_write_X509(fp, cert);
}
int getCertLength(const std::string &path)
{
auto fp = fopenUtf8Path(path.c_str(), "r");
if (!fp) {
throw std::runtime_error("could not open certificate output path");
return -1;
}
EVP_PKEY *privateKey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
fclose(fp);
if (!privateKey) {
throw std::runtime_error("could not open certificate");
return -1;
}
if (EVP_PKEY_base_id(privateKey) != EVP_PKEY_RSA) {
throw std::runtime_error("not an RSA key");
return -1;
}
int size = EVP_PKEY_get_bits(privateKey);
EVP_PKEY_free(privateKey);
return size;
}
} // namespace deskflow

View File

@ -27,4 +27,8 @@ std::string formatSSLFingerprint(const std::vector<uint8_t> &fingerprint, bool e
std::vector<std::uint8_t> SSLCertFingerprint(X509 *cert, FingerprintType type);
std::vector<std::uint8_t> pemFileCertFingerprint(const std::string &path, FingerprintType type);
void generatePemSelfSignedCert(const std::string &path, int keyLength = 2048);
int getCertLength(const std::string &path);
} // namespace deskflow