/* * File: K7Main.cpp * Author: SET - nmset@yandex.com * License : GPL v2 * Copyright SET - © 2019 * * Created on 7 octobre 2019, 21:29 */ #include "K7Main.h" #include "global.h" #include #include #include #include #include #include #include #include #include "GpgMEWorker.h" #include "GpgMECWorker.h" #include "Tools.h" #include "SensitiveTreeTableNodeText.h" #include "GpgMELogger.h" using namespace std; K7Main::K7Main(const WEnvironment& env) : WApplication(env) { m_config = NULL; m_btnUpload = NULL; m_btnImport = NULL; m_btnDelete = NULL; m_btnCreate = NULL; m_btnExport = NULL; 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(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(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"); 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"); m_keyEdit = new KeyEdit(this); WLink link; const WString cssFile = WApplication::appRoot() + _APPNAME_ + WString(".css"); link.setUrl(cssFile.toUTF8()); WApplication::useStyleSheet(link); } K7Main::~K7Main() { delete m_config; delete m_keyEdit; delete m_keyringIO; } void K7Main::Create() { WContainerWidget * cwHeader = new WContainerWidget(); WHBoxLayout * hblHeader = new WHBoxLayout(); cwHeader->setLayout(unique_ptr (hblHeader)); WText * lblTitle = hblHeader->addWidget(cpp14::make_unique(_APPNAME_)); lblTitle->setStyleClass("title"); // Error messages will go here m_tmwMessage = new TransientMessageWidget(); m_tmwMessage->setTextAlignment(AlignmentFlag::Right); hblHeader->addWidget(unique_ptr(m_tmwMessage)); root()->addWidget(unique_ptr (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; m_cwMain = new WContainerWidget(); WGridLayout * grlMain = new WGridLayout(); grlMain->setColumnStretch(0, 1); grlMain->setColumnStretch(1, 0); m_cwMain->setLayout(unique_ptr (grlMain)); // Add a search zone : line edit and button m_leSearch = new WLineEdit(); grlMain->addWidget(unique_ptr (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 (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 (m_ttbKeys), 1, 0); WContainerWidget * cwButtons = new WContainerWidget(); WVBoxLayout * vblButtons = new WVBoxLayout(); cwButtons->setLayout(unique_ptr (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 (m_btnUpload)); m_btnImport = new WPushButton(TR("Import")); m_btnImport->setToolTip(TR("TTTImport")); vblButtons->addWidget(unique_ptr (m_btnImport)); 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 (m_btnDelete)); m_btnDelete->hide(); } vblButtons->addSpacing(150); vblButtons->addStretch(1); // Everyone can export a key m_btnExport = new WPushButton(TR("Export")); m_btnExport->hide(); vblButtons->addWidget(unique_ptr (m_btnExport)); if (m_config->CanCreateKeys()) { m_btnCreate = new WPushButton(TR("Create")); m_btnCreate->setToolTip(TR("TTTCreate")); vblButtons->addWidget(unique_ptr (m_btnCreate)); } grlMain->addWidget(unique_ptr (cwButtons), 1, 1); // Add and hide detail tables m_ttbUids = new WTreeTable(); grlMain->addWidget(unique_ptr (m_ttbUids), 2, 0); m_ttbUids->hide(); m_ttbSubKeys = new WTreeTable(); grlMain->addWidget(unique_ptr (m_ttbSubKeys), 3, 0); m_ttbSubKeys->hide(); root()->addWidget(cpp14::make_unique()); root()->addWidget(unique_ptr (m_cwMain)); m_keyringIO = new KeyringIO(this); #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 if (m_btnDelete) m_btnDelete->hide(); m_ttbSubKeys->hide(); m_ttbUids->hide(); Error e; vector configPrivKeys = m_config->PrivateKeyIds(); GpgMEWorker gpgw; vector 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 lst = gpgw.FindKeys(configPrivKeys.at(i).toUTF8().c_str(), true, e); if (e.code() != 0) { privkList.clear(); m_tmwMessage->SetText(e.asString()); LGE(e); 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); if (!Tools::ConfigKeyIdMatchesKey(k, configPrivKeys.at(i))) { 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()); LGE(e); return; } } if (m_ttbKeys->columnCount() == 1) { m_ttbKeys->addColumn(TR("ID"), 120); m_ttbKeys->addColumn(TR("OwnerTrust"), 210); 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 (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); m_btnExport->hide(); } 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& kList, const WString& grpLabel, bool expand) { WTreeTableNode * grpNode = new WTreeTableNode(grpLabel); m_ttbKeys->treeRoot()->addChildNode(unique_ptr (grpNode)); vector ourKeys = m_config->PrivateKeyIds(); for (uint i = 0; i < kList.size(); i++) { const GpgME::Key k = kList.at(i); WTreeTableNode * keyNode = new WTreeTableNode(k.userID(0).name()); keyNode->setToolTip(Tools::GetKeyStatus(k)); WLink ln; ln.setUrl(WString(L"javascript:void(0)").toUTF8()); WAnchor * anc = new WAnchor(ln, k.shortKeyID()); anc->setId(k.primaryFingerprint()); // 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 (anc)); WText * lblOwnerTrust = new WText(OwnerTrustLevel[k.ownerTrust()]); if (m_config->CanEditOwnerTrust()) { /* * 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. */ bool isOurKey = Tools::IsOurKey(k.primaryFingerprint(), ourKeys); if (!isOurKey || (isOurKey && k.hasSecret())) { lblOwnerTrust->doubleClicked().connect(std::bind(&KeyEdit::OnOwnerTrustDoubleClicked, m_keyEdit, keyNode, k.hasSecret())); lblOwnerTrust->setToolTip(TR("TTTDoubleCLick")); } } keyNode->setColumnWidget(2, unique_ptr (lblOwnerTrust)); TreeTableNodeText * ttntFpr = new TreeTableNodeText(k.primaryFingerprint(), keyNode, 3); if (k.isBad()) ttntFpr->addStyleClass("red", true); if (!k.hasSecret() && Tools::KeyHasSecret(k.primaryFingerprint())) ttntFpr->addStyleClass("bold", true); keyNode->setColumnWidget(3, unique_ptr (ttntFpr)); grpNode->addChildNode(unique_ptr (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(); // With secret = false, key signatures can be retrieved. DisplayUids(id); DisplaySubKeys(id, secret); if (m_config->CanDelete()) // m_btnDelete is NULL otherwise m_btnDelete->setHidden(!m_keyringIO->CanKeyBeDeleted(id)); m_btnExport->show(); m_keyringIO->PrepareExport(id, secret); } 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()); LGE(e); return; } if (m_ttbUids->columnCount() == 1) { m_ttbUids->addColumn(TR("Email"), 200); m_ttbUids->addColumn(TR("Trust"), 110); m_ttbUids->addColumn(TR("Comment"), 300); } WTreeTableNode * rootNode = new WTreeTableNode(fullKeyID); rootNode->setChildCountPolicy(ChildCountPolicy::Enabled); m_ttbUids->setTreeRoot(unique_ptr (rootNode), TR("UIDs")); rootNode->expand(); vector ourKeys = m_config->PrivateKeyIds(); bool canAddRevokeUid = m_config->CanAddRevokeUid() && Tools::KeyHasSecret(k.primaryFingerprint()) && Tools::IsOurKey(k.primaryFingerprint(), ourKeys); for (uint i = 0; i < k.numUserIDs(); i++) { UserID uid = k.userID(i); WTreeTableNode * uidNode = new WTreeTableNode(uid.name()); uidNode->setToolTip(Tools::GetUidStatus(uid)); WText * lblUidEmail = new WText(uid.email()); if (canAddRevokeUid) { lblUidEmail->setToolTip(TR("TTTDoubleCLick")); lblUidEmail->doubleClicked().connect(std::bind(&KeyEdit::OnUidEmailClicked, m_keyEdit, uidNode, WString(k.primaryFingerprint()))); } if (uid.isBad()) lblUidEmail->setStyleClass("red"); uidNode->setColumnWidget(1, unique_ptr (lblUidEmail)); // 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, ourKeys, WString(k.primaryFingerprint()))); } uidNode->setColumnWidget(2, unique_ptr (lblUidValidity)); TreeTableNodeText * ttntUidComment = new TreeTableNodeText(uid.comment(), uidNode, 3); uidNode->setColumnWidget(3, unique_ptr (ttntUidComment)); rootNode->addChildNode(unique_ptr (uidNode)); // Set context KeyListMode to ::Signatures | ::Validate 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); sigNode->setToolTip(Tools::GetSigStatus(sig)); TreeTableNodeText * ttntEmail = new TreeTableNodeText(sig.signerEmail(), sigNode, 1); if (sig.isBad()) ttntEmail->addStyleClass("red", true); sigNode->setColumnWidget(1, unique_ptr (ttntEmail)); WString exp = TR("Expiration") + _SPACE_ + _COLON_ + _SPACE_; exp += sig.neverExpires() ? TR("Never") : MakeDateTimeLabel(sig.expirationTime()); sigNode->setColumnWidget(2, cpp14::make_unique (exp)); TreeTableNodeText * ttntComment = new TreeTableNodeText(sig.signerComment(), sigNode, 3); sigNode->setColumnWidget(3, unique_ptr (ttntComment)); uidNode->addChildNode(unique_ptr (sigNode)); } } 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()); LGE(e); 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); m_ttbSubKeys->addColumn(TR("Algorithms"), 50); } WTreeTableNode * rootNode = new WTreeTableNode(fullKeyID); rootNode->setChildCountPolicy(ChildCountPolicy::Enabled); m_ttbSubKeys->setTreeRoot(unique_ptr (rootNode), TR("SubKeys")); rootNode->expand(); vector ourKeys = m_config->PrivateKeyIds(); bool canEditExpiry = m_config->CanEditExpiryTime() && Tools::KeyHasSecret(k.primaryFingerprint()) && Tools::IsOurKey(k.primaryFingerprint(), ourKeys); for (uint i = 0; i < k.numSubkeys(); i++) { Subkey sk = k.subkey(i); WTreeTableNode * skNode = new WTreeTableNode(sk.keyID()); skNode->setToolTip(Tools::GetKeyStatus(k)); TreeTableNodeText * ttntFpr = new TreeTableNodeText(sk.fingerprint(), skNode, 1); if (sk.isBad()) ttntFpr->setStyleClass("red"); skNode->setColumnWidget(1, unique_ptr (ttntFpr)); WString exp = sk.neverExpires() ? TR("Never") : MakeDateTimeLabel(sk.expirationTime()); WText * lblExpiry = new WText(exp); if (canEditExpiry) { lblExpiry->setToolTip(TR("TTTDoubleCLick")); lblExpiry->doubleClicked().connect(std::bind(&KeyEdit::OnExpiryClicked, m_keyEdit, skNode, WString(k.primaryFingerprint()), WString(sk.fingerprint()))); } skNode->setColumnWidget(2, unique_ptr (lblExpiry)); 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 (usage)); const WString isSecret = sk.isSecret() ? TR("Yes") : TR("No"); skNode->setColumnWidget(4, cpp14::make_unique (isSecret)); const WString algos = sk.publicKeyAlgorithmAsString() + _SPACE_ + _DASH_ + _SPACE_ + sk.algoName(); skNode->setColumnWidget(5, cpp14::make_unique (algos)); rootNode->addChildNode(unique_ptr (skNode)); } m_ttbSubKeys->show(); }