Scrap Public Key Cryptography code based on QCA as QDbus is secure enough

Summary: QDbus already provides enough security to the calls made by the application to the helper. Hence no need to encrypt, sign the requests of the application and verify their integrity at the helper side.

Reviewers: stikonas, cjlcarvalho

Reviewed By: stikonas

Subscribers: #kde_partition_manager

Tags: #kde_partition_manager

Differential Revision: https://phabricator.kde.org/D21275
This commit is contained in:
Shubham Jangra 2019-05-19 00:20:10 +05:30
parent 6f8e6403f8
commit a7532d4041
4 changed files with 41 additions and 224 deletions

View File

@ -42,8 +42,6 @@
#include <QThread> #include <QThread>
#include <QVariant> #include <QVariant>
#include <QtCrypto>
#include <KAuth> #include <KAuth>
#include <KJob> #include <KJob>
#include <KLocalizedString> #include <KLocalizedString>
@ -61,8 +59,6 @@ struct ExternalCommandPrivate
}; };
KAuth::ExecuteJob* ExternalCommand::m_job; KAuth::ExecuteJob* ExternalCommand::m_job;
QCA::PrivateKey* ExternalCommand::privateKey;
QCA::Initializer* ExternalCommand::init;
bool ExternalCommand::helperStarted = false; bool ExternalCommand::helperStarted = false;
QWidget* ExternalCommand::parent; QWidget* ExternalCommand::parent;
@ -106,13 +102,16 @@ ExternalCommand::ExternalCommand(Report& report, const QString& cmd, const QStri
ExternalCommand::~ExternalCommand() ExternalCommand::~ExternalCommand()
{ {
} }
// void ExternalCommand::setup() /*
// { void ExternalCommand::setup()
// connect(this, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, &ExternalCommand::onFinished); {
// connect(this, &ExternalCommand::readyReadStandardOutput, this, &ExternalCommand::onReadOutput); connect(this, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, &ExternalCommand::onFinished);
// } connect(this, &ExternalCommand::readyReadStandardOutput, this, &ExternalCommand::onReadOutput);
}
*/
/** Executes the external command. /** Executes the external command.
@param timeout timeout to wait for the process to start @param timeout timeout to wait for the process to start
@ -146,19 +145,8 @@ bool ExternalCommand::start(int timeout)
interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days
bool rval = false; bool rval = false;
QByteArray request;
const quint64 nonce = interface->getNonce();
request.setNum(nonce);
request.append(cmd.toUtf8());
for (const auto &argument : qAsConst(d->m_Args))
request.append(argument.toUtf8());
request.append(d->m_Input);
request.append(d->processChannelMode);
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); QDBusPendingCall pcall = interface->start(cmd, args(), d->m_Input, d->processChannelMode);
QDBusPendingCall pcall = interface->start(privateKey->signMessage(hash, QCA::EMSA3_Raw),
nonce, cmd, args(), d->m_Input, d->processChannelMode);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
QEventLoop loop; QEventLoop loop;
@ -200,22 +188,9 @@ bool ExternalCommand::copyBlocks(const CopySource& source, CopyTarget& target)
auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"),
QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); QStringLiteral("/Helper"), QDBusConnection::systemBus(), this);
interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days
QByteArray request;
const quint64 nonce = interface->getNonce(); QDBusPendingCall pcall = interface->copyblocks(source.path(), source.firstByte(), source.length(),
request.setNum(nonce); target.path(), target.firstByte(), blockSize);
request.append(source.path().toUtf8());
request.append(QByteArray::number(source.firstByte()));
request.append(QByteArray::number(source.length()));
request.append(target.path().toUtf8());
request.append(QByteArray::number(target.firstByte()));
request.append(QByteArray::number(blockSize));
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
QDBusPendingCall pcall = interface->copyblocks(privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce,
source.path(), source.firstByte(), source.length(),
target.path(), target.firstByte(), blockSize);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
QEventLoop loop; QEventLoop loop;
@ -257,18 +232,8 @@ bool ExternalCommand::writeData(Report& commandReport, const QByteArray& buffer,
auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"),
QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); QStringLiteral("/Helper"), QDBusConnection::systemBus(), this);
interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days
QByteArray request;
QDBusPendingCall pcall = interface->writeData(buffer, deviceNode, firstByte);
const quint64 nonce = interface->getNonce();
request.setNum(nonce);
request.append(buffer);
request.append(deviceNode.toUtf8());
request.append(QByteArray::number(firstByte));
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
QDBusPendingCall pcall = interface->writeData(privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce,
buffer, deviceNode, firstByte);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
QEventLoop loop; QEventLoop loop;
@ -389,33 +354,11 @@ bool ExternalCommand::startHelper()
d->m_thread = new DBusThread; d->m_thread = new DBusThread;
d->m_thread->start(); d->m_thread->start();
init = new QCA::Initializer;
// Generate RSA key pair for signing external command requests
if (!QCA::isSupported("pkey") || !QCA::PKey::supportedIOTypes().contains(QCA::PKey::RSA)) {
qCritical() << xi18n("QCA does not support RSA.");
return false;
}
privateKey = new QCA::PrivateKey;
*privateKey = QCA::KeyGenerator().createRSA(4096);
if(privateKey->isNull()) {
qCritical() << xi18n("Failed to make private RSA key.");
return false;
}
if (!privateKey->canSign()) {
qCritical() << xi18n("Generated key cannot be used for signatures.");
return false;
}
QCA::PublicKey pubkey = privateKey->toPublicKey();
KAuth::Action action = KAuth::Action(QStringLiteral("org.kde.kpmcore.externalcommand.init")); KAuth::Action action = KAuth::Action(QStringLiteral("org.kde.kpmcore.externalcommand.init"));
action.setHelperId(QStringLiteral("org.kde.kpmcore.externalcommand")); action.setHelperId(QStringLiteral("org.kde.kpmcore.externalcommand"));
action.setTimeout(10 * 24 * 3600 * 1000); // 10 days action.setTimeout(10 * 24 * 3600 * 1000); // 10 days
action.setParentWidget(parent); action.setParentWidget(parent);
QVariantMap arguments; QVariantMap arguments;
arguments.insert(QStringLiteral("pubkey"), pubkey.toDER());
action.setArguments(arguments); action.setArguments(arguments);
m_job = action.execute(); m_job = action.execute();
m_job->start(); m_job->start();
@ -435,38 +378,9 @@ bool ExternalCommand::startHelper()
void ExternalCommand::stopHelper() void ExternalCommand::stopHelper()
{ {
auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"),
QStringLiteral("/Helper"), QDBusConnection::systemBus()); QStringLiteral("/Helper"), QDBusConnection::systemBus());
QByteArray request; interface->exit();
const quint64 nonce = interface->getNonce();
request.setNum(nonce);
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
interface->exit(privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce);
delete privateKey;
delete init;
}
quint64 ExternalCommand::getNonce(QDBusInterface& iface)
{
QDBusPendingCall pcall = iface.asyncCall(QStringLiteral("getNonce"));
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall);
QEventLoop loop;
quint64 rval = 0;
auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) {
loop.exit();
if (watcher->isError())
qWarning() << watcher->error();
else {
QDBusPendingReply<quint64> reply = *watcher;
rval = reply;
}
};
connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop);
loop.exec();
return rval;
} }
void DBusThread::run() void DBusThread::run()

View File

@ -31,13 +31,14 @@
#include <memory> #include <memory>
class KJob;
namespace KAuth { class ExecuteJob; } namespace KAuth { class ExecuteJob; }
namespace QCA { class PrivateKey; class Initializer; }
class KJob;
class Report; class Report;
class CopySource; class CopySource;
class CopyTarget; class CopyTarget;
class QDBusInterface; class QDBusInterface;
struct ExternalCommandPrivate; struct ExternalCommandPrivate;
class DBusThread : public QThread class DBusThread : public QThread
@ -126,18 +127,13 @@ public Q_SLOTS:
private: private:
void setExitCode(int i); void setExitCode(int i);
void onReadOutput(); void onReadOutput();
static quint64 getNonce(QDBusInterface& iface);
private: private:
std::unique_ptr<ExternalCommandPrivate> d; std::unique_ptr<ExternalCommandPrivate> d;
// KAuth // KAuth
static quint64 m_Nonce;
static KAuth::ExecuteJob *m_job; static KAuth::ExecuteJob *m_job;
static QCA::Initializer *init;
static QCA::PrivateKey *privateKey;
static bool helperStarted; static bool helperStarted;
static QWidget *parent; static QWidget *parent;
}; };

View File

@ -39,11 +39,12 @@
* *
* This helper also starts another DBus interface where it listens to * This helper also starts another DBus interface where it listens to
* command execution requests from the application that started the helper. * command execution requests from the application that started the helper.
* These requests are validated using public key cryptography, to prevent *
* other unprivileged applications from gaining root privileges.
*/ */
ActionReply ExternalCommandHelper::init(const QVariantMap& args) ActionReply ExternalCommandHelper::init(const QVariantMap& args)
{ {
Q_UNUSED(args)
ActionReply reply; ActionReply reply;
if (!QDBusConnection::systemBus().isConnected() || !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || if (!QDBusConnection::systemBus().isConnected() || !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) ||
@ -52,9 +53,7 @@ ActionReply ExternalCommandHelper::init(const QVariantMap& args)
reply.addData(QStringLiteral("success"), false); reply.addData(QStringLiteral("success"), false);
return reply; return reply;
} }
m_publicKey = QCA::PublicKey::fromDER(args[QStringLiteral("pubkey")].toByteArray());
m_loop = std::make_unique<QEventLoop>(); m_loop = std::make_unique<QEventLoop>();
HelperSupport::progressStep(QVariantMap()); HelperSupport::progressStep(QVariantMap());
@ -75,16 +74,6 @@ ActionReply ExternalCommandHelper::init(const QVariantMap& args)
return reply; return reply;
} }
/** Generates cryptographic nonce
* @return nonce
*/
quint64 ExternalCommandHelper::getNonce()
{
const quint64 nonce = m_Generator.generate();
m_Nonces.insert(nonce);
return nonce;
}
/** Reads the given number of bytes from the sourceDevice into the given buffer. /** Reads the given number of bytes from the sourceDevice into the given buffer.
@param sourceDevice device or file to read from @param sourceDevice device or file to read from
@param buffer buffer to store the bytes read in @param buffer buffer to store the bytes read in
@ -145,35 +134,11 @@ bool ExternalCommandHelper::writeData(const QString &targetDevice, const QByteAr
} }
// If targetDevice is empty then return QByteArray with data that was read from disk. // If targetDevice is empty then return QByteArray with data that was read from disk.
QVariantMap ExternalCommandHelper::copyblocks(const QByteArray& signature, const quint64 nonce, const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize) QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize)
{ {
QVariantMap reply; QVariantMap reply;
reply[QStringLiteral("success")] = true; reply[QStringLiteral("success")] = true;
if (m_Nonces.find(nonce) != m_Nonces.end())
m_Nonces.erase( nonce );
else {
reply[QStringLiteral("success")] = false;
return reply;
}
QByteArray request;
request.setNum(nonce);
request.append(sourceDevice.toUtf8());
request.append(QByteArray::number(sourceFirstByte));
request.append(QByteArray::number(sourceLength));
request.append(targetDevice.toUtf8());
request.append(QByteArray::number(targetFirstByte));
request.append(QByteArray::number(blockSize));
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) {
qCritical() << xi18n("Invalid cryptographic signature");
reply[QStringLiteral("success")] = false;
return reply;
}
const qint64 blocksToCopy = sourceLength / blockSize; const qint64 blocksToCopy = sourceLength / blockSize;
qint64 readOffset = sourceFirstByte; qint64 readOffset = sourceFirstByte;
qint64 writeOffset = targetFirstByte; qint64 writeOffset = targetFirstByte;
@ -258,65 +223,27 @@ QVariantMap ExternalCommandHelper::copyblocks(const QByteArray& signature, const
return reply; return reply;
} }
bool ExternalCommandHelper::writeData(const QByteArray& signature, const quint64 nonce, const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte) bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte)
{ {
if (m_Nonces.find(nonce) != m_Nonces.end())
m_Nonces.erase( nonce );
else
return false;
QByteArray request;
request.setNum(nonce);
request.append(buffer);
request.append(targetDevice.toUtf8());
request.append(QByteArray::number(targetFirstByte));
// Do not allow using this helper for writing to arbitrary location // Do not allow using this helper for writing to arbitrary location
if ( targetDevice.left(5) != QStringLiteral("/dev/") && !targetDevice.contains(QStringLiteral("/etc/fstab"))) if ( targetDevice.left(5) != QStringLiteral("/dev/") && !targetDevice.contains(QStringLiteral("/etc/fstab")))
return false; return false;
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) {
qCritical() << xi18n("Invalid cryptographic signature");
return false;
}
return writeData(targetDevice, buffer, targetFirstByte); return writeData(targetDevice, buffer, targetFirstByte);
} }
QVariantMap ExternalCommandHelper::start(const QByteArray& signature, const quint64 nonce, const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode) QVariantMap ExternalCommandHelper::start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode)
{ {
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
QVariantMap reply; QVariantMap reply;
reply[QStringLiteral("success")] = true; reply[QStringLiteral("success")] = true;
if (m_Nonces.find(nonce) != m_Nonces.end())
m_Nonces.erase( nonce );
else {
reply[QStringLiteral("success")] = false;
return reply;
}
if (command.isEmpty()) { if (command.isEmpty()) {
reply[QStringLiteral("success")] = false; reply[QStringLiteral("success")] = false;
return reply; return reply;
} }
QByteArray request;
request.setNum(nonce);
request.append(command.toUtf8());
for (const auto &argument : arguments)
request.append(argument.toUtf8());
request.append(input);
request.append(processChannelMode);
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) {
qCritical() << xi18n("Invalid cryptographic signature");
reply[QStringLiteral("success")] = false;
return reply;
}
// Compare with command whitelist // Compare with command whitelist
QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1);
if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) {
@ -326,7 +253,7 @@ QVariantMap ExternalCommandHelper::start(const QByteArray& signature, const quin
return reply; return reply;
} }
// connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput); // connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput);
m_cmd.setEnvironment( { QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1") } ); m_cmd.setEnvironment( { QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1") } );
m_cmd.setProcessChannelMode(static_cast<QProcess::ProcessChannelMode>(processChannelMode)); m_cmd.setProcessChannelMode(static_cast<QProcess::ProcessChannelMode>(processChannelMode));
@ -341,19 +268,8 @@ QVariantMap ExternalCommandHelper::start(const QByteArray& signature, const quin
return reply; return reply;
} }
void ExternalCommandHelper::exit(const QByteArray& signature, const quint64 nonce) void ExternalCommandHelper::exit()
{ {
QByteArray request;
if (m_Nonces.find(nonce) == m_Nonces.end())
return;
request.setNum(nonce);
QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512);
if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) {
qCritical() << xi18n("Invalid cryptographic signature");
return;
}
m_loop->exit(); m_loop->exit();
QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper"));
@ -362,18 +278,18 @@ void ExternalCommandHelper::exit(const QByteArray& signature, const quint64 nonc
void ExternalCommandHelper::onReadOutput() void ExternalCommandHelper::onReadOutput()
{ {
// const QByteArray s = cmd.readAllStandardOutput(); /* const QByteArray s = cmd.readAllStandardOutput();
// if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems
// if (report()) if (report())
// report()->line() << xi18nc("@info:status", "(Command is printing too much output)"); report()->line() << xi18nc("@info:status", "(Command is printing too much output)");
// return; return;
// } }
// output += s; output += s;
// if (report()) if (report())
// *report() << QString::fromLocal8Bit(s); *report() << QString::fromLocal8Bit(s);*/
} }
KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper) KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper)

View File

@ -24,12 +24,9 @@
#include <KAuth> #include <KAuth>
#include <QEventLoop> #include <QEventLoop>
#include <QRandomGenerator64>
#include <QString> #include <QString>
#include <QProcess> #include <QProcess>
#include <QtCrypto>
using namespace KAuth; using namespace KAuth;
class ExternalCommandHelper : public QObject class ExternalCommandHelper : public QObject
@ -47,23 +44,17 @@ public:
public Q_SLOTS: public Q_SLOTS:
ActionReply init(const QVariantMap& args); ActionReply init(const QVariantMap& args);
Q_SCRIPTABLE quint64 getNonce(); Q_SCRIPTABLE QVariantMap start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode);
Q_SCRIPTABLE QVariantMap start(const QByteArray& signature, const quint64 nonce, const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode); Q_SCRIPTABLE QVariantMap copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize);
Q_SCRIPTABLE QVariantMap copyblocks(const QByteArray& signature, const quint64 nonce, const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize); Q_SCRIPTABLE bool writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte);
Q_SCRIPTABLE bool writeData(const QByteArray& signature, const quint64 nonce, const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte); Q_SCRIPTABLE void exit();
Q_SCRIPTABLE void exit(const QByteArray& signature, const quint64 nonce);
private: private:
void onReadOutput(); void onReadOutput();
std::unique_ptr<QEventLoop> m_loop; std::unique_ptr<QEventLoop> m_loop;
QCA::Initializer initializer;
QCA::PublicKey m_publicKey;
QRandomGenerator64 m_Generator;
std::unordered_set<quint64> m_Nonces;
QProcess m_cmd; QProcess m_cmd;
// QByteArray output;
// QByteArray output;
}; };
#endif #endif