Page MenuHome GnuPG

No OneTemporary

diff --git a/client/rootcagenerator/controller.cpp b/client/rootcagenerator/controller.cpp
index 7553282..b134e28 100644
--- a/client/rootcagenerator/controller.cpp
+++ b/client/rootcagenerator/controller.cpp
@@ -1,415 +1,410 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "controller.h"
#include "truststore.h"
#include "../utils/formatter.h"
#include <QDate>
#include <QDir>
#include <QProcess>
#include <QSaveFile>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QGpgME/ExportJob>
#include <QGpgME/ImportJob>
#include <QGpgME/KeyGenerationJob>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <gpgme++/context.h>
#include <Libkleo/Formatting>
#include <Libkleo/KeyParameters>
#include <Libkleo/KeyUsage>
#include <KLocalizedString>
using namespace Qt::StringLiterals;
using namespace Kleo;
using namespace GpgME;
static KeyParameters createRootCaParms()
{
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setKeyType(GpgME::Subkey::PubkeyAlgo::AlgoRSA);
keyParameters.setKeyUsage(KeyUsage{KeyUsage::Sign | KeyUsage::Certify});
keyParameters.setDN(u"CN=GnuPG Outlook Add-in Local Root CA"_s);
keyParameters.setEmail(u"localroot@gpgoljs.local"_s);
keyParameters.setKeyLength(3072);
keyParameters.setExpirationDate(QDate(2060, 10, 10));
keyParameters.setUseRandomSerial();
keyParameters.setControlStatements({u"%no-protection"_s});
return keyParameters;
}
static KeyParameters createTlsCertParms(QLatin1StringView keyGrip)
{
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setKeyType(GpgME::Subkey::PubkeyAlgo::AlgoRSA);
keyParameters.setKeyUsage(KeyUsage{KeyUsage::Sign | KeyUsage::Encrypt});
keyParameters.setDN(u"CN=GnuPG Outlook Add-in Local Server Certificate"_s);
keyParameters.setEmail(u"local@gpgoljs.local"_s);
keyParameters.setKeyLength(3072);
keyParameters.setExpirationDate(QDate(2060, 10, 10));
keyParameters.setIssuerDN(u"CN=GnuPG Outlook Add-in Local Root CA"_s);
keyParameters.setSigningKey(keyGrip);
keyParameters.setUseRandomSerial();
keyParameters.addDomainName(u"localhost"_s);
keyParameters.setControlStatements({u"%no-protection"_s});
return keyParameters;
}
Controller::Controller(QObject *parent)
: KJob(parent)
{
}
Controller::~Controller()
{
if (m_tmpdir) {
QProcess p;
p.start(u"gpgconf"_s, {u"--homedir"_s, m_tmpdir->path(), u"--kill"_s, u"all"_s});
p.waitForFinished();
}
}
-QString Controller::caUniqueName() const
-{
- return u"GPGOL2 CA "_s + QString::fromLatin1(m_ca.issuerSerial());
-}
-
QByteArray Controller::caCert() const
{
return m_publicCA;
}
bool Controller::certificateAlreadyGenerated()
{
auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
return !certPath.isEmpty();
}
void Controller::setHomeDirForJob(QGpgME::Job *job)
{
auto context = QGpgME::Job::context(job);
context->setEngineHomeDirectory(m_tmpdir->path().toUtf8().constData());
}
void Controller::start()
{
if (certificateAlreadyGenerated()) {
emitResult();
return;
}
m_tmpdir = std::make_unique<QTemporaryDir>();
auto keyGenerationJob = QGpgME::smime()->keyGenerationJob();
setHomeDirForJob(keyGenerationJob);
connect(keyGenerationJob, &QGpgME::KeyGenerationJob::result, this, &Controller::slotRootCaCreatedSlot);
keyGenerationJob->start(createRootCaParms().toString());
}
void Controller::slotRootCaCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData, const QString &auditLog)
{
Q_UNUSED(auditLog)
if (result.error().code()) {
setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
: i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
setError(UserDefinedError);
emitResult();
return;
}
auto importJob = QGpgME::smime()->importJob();
setHomeDirForJob(importJob);
connect(importJob, &QGpgME::ImportJob::result, this, &Controller::slotRootCaImportedSlot);
importJob->start(pubKeyData);
}
void Controller::slotRootCaImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
Q_EMIT debutOutput(i18nc("Debug message", "Imported root CA"));
if (result.error().code()) {
setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
: i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
setError(UserDefinedError);
emitResult();
return;
}
// Get the keygrip
auto keyListJob = QGpgME::smime()->keyListJob();
setHomeDirForJob(keyListJob);
connect(keyListJob, &QGpgME::KeyListJob::result, this, &Controller::slotKeyGripOptained);
keyListJob->start({u"GnuPG Outlook Add-in Local Root CA"_s}, true);
// Export public key
auto exportJob = QGpgME::smime()->publicKeyExportJob(true);
setHomeDirForJob(exportJob);
const auto imports = result.imports();
const auto fingerprint = imports[0].fingerprint();
m_fingerPrint = Formatter::formatX509Fingerprint(QByteArray(fingerprint));
Q_EMIT debutOutput(i18nc("Debug message, %1 is fingerprint", "Root CA created: %1", m_fingerPrint));
exportJob->start({QString::fromLatin1(fingerprint)});
connect(exportJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
setError(UserDefinedError);
emitResult();
return;
}
m_publicCA = keyData;
checkFinished();
});
// Export private key
auto exportSecretJob = QGpgME::smime()->secretKeyExportJob(true);
setHomeDirForJob(exportSecretJob);
exportSecretJob->start({QString::fromLatin1(fingerprint)});
connect(exportSecretJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
setError(UserDefinedError);
emitResult();
return;
}
m_secretCA = keyData;
checkFinished();
});
}
void Controller::slotKeyGripOptained(const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError)
{
Q_EMIT debutOutput(i18nc("Debug message", "Got the key grip of Root CA"));
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
if (result.error().code()) {
setErrorText(result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not get keygrip : %1", Formatting::errorAsString(result.error())));
setError(UserDefinedError);
emitResult();
return;
}
if (keys.size() != 1) {
setErrorText(i18n("More than one root certificate found"));
setError(UserDefinedError);
emitResult();
return;
}
m_ca = keys[0];
auto keyGenerationJob = QGpgME::smime()->keyGenerationJob();
setHomeDirForJob(keyGenerationJob);
connect(keyGenerationJob, &QGpgME::KeyGenerationJob::result, this, &Controller::slotCertCreatedSlot);
keyGenerationJob->start(createTlsCertParms(QLatin1StringView(keys[0].subkey(0).keyGrip())).toString());
}
void Controller::slotCertCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData, const QString &auditLog)
{
Q_EMIT debutOutput(i18nc("Debug message", "TLS certificate created"));
Q_UNUSED(auditLog)
if (result.error().code()) {
setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
: i18n("Could not create key pair for cert: %1", Formatting::errorAsString(result.error())));
setError(UserDefinedError);
emitResult();
return;
}
auto importJob = QGpgME::smime()->importJob();
setHomeDirForJob(importJob);
connect(importJob, &QGpgME::ImportJob::result, this, &Controller::slotCertImportedSlot);
importJob->start(pubKeyData);
}
void Controller::slotCertImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
if (result.error().code()) {
setErrorText(result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not import cert: %1", Formatting::errorAsString(result.error())));
setError(UserDefinedError);
emitResult();
return;
}
auto keyListJob = QGpgME::smime()->keyListJob();
setHomeDirForJob(keyListJob);
connect(keyListJob,
&QGpgME::KeyListJob::result,
this,
[this](const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
Q_UNUSED(result);
Q_UNUSED(auditLogAsHtml);
Q_UNUSED(auditLogError);
m_tls = keys[0];
checkFinished();
});
keyListJob->start({u"GnuPG Outlook Add-in Local Root CA"_s}, true);
// Export public key
auto exportJob = QGpgME::smime()->publicKeyExportJob(true);
setHomeDirForJob(exportJob);
const auto imports = result.imports();
const auto fingerprint = imports[0].fingerprint();
exportJob->start({QString::fromLatin1(fingerprint)});
connect(exportJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
setError(UserDefinedError);
emitResult();
return;
}
m_publicTLS = keyData;
checkFinished();
});
// Export private key
auto exportSecretJob = QGpgME::smime()->secretKeyExportJob(true);
setHomeDirForJob(exportSecretJob);
exportSecretJob->start({QString::fromLatin1(fingerprint)});
connect(exportSecretJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
setError(UserDefinedError);
emitResult();
return;
}
m_secretTLS = keyData;
checkFinished();
});
}
void Controller::checkFinished()
{
if (!m_secretCA.isEmpty() && !m_publicCA.isEmpty() && !m_publicTLS.isEmpty() && !m_secretTLS.isEmpty() && !m_ca.isNull() && !m_tls.isNull()) {
Q_EMIT generationDone();
}
}
void Controller::install()
{
auto keyPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate-key.pem"));
// Install for gpgol-client
{
auto certPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
Q_EMIT debutOutput(i18nc("Debug message, %1 is a path", "Installing certificate for gpgol-client in %1", certPath));
QDir dir;
if (!dir.mkpath(certPath)) {
Q_EMIT debutOutput(i18nc("Debug message, %1 is a path", "Unable to create the following path: ", certPath));
setError(UserDefinedError);
emitResult();
return;
}
QSaveFile localhostPub(certPath + u"/certificate.pem"_s);
if (localhostPub.open(QIODeviceBase::WriteOnly)) {
localhostPub.write(m_publicTLS);
localhostPub.commit();
} else {
Q_EMIT debutOutput(
i18nc("Debug message. %1 is a filename. %2 is a path", "No permission to write: %1 in %2", localhostPub.fileName(), dir.absolutePath()));
setError(UserDefinedError);
emitResult();
return;
}
QSaveFile rootCaPub(certPath + u"/root-ca.pem"_s);
if (rootCaPub.open(QIODeviceBase::WriteOnly)) {
rootCaPub.write(m_publicCA);
rootCaPub.commit();
} else {
Q_EMIT debutOutput(
i18nc("Debug message. %1 is a filename. %2 is a path", "No permission to write: %1 in %2", rootCaPub.fileName(), dir.absolutePath()));
setError(UserDefinedError);
emitResult();
return;
}
}
// Install for gpgol-server
{
auto certPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).chopped(QStringLiteral(u"gpgol-client").length()).append(u"gpgol-server");
Q_EMIT debutOutput(i18nc("Debug message. %1 is a path", "Installing certificate for gpgol-server in %1", certPath));
QDir dir;
if (!dir.mkpath(certPath)) {
Q_EMIT debutOutput(i18nc("Debug message. %1 is a path", "Unable to create the following path: %1", certPath));
setError(UserDefinedError);
emitResult();
return;
}
QSaveFile localhostPub(certPath + u"/certificate.pem"_s);
if (localhostPub.open(QIODeviceBase::WriteOnly)) {
localhostPub.write(m_publicTLS);
localhostPub.commit();
} else {
Q_EMIT debutOutput(
i18nc("Debug message, %1 is a filename. %2 is a path", "No permission to write: %1 in %2", localhostPub.fileName(), dir.absolutePath()));
setError(UserDefinedError);
emitResult();
return;
}
QSaveFile localhostKey(certPath + u"/certificate-key.pem"_s);
if (localhostKey.open(QIODeviceBase::WriteOnly)) {
localhostKey.write(m_secretTLS);
localhostKey.commit();
} else {
Q_EMIT debutOutput(
i18nc("Debug message. %1 is a filename. %2 is a path.", "No permission to write: %1 in %2", localhostKey.fileName(), dir.absolutePath()));
setError(UserDefinedError);
emitResult();
return;
}
QSaveFile rootCaPub(certPath + u"/root-ca.pem"_s);
if (rootCaPub.open(QIODeviceBase::WriteOnly)) {
rootCaPub.write(m_publicCA);
rootCaPub.commit();
} else {
Q_EMIT debutOutput(
i18nc("Debug message. %1 is a filename. %2 is a path", "No permission to write: %1 in %2", rootCaPub.fileName(), dir.absolutePath()));
setError(UserDefinedError);
emitResult();
return;
}
}
auto trustStore = TrustStore();
if (!trustStore.install(*this)) {
Q_EMIT debutOutput(i18nc("Debug message", "Installing certificate to browser failed"));
}
emitResult();
}
QString Controller::rootFingerprint() const
{
return m_fingerPrint;
}
diff --git a/client/rootcagenerator/controller.h b/client/rootcagenerator/controller.h
index 70322da..dd69719 100644
--- a/client/rootcagenerator/controller.h
+++ b/client/rootcagenerator/controller.h
@@ -1,68 +1,67 @@
// SPDX-FileCopyrightText: 2024 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <KJob>
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/keylistresult.h>
#include <qgpgme/job.h>
class QTemporaryDir;
static const QLatin1StringView ROOT_NAME = QLatin1StringView("rootCA.pem");
class Controller : public KJob
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
static bool certificateAlreadyGenerated();
- QString caUniqueName() const;
QByteArray caCert() const;
QString rootFingerprint() const;
void start() override;
void install();
~Controller();
Q_SIGNALS:
void debutOutput(const QString &output);
void generationDone();
private:
void slotRootCaCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog);
void slotRootCaImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError);
void slotKeyGripOptained(const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError);
void slotCertCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog);
void slotCertImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError);
void checkFinished();
void setHomeDirForJob(QGpgME::Job *job);
private:
GpgME::Key m_ca;
QByteArray m_publicCA;
QByteArray m_secretCA;
std::unique_ptr<QTemporaryDir> m_tmpdir;
GpgME::Key m_tls;
QByteArray m_publicTLS;
QByteArray m_secretTLS;
QString m_fingerPrint;
};
diff --git a/client/rootcagenerator/truststore.cpp b/client/rootcagenerator/truststore.cpp
index 126057b..bdbe47f 100644
--- a/client/rootcagenerator/truststore.cpp
+++ b/client/rootcagenerator/truststore.cpp
@@ -1,106 +1,103 @@
// SPDX-FileCopyrightText: 2023, 2024, 2025 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-Contributor: Sune Stolborg Vuorela <sune@vuorela.dk>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "truststore.h"
#include "controller.h"
#include <QString>
#ifdef Q_OS_WIN
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QIODevice>
#include <QTemporaryFile>
#include <windows.h>
// include after windows.h
#include <shellapi.h>
#endif
#ifdef Q_OS_LINUX
+#include <QCryptographicHash>
#include <QFileInfo>
#include <QProcess>
#include <QStandardPaths>
#include <KAuth/ExecuteJob>
#endif
using namespace Qt::StringLiterals;
TrustStore::TrustStore() = default;
TrustStore::~TrustStore() = default;
-QString TrustStore::systemTrustFilename(const Controller &controller) const
-{
- return controller.caUniqueName().replace(u" "_s, u"_"_s);
-}
-
bool TrustStore::uninstall(const Controller &controller)
{
Q_UNUSED(controller);
return true;
}
#ifdef Q_OS_LINUX
bool TrustStore::install(const Controller &controller)
{
QVariantMap args;
- args["caUniqueName"_L1] = systemTrustFilename(controller);
+ // CA name should be unique on multi-user machines. For this we simply append a hashed user name.
+ args["caUniqueName"_L1] = QVariant(u"GPGOL2_CA_"_s + QString::fromLatin1(QCryptographicHash::hash(qgetenv("USER"), QCryptographicHash::Sha256).toBase64()));
args["caCert"_L1] = controller.caCert();
KAuth::Action installAction(u"com.gnupg.gpgolweb.truststore.install"_s);
installAction.setHelperId(u"com.gnupg.gpgolweb.truststore"_s);
installAction.setArguments(args);
KAuth::ExecuteJob *job = installAction.execute();
if (!job->exec()) {
qDebug() << "KAuth returned an error code:" << job->error() << job->errorText();
return false;
} else {
QString contents = job->data()[u"contents"_s].toString();
qDebug() << "KAuth succeeded. Contents: " << contents;
return true;
}
}
#endif
#ifdef Q_OS_WIN
bool TrustStore::install(const Controller &controller)
{
QTemporaryFile file;
file.open();
file.setAutoRemove(false);
file.write(controller.caCert());
file.close();
const QString args = u"-NoProfile -ExecutionPolicy Bypass -File \"%1\" CurrentUser \"%2\""_s.arg(
QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + u"/install.ps1"_s),
QDir::toNativeSeparators(file.fileName()));
HINSTANCE result = ShellExecuteW(nullptr, L"runas", L"powershell.exe", args.toStdWString().c_str(), nullptr, SW_SHOWNORMAL);
if ((intptr_t)result <= 32) {
DWORD errCode = GetLastError();
// Format the error message
LPWSTR errorMsg = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errCode,
0,
(LPWSTR)&errorMsg,
0,
nullptr);
qDebug() << "ShellExecuteW failed with code:" << (intptr_t)result;
qDebug() << "GetLastError():" << errCode;
if (errorMsg) {
qDebug() << "Error message:" << QString::fromWCharArray(errorMsg);
LocalFree(errorMsg);
}
} else {
qDebug() << "Root CA installation succeeded.";
}
return true;
}
#endif
diff --git a/client/rootcagenerator/truststore.h b/client/rootcagenerator/truststore.h
index fd83c44..3cf7570 100644
--- a/client/rootcagenerator/truststore.h
+++ b/client/rootcagenerator/truststore.h
@@ -1,25 +1,23 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QtGlobal>
#include <memory>
class Controller;
class TrustStore
{
public:
explicit TrustStore();
~TrustStore();
/// Install the trust store to the platform
bool install(const Controller &controller);
/// Uninstall the trust store to the platform
bool uninstall(const Controller &controller);
-
- QString systemTrustFilename(const Controller &controller) const;
};

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 6:43 PM (8 h, 51 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
03/2f/9127ced233c4449c0cfab3f9509b

Event Timeline