2019-10-25 20:16:43 +02:00
|
|
|
/*
|
|
|
|
|
* File: K7Main.cpp
|
2019-10-28 14:12:50 +01:00
|
|
|
* Author: SET - nmset@yandex.com
|
2019-10-25 20:16:43 +02:00
|
|
|
* License : GPL v2
|
|
|
|
|
* Copyright SET - © 2019
|
|
|
|
|
*
|
|
|
|
|
* Created on 7 octobre 2019, 21:29
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "K7Main.h"
|
|
|
|
|
#include "global.h"
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <Wt/WBreak.h>
|
|
|
|
|
#include <Wt/WPushButton.h>
|
2020-10-25 17:28:47 +01:00
|
|
|
#include <Wt/WComboBox.h>
|
2019-10-25 20:16:43 +02:00
|
|
|
#include <Wt/WGridLayout.h>
|
|
|
|
|
#include <Wt/WHBoxLayout.h>
|
|
|
|
|
#include <Wt/WVBoxLayout.h>
|
|
|
|
|
#include <Wt/WLink.h>
|
|
|
|
|
#include "GpgMEWorker.h"
|
|
|
|
|
#include "GpgMECWorker.h"
|
2020-11-03 11:06:25 +01:00
|
|
|
#include "Tools.h"
|
2020-11-03 22:48:31 +01:00
|
|
|
#include "SensitiveTreeTableNodeText.h"
|
2019-10-25 20:16:43 +02:00
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
K7Main::K7Main(const WEnvironment& env)
|
|
|
|
|
: WApplication(env)
|
|
|
|
|
{
|
|
|
|
|
m_config = NULL;
|
2019-10-28 11:50:19 +01:00
|
|
|
m_btnUpload = NULL; m_btnImport = NULL; m_btnDelete = NULL;
|
2020-11-13 18:28:17 +01:00
|
|
|
m_btnCreate = NULL; m_popupCreate = NULL;
|
2019-10-25 20:16:43 +02:00
|
|
|
WApplication::setTitle(_APPNAME_);
|
|
|
|
|
const WString bundle = WApplication::appRoot() + _APPNAME_;
|
|
|
|
|
WApplication::instance()->messageResourceBundle().use(bundle.toUTF8());
|
|
|
|
|
if (!env.javaScript() || !env.ajax())
|
|
|
|
|
{
|
|
|
|
|
root()->addWidget(cpp14::make_unique<WText>(TR("NoJS")));
|
|
|
|
|
WApplication::quit(WString::Empty);
|
|
|
|
|
}
|
|
|
|
|
// We want users to authenticate with an X509 certificate
|
|
|
|
|
if (WApplication::instance()->environment().sslInfo() == NULL)
|
|
|
|
|
{
|
|
|
|
|
root()->addWidget(cpp14::make_unique<WText>(TR("NoAuth")));
|
|
|
|
|
WApplication::quit(WString::Empty);
|
|
|
|
|
}
|
|
|
|
|
// Translate UID trusts to string
|
|
|
|
|
UidValidities[UserID::Validity::Full] = TR("UidFull");
|
|
|
|
|
UidValidities[UserID::Validity::Marginal] = TR("UidMarginal");
|
|
|
|
|
UidValidities[UserID::Validity::Never] = TR("UidNever");
|
|
|
|
|
UidValidities[UserID::Validity::Ultimate] = TR("UidUltimate");
|
|
|
|
|
UidValidities[UserID::Validity::Undefined] = TR("UidUndefined");
|
|
|
|
|
UidValidities[UserID::Validity::Unknown] = TR("UidUnknown");
|
2020-10-19 19:28:48 +02:00
|
|
|
|
|
|
|
|
OwnerTrustLevel[GpgME::Key::OwnerTrust::Full] = TR("UidFull");
|
|
|
|
|
OwnerTrustLevel[GpgME::Key::OwnerTrust::Marginal] = TR("UidMarginal");
|
|
|
|
|
OwnerTrustLevel[GpgME::Key::OwnerTrust::Never] = TR("UidNever");
|
|
|
|
|
OwnerTrustLevel[GpgME::Key::OwnerTrust::Ultimate] = TR("UidUltimate");
|
|
|
|
|
OwnerTrustLevel[GpgME::Key::OwnerTrust::Undefined] = TR("UidUndefined");
|
|
|
|
|
OwnerTrustLevel[GpgME::Key::OwnerTrust::Unknown] = TR("UidUnknown");
|
2020-11-13 18:45:29 +01:00
|
|
|
m_popupUpload = NULL; m_popupDelete = NULL;
|
2020-10-25 17:28:47 +01:00
|
|
|
m_keyEdit = new KeyEdit(this);
|
2020-11-03 14:15:11 +01:00
|
|
|
|
|
|
|
|
WLink link;
|
|
|
|
|
const WString cssFile = WApplication::appRoot() + _APPNAME_
|
|
|
|
|
+ WString(".css");
|
|
|
|
|
link.setUrl(cssFile.toUTF8());
|
|
|
|
|
WApplication::useStyleSheet(link);
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
K7Main::~K7Main()
|
|
|
|
|
{
|
2020-11-13 18:45:29 +01:00
|
|
|
delete m_config; delete m_popupUpload; delete m_popupDelete;
|
2020-11-13 18:28:17 +01:00
|
|
|
delete m_keyEdit; delete m_popupCreate;
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
K7Main::Create()
|
|
|
|
|
{
|
|
|
|
|
WContainerWidget * cwHeader = new WContainerWidget();
|
|
|
|
|
WHBoxLayout * hblHeader = new WHBoxLayout();
|
|
|
|
|
cwHeader->setLayout(unique_ptr<WHBoxLayout> (hblHeader));
|
|
|
|
|
hblHeader->addWidget(cpp14::make_unique<WText>(_APPNAME_));
|
|
|
|
|
// Error messages will go here
|
|
|
|
|
m_tmwMessage = new TransientMessageWidget();
|
|
|
|
|
m_tmwMessage->setTextAlignment(AlignmentFlag::Right);
|
|
|
|
|
hblHeader->addWidget(unique_ptr<TransientMessageWidget>(m_tmwMessage));
|
|
|
|
|
root()->addWidget(unique_ptr<WContainerWidget> (cwHeader));
|
|
|
|
|
hblHeader->setStretchFactor(m_tmwMessage, 1);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Load config JSON file.
|
|
|
|
|
* On error, just abort, AppConfig will print an error message in m_tmwMessage
|
|
|
|
|
*/
|
|
|
|
|
m_config = new AppConfig(m_tmwMessage);
|
|
|
|
|
if (!m_config->LoadConfig())
|
|
|
|
|
return;
|
2020-11-11 21:37:31 +01:00
|
|
|
|
2019-10-25 20:16:43 +02:00
|
|
|
m_cwMain = new WContainerWidget();
|
|
|
|
|
WGridLayout * grlMain = new WGridLayout();
|
|
|
|
|
grlMain->setColumnStretch(0, 1);
|
|
|
|
|
grlMain->setColumnStretch(1, 0);
|
|
|
|
|
m_cwMain->setLayout(unique_ptr<WGridLayout> (grlMain));
|
|
|
|
|
// Add a search zone : line edit and button
|
|
|
|
|
m_leSearch = new WLineEdit();
|
|
|
|
|
grlMain->addWidget(unique_ptr<WLineEdit> (m_leSearch), 0, 0);
|
|
|
|
|
m_leSearch->enterPressed().connect(this, &K7Main::Search);
|
|
|
|
|
WPushButton * btnSearch = new WPushButton(TR("Search"));
|
|
|
|
|
btnSearch->setToolTip(TR("TTTSearch"));
|
|
|
|
|
grlMain->addWidget(unique_ptr<WPushButton> (btnSearch), 0, 1);
|
|
|
|
|
btnSearch->clicked().connect(this, &K7Main::Search);
|
|
|
|
|
// Add a result zone as a tree table
|
|
|
|
|
m_ttbKeys = new WTreeTable();
|
|
|
|
|
grlMain->addWidget(unique_ptr<WTreeTable> (m_ttbKeys), 1, 0);
|
|
|
|
|
|
|
|
|
|
WContainerWidget * cwButtons = new WContainerWidget();
|
|
|
|
|
WVBoxLayout * vblButtons = new WVBoxLayout();
|
|
|
|
|
cwButtons->setLayout(unique_ptr<WVBoxLayout> (vblButtons));
|
|
|
|
|
// Add an import button if current user is allowed
|
|
|
|
|
if (m_config->CanImport())
|
|
|
|
|
{
|
|
|
|
|
m_btnUpload = new WPushButton(TR("Upload"));
|
|
|
|
|
m_btnUpload->setToolTip(TR("TTTUpload"));
|
|
|
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnUpload));
|
2020-11-13 18:45:29 +01:00
|
|
|
m_btnUpload->clicked().connect(this, &K7Main::ShowPopupUpload);
|
2019-10-25 20:16:43 +02:00
|
|
|
m_btnImport = new WPushButton(TR("Import"));
|
|
|
|
|
m_btnImport->setToolTip(TR("TTTImport"));
|
|
|
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnImport));
|
|
|
|
|
m_btnImport->clicked().connect(this, &K7Main::DoImportKey);
|
|
|
|
|
m_btnImport->hide();
|
|
|
|
|
}
|
|
|
|
|
// Add a delete button if current user is allowed
|
|
|
|
|
if (m_config->CanDelete())
|
|
|
|
|
{
|
|
|
|
|
m_btnDelete = new WPushButton(TR("Delete"));
|
|
|
|
|
m_btnDelete->setToolTip(TR("TTTDelete"));
|
|
|
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnDelete));
|
2020-11-13 18:45:29 +01:00
|
|
|
m_btnDelete->clicked().connect(this, &K7Main::ShowPopupDelete);
|
2019-10-25 20:16:43 +02:00
|
|
|
m_btnDelete->hide();
|
|
|
|
|
}
|
2020-11-13 18:28:17 +01:00
|
|
|
vblButtons->addSpacing(150);
|
|
|
|
|
vblButtons->addStretch(1);
|
|
|
|
|
if (m_config->CanCreateKeys()) {
|
|
|
|
|
m_btnCreate = new WPushButton(TR("Create"));
|
|
|
|
|
m_btnCreate->setToolTip(TR("TTTCreate"));
|
|
|
|
|
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnCreate));
|
|
|
|
|
m_btnCreate->clicked().connect(this, &K7Main::ShowPopupCreate);
|
|
|
|
|
}
|
2019-10-25 20:16:43 +02:00
|
|
|
grlMain->addWidget(unique_ptr<WContainerWidget> (cwButtons), 1, 1);
|
|
|
|
|
|
|
|
|
|
// Add and hide detail tables
|
|
|
|
|
m_ttbUids = new WTreeTable();
|
|
|
|
|
grlMain->addWidget(unique_ptr<WTreeTable> (m_ttbUids), 2, 0);
|
|
|
|
|
m_ttbUids->hide();
|
|
|
|
|
m_ttbSubKeys = new WTreeTable();
|
|
|
|
|
grlMain->addWidget(unique_ptr<WTreeTable> (m_ttbSubKeys), 3, 0);
|
|
|
|
|
m_ttbSubKeys->hide();
|
|
|
|
|
|
|
|
|
|
root()->addWidget(cpp14::make_unique<WBreak>());
|
|
|
|
|
root()->addWidget(unique_ptr<WContainerWidget> (m_cwMain));
|
|
|
|
|
#ifdef DEVTIME
|
|
|
|
|
// Save my fingertips.
|
|
|
|
|
m_leSearch->setText("s");
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::Search()
|
|
|
|
|
{
|
|
|
|
|
#ifndef DEVTIME
|
|
|
|
|
// A reasonable minimal search criteria
|
|
|
|
|
if (!m_leSearch->text().empty() && m_leSearch->text().toUTF8().length() < 3)
|
|
|
|
|
{
|
|
|
|
|
m_tmwMessage->SetText(TR("CriteriaTooShort"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2019-10-28 11:50:19 +01:00
|
|
|
if (m_btnDelete)
|
|
|
|
|
m_btnDelete->hide();
|
2019-10-25 20:16:43 +02:00
|
|
|
m_ttbSubKeys->hide();
|
|
|
|
|
m_ttbUids->hide();
|
|
|
|
|
Error e;
|
|
|
|
|
vector<WString> configPrivKeys = m_config->PrivateKeyIds();
|
|
|
|
|
GpgMEWorker gpgw;
|
|
|
|
|
vector<GpgME::Key> privkList, pubkList;
|
|
|
|
|
|
|
|
|
|
// Find private keys if any is declared in config file for current user.
|
|
|
|
|
for (uint i = 0; i < configPrivKeys.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* User may manage more than one private key.
|
|
|
|
|
* configPrivKeys.at(i) : we want this to be a full key id(short, normal or fpr)
|
|
|
|
|
* But it can be any string in the config file (name, email...).
|
|
|
|
|
* lst can hence contain many keys.
|
|
|
|
|
*/
|
|
|
|
|
vector<GpgME::Key> lst = gpgw.FindKeys(configPrivKeys.at(i).toUTF8().c_str(), true, e);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
{
|
|
|
|
|
privkList.clear();
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* If any key is not a full key id, abort.
|
|
|
|
|
*/
|
|
|
|
|
for (uint j = 0; j < lst.size(); j++)
|
|
|
|
|
{
|
|
|
|
|
const GpgME::Key k = lst.at(j);
|
2020-11-03 11:06:25 +01:00
|
|
|
if (!Tools::ConfigKeyIdMatchesKey(k, configPrivKeys.at(i)))
|
2019-10-25 20:16:43 +02:00
|
|
|
{
|
|
|
|
|
m_tmwMessage->SetText(configPrivKeys.at(i) + TR("BadConfigKeyId"));
|
|
|
|
|
privkList.clear();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
privkList.push_back(k);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Find public keys as per criteria
|
|
|
|
|
if (!m_leSearch->text().empty())
|
|
|
|
|
{
|
|
|
|
|
pubkList = gpgw.FindKeys(m_leSearch->text().toUTF8().c_str(), false, e);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
{
|
|
|
|
|
pubkList.clear();
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_ttbKeys->columnCount() == 1) {
|
|
|
|
|
m_ttbKeys->addColumn(TR("ID"), 120);
|
2020-10-19 19:28:48 +02:00
|
|
|
m_ttbKeys->addColumn(TR("OwnerTrust"), 210);
|
2019-10-25 20:16:43 +02:00
|
|
|
m_ttbKeys->addColumn(TR("Fpr"), 300);
|
|
|
|
|
}
|
|
|
|
|
// The previous tree root is auto deleted by the use of smart pointers !!
|
|
|
|
|
WTreeTableNode * rootNode = new WTreeTableNode(TR("Keys"));
|
|
|
|
|
rootNode->setChildCountPolicy(ChildCountPolicy::Enabled);
|
|
|
|
|
m_ttbKeys->setTreeRoot(unique_ptr<WTreeTableNode> (rootNode), TR("KeyRing"));
|
|
|
|
|
rootNode->expand();
|
|
|
|
|
// Show available keys
|
|
|
|
|
if (pubkList.size())
|
|
|
|
|
DisplayKeys(pubkList, TR("Publics"), true);
|
|
|
|
|
if (privkList.size() && m_config->PrivateKeyIds().size() > 0)
|
|
|
|
|
DisplayKeys(privkList, TR("Secrets"), false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WString K7Main::MakeDateTimeLabel(time_t ticks)
|
|
|
|
|
{
|
|
|
|
|
std::chrono::minutes offset = WApplication::instance()->environment().timeZoneOffset();
|
|
|
|
|
const WDateTime dt = WDateTime::fromTime_t(ticks + (time_t) offset.count() * 60);
|
|
|
|
|
return dt.toString(WString("yyyy-MM-dd - hh:mm:ss"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::DisplayKeys(const vector<GpgME::Key>& kList, const WString& grpLabel, bool expand)
|
|
|
|
|
{
|
|
|
|
|
WTreeTableNode * grpNode = new WTreeTableNode(grpLabel);
|
|
|
|
|
m_ttbKeys->treeRoot()->addChildNode(unique_ptr<WTreeTableNode> (grpNode));
|
|
|
|
|
for (uint i = 0; i < kList.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
const GpgME::Key k = kList.at(i);
|
|
|
|
|
WTreeTableNode * keyNode = new WTreeTableNode(k.userID(0).name());
|
2020-11-04 21:45:48 +01:00
|
|
|
keyNode->setToolTip(Tools::GetKeyStatus(k));
|
2019-10-25 20:16:43 +02:00
|
|
|
WLink ln;
|
|
|
|
|
ln.setUrl(WString(L"javascript:void(0)").toUTF8());
|
|
|
|
|
WAnchor * anc = new WAnchor(ln, k.shortKeyID());
|
|
|
|
|
anc->setId(k.shortKeyID());
|
|
|
|
|
// We use widget attribute values to buffer properties.
|
|
|
|
|
anc->setAttributeValue("hasSecret", k.hasSecret() ? "1" : "0");
|
|
|
|
|
anc->clicked().connect(std::bind(&K7Main::OnKeyAnchorClicked, this, anc));
|
|
|
|
|
keyNode->setColumnWidget(1, unique_ptr<WAnchor> (anc));
|
2020-10-25 17:28:47 +01:00
|
|
|
WText * lblOwnerTrust = new WText(OwnerTrustLevel[k.ownerTrust()]);
|
|
|
|
|
if (m_config->CanEditOwnerTrust()) {
|
2020-11-04 21:00:03 +01:00
|
|
|
/*
|
|
|
|
|
* Here we allow the owner trust level of primary keys to be changed anytime.
|
|
|
|
|
* Kleopatra doesn't do that for primary keys having ultimate trust level.
|
|
|
|
|
*/
|
2020-11-06 21:42:50 +01:00
|
|
|
lblOwnerTrust->doubleClicked().connect(std::bind(&KeyEdit::OnOwnerTrustDoubleClicked, m_keyEdit, keyNode, k.hasSecret()));
|
2020-11-04 21:00:03 +01:00
|
|
|
lblOwnerTrust->setToolTip(TR("TTTDoubleCLick"));
|
2020-10-25 17:28:47 +01:00
|
|
|
}
|
|
|
|
|
keyNode->setColumnWidget(2, unique_ptr<WText> (lblOwnerTrust));
|
2020-11-03 22:48:31 +01:00
|
|
|
TreeTableNodeText * ttntFpr = new TreeTableNodeText(k.primaryFingerprint(), keyNode, 3);
|
|
|
|
|
keyNode->setColumnWidget(3, unique_ptr<TreeTableNodeText> (ttntFpr));
|
2019-10-25 20:16:43 +02:00
|
|
|
grpNode->addChildNode(unique_ptr<WTreeTableNode> (keyNode));
|
|
|
|
|
}
|
|
|
|
|
if (expand)
|
|
|
|
|
grpNode->expand();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::OnKeyAnchorClicked(WAnchor * source)
|
|
|
|
|
{
|
|
|
|
|
// Show key details
|
|
|
|
|
const WString hasSecret = source->attributeValue("hasSecret");
|
|
|
|
|
bool secret = true;
|
|
|
|
|
if (hasSecret == WString("0"))
|
|
|
|
|
secret = false;
|
|
|
|
|
const string id = source->id();
|
2020-11-07 12:21:49 +01:00
|
|
|
// With secret = false, key signatures can be retrieved.
|
|
|
|
|
DisplayUids(id);
|
2019-10-25 20:16:43 +02:00
|
|
|
DisplaySubKeys(id, secret);
|
|
|
|
|
if (m_config->CanDelete()) // m_btnDelete is NULL otherwise
|
|
|
|
|
m_btnDelete->setHidden(!CanKeyBeDeleted(id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::DisplayUids(const WString& fullKeyID, bool secret)
|
|
|
|
|
{
|
|
|
|
|
// Show UID details for a key
|
|
|
|
|
Error e;
|
|
|
|
|
GpgMEWorker gpgw;
|
|
|
|
|
m_ttbUids->hide();
|
|
|
|
|
const GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, secret);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
{
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_ttbUids->columnCount() == 1)
|
|
|
|
|
{
|
|
|
|
|
m_ttbUids->addColumn(TR("Email"), 200);
|
|
|
|
|
m_ttbUids->addColumn(TR("Trust"), 100);
|
|
|
|
|
m_ttbUids->addColumn(TR("Comment"), 300);
|
|
|
|
|
}
|
|
|
|
|
WTreeTableNode * rootNode = new WTreeTableNode(fullKeyID);
|
|
|
|
|
rootNode->setChildCountPolicy(ChildCountPolicy::Enabled);
|
|
|
|
|
m_ttbUids->setTreeRoot(unique_ptr<WTreeTableNode> (rootNode), TR("UIDs"));
|
|
|
|
|
rootNode->expand();
|
2020-11-03 11:06:25 +01:00
|
|
|
vector<WString> privateKeys = m_config->PrivateKeyIds();
|
2019-10-25 20:16:43 +02:00
|
|
|
for (uint i = 0; i < k.numUserIDs(); i++)
|
|
|
|
|
{
|
|
|
|
|
UserID uid = k.userID(i);
|
|
|
|
|
WTreeTableNode * uidNode = new WTreeTableNode(uid.name());
|
2020-11-04 21:45:48 +01:00
|
|
|
uidNode->setToolTip(Tools::GetUidStatus(uid));
|
2020-11-03 22:48:31 +01:00
|
|
|
TreeTableNodeText * ttntUidEmail = new TreeTableNodeText(uid.email(), uidNode, 1);
|
|
|
|
|
uidNode->setColumnWidget(1, unique_ptr<TreeTableNodeText> (ttntUidEmail));
|
2020-11-03 11:06:25 +01:00
|
|
|
// Show key certify popup on double click
|
|
|
|
|
WText * lblUidValidity = new WText(UidValidities[uid.validity()]);
|
|
|
|
|
if (m_config->CanEditUidValidity()) {
|
|
|
|
|
lblUidValidity->setToolTip(TR("TTTDoubleCLick"));
|
|
|
|
|
lblUidValidity->doubleClicked().connect(std::bind(&KeyEdit::OnUidValidityClicked, m_keyEdit, uidNode, privateKeys, WString(k.primaryFingerprint())));
|
|
|
|
|
}
|
|
|
|
|
uidNode->setColumnWidget(2, unique_ptr<WText> (lblUidValidity));
|
2020-11-03 22:48:31 +01:00
|
|
|
TreeTableNodeText * ttntUidComment = new TreeTableNodeText(uid.comment(), uidNode, 3);
|
|
|
|
|
uidNode->setColumnWidget(3, unique_ptr<TreeTableNodeText> (ttntUidComment));
|
2019-10-25 20:16:43 +02:00
|
|
|
rootNode->addChildNode(unique_ptr<WTreeTableNode> (uidNode));
|
2020-11-07 12:21:49 +01:00
|
|
|
// Set context KeyListMode to ::Signatures | ::Validate
|
2019-10-25 20:16:43 +02:00
|
|
|
for (uint s = 0; s < uid.numSignatures(); s++)
|
|
|
|
|
{
|
|
|
|
|
UserID::Signature sig = uid.signature(s);
|
|
|
|
|
const WString signer = WString(sig.signerName()) + _SPACE_
|
|
|
|
|
+ WString(sig.signerKeyID());
|
|
|
|
|
WTreeTableNode * sigNode = new WTreeTableNode(signer);
|
2020-11-04 21:45:48 +01:00
|
|
|
sigNode->setToolTip(Tools::GetSigStatus(sig));
|
2020-11-03 22:48:31 +01:00
|
|
|
TreeTableNodeText * ttntEmail = new TreeTableNodeText(sig.signerEmail(), sigNode, 1);
|
|
|
|
|
sigNode->setColumnWidget(1, unique_ptr<TreeTableNodeText> (ttntEmail));
|
2019-10-25 20:16:43 +02:00
|
|
|
WString exp = TR("Expiration") + _SPACE_ + _COLON_ + _SPACE_;
|
|
|
|
|
exp += sig.neverExpires() ? TR("Never") : MakeDateTimeLabel(sig.expirationTime());
|
|
|
|
|
sigNode->setColumnWidget(2, cpp14::make_unique<WText> (exp));
|
2020-11-03 22:48:31 +01:00
|
|
|
TreeTableNodeText * ttntComment = new TreeTableNodeText(sig.signerComment(), sigNode, 3);
|
|
|
|
|
sigNode->setColumnWidget(3, unique_ptr<TreeTableNodeText> (ttntComment));
|
2020-11-07 12:21:49 +01:00
|
|
|
uidNode->addChildNode(unique_ptr<WTreeTableNode> (sigNode));
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_ttbUids->show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::DisplaySubKeys(const WString& fullKeyID, bool secret)
|
|
|
|
|
{
|
|
|
|
|
// Show subkey details for a key
|
|
|
|
|
Error e;
|
|
|
|
|
GpgMEWorker gpgw;
|
|
|
|
|
m_ttbSubKeys->hide();
|
|
|
|
|
const GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, secret);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
{
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_ttbSubKeys->columnCount() == 1)
|
|
|
|
|
{
|
|
|
|
|
m_ttbSubKeys->addColumn(TR("Fpr"), 300);
|
|
|
|
|
m_ttbSubKeys->addColumn(TR("Expiration"), 150);
|
|
|
|
|
m_ttbSubKeys->addColumn(TR("Usage"), 70);
|
|
|
|
|
m_ttbSubKeys->addColumn(TR("Secret"), 50);
|
|
|
|
|
}
|
|
|
|
|
WTreeTableNode * rootNode = new WTreeTableNode(fullKeyID);
|
|
|
|
|
rootNode->setChildCountPolicy(ChildCountPolicy::Enabled);
|
|
|
|
|
m_ttbSubKeys->setTreeRoot(unique_ptr<WTreeTableNode> (rootNode), TR("SubKeys"));
|
|
|
|
|
rootNode->expand();
|
2020-11-07 22:17:44 +01:00
|
|
|
bool canEditExpiry = m_config->CanEditExpiryTime()
|
|
|
|
|
&& Tools::KeyHasSecret(k.primaryFingerprint())
|
|
|
|
|
&& m_keyEdit->IsOurKey(k.primaryFingerprint());
|
2019-10-25 20:16:43 +02:00
|
|
|
for (uint i = 0; i < k.numSubkeys(); i++)
|
|
|
|
|
{
|
|
|
|
|
Subkey sk = k.subkey(i);
|
|
|
|
|
WTreeTableNode * skNode = new WTreeTableNode(sk.keyID());
|
2020-11-04 21:45:48 +01:00
|
|
|
skNode->setToolTip(Tools::GetKeyStatus(k));
|
2020-11-03 22:48:31 +01:00
|
|
|
TreeTableNodeText * ttntFpr = new TreeTableNodeText(sk.fingerprint(), skNode, 1);
|
|
|
|
|
skNode->setColumnWidget(1, unique_ptr<TreeTableNodeText> (ttntFpr));
|
2019-10-25 20:16:43 +02:00
|
|
|
WString exp = sk.neverExpires() ? TR("Never") : MakeDateTimeLabel(sk.expirationTime());
|
2020-11-07 22:17:44 +01:00
|
|
|
WText * lblExpiry = new WText(exp);
|
|
|
|
|
if (canEditExpiry) {
|
|
|
|
|
lblExpiry->setToolTip(TR("TTTDoubleCLick"));
|
|
|
|
|
lblExpiry->doubleClicked().connect(std::bind(&KeyEdit::OnExpiryClicked, m_keyEdit, skNode, WString(k.primaryFingerprint())));
|
|
|
|
|
}
|
|
|
|
|
skNode->setColumnWidget(2, unique_ptr<WText> (lblExpiry));
|
2019-10-25 20:16:43 +02:00
|
|
|
WString usage = sk.canAuthenticate() ? WString("A") : WString::Empty;
|
|
|
|
|
usage += sk.canCertify() ? WString("C") : WString::Empty;
|
|
|
|
|
usage += sk.canEncrypt() ? WString("E") : WString::Empty;
|
|
|
|
|
usage += sk.canSign() ? WString("S") : WString::Empty;
|
|
|
|
|
skNode->setColumnWidget(3, cpp14::make_unique<WText> (usage));
|
|
|
|
|
const WString isSecret = sk.isSecret() ? TR("Yes") : TR("No");
|
|
|
|
|
skNode->setColumnWidget(4, cpp14::make_unique<WText> (isSecret));
|
|
|
|
|
rootNode->addChildNode(unique_ptr<WTreeTableNode> (skNode));
|
|
|
|
|
}
|
|
|
|
|
m_ttbSubKeys->show();
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 18:45:29 +01:00
|
|
|
void K7Main::ShowPopupUpload() {
|
|
|
|
|
if (m_popupUpload == NULL) {
|
|
|
|
|
m_popupUpload = new PopupUpload(m_btnUpload, m_tmwMessage);
|
|
|
|
|
m_popupUpload->Create();
|
|
|
|
|
m_popupUpload->UploadDone().connect(this, &K7Main::OnUploadCompleted);
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
2020-11-13 18:45:29 +01:00
|
|
|
m_popupUpload->show();
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::OnUploadCompleted(const WString& spool) {
|
|
|
|
|
// Buffer the spool file name in the import button
|
|
|
|
|
m_btnImport->setAttributeValue("spool", spool);
|
|
|
|
|
m_btnImport->show();
|
2020-11-13 18:45:29 +01:00
|
|
|
m_popupUpload->hide();
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::DoImportKey() {
|
|
|
|
|
const WString spool = m_btnImport->attributeValue("spool");
|
|
|
|
|
Error e;
|
|
|
|
|
GpgMEWorker gpgw;
|
2020-11-11 21:09:43 +01:00
|
|
|
const WString fpr = gpgw.ImportKey(spool.toUTF8().c_str(), e);
|
2019-10-25 20:16:43 +02:00
|
|
|
m_btnImport->hide();
|
|
|
|
|
m_btnImport->setAttributeValue("spool", "");
|
|
|
|
|
if (e.code() != 0) {
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-11-11 21:09:43 +01:00
|
|
|
if (fpr.empty()) {
|
|
|
|
|
m_tmwMessage->SetText(TR("ImportError") + fpr);
|
2019-10-25 20:16:43 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Show the imported key
|
2020-11-11 21:09:43 +01:00
|
|
|
GpgME::Key k = gpgw.FindKey(fpr.toUTF8().c_str(), e, false); // A public is present anyway
|
2019-10-25 20:16:43 +02:00
|
|
|
if (e.code() != 0) {
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-11-11 21:09:43 +01:00
|
|
|
m_tmwMessage->SetText(TR("ImportSuccess") + fpr + WString(" - ") + WString(k.userID(0).name()));
|
|
|
|
|
m_leSearch->setText(fpr);
|
2020-11-11 21:37:31 +01:00
|
|
|
if (Tools::KeyHasSecret(fpr))
|
|
|
|
|
m_config->UpdateSecretKeyOwnership(fpr, true);
|
2019-10-25 20:16:43 +02:00
|
|
|
Search();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool K7Main::CanKeyBeDeleted(const WString& fullKeyID) {
|
|
|
|
|
// Caller should check m_config->canDelete first. m_btnDelete is null if can't delete.
|
|
|
|
|
Error e;
|
|
|
|
|
GpgMEWorker gpgw;
|
|
|
|
|
GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, true); // Look for a private key
|
|
|
|
|
if (e.code() != 0 && e.code() != 16383) { // 16383 : end of file, when key is not private
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// k can now be secret or public
|
|
|
|
|
if (k.isNull()) {// Is a public key
|
|
|
|
|
k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, false);
|
|
|
|
|
// Prepare actual delete
|
|
|
|
|
m_btnDelete->setAttributeValue("keyid", k.keyID());
|
|
|
|
|
m_btnDelete->setAttributeValue("hasSecret", "0");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* k is now secret
|
|
|
|
|
* Is selected private key one of those that the current user manages ?
|
|
|
|
|
*/
|
|
|
|
|
vector<WString> curUserPrivKeys = m_config->PrivateKeyIds();
|
|
|
|
|
vector<WString>::iterator it;
|
|
|
|
|
for (it = curUserPrivKeys.begin(); it != curUserPrivKeys.end(); it++) {
|
2020-11-03 11:06:25 +01:00
|
|
|
if (Tools::ConfigKeyIdMatchesKey(k, *it)) {
|
2019-10-25 20:16:43 +02:00
|
|
|
m_btnDelete->setAttributeValue("keyid", k.keyID());
|
|
|
|
|
m_btnDelete->setAttributeValue("hasSecret", "1");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 18:45:29 +01:00
|
|
|
void K7Main::ShowPopupDelete() {
|
|
|
|
|
if (m_popupDelete == NULL) {
|
|
|
|
|
m_popupDelete = new PopupDelete(m_btnDelete, m_tmwMessage);
|
|
|
|
|
m_popupDelete->Create();
|
|
|
|
|
m_popupDelete->GetDeleteButton()->clicked().connect(this, &K7Main::DoDeleteKey);
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
2020-11-13 18:45:29 +01:00
|
|
|
m_popupDelete->show();
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::DoDeleteKey() {
|
|
|
|
|
// Deleting keys requires the GPGME C API
|
|
|
|
|
Error c_e, e;
|
|
|
|
|
GpgMECWorker gpgcw;
|
|
|
|
|
GpgMEWorker gpgw;
|
|
|
|
|
const WString fullKeyID = m_btnDelete->attributeValue("keyid");
|
|
|
|
|
const WString hasSecret = m_btnDelete->attributeValue("hasSecret");
|
|
|
|
|
bool secret = true;
|
|
|
|
|
if (hasSecret == WString("0"))
|
|
|
|
|
secret = false;
|
|
|
|
|
// Get the key before deletion, to show its ID on success
|
|
|
|
|
GpgME::Key k = gpgw.FindKey(fullKeyID.toUTF8().c_str(), e, secret);
|
|
|
|
|
if (e.code() != 0) {
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Delete the key using the C API
|
2020-11-11 21:09:43 +01:00
|
|
|
const WString fpr(k.primaryFingerprint());
|
|
|
|
|
bool res = gpgcw.DeleteKey(k.primaryFingerprint(), secret, c_e);
|
2019-10-25 20:16:43 +02:00
|
|
|
if (c_e.code() != 0) {
|
|
|
|
|
m_tmwMessage->SetText(c_e.asString());
|
|
|
|
|
} else {
|
2020-11-11 21:37:31 +01:00
|
|
|
m_tmwMessage->SetText(TR("DeleteSuccess") + fpr + WString(" - ") + WString(k.userID(0).name()));
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
m_btnDelete->hide();
|
2020-11-13 18:45:29 +01:00
|
|
|
m_popupDelete->hide();
|
2020-11-11 21:37:31 +01:00
|
|
|
if (secret)
|
|
|
|
|
m_config->UpdateSecretKeyOwnership(fpr, false);
|
2019-10-25 20:16:43 +02:00
|
|
|
// Show that the key is no longer available
|
2020-11-11 21:09:43 +01:00
|
|
|
m_leSearch->setText(fpr);
|
2019-10-25 20:16:43 +02:00
|
|
|
Search();
|
|
|
|
|
}
|
2020-11-13 18:28:17 +01:00
|
|
|
|
|
|
|
|
void K7Main::ShowPopupCreate()
|
|
|
|
|
{
|
|
|
|
|
if (m_popupCreate == NULL)
|
|
|
|
|
{
|
|
|
|
|
m_popupCreate = new PopupCreate(m_btnCreate, m_tmwMessage);
|
|
|
|
|
m_popupCreate->Create();
|
|
|
|
|
m_popupCreate->GetApplyButton()->clicked().connect(this, &K7Main::DoCreateKey);
|
|
|
|
|
}
|
|
|
|
|
m_popupCreate->show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void K7Main::DoCreateKey()
|
|
|
|
|
{
|
|
|
|
|
if (!m_popupCreate->Validate())
|
|
|
|
|
return;
|
|
|
|
|
Error e;
|
|
|
|
|
GpgME::Key k;
|
|
|
|
|
GpgMEWorker gpgw;
|
|
|
|
|
if (m_popupCreate->UseDefaultEngineAlgorithms())
|
|
|
|
|
{
|
|
|
|
|
e = gpgw.CreateKeyWithEngineDefaultAlgo(k, m_popupCreate->GetName().toUTF8(),
|
|
|
|
|
m_popupCreate->GetEmail().toUTF8(),
|
|
|
|
|
m_popupCreate->GetComment().toUTF8(),
|
|
|
|
|
m_popupCreate->GetPassphrase().toUTF8(),
|
|
|
|
|
m_popupCreate->GetExpiry());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
e = gpgw.CreateKey(k, m_popupCreate->GetName().toUTF8(),
|
|
|
|
|
m_popupCreate->GetEmail().toUTF8(),
|
|
|
|
|
m_popupCreate->GetComment().toUTF8(),
|
|
|
|
|
m_popupCreate->GetArbitraryKeyAlgo().toUTF8().c_str(),
|
|
|
|
|
m_popupCreate->GetPassphrase().toUTF8(),
|
|
|
|
|
m_popupCreate->GetExpiry());
|
|
|
|
|
// GPGME accepts a missing subkey.
|
|
|
|
|
if (e.code() == 0 && !m_popupCreate->GetArbitrarySubkeyAlgo().empty())
|
|
|
|
|
e = gpgw.CreateSubKey(k,
|
|
|
|
|
m_popupCreate->GetArbitrarySubkeyAlgo().toUTF8().c_str(),
|
|
|
|
|
m_popupCreate->GetPassphrase().toUTF8(),
|
|
|
|
|
m_popupCreate->GetExpiry());
|
|
|
|
|
}
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
{
|
|
|
|
|
m_tmwMessage->SetText(e.asString());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const WString fpr(k.primaryFingerprint());
|
|
|
|
|
m_tmwMessage->SetText(TR("CreateSuccess")
|
|
|
|
|
+ fpr + WString(" - ") + WString(k.userID(0).name()));
|
|
|
|
|
// Add the key fingerprint to the list of keys managed by the user.
|
|
|
|
|
m_config->UpdateSecretKeyOwnership(fpr, true);
|
|
|
|
|
m_popupCreate->hide();
|
|
|
|
|
#ifndef DEVTIME
|
|
|
|
|
m_popupCreate->Reset();
|
|
|
|
|
#endif
|
|
|
|
|
m_leSearch->setText(fpr);
|
|
|
|
|
Search();
|
|
|
|
|
}
|
|
|
|
|
}
|