refactor: generate Certs using secureUtils method
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user