Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34135282
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
22 KB
Subscribers
None
View Options
diff --git a/src/gpgolkeyadder/gpgolkeyadder-options.h b/src/gpgolkeyadder/gpgolkeyadder-options.h
index 038e411..f4de06d 100644
--- a/src/gpgolkeyadder/gpgolkeyadder-options.h
+++ b/src/gpgolkeyadder/gpgolkeyadder-options.h
@@ -1,37 +1,43 @@
#ifndef GPGOLKEYADDER_OPTIONS
#define GPGOLKEYADDER_OPTIONS
/* Copyright (C) 2018 by Intevation GmbH <info@intevation.de>
*
* This file is Free Software under the GNU GPL (v>=2)
* and comes with ABSOLUTELY NO WARRANTY!
* See LICENSE.txt for details.
*/
#include <QCommandLineParser>
#include <QList>
/** @file Commandline options*/
static void options(QCommandLineParser &parser)
{
QList<QCommandLineOption> options;
options
<< QCommandLineOption(QStringList() << QStringLiteral("debug"),
QStringLiteral("Print debug output."))
+ << QCommandLineOption(QStringList() << QStringLiteral("cms"),
+ QStringLiteral("Add S/MIME settings"))
<< QCommandLineOption(QStringLiteral("hwnd"),
QStringLiteral("Parent Window"),
QStringLiteral("windows window handle"))
+ << QCommandLineOption(QStringList() << QStringLiteral("sign"),
+ QStringLiteral("Always sign"))
+ << QCommandLineOption(QStringList() << QStringLiteral("encrypt"),
+ QStringLiteral("Always enccrypt"))
<< QCommandLineOption(QStringLiteral("username"),
QStringLiteral("Name"),
QStringLiteral("username"))
<< QCommandLineOption(QStringLiteral("lang"),
QStringLiteral("Language"),
QStringLiteral("Language to be used e.g. de_DE"));
for (const auto &opt: options) {
parser.addOption(opt);
}
parser.addVersionOption();
parser.addHelpOption();
}
#endif
diff --git a/src/gpgolkeyadder/gpgolkeyadder.cpp b/src/gpgolkeyadder/gpgolkeyadder.cpp
index 6efc183..4182a2f 100644
--- a/src/gpgolkeyadder/gpgolkeyadder.cpp
+++ b/src/gpgolkeyadder/gpgolkeyadder.cpp
@@ -1,249 +1,536 @@
/* Copyright (C) 2018 by Intevation GmbH <info@intevation.de>
*
* This file is Free Software under the GNU GPL (v>=2)
* and comes with ABSOLUTELY NO WARRANTY!
* See LICENSE.txt for details.
*/
#include "gpgolkeyadder.h"
#include "w32-gettext.h"
#include "w32-util.h"
#include "w32-qt-util.h"
#include "stdinreader.h"
#include <QLabel>
#include <QWidget>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QCommandLineParser>
+#include <QCheckBox>
#include <QDebug>
#include <QTextEdit>
#include <QFont>
#include <QFontMetrics>
#include <QMessageBox>
#include <QDateTime>
#include <QLocale>
-
+#include <QTabWidget>
+#include <QToolTip>
+#include <QThread>
+#include <QProgressDialog>
+
+#include <gpgme++/context.h>
+#include <gpgme++/keylistresult.h>
+#include <gpgme++/importresult.h>
#include <gpgme++/data.h>
#include <gpgme++/key.h>
#include <iostream>
+namespace
+{
+
+static char **vector_to_cArray(const std::vector<std::string> &vec)
+{
+ char ** ret = (char**) malloc (sizeof (char*) * (vec.size() + 1));
+ for (size_t i = 0; i < vec.size(); i++)
+ {
+ ret[i] = strdup (vec[i].c_str());
+ }
+ ret[vec.size()] = NULL;
+ return ret;
+}
+
+static void release_cArray (char **carray)
+{
+ if (carray)
+ {
+ for (int idx = 0; carray[idx]; idx++)
+ {
+ free (carray[idx]);
+ }
+ free (carray);
+ }
+}
+
+class CMSImportThread: public QThread
+{
+ Q_OBJECT
+
+ public:
+ explicit CMSImportThread(const GpgME::Data &data):
+ mData(data)
+ {
+ }
+
+ std::vector<GpgME::Key> certs()
+ {
+ return mCerts;
+ }
+
+ protected:
+ void run() override
+ {
+ auto ctx = GpgME::Context::create(GpgME::CMS);
+
+ if (!ctx) {
+ qDebug () << "No ctx";
+ return;
+ }
+
+ const auto result = ctx->importKeys(mData);
+ std::vector<std::string> fingerprints;
+
+ for (const auto import: result.imports()) {
+ if (import.error()){
+ qDebug() << "Error importing:" << import.error().asString();
+ continue;
+ }
+ const char *fpr = import.fingerprint ();
+ if (!fpr)
+ {
+ qDebug() << "Import with no fpr.";
+ continue;
+ }
+ fingerprints.push_back (fpr);
+ qDebug () << "imported: " << fpr;
+ }
+ if (!fingerprints.size()) {
+ qDebug () << "Nothing imported";
+ return;
+ }
+ ctx = GpgME::Context::create(GpgME::CMS);
+ ctx->setKeyListMode (GpgME::KeyListMode::Local |
+ GpgME::KeyListMode::Validate);
+
+ GpgME::Error err;
+ char **patterns = vector_to_cArray(fingerprints);
+ err = ctx->startKeyListing((const char**)patterns, false);
+ release_cArray(patterns);
+ if (err) {
+ qDebug() << "Failed to start keylisting err:" << err.asString();
+ return;
+ }
+
+ while (!err) {
+ const auto key = ctx->nextKey(err);
+ if (err || key.isNull()) {
+ break;
+ }
+ mCerts.push_back(key);
+ }
+ return;
+ }
+
+ private:
+ GpgME::Data mData;
+ std::vector<GpgME::Key> mCerts;
+};
+} // Namespace
+
+
GpgOLKeyAdder::GpgOLKeyAdder(const QCommandLineParser &parser):
QDialog(nullptr),
- mEdit(new QTextEdit)
+ mEdit(new QTextEdit),
+ mCMSEdit(new QTextEdit),
+ mAlwaysSec(new QCheckBox)
{
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
mName = parser.value(QStringLiteral("username"));
+ mShowCMS = parser.isSet(QStringLiteral("cms"));
- setWindowTitle(_("GpgOL") + QStringLiteral(" - ") + _("Configure key for:") +
+ setWindowTitle(_("GpgOL") + QStringLiteral(" - ") + _("Settings for:") +
QStringLiteral(" ") + mName);
setWindowIcon(QIcon(":/gpgol-icon.svg"));
const auto hwnd = parser.value(QStringLiteral("hwnd"));
if (!hwnd.isEmpty()) {
bool ok;
WId id = (WId) hwnd.toInt(&ok);
if (!ok) {
qDebug() << "invalid hwnd value";
} else {
W32::setupForeignParent(id, this, true);
setModal(true);
}
}
+ mAlwaysSec->setChecked(parser.isSet("encrypt") && parser.isSet("sign"));
+
setupGUI();
// Deletes itself
auto reader = new StdinReader;
connect (reader, &StdinReader::stdinRead, this, [this] (const QByteArray &data) {
if (data.size() > 1) {
- mEdit->setPlainText(QString::fromUtf8(data));
+ handleInput(data);
}
});
reader->start();
}
+void GpgOLKeyAdder::handleInput(const QByteArray &data) {
+ const auto stringData = QString::fromUtf8(data);
+ const auto splitData = stringData.split("BEGIN CMS DATA\n");
+ if (splitData.size() != 2) {
+ qDebug() << "Failed to split data. Old GpgOL Version?";
+ mEdit->setPlainText(stringData);
+ } else {
+ mEdit->setPlainText(splitData[0]);
+ mCMSEdit->setPlainText(splitData[1]);
+ }
+}
+
void GpgOLKeyAdder::setupGUI()
{
- /* Setup Edit */
+ /* Setup Edits */
auto fixedFont = QFont("Monospace", 10);
fixedFont.setStyleHint(QFont::TypeWriter);
- mEdit->setFont(fixedFont);
resize(QFontMetrics(fixedFont).averageCharWidth() * 80,
QFontMetrics(fixedFont).height() * 30);
+
+ mEdit->setFont(fixedFont);
mEdit->setPlaceholderText(QString::fromUtf8(_("Paste a public key export here. It should look like:")) +
QStringLiteral("\n\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n"
"mQENBEzJavsBCADG/guWL6AxGgngUxp/DcmoitJjaJMqcJkBtD3uKrW81Pbnm3LI\n"
"...\n"
"dCl8hHggB9x2\n"
"=oShe\n"
"-----END PGP PUBLIC KEY BLOCK-----"));
mEdit->setUndoRedoEnabled(true);
+
+ mCMSEdit->setFont(fixedFont);
+ mCMSEdit->setPlaceholderText(QString::fromUtf8(_("Paste the certificate here. It should look like:")) +
+ QStringLiteral("\n\n-----BEGIN CERTIFICATE-----\n\n"
+ "MIICeDCCAeGgAwIBAgIJANNFIDoYY4XJMA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV\n"
+ "...\n"
+ "dCl8hHggB9x2\n"
+ "-----END CERTIFICATE-----"));
+ mCMSEdit->setUndoRedoEnabled(true);
+
/* Setup buttons */
QDialogButtonBox *buttonBox = new QDialogButtonBox();
buttonBox->setStandardButtons(QDialogButtonBox::Cancel |
QDialogButtonBox::Ok);
connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked,
this, [this] () {
checkAccept();
});
connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked,
this, [this] () {
qApp->quit();
});
auto okBtn = buttonBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(false);
connect(mEdit, &QTextEdit::textChanged, this, [this, okBtn] () {
const auto text = mEdit->toPlainText().trimmed().toUtf8();
if (!text.size()) {
okBtn->setEnabled(true);
return;
}
GpgME::Data data(text.constData(), text.size(), false);
okBtn->setEnabled(data.type() == GpgME::Data::PGPKey);
});
+ connect(mCMSEdit, &QTextEdit::textChanged, this, [this, okBtn] () {
+ const auto text = mCMSEdit->toPlainText().trimmed().toUtf8();
+ if (!text.size()) {
+ okBtn->setEnabled(true);
+ return;
+ }
+ GpgME::Data data(text.constData(), text.size(), false);
+
+ okBtn->setEnabled(data.type() == GpgME::Data::X509Cert);
+ });
+
+ /* The always secure box */
+ mAlwaysSec->setText(QString::fromUtf8(_("Always secure mails")));
/* Setup layout */
auto layout = new QVBoxLayout;
setLayout(layout);
- layout->addWidget(mEdit);
+
+ layout->addWidget(mAlwaysSec);
+
+ auto hBox = new QHBoxLayout;
+
+ hBox->addWidget(new QLabel(
+ _("Use specific keys / certs for this contact:")));
+
+ auto infoBtn = new QPushButton;
+ infoBtn->setIcon(QIcon::fromTheme("help-contextual"));
+ infoBtn->setFlat(true);
+ hBox->addWidget(infoBtn);
+ hBox->addStretch(1);
+
+ connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () {
+ const QString generalMsg = QString::fromUtf8(_("You can use this to override the keys / certificates used "
+ "for this contact. The keys will be imported and used for "
+ "this contact, regardless of OpenPGP trust."));
+ const QString smimeMsg = QString::fromUtf8(_("For S/MIME the root certificate has to be trusted."));
+ const QString multiMsg = QString::fromUtf8(_("Place multiple keys / certificates in here to encrypt to all of them."));
+ QToolTip::showText(infoBtn->mapToGlobal(QPoint()),
+ QStringLiteral("%1<br/><br/>%2").arg(generalMsg).arg(
+ (mShowCMS ? smimeMsg + QStringLiteral("<br/><br/>") + multiMsg :
+ multiMsg)), infoBtn, QRect(), 30000);
+ });
+
+
+ layout->addLayout(hBox);
+
+ if (mShowCMS) {
+ auto tab = new QTabWidget;
+ tab->addTab(mEdit, QStringLiteral("OpenPGP"));
+ tab->addTab(mCMSEdit, QStringLiteral("S/MIME (X509)"));
+ layout->addWidget(tab);
+ } else {
+ layout->addWidget(mEdit);
+ }
layout->addWidget(buttonBox);
}
-static void save(const QByteArray &data)
+void GpgOLKeyAdder::save()
{
- if (!data.size()) {
+ std::cout << "BEGIN KEYADDER PGP DATA" << std::endl;
+ const QByteArray &pgpData = mEdit->toPlainText().trimmed().toUtf8();
+ if (!pgpData.size()) {
+ /* Empty is a special case which can mean that an
+ * existing key should be removed. */
+ std::cout << "empty" << std::endl;
+ } else {
+ std::cout << pgpData.constData() << std::endl;
+ }
+ std::cout << "END KEYADDER PGP DATA" << std::endl;
+
+ std::cout << "BEGIN KEYADDER CMS DATA" << std::endl;
+ const QByteArray &cmsData = mCMSEdit->toPlainText().trimmed().toUtf8();
+ if (!cmsData.size()) {
/* Empty is a special case which can mean that an
* existing key should be removed. */
std::cout << "empty" << std::endl;
} else {
- std::cout << data.constData() << std::endl;
+ std::cout << cmsData.constData() << std::endl;
}
+ std::cout << "END KEYADDER CMS DATA" << std::endl;
+ std::cout << "BEGIN KEYADDER OPTIONS" << std::endl;
+ std::cout << "secure=" << (mAlwaysSec->isChecked() ? "3" : "0") << std::endl;
+ std::cout << "END KEYADDER OPTIONS" << std::endl;
+
qApp->quit();
}
static QString prettyNameAndEMail(const QString &id, const QString &name, const QString &email, const QString &comment)
{
if (name.isEmpty()) {
if (email.isEmpty()) {
return QString();
} else if (comment.isEmpty()) {
return QStringLiteral("<%1>").arg(email);
} else {
return QStringLiteral("(%2) <%1>").arg(email, comment);
}
}
if (email.isEmpty()) {
if (comment.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 (%2)").arg(name, comment);
}
}
if (comment.isEmpty()) {
return QStringLiteral("%1 <%2>").arg(name, email);
} else {
return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
}
return QString();
}
static QString prettyNameAndEMail(const char *id, const char *name_, const std::string &email_, const char *comment_)
{
return prettyNameAndEMail(QString::fromUtf8(id), QString::fromUtf8(name_), QString::fromStdString(email_),
QString::fromUtf8(comment_));
}
static QString prettyNameAndEMail(const GpgME::UserID &uid)
{
return prettyNameAndEMail(uid.id(), uid.name(), uid.addrSpec(), uid.comment());
}
static QString time_t2string(time_t t)
{
QDateTime dt;
dt.setTime_t(t);
return QLocale().toString(dt, QLocale::ShortFormat);
}
-void GpgOLKeyAdder::checkAccept()
+static QStringList
+buildKeyInfos(const std::vector<GpgME::Key> keys, QWidget *parent,
+ bool *bOk)
{
- const auto text = mEdit->toPlainText().trimmed().toUtf8();
-
- if (text.isEmpty()) {
- save(text);
- // Save exits
- return;
- }
-
- GpgME::Data data(text.constData(), text.size(), false);
-
- const auto keys = data.toKeys();
+ QStringList keyInfos;
- if (!keys.size()) {
- QMessageBox::warning(this, QString::fromUtf8 (_("Error")),
- QStringLiteral("<b>%1</b><br/><br/>").arg(QString::fromUtf8(_("Failed to parse any public key."))));
- return;
+ if (!bOk) {
+ qDebug() << "Invalid call";
+ return keyInfos;
}
-
- QStringList keyInfos;
+ *bOk = false;
for (const auto &key: keys) {
if (key.isNull() || !key.numSubkeys()) {
qDebug() << "Null key?";
continue;
}
-
- if (key.hasSecret()) {
- QMessageBox::warning(this, QString::fromUtf8 (_("Error")),
+ if (key.hasSecret() && key.protocol() == GpgME::OpenPGP) {
+ QMessageBox::warning(parent, QString::fromUtf8 (_("Error")),
QStringLiteral("<b>%1</b><br/><br/>").arg(QString::fromUtf8(_("Secret key detected."))) +
QString::fromUtf8(_("You can only configure public keys in Outlook."
" Import secret keys with Kleopatra.")));
- return;
+ return QStringList();
}
- if (key.isRevoked() || key.isExpired() || key.isInvalid() || key.isDisabled() || !key.canEncrypt()) {
- QMessageBox::warning(this, QString::fromUtf8 (_("Error")),
+ if (key.isBad() || !key.canEncrypt()) {
+ QMessageBox::warning(parent, QString::fromUtf8 (_("Error")),
QStringLiteral("<b>%1</b><br/><br/>%2<br/><br/>").arg(QString::fromUtf8(_("Invalid key detected."))).arg(
key.primaryFingerprint()) +
QString::fromUtf8(_("The key is unusable for Outlook."
" Please check Kleopatra for more information.")));
- return;
+ return QStringList();
}
const auto subkey = key.subkey(0);
QString info = QString::fromLatin1(key.primaryFingerprint()) + "<br/>" +
QString::fromUtf8(_("Created:")) + " " + time_t2string(subkey.creationTime()) + "<br/>" +
QString::fromUtf8(_("User Ids:")) + "<br/>";
for (const auto &uid: key.userIDs()) {
if (uid.isNull() || uid.isRevoked() || uid.isInvalid()) {
continue;
}
info += " " + prettyNameAndEMail(uid).toHtmlEscaped() + "<br/>";
}
keyInfos << info;
}
+ *bOk = true;
+ return keyInfos;
+}
+
+void GpgOLKeyAdder::checkAcceptBottom(const std::vector<GpgME::Key> &pgpKeys, const std::vector<GpgME::Key> &cmsCerts)
+{
+ bool ok;
+ QString msg;
+
+ if (!pgpKeys.size() && (mShowCMS && !pgpKeys.size() && !cmsCerts.size())) {
+ QMessageBox::warning(this, QString::fromUtf8 (_("Error")),
+ QStringLiteral("<b>%1</b><br/><br/>").arg(QString::fromUtf8(_("Failed to parse any public key."))));
+ return;
+ }
- QString msg = QString::fromUtf8(_("You are about to configure the following %1 for:")).arg(
- (keyInfos.size() > 1 ? QString::fromUtf8(_("keys")) : QString::fromUtf8(_("key")))) +
+ if (pgpKeys.size())
+ {
+ const auto keyInfos = buildKeyInfos (pgpKeys, this, &ok);
+ if (!ok) {
+ return;
+ }
+ msg += QString::fromUtf8(_("You are about to configure the following OpenPGP %1 for:")).arg(
+ (keyInfos.size() > 1 ? QString::fromUtf8(_("keys")) : QString::fromUtf8(_("key")))) +
QStringLiteral("<br/>\t\"%1\"<br/><br/>").arg(mName.toHtmlEscaped()) +
- keyInfos.join("<br/><br/>");
+ keyInfos.join("<br/><br/>");
+
+ }
+ if (mShowCMS && cmsCerts.size())
+ {
+ std::vector<GpgME::Key> leafs;
+ std::remove_copy_if(cmsCerts.begin(), cmsCerts.end(),
+ std::back_inserter(leafs),
+ [cmsCerts] (const auto &k) {
+ /* Check if a key has this fingerprint in the
+ * chain ID. Meaning that there is any child of
+ * this certificate. In that case remove it. */
+ for (const auto &c: cmsCerts) {
+ if (!c.chainID()) {
+ continue;
+ }
+ if (!k.primaryFingerprint() || !c.primaryFingerprint()) {
+ /* WTF? */
+ continue;
+ }
+ if (!strcmp (c.chainID(), k.primaryFingerprint())) {
+ qDebug() << "Filtering out non leaf cert" << k.primaryFingerprint();
+ return true;
+ }
+ }
+ return false;
+ });
+
+ const auto certInfos = buildKeyInfos (leafs, this, &ok);
+ if (!ok) {
+ return;
+ }
+ msg += QString::fromUtf8(_("You are about to configure the following S/MIME %1 for:")).arg(
+ (certInfos.size() > 1 ? QString::fromUtf8(_("certificates")) : QString::fromUtf8(_("certificate")))) +
+ QStringLiteral("<br/>\t\"%1\"<br/><br/>").arg(mName.toHtmlEscaped()) +
+ certInfos.join("<br/><br/>");
+ }
msg += "<br/><br/>" + QString::fromUtf8 (_("Continue?"));
- const auto ret = QMessageBox::question(this, QString::fromUtf8(_("Confirm keys")), msg,
+ const auto ret = QMessageBox::question(this, QString::fromUtf8(_("Confirm")), msg,
QMessageBox::Yes | QMessageBox::Abort, QMessageBox::Yes);
if (ret == QMessageBox::Yes) {
- save(text);
+ save();
return;
}
}
+
+void GpgOLKeyAdder::checkAccept()
+{
+ const auto openpgpText = mEdit->toPlainText().trimmed().toUtf8();
+ const auto cmsText = mCMSEdit->toPlainText().trimmed().toUtf8();
+
+ GpgME::Data pgp(openpgpText.constData(), openpgpText.size(), false);
+ GpgME::Data cms(cmsText.constData(), cmsText.size(), false);
+
+ const auto keys = pgp.toKeys(GpgME::OpenPGP);
+
+ if (!mShowCMS || !cmsText.size()) {
+ checkAcceptBottom (keys, std::vector<GpgME::Key>());
+ return;
+ }
+
+ auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog);
+ progress->setAutoClose(true);
+ progress->setMinimumDuration(0);
+ progress->setMaximum(0);
+ progress->setMinimum(0);
+ progress->setModal(true);
+ progress->setCancelButton(nullptr);
+ progress->setWindowTitle(_("Validating S/MIME certificates"));
+ progress->setLabel(new QLabel(_("This may take several minutes...")));
+ auto workerThread = new CMSImportThread(cms);
+ connect(workerThread, &QThread::finished, this, [this, workerThread, progress, keys] {
+ progress->accept();
+ progress->deleteLater();
+ checkAcceptBottom(keys, workerThread->certs());
+ delete workerThread;
+ });
+ workerThread->start();
+ progress->exec();
+}
+
+#include "gpgolkeyadder.moc"
diff --git a/src/gpgolkeyadder/gpgolkeyadder.h b/src/gpgolkeyadder/gpgolkeyadder.h
index 22c9d80..7813653 100644
--- a/src/gpgolkeyadder/gpgolkeyadder.h
+++ b/src/gpgolkeyadder/gpgolkeyadder.h
@@ -1,33 +1,44 @@
#ifndef GPGOLKEYADDER_H
#define GPGOLKEYADDER_H
/* Copyright (C) 2018 by Intevation GmbH <info@intevation.de>
*
* This file is Free Software under the GNU GPL (v>=2)
* and comes with ABSOLUTELY NO WARRANTY!
* See LICENSE.txt for details.
*/
#include <QDialog>
#include <QString>
+#include <vector>
+#include <gpgme++/key.h>
+
class QCommandLineParser;
class QTextEdit;
+class QCheckBox;
+
class GpgOLKeyAdder: public QDialog
{
Q_OBJECT
public:
GpgOLKeyAdder(const QCommandLineParser &parser);
protected:
/** @brief UI setup */
void setupGUI();
private:
void checkAccept();
+ void checkAcceptBottom(const std::vector<GpgME::Key> &pgpKeys, const std::vector<GpgME::Key> &cmsKeys);
+ void save();
+ void handleInput(const QByteArray &data);
QTextEdit *mEdit;
+ QTextEdit *mCMSEdit;
QString mName;
+ QCheckBox *mAlwaysSec;
+ bool mShowCMS;
};
#endif // GPGOLKEYADDER_H
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 8, 11:36 AM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a0/a7/b6b37d842391a91d5485c217a379
Attached To
rGTO Gpg4win-Tools
Event Timeline
Log In to Comment