From 7f8af95d3ad9d4aec6aeb506b353c9e69f63ddde Mon Sep 17 00:00:00 2001 From: SET Date: Tue, 3 Nov 2020 11:06:25 +0100 Subject: [PATCH] Certify key. In-place editing with a popup if user is allowed in app config file. User must of course manage at least a private key. User identities of target key may be selectively chosen. Optionally, certification may be exportable and non-revocable. --- AppConfig.cpp | 16 +- AppConfig.h | 6 + GpgMEWorker.cpp | 41 +++++ GpgMEWorker.h | 44 ++++- K7Main.cpp | 22 +-- K7Main.h | 7 - KeyEdit.cpp | 47 ++++++ KeyEdit.h | 17 +- LoopbackPassphraseProvider.cpp | 59 +++++++ LoopbackPassphraseProvider.h | 56 +++++++ PopupCertifyUserId.cpp | 239 +++++++++++++++++++++++++++ PopupCertifyUserId.h | 172 +++++++++++++++++++ Tools.cpp | 35 ++++ Tools.h | 46 ++++++ WTAPPROOT/K7/K7.xml | 11 ++ WTAPPROOT/K7/K7_fr.xml | 11 ++ WTAPPROOT/K7/k7config.json | 1 + global.h | 4 + nbproject/Makefile-ARM-Release.mk | 18 ++ nbproject/Makefile-Debug.mk | 18 ++ nbproject/Makefile-Release.mk | 18 ++ nbproject/configurations.xml | 56 +++++++ nbproject/private/configurations.xml | 2 + 23 files changed, 922 insertions(+), 24 deletions(-) create mode 100644 LoopbackPassphraseProvider.cpp create mode 100644 LoopbackPassphraseProvider.h create mode 100644 PopupCertifyUserId.cpp create mode 100644 PopupCertifyUserId.h create mode 100644 Tools.cpp create mode 100644 Tools.h diff --git a/AppConfig.cpp b/AppConfig.cpp index f94ee47..fd58988 100644 --- a/AppConfig.cpp +++ b/AppConfig.cpp @@ -30,6 +30,7 @@ using namespace std; "canImport" : true, "canDelete" : true, "canEditOwnerTrust" : true, + "canEditUidValidity" : true, "privKeyIds" : [ "fullKeyId1", "fullKeyId2" @@ -127,6 +128,19 @@ bool AppConfig::CanEditOwnerTrust() const return cnObject.get("canEditOwnerTrust"); } +bool AppConfig::CanEditUidValidity() const +{ + if (PrivateKeyIds().size() == 0) + return false; + const WString commonName = GetSubjectDnAttribute(WSslCertificate::DnAttributeName::CommonName); + if (!m_SubjectCNObject.contains(commonName.toUTF8())) + return false; + Json::Object cnObject = m_SubjectCNObject.get(commonName.toUTF8()); + if (!cnObject.contains("canEditUidValidity")) + return false; + return cnObject.get("canEditUidValidity"); +} + vector AppConfig::PrivateKeyIds() const { // List private key identifiers. @@ -156,4 +170,4 @@ const WString AppConfig::GetSubjectDnAttribute(const WSslCertificate::DnAttribut return dnAttr->at(i).value(); } return WString::Empty; -} \ No newline at end of file +} diff --git a/AppConfig.h b/AppConfig.h index ae2ed89..9e24383 100644 --- a/AppConfig.h +++ b/AppConfig.h @@ -48,6 +48,12 @@ public: * @return */ bool CanEditOwnerTrust() const; + /** + * Allows to edit validity of user identity. + * Only users who manage private keys can do that. + * @return + */ + bool CanEditUidValidity() const; /** * List of full private key identifiers. The user may delete these private keys. * Must be full keyid, short keyid or fingerprint. diff --git a/GpgMEWorker.cpp b/GpgMEWorker.cpp index 7140ad5..a2ad2a1 100644 --- a/GpgMEWorker.cpp +++ b/GpgMEWorker.cpp @@ -17,10 +17,12 @@ GpgMEWorker::GpgMEWorker() { m_ctx = Context::createForProtocol(Protocol::OpenPGP); + m_ppp = NULL; } GpgMEWorker::~GpgMEWorker() { + delete m_ppp; delete m_ctx; } @@ -89,3 +91,42 @@ const Error GpgMEWorker::EditOwnerTrust(const char* anyFullId, GpgME::Key::Owner GpgME::Data d; // Internal processing data return m_ctx->edit(k, std::unique_ptr (interactor), d); } + +const Error GpgMEWorker::CertifyKey(const char* fprSigningKey, + const char * fprKeyToSign, + vector& userIDsToSign, int options, + const string& passphrase) +{ + Error e; + Key signingKey = FindKey(fprSigningKey, e, true); + if (e.code() != 0) + return e; + e = m_ctx->addSigningKey(signingKey); // +++ + if (e.code() != 0) + return e; + Key keyToSign = FindKey(fprKeyToSign, e, false); + if (e.code() != 0) + return e; + + // GPG engine will fetch for passphrase in the custom provider. + m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback); + if (m_ppp == NULL) + m_ppp = new LoopbackPassphraseProvider(); + m_ppp->SetPassphrase(passphrase); + m_ctx->setPassphraseProvider(m_ppp); + + SetSignKeyEditInteractor * interactor = new SetSignKeyEditInteractor(); + interactor->setKey(keyToSign); + interactor->setUserIDsToSign(userIDsToSign); + interactor->setSigningOptions(options); + // What's that check level ? + // interactor->setCheckLevel(2); + GpgME::Data d; + e = m_ctx->edit(keyToSign, std::unique_ptr (interactor), d); + m_ctx->clearSigningKeys(); + /* + * On error, always : code = 1024 | asString = User defined error code 1 + * Can't distinguish between bad password or whatever cause. + */ + return e; +} diff --git a/GpgMEWorker.h b/GpgMEWorker.h index 36de407..32a3acc 100644 --- a/GpgMEWorker.h +++ b/GpgMEWorker.h @@ -14,7 +14,9 @@ #include #include #include +#include #include +#include "LoopbackPassphraseProvider.h" using namespace std; using namespace GpgME; @@ -59,9 +61,25 @@ public: * @return */ const Error EditOwnerTrust(const char * anyFullId, GpgME::Key::OwnerTrust trustLevel); + /** + * Certify (sign) selected key. + * @param fprSigningKey + * @param fprKeyToSign + * @param userIDsToSign : index of each user identity in a vector. + * @param options : Exportable (1), Non revocable (2). + * \nTrust(4) is not implemented. + * @param passphrase + * @return + */ + const Error CertifyKey(const char * fprSigningKey, + const char * fprKeyToSign, + vector& userIDsToSign, int options, + const string& passphrase); private: Context * m_ctx; + // GPG will fetch a password here. + LoopbackPassphraseProvider * m_ppp; }; /** @@ -70,14 +88,36 @@ private: class SetOwnerTrustEditInteractor : public GpgSetOwnerTrustEditInteractor { public: + /** * * @param ownerTrust : New trust level */ SetOwnerTrustEditInteractor(GpgME::Key::OwnerTrust ownerTrust) - : GpgSetOwnerTrustEditInteractor(ownerTrust) {} + : GpgSetOwnerTrustEditInteractor(ownerTrust) + { + } - virtual ~SetOwnerTrustEditInteractor() {} + virtual ~SetOwnerTrustEditInteractor() + { + } + +}; + +/** + * Passed to GPG engine to certify (sign) a key. + */ +class SetSignKeyEditInteractor : public GpgSignKeyEditInteractor +{ +public: + + SetSignKeyEditInteractor() : GpgSignKeyEditInteractor() + { + }; + + virtual ~SetSignKeyEditInteractor() + { + }; }; diff --git a/K7Main.cpp b/K7Main.cpp index 0e84955..d8d0cf3 100644 --- a/K7Main.cpp +++ b/K7Main.cpp @@ -19,6 +19,7 @@ #include #include "GpgMEWorker.h" #include "GpgMECWorker.h" +#include "Tools.h" using namespace std; @@ -189,7 +190,7 @@ void K7Main::Search() for (uint j = 0; j < lst.size(); j++) { const GpgME::Key k = lst.at(j); - if (!ConfigKeyIdMatchesKey(k, configPrivKeys.at(i))) + if (!Tools::ConfigKeyIdMatchesKey(k, configPrivKeys.at(i))) { m_tmwMessage->SetText(configPrivKeys.at(i) + TR("BadConfigKeyId")); privkList.clear(); @@ -227,14 +228,6 @@ void K7Main::Search() DisplayKeys(privkList, TR("Secrets"), false); } -bool K7Main::ConfigKeyIdMatchesKey(const GpgME::Key& k, const WString& configKeyId) const -{ - // We want key identifier in config file to be real and complete. - return (configKeyId == WString(k.shortKeyID()) - || configKeyId == WString(k.keyID()) - || configKeyId == WString(k.primaryFingerprint())); -} - WString K7Main::MakeDateTimeLabel(time_t ticks) { std::chrono::minutes offset = WApplication::instance()->environment().timeZoneOffset(); @@ -311,12 +304,19 @@ void K7Main::DisplayUids(const WString& fullKeyID, bool secret) rootNode->setChildCountPolicy(ChildCountPolicy::Enabled); m_ttbUids->setTreeRoot(unique_ptr (rootNode), TR("UIDs")); rootNode->expand(); + vector privateKeys = m_config->PrivateKeyIds(); for (uint i = 0; i < k.numUserIDs(); i++) { UserID uid = k.userID(i); WTreeTableNode * uidNode = new WTreeTableNode(uid.name()); uidNode->setColumnWidget(1, cpp14::make_unique (uid.email())); - uidNode->setColumnWidget(2, cpp14::make_unique (UidValidities[uid.validity()])); + // 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 (lblUidValidity)); uidNode->setColumnWidget(3, cpp14::make_unique (uid.comment())); rootNode->addChildNode(unique_ptr (uidNode)); // uid.numSignatures() is always 0, even for signed keys ! @@ -445,7 +445,7 @@ bool K7Main::CanKeyBeDeleted(const WString& fullKeyID) { vector curUserPrivKeys = m_config->PrivateKeyIds(); vector::iterator it; for (it = curUserPrivKeys.begin(); it != curUserPrivKeys.end(); it++) { - if (ConfigKeyIdMatchesKey(k, *it)) { + if (Tools::ConfigKeyIdMatchesKey(k, *it)) { m_btnDelete->setAttributeValue("keyid", k.keyID()); m_btnDelete->setAttributeValue("hasSecret", "1"); return true; diff --git a/K7Main.h b/K7Main.h index c9e793a..4f5ba66 100644 --- a/K7Main.h +++ b/K7Main.h @@ -66,13 +66,6 @@ private: * \nShows the keys in tree table. */ void Search(); - /** - * We want key identifier in config file to be real and complete. - * @param k - * @param configKeyId key identifier as entered in configuration file - * @return - */ - bool ConfigKeyIdMatchesKey(const GpgME::Key& k, const WString& configKeyId) const; /** * Show keys in tree table */ diff --git a/KeyEdit.cpp b/KeyEdit.cpp index 64a68c9..38651d0 100644 --- a/KeyEdit.cpp +++ b/KeyEdit.cpp @@ -16,12 +16,16 @@ using namespace std; KeyEdit::KeyEdit(K7Main * owner) +:WObject() { m_owner = owner; + m_popupUid = NULL; + m_targetKeyFpr = WString::Empty; } KeyEdit::~KeyEdit() { + delete m_popupUid; } void KeyEdit::OnOwnerTrustDoubleClicked(WTreeTableNode * keyNode) @@ -100,3 +104,46 @@ bool KeyEdit::IsOurKey(const WString& fpr) } return false; } + +void KeyEdit::OnUidValidityClicked(WTreeTableNode* uidNode, vector& privateKeys, const WString& targetKeyFpr) +{ + if (targetKeyFpr != m_targetKeyFpr) { + bool passwordVisibility = true; + if (m_popupUid) + passwordVisibility = m_popupUid->IsPasswordVisible(); + delete m_popupUid; + WText * lblUidValidity = static_cast (uidNode->columnWidget(2)); + m_popupUid = new PopupCertifyUserId(lblUidValidity, m_owner->m_tmwMessage); + m_popupUid->Create(privateKeys, targetKeyFpr); + m_popupUid->ShowPassphrase(passwordVisibility); + m_targetKeyFpr = targetKeyFpr; + m_popupUid->GetCertifyButton()->clicked().connect(this, &KeyEdit::CertifyKey); + } + m_popupUid->show(); +} + +void KeyEdit::CertifyKey() +{ + vector& uidsToSign = m_popupUid->GetUidsToSign(); + if (uidsToSign.size() == 0) { + m_owner->m_tmwMessage->SetText(TR("NoUidSelected")); + return; + } + const WString signingKey = m_popupUid->GetSelectedKey(); + const WString keyToSign = m_popupUid->GetKeyToSign(); + int options = m_popupUid->GetCertifyOptions(); + GpgMEWorker gpgWorker; + GpgME::Error e = gpgWorker.CertifyKey(signingKey.toUTF8().c_str(), + keyToSign.toUTF8().c_str(), + uidsToSign, options, + m_popupUid->GetPassphrase()); + if (e.code() != 0) + { + m_owner->m_tmwMessage->SetText(TR("CertificationFailure")); + m_popupUid->ShowPassphrase(true); + return; + } + m_owner->m_tmwMessage->SetText(TR("CertificationSuccess")); + m_popupUid->ShowPassphrase(false); + m_owner->DisplayUids(keyToSign); +} diff --git a/KeyEdit.h b/KeyEdit.h index 0edcbfa..044347d 100644 --- a/KeyEdit.h +++ b/KeyEdit.h @@ -13,6 +13,7 @@ #include #include "K7Main.h" #include +#include "PopupCertifyUserId.h" using namespace Wt; @@ -20,9 +21,9 @@ class K7Main; /** * Some key editing functionalities are or will be implemented here. For now, - * only owner trust level is editable. + * only owner trust level and key certification are implemented. */ -class KeyEdit +class KeyEdit : public WObject { public: KeyEdit(K7Main * owner); @@ -43,11 +44,21 @@ public: * @return */ bool IsOurKey(const WString& fpr); - + /** + * Shows a popup with parameters for key certification. + * @param uidNode + * @param privateKeys : A list of our private keys. + * @param targetKeyFpr : The key to sign. + */ + void OnUidValidityClicked(WTreeTableNode * uidNode, vector& privateKeys, const WString& targetKeyFpr); + private: K7Main * m_owner; + PopupCertifyUserId * m_popupUid; + WString m_targetKeyFpr; void FillOwnerTrustCombo(WComboBox * cmb); + void CertifyKey(); }; #endif /* KEYEDIT_H */ diff --git a/LoopbackPassphraseProvider.cpp b/LoopbackPassphraseProvider.cpp new file mode 100644 index 0000000..7392e84 --- /dev/null +++ b/LoopbackPassphraseProvider.cpp @@ -0,0 +1,59 @@ +/* + * File: LoopbackPassphraseProvider.cpp + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on November 2, 2020, 2:57 PM + */ + +#include "LoopbackPassphraseProvider.h" +#include +#include + +LoopbackPassphraseProvider::LoopbackPassphraseProvider() +: PassphraseProvider() +{ + m_passphrase = strdup(""); +} + +LoopbackPassphraseProvider::LoopbackPassphraseProvider(const string& passphrase) +: PassphraseProvider() +{ + m_passphrase = strdup(passphrase.c_str()); +} + +LoopbackPassphraseProvider::~LoopbackPassphraseProvider() +{ +} + +/* + * Called by GPG engine. + * Is not called if gpg-agent already has a valid password. + */ +char* LoopbackPassphraseProvider::getPassphrase(const char* useridHint, + const char* description, + bool previousWasBad, + bool& canceled) +{ + /* + cout << useridHint << endl; // + cout << description << endl; // 1 0 + cout << previousWasBad << endl; // Always 0, even with bad password + cout << canceled << endl; // Always 0 + */ + return m_passphrase; +} + +void LoopbackPassphraseProvider::SetPassphrase(const string& passphrase) +{ + /* + * Memory leak here ? + */ + m_passphrase = strdup(passphrase.c_str()); +} + +const string LoopbackPassphraseProvider::GetPassphrase() +{ + return m_passphrase; +} diff --git a/LoopbackPassphraseProvider.h b/LoopbackPassphraseProvider.h new file mode 100644 index 0000000..f64a630 --- /dev/null +++ b/LoopbackPassphraseProvider.h @@ -0,0 +1,56 @@ +/* + * File: LoopbackPassphraseProvider.h + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on November 2, 2020, 2:57 PM + */ + +#ifndef LOOPBACKPASSPHRASEPROVIDER_H +#define LOOPBACKPASSPHRASEPROVIDER_H + +#include +#include + +using namespace std; + +/** + * Replaces default pinentry on a web server. + */ +class LoopbackPassphraseProvider : public GpgME::PassphraseProvider +{ +public: + LoopbackPassphraseProvider(); + LoopbackPassphraseProvider(const string& passphrase); + virtual ~LoopbackPassphraseProvider(); + /** + * GPG engine gets its passphrase here. + * \n The usefulness of the (out?) parameters needs to be better + * understood (by me!). + * @param useridHint : Signing keyid | key name | email + * @param description : Signing keyid | Signing keyid | 1 | 0 + * @param previousWasBad : Always 0, even with bad password + * @param canceled : Always 0 + * @return + */ + virtual char *getPassphrase(const char *useridHint, const char *description, + bool previousWasBad, bool &canceled); + /** + * Application must do that. + * @param passphrase + */ + void SetPassphrase(const string& passphrase); + /** + * Application may need it, though it knows what it stored here. + * @return + */ + const string GetPassphrase(); + +private: + char * m_passphrase; + +}; + +#endif /* LOOPBACKPASSPHRASEPROVIDER_H */ + diff --git a/PopupCertifyUserId.cpp b/PopupCertifyUserId.cpp new file mode 100644 index 0000000..78f0186 --- /dev/null +++ b/PopupCertifyUserId.cpp @@ -0,0 +1,239 @@ +/* + * File: PopupCertifyUserId.cpp + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on October 30, 2020, 7:50 PM + */ + +#include "PopupCertifyUserId.h" +#include "global.h" +#include "Tools.h" +#include +#include +#include + +using namespace std; + +PopupCertifyUserId::PopupCertifyUserId(WWidget * anchorWidget, TransientMessageWidget * txtMessage, + const WLength& width) +: WPopupWidget(cpp14::make_unique()) +{ + m_tmwMessage = txtMessage; + m_cwMain = NULL; + m_cmbPrivKeys = NULL; + m_cbOptionExportable = NULL; + m_cbOptionNonRevocable = NULL; + // m_cbOptionTrust = NULL; + m_lblPassphrase = NULL; + m_lePassphrase = NULL; + m_btnApply = NULL; + m_certifyOptions = 0; + setTransient(true); + setAnchorWidget(anchorWidget); + setWidth(width); +} + +PopupCertifyUserId::~PopupCertifyUserId() +{ +} + +void PopupCertifyUserId::Create(vector& privateKeys, + const WString& fprKeyToSign) +{ + m_fprKeyToSign = fprKeyToSign; + m_cwMain = static_cast (implementation()); + + WVBoxLayout * vblMain = new WVBoxLayout(); + m_cwMain->setLayout(unique_ptr (vblMain)); + + m_cmbPrivKeys = new WComboBox(); + vblMain->addWidget(unique_ptr (m_cmbPrivKeys)); + FillPrivateKeyComboBox(privateKeys); + /* + * Column 0 : user identities in checkboxes + * Column 1 : signing options in checkboxes + */ + m_hblPreferences = new WHBoxLayout(); + vblMain->addLayout(unique_ptr (m_hblPreferences)); + // Column 0 + m_vblEmail = new WVBoxLayout(); + m_hblPreferences->addLayout(unique_ptr (m_vblEmail)); + PresentEmail(); + // Column 1 + WVBoxLayout * vblOptions = new WVBoxLayout(); + m_hblPreferences->addLayout(unique_ptr (vblOptions)); + WText * lblOptions = new WText(TR("Options")); + vblOptions->addWidget(unique_ptr (lblOptions)); + m_cbOptionExportable = new WCheckBox(TR("ExportableCertification")); + vblOptions->addWidget(unique_ptr (m_cbOptionExportable)); + m_cbOptionNonRevocable = new WCheckBox(TR("NonRevocableCertification")); + vblOptions->addWidget(unique_ptr (m_cbOptionNonRevocable)); + /*m_cbOptionTrust = new WCheckBox(TR("TrustCertification")); + vblOptions->addWidget(unique_ptr (m_cbOptionTrust));*/ + + WHBoxLayout * hblPassphrase = new WHBoxLayout(); + m_lblPassphrase = new WText(TR("Passphrase")); + hblPassphrase->addWidget(unique_ptr (m_lblPassphrase), 0); + m_lePassphrase = new WLineEdit(); + m_lePassphrase->setEchoMode(EchoMode::Password); + hblPassphrase->addWidget(unique_ptr (m_lePassphrase), 1); + vblMain->addLayout(unique_ptr (hblPassphrase)); + + m_btnApply = new WPushButton(TR("Apply")); + vblMain->addWidget(unique_ptr (m_btnApply)); + + m_cmbPrivKeys->changed().connect(std::bind(&PopupCertifyUserId::ShowPassphrase, this, true)); + m_cbOptionExportable->checked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionChecked, this, 1)); + m_cbOptionNonRevocable->checked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionChecked, this, 2)); + // m_cbOptionTrust->checked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionChecked, this, 4)); + m_cbOptionExportable->unChecked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionUnChecked, this, 1)); + m_cbOptionNonRevocable->unChecked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionUnChecked, this, 2)); + // m_cbOptionTrust->unChecked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionUnChecked, this, 4)); + +} + +void PopupCertifyUserId::FillPrivateKeyComboBox(vector& privateKeys) +{ + if (m_cmbPrivKeys == NULL) + return; + vector::iterator it; + GpgMEWorker gpgw; + GpgME::Error e; + shared_ptr iModel = + make_shared (0, 2); + for (it = privateKeys.begin(); it != privateKeys.end(); it++) + { + vector lst = gpgw.FindKeys((*it).toUTF8().c_str(), true, e); + if (e.code() != 0) + { + m_tmwMessage->SetText(e.asString()); + return; + } + // We are expecting one single key from a full fpr + const GpgME::Key k = lst.at(0); + if (!Tools::ConfigKeyIdMatchesKey(k, (*it))) + { + m_tmwMessage->SetText((*it) + TR("BadConfigKeyId")); + return; + } + /* Limit to first email. name should be the same + * for all UIDs of the key. + */ + const WString displayed = WString(k.userID(0).name()) + + _SPACE_ + + _ANGLE_BRACKET_OPEN_ + k.userID(0).email() + + _ANGLE_BRACKET_CLOSE_ + + _SPACE_ + + _BRACKET_OPEN_ + k.shortKeyID() + _BRACKET_CLOSE_; + iModel->appendRow(cpp14::make_unique (*it)); + iModel->setItem(iModel->rowCount() - 1, 1, cpp14::make_unique (displayed)); + } + m_cmbPrivKeys->setModel(iModel); + m_cmbPrivKeys->setModelColumn(1); +} + +void PopupCertifyUserId::PresentEmail() +{ + WText * lblEmail = new WText(TR("Email")); + m_vblEmail->addWidget(unique_ptr (lblEmail)); + + GpgMEWorker gpgw; + GpgME::Error e; + vector lst = gpgw.FindKeys(m_fprKeyToSign.toUTF8().c_str(), false, e); + if (e.code() != 0) + { + m_tmwMessage->SetText(e.asString()); + return; + } + if (lst.size() != 1) + { + m_tmwMessage->SetText(m_fprKeyToSign + TR("BadKeyCount")); + return; + } + const GpgME::Key k = lst.at(0); + + vector uids = k.userIDs(); + vector::iterator it; + uint id = 0; + for (it = uids.begin(); it != uids.end(); it++) + { + WCheckBox * cbEmail = new WCheckBox(it->email()); + m_vblEmail->addWidget(unique_ptr (cbEmail)); + cbEmail->setId(std::to_string(id)); + cbEmail->checked().connect(std::bind(&PopupCertifyUserId::OnEmailChecked, this, cbEmail)); + cbEmail->unChecked().connect(std::bind(&PopupCertifyUserId::OnEmailUnChecked, this, cbEmail)); + id++; + } +} + +WString PopupCertifyUserId::GetSelectedKey() const +{ + if (!m_cmbPrivKeys->count()) + return WString::Empty; + shared_ptr aiModel = m_cmbPrivKeys->model(); + WStandardItemModel * iModel = static_cast (aiModel.get()); + if (!iModel) return WString::Empty; + WStandardItem * item = iModel->item(m_cmbPrivKeys->currentIndex(), 0); + if (!item) return WString::Empty; + return item->text(); +} + +void PopupCertifyUserId::ShowPassphrase(bool show) +{ + if (show) + { + m_lblPassphrase->show(); + m_lePassphrase->show(); + /* + * We cannot know the reason of a certify failure. + * Empty the passphrase widget in any case. + */ + m_lePassphrase->setText(WString::Empty); + } + else + { + m_lblPassphrase->hide(); + m_lePassphrase->hide(); + /* + * The passphrase widget is not cleared. + * gpg-agent will not fetch it from the loopback passphrase provider + * as long as its caching timeout is not reached. + * To better mimic the behavior of the default pinentry mechanism, + * we should probably clear m_lePassphrase AND the passphrase stored + * in the loopback passphrase provider. + */ + } +} + +void PopupCertifyUserId::OnEmailChecked(WCheckBox* cb) +{ + int id = Tools::ToInt(cb->id()); + m_uidsToSign.push_back(id); +} + +void PopupCertifyUserId::OnEmailUnChecked(WCheckBox* cb) +{ + // Any tip to locate a known value in a vector without iterating over? + const uint id = Tools::ToInt(cb->id()); + vector::iterator it; + for (it = m_uidsToSign.begin(); it != m_uidsToSign.end(); it++) + { + if ((*it) == id) + { + m_uidsToSign.erase(it); + return; + } + } +} + +void PopupCertifyUserId::OnCertifyOptionChecked(int id) +{ + m_certifyOptions += id; +} + +void PopupCertifyUserId::OnCertifyOptionUnChecked(int id) +{ + m_certifyOptions -= id; +} diff --git a/PopupCertifyUserId.h b/PopupCertifyUserId.h new file mode 100644 index 0000000..7e2ac50 --- /dev/null +++ b/PopupCertifyUserId.h @@ -0,0 +1,172 @@ +/* + * File: PopupCertifyUserId.h + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on October 30, 2020, 7:50 PM + */ + +#ifndef POPUPCERTIFYUSERID_H +#define POPUPCERTIFYUSERID_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TransientMessageWidget.h" + +using namespace Wt; + +/** + * A popup with required parameters to certify a key : + *
    + *
  • Signer's private keys
  • + *
  • Target key user identities (email)
  • + *
  • Signing options : Exportable and non-revocable
  • + *
  • Passphrase for selected private key
  • + *
+ * The passphrase is cached by gpg-agent for default 10 mins. + * \n The popup hides the passphrase widget until a certify op fails. + * \n UserID::Validity:: lists many validity levels. How to selectively apply + * an arbitrary level ? Before signing, it's 'Unknown'. After signing, it's + * always 'Full'. + */ +class PopupCertifyUserId : public WPopupWidget +{ +public: + PopupCertifyUserId(WWidget * anchorWidget, TransientMessageWidget * txtMessage, + const WLength& width = 500); + virtual ~PopupCertifyUserId(); + /** + * Must be called on its own. + * @param privateKeys + * @param fprKeyToSign + */ + void Create(vector& privateKeys, const WString& fprKeyToSign); + /** + * + * @return : Fingerprint of selected signing key + */ + WString GetSelectedKey() const; + /** + * Controls visibility of passphrase widgets. + * \n Need not be always visible as the passphrase is cached by gpg-agent. + * \n During that caching period, a wrong input passphrase will not be looked + * for by GPG engine, and may be confusing. + * @param show + */ + void ShowPassphrase(bool show = true); + /** + * Used to restore the state of the widgets when target key changes. + * @return + */ + bool IsPasswordVisible() const + { + return m_lePassphrase->isVisible(); + } + /** + * Used to forward the passphrase to the loopback passphrase provider. + * @return + */ + const string GetPassphrase() + { + return m_lePassphrase->text().toUTF8(); + } + /** + * Obviously. + * @return + */ + const WString GetKeyToSign() + { + return m_fprKeyToSign; + } + /** + * We can select what identity (email) to certify. GPG expects it as a + * vector of indices. + * @return + */ + vector& GetUidsToSign() + { + return m_uidsToSign; + } + /** + * Sum of option values + *
    + *
  • Exportable : 1
  • + *
  • Non revocable : 2
  • + *
+ * Trust(4) is not implemented as it always fails. + * @return + */ + int GetCertifyOptions() + { + return m_certifyOptions; + } + /** + * Caller binds its certify function here. + * @return + */ + WPushButton* GetCertifyButton() + { + return m_btnApply; + } +private: + TransientMessageWidget * m_tmwMessage; + WContainerWidget * m_cwMain; + WString m_fprKeyToSign; + + WComboBox * m_cmbPrivKeys; + WHBoxLayout * m_hblPreferences; + WVBoxLayout * m_vblEmail; + vector m_lstUids; + WCheckBox * m_cbOptionExportable; + WCheckBox * m_cbOptionNonRevocable; + // WCheckBox * m_cbOptionTrust; // Always fails + WText * m_lblPassphrase; + WLineEdit * m_lePassphrase; + WPushButton * m_btnApply; + + vector m_uidsToSign; + int m_certifyOptions; + /** + * Available private fingerprints in a combobox. The selected item is the + * signing key. + * @param privateKeys + */ + void FillPrivateKeyComboBox(vector& privateKeys); + /** + * Show user identities of key to sign. Can be independently selected + * using checkboxes. + */ + void PresentEmail(); + /** + * Add the identity index in a vector. + * @param cb + */ + void OnEmailChecked(WCheckBox * cb); + /** + * Removes the identity index in a vector. + * @param cb + */ + void OnEmailUnChecked(WCheckBox * cb); + /** + * Adds the option value in m_certifyOptions. + * @param id + */ + void OnCertifyOptionChecked(int id); + /** + * Subtract the option value from m_certifyOptions. + * @param id + */ + void OnCertifyOptionUnChecked(int id); +}; + +#endif /* POPUPCERTIFYUSERID_H */ + diff --git a/Tools.cpp b/Tools.cpp new file mode 100644 index 0000000..7f82882 --- /dev/null +++ b/Tools.cpp @@ -0,0 +1,35 @@ +/* + * File: Tools.cpp + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on November 1, 2020, 8:31 PM + */ + +#include "Tools.h" +#include +#include + +using namespace std; + +Tools::Tools() { +} + +Tools::~Tools() { +} + +bool Tools::ConfigKeyIdMatchesKey(const GpgME::Key& k, const WString& configKeyId) +{ + // We want key identifier in config file to be real and complete. + return (configKeyId == WString(k.shortKeyID()) + || configKeyId == WString(k.keyID()) + || configKeyId == WString(k.primaryFingerprint())); +} + +int Tools::ToInt(const string& s) { + istringstream buffer(s.c_str()); + int num; + buffer >> num; + return num; +} diff --git a/Tools.h b/Tools.h new file mode 100644 index 0000000..ab01b79 --- /dev/null +++ b/Tools.h @@ -0,0 +1,46 @@ +/* + * File: Tools.h + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on November 1, 2020, 8:31 PM + */ + +#ifndef TOOLS_H +#define TOOLS_H + +#include +#include "GpgMEWorker.h" + +using namespace Wt; + +/** + * A few general usage functions. + */ +class Tools { +public: + Tools(); + virtual ~Tools(); + + /** + * We want key identifier in config file to be real and complete. + * @param k + * @param configKeyId key identifier as entered in configuration file + * @return + */ + static bool ConfigKeyIdMatchesKey(const GpgME::Key& k, + const WString& configKeyId); + /** + * Integer value of a string. + * @param s + * @return + */ + static int ToInt(const string& s); + +private: + +}; + +#endif /* TOOLS_H */ + diff --git a/WTAPPROOT/K7/K7.xml b/WTAPPROOT/K7/K7.xml index 20ba155..5413515 100644 --- a/WTAPPROOT/K7/K7.xml +++ b/WTAPPROOT/K7/K7.xml @@ -68,4 +68,15 @@ This is your key Owner trust level succesfully changed Owner trust level failed to be changed + + Too many keys; a single key is expected + Options + Certify for everyone to see + Non revocable + Trust + Passphrase + No email address is selected + Apply + Key succesfully certified + Key certification failed \ No newline at end of file diff --git a/WTAPPROOT/K7/K7_fr.xml b/WTAPPROOT/K7/K7_fr.xml index 0a351f6..5fbc74c 100644 --- a/WTAPPROOT/K7/K7_fr.xml +++ b/WTAPPROOT/K7/K7_fr.xml @@ -68,4 +68,15 @@ C'est votre clé Confiance dans la certification changée avec succès Échec de changement de la confiance dans la certification + + Trop de clés; une seule attendue + Options + Certifier pour une visibilité par tous + Non révocable + Confiance totale + Phrase de passe + Aucun courriel n'est sélectionné + Appliquer + Succès de certification de la clé + Échec de certification de la clé diff --git a/WTAPPROOT/K7/k7config.json b/WTAPPROOT/K7/k7config.json index 364b840..601d446 100644 --- a/WTAPPROOT/K7/k7config.json +++ b/WTAPPROOT/K7/k7config.json @@ -4,6 +4,7 @@ "canImport" : true, "canDelete" : true, "canEditOwnerTrust" : true, + "canEditUidValidity" : true, "privKeyIds" : [ "FullKeyId1", "FullKeyId2" diff --git a/global.h b/global.h index 2d51e50..321c187 100644 --- a/global.h +++ b/global.h @@ -20,6 +20,10 @@ const WString _APPNAME_("K7"); const WString _APPVERSION_("1"); const WString _SPACE_(" "); const WString _COLON_(":"); +const WString _BRACKET_OPEN_("("); +const WString _BRACKET_CLOSE_(")"); +const WString _ANGLE_BRACKET_OPEN_("<"); +const WString _ANGLE_BRACKET_CLOSE_(">"); typedef map UidValidityMap; typedef map OwnerTrustMap; diff --git a/nbproject/Makefile-ARM-Release.mk b/nbproject/Makefile-ARM-Release.mk index fe8eda0..621fc36 100644 --- a/nbproject/Makefile-ARM-Release.mk +++ b/nbproject/Makefile-ARM-Release.mk @@ -40,8 +40,11 @@ OBJECTFILES= \ ${OBJECTDIR}/GpgMEWorker.o \ ${OBJECTDIR}/K7Main.o \ ${OBJECTDIR}/KeyEdit.o \ + ${OBJECTDIR}/LoopbackPassphraseProvider.o \ + ${OBJECTDIR}/PopupCertifyUserId.o \ ${OBJECTDIR}/PopupDeleter.o \ ${OBJECTDIR}/PopupUploader.o \ + ${OBJECTDIR}/Tools.o \ ${OBJECTDIR}/TransientMessageWidget.o \ ${OBJECTDIR}/main.o @@ -95,6 +98,16 @@ ${OBJECTDIR}/KeyEdit.o: KeyEdit.cpp ${RM} "$@.d" $(COMPILE.cc) -O2 -s -DLARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/KeyEdit.o KeyEdit.cpp +${OBJECTDIR}/LoopbackPassphraseProvider.o: LoopbackPassphraseProvider.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -O2 -s -DLARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/LoopbackPassphraseProvider.o LoopbackPassphraseProvider.cpp + +${OBJECTDIR}/PopupCertifyUserId.o: PopupCertifyUserId.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -O2 -s -DLARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupCertifyUserId.o PopupCertifyUserId.cpp + ${OBJECTDIR}/PopupDeleter.o: PopupDeleter.cpp ${MKDIR} -p ${OBJECTDIR} ${RM} "$@.d" @@ -105,6 +118,11 @@ ${OBJECTDIR}/PopupUploader.o: PopupUploader.cpp ${RM} "$@.d" $(COMPILE.cc) -O2 -s -DLARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupUploader.o PopupUploader.cpp +${OBJECTDIR}/Tools.o: Tools.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -O2 -s -DLARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/Tools.o Tools.cpp + ${OBJECTDIR}/TransientMessageWidget.o: TransientMessageWidget.cpp ${MKDIR} -p ${OBJECTDIR} ${RM} "$@.d" diff --git a/nbproject/Makefile-Debug.mk b/nbproject/Makefile-Debug.mk index ffec279..d7b0b80 100644 --- a/nbproject/Makefile-Debug.mk +++ b/nbproject/Makefile-Debug.mk @@ -40,8 +40,11 @@ OBJECTFILES= \ ${OBJECTDIR}/GpgMEWorker.o \ ${OBJECTDIR}/K7Main.o \ ${OBJECTDIR}/KeyEdit.o \ + ${OBJECTDIR}/LoopbackPassphraseProvider.o \ + ${OBJECTDIR}/PopupCertifyUserId.o \ ${OBJECTDIR}/PopupDeleter.o \ ${OBJECTDIR}/PopupUploader.o \ + ${OBJECTDIR}/Tools.o \ ${OBJECTDIR}/TransientMessageWidget.o \ ${OBJECTDIR}/main.o @@ -95,6 +98,16 @@ ${OBJECTDIR}/KeyEdit.o: KeyEdit.cpp ${RM} "$@.d" $(COMPILE.cc) -g -DDEVTIME -I/usr/local/Wt-Debug/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/KeyEdit.o KeyEdit.cpp +${OBJECTDIR}/LoopbackPassphraseProvider.o: LoopbackPassphraseProvider.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -g -DDEVTIME -I/usr/local/Wt-Debug/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/LoopbackPassphraseProvider.o LoopbackPassphraseProvider.cpp + +${OBJECTDIR}/PopupCertifyUserId.o: PopupCertifyUserId.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -g -DDEVTIME -I/usr/local/Wt-Debug/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupCertifyUserId.o PopupCertifyUserId.cpp + ${OBJECTDIR}/PopupDeleter.o: PopupDeleter.cpp ${MKDIR} -p ${OBJECTDIR} ${RM} "$@.d" @@ -105,6 +118,11 @@ ${OBJECTDIR}/PopupUploader.o: PopupUploader.cpp ${RM} "$@.d" $(COMPILE.cc) -g -DDEVTIME -I/usr/local/Wt-Debug/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupUploader.o PopupUploader.cpp +${OBJECTDIR}/Tools.o: Tools.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -g -DDEVTIME -I/usr/local/Wt-Debug/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/Tools.o Tools.cpp + ${OBJECTDIR}/TransientMessageWidget.o: TransientMessageWidget.cpp ${MKDIR} -p ${OBJECTDIR} ${RM} "$@.d" diff --git a/nbproject/Makefile-Release.mk b/nbproject/Makefile-Release.mk index 86df5fc..8996b57 100644 --- a/nbproject/Makefile-Release.mk +++ b/nbproject/Makefile-Release.mk @@ -40,8 +40,11 @@ OBJECTFILES= \ ${OBJECTDIR}/GpgMEWorker.o \ ${OBJECTDIR}/K7Main.o \ ${OBJECTDIR}/KeyEdit.o \ + ${OBJECTDIR}/LoopbackPassphraseProvider.o \ + ${OBJECTDIR}/PopupCertifyUserId.o \ ${OBJECTDIR}/PopupDeleter.o \ ${OBJECTDIR}/PopupUploader.o \ + ${OBJECTDIR}/Tools.o \ ${OBJECTDIR}/TransientMessageWidget.o \ ${OBJECTDIR}/main.o @@ -95,6 +98,16 @@ ${OBJECTDIR}/KeyEdit.o: KeyEdit.cpp ${RM} "$@.d" $(COMPILE.cc) -O2 -s -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/KeyEdit.o KeyEdit.cpp +${OBJECTDIR}/LoopbackPassphraseProvider.o: LoopbackPassphraseProvider.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -O2 -s -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/LoopbackPassphraseProvider.o LoopbackPassphraseProvider.cpp + +${OBJECTDIR}/PopupCertifyUserId.o: PopupCertifyUserId.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -O2 -s -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupCertifyUserId.o PopupCertifyUserId.cpp + ${OBJECTDIR}/PopupDeleter.o: PopupDeleter.cpp ${MKDIR} -p ${OBJECTDIR} ${RM} "$@.d" @@ -105,6 +118,11 @@ ${OBJECTDIR}/PopupUploader.o: PopupUploader.cpp ${RM} "$@.d" $(COMPILE.cc) -O2 -s -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupUploader.o PopupUploader.cpp +${OBJECTDIR}/Tools.o: Tools.cpp + ${MKDIR} -p ${OBJECTDIR} + ${RM} "$@.d" + $(COMPILE.cc) -O2 -s -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/Tools.o Tools.cpp + ${OBJECTDIR}/TransientMessageWidget.o: TransientMessageWidget.cpp ${MKDIR} -p ${OBJECTDIR} ${RM} "$@.d" diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 2ce1f9c..dabe06a 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -9,16 +9,21 @@ GpgMEWorker.h K7Main.h KeyEdit.h + LoopbackPassphraseProvider.h + PopupCertifyUserId.h PopupDeleter.h PopupUploader.h + Tools.h TransientMessageWidget.h global.h + WTAPPROOT/K7/K7.css WTAPPROOT/K7/K7.xml WTAPPROOT/K7/K7_fr.xml + WTAPPROOT/K7/k7config.json GpgMEWorker.cpp K7Main.cpp KeyEdit.cpp + LoopbackPassphraseProvider.cpp + PopupCertifyUserId.cpp PopupDeleter.cpp PopupUploader.cpp + Tools.cpp TransientMessageWidget.cpp main.cpp @@ -96,6 +104,14 @@ + + + + + + + + @@ -104,14 +120,22 @@ + + + + + + + + @@ -176,6 +200,14 @@ + + + + + + + + @@ -184,14 +216,22 @@ + + + + + + + + @@ -260,6 +300,14 @@ + + + + + + + + @@ -268,14 +316,22 @@ + + + + + + + + diff --git a/nbproject/private/configurations.xml b/nbproject/private/configurations.xml index c6b9916..3db8f9e 100644 --- a/nbproject/private/configurations.xml +++ b/nbproject/private/configurations.xml @@ -13,6 +13,8 @@ + +