diff --git a/GpgMELogger.h b/GpgMELogger.h new file mode 100644 index 0000000..f915a3e --- /dev/null +++ b/GpgMELogger.h @@ -0,0 +1,57 @@ +/* + * File: GpgMELogger.h + * Author: SET - nmset@yandex.com + * License : GPL v2 + * Copyright SET - © 2019 + * + * Created on November 23, 2020, 4:00 PM + */ + +#ifndef GPGMELOGGER_H +#define GPGMELOGGER_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace Wt; + +static std::mutex gs_logGpgMEError; +#define LGE(e) LogGpgMEError(e) + +static void LogGpgMEError(const GpgME::Error& e) +{ + if (e.code() == 0) + return; + std::unique_lock lock(gs_logGpgMEError); + + const vector * dnAttr + = &(WApplication::instance()->environment().sslInfo() + ->clientCertificate().subjectDn()); + WString subjectCommonName = WString::Empty; + for (uint i = 0; i < dnAttr->size(); i++) + { + if (dnAttr->at(i).name() == WSslCertificate::DnAttributeName::CommonName) + { + subjectCommonName = dnAttr->at(i).value(); + break; + } + } + + + const string logfile = WApplication::appRoot() + string("gpgme.log"); + WLogger logger; + logger.setFile(logfile); + logger.entry("") << WLogger::timestamp + << WLogger::sep << subjectCommonName + << WLogger::sep << WApplication::instance()->environment().userAgent() + << WLogger::sep << std::to_string(e.code()) + << WLogger::sep << e.asString(); +} + +#endif /* GPGMELOGGER_H */ + diff --git a/GpgMEWorker.cpp b/GpgMEWorker.cpp index ee8d7e0..fe3ff0f 100644 --- a/GpgMEWorker.cpp +++ b/GpgMEWorker.cpp @@ -145,9 +145,38 @@ const Error GpgMEWorker::CertifyKey(const char* fprSigningKey, return e; } -const Error GpgMEWorker::SetExpiryTime(const char * keyFpr, - const string& passphrase, - const string& timeString) +const Error GpgMEWorker::RevokeKeyCertifications(const char* fprSigningKey, + const char* fprKeyToSign, + vector& userIDsToRevoke, + 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; + + m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback); + if (m_ppp == NULL) + m_ppp = new LoopbackPassphraseProvider(); + m_ppp->SetPassphrase(passphrase); + m_ctx->setPassphraseProvider(m_ppp); + + e = m_ctx->revokeSignature(keyToSign, signingKey, userIDsToRevoke); + m_ctx->clearSigningKeys(); + + return e; +} + +const Error GpgMEWorker::SetKeyExpiryTime(const char* keyFpr, + const char* subkeyFpr, + const string& passphrase, + ulong expires) { Error e; Key k = FindKey(keyFpr, e, true); @@ -157,17 +186,26 @@ const Error GpgMEWorker::SetExpiryTime(const char * keyFpr, if (e.code() != 0) return e; + vector subkey; + for (uint i = 0; i < k.subkeys().size(); i++) + { + GpgME::Subkey sk = k.subkey(i); + if (string(sk.fingerprint()) == string(subkeyFpr)) + { + subkey.push_back(sk); + break; + } + } + // There should always be at least one subkey (the key itself). + m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback); if (m_ppp == NULL) m_ppp = new LoopbackPassphraseProvider(); m_ppp->SetPassphrase(passphrase); m_ctx->setPassphraseProvider(m_ppp); - SetExpiryTimeEditInteractor * interactor - = new SetExpiryTimeEditInteractor(timeString); - GpgME::Data d; - e = m_ctx->edit(k, std::unique_ptr (interactor), d); - m_ctx->clearSigningKeys(); + // setExpire() allows to expire all subkeys at once. Not implemented here. + e = m_ctx->setExpire(k, expires, subkey); return e; } diff --git a/GpgMEWorker.h b/GpgMEWorker.h index 55f29cc..bb9042f 100644 --- a/GpgMEWorker.h +++ b/GpgMEWorker.h @@ -78,13 +78,32 @@ public: vector& userIDsToSign, int options, const string& passphrase); /** - * Set new expiry time of a secret key. - * @param timeString + * Revoke UserID certifications. + * \n Requires GnuPG >= 2.2.24 + * @param fprSigningKey + * @param fprKeyToSign + * @param userIDsToRevoke : vector of ::UserID + * @param passphrase * @return */ - const Error SetExpiryTime(const char * keyFpr, - const string& passphrase, - const string& timeString = "0"); + const Error RevokeKeyCertifications(const char * fprSigningKey, + const char * fprKeyToSign, + vector& userIDsToRevoke, + const string& passphrase); + /** + * Sets the expiry time of a single (sub)key. Requires GPGME >= 1.15.0. + * \n If no subkey is found (wrong fpr) or not provided, the expiry time of + * key is set instead. + * @param keyFpr + * @param subkeyFpr + * @param passphrase + * @param expires : seconds from now. Use 0 for no expiry. + * @return + */ + const Error SetKeyExpiryTime(const char * keyFpr, + const char * subkeyFpr, + const string& passphrase, + ulong expires = 63072000); /** * Adds a user identity to a key. * \n The email parameter must have a valid email address format here, else @@ -111,8 +130,8 @@ public: * @return */ const Error RevokeUserID(const char * keyFpr, const string& passphrase, - const string& name, const string& email, - const string& comment); + const string& name, const string& email, + const string& comment); /** * Creates a pair of secret and public keys with the default engine * algorithms. Default expiry time is 2 * 365 days. @@ -225,20 +244,6 @@ public: }; -class SetExpiryTimeEditInteractor : public GpgSetExpiryTimeEditInteractor -{ -public: - - SetExpiryTimeEditInteractor(const std::string& timeString = "0") - : GpgSetExpiryTimeEditInteractor(timeString) - { - }; - - virtual ~SetExpiryTimeEditInteractor() - { - }; -}; - class AddUserIDEditInteractor : public GpgAddUserIDEditInteractor { public: diff --git a/K7Main.cpp b/K7Main.cpp index def4cfd..edf150e 100644 --- a/K7Main.cpp +++ b/K7Main.cpp @@ -21,6 +21,7 @@ #include "GpgMECWorker.h" #include "Tools.h" #include "SensitiveTreeTableNodeText.h" +#include "GpgMELogger.h" using namespace std; @@ -83,7 +84,9 @@ K7Main::Create() WContainerWidget * cwHeader = new WContainerWidget(); WHBoxLayout * hblHeader = new WHBoxLayout(); cwHeader->setLayout(unique_ptr (hblHeader)); - hblHeader->addWidget(cpp14::make_unique(_APPNAME_)); + 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); @@ -204,6 +207,7 @@ void K7Main::Search() { privkList.clear(); m_tmwMessage->SetText(e.asString()); + LGE(e); return; } /* @@ -229,6 +233,7 @@ void K7Main::Search() { pubkList.clear(); m_tmwMessage->SetText(e.asString()); + LGE(e); return; } } @@ -293,6 +298,10 @@ void K7Main::DisplayKeys(const vector& kList, const WString& grpLabe } 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)); } @@ -328,6 +337,7 @@ void K7Main::DisplayUids(const WString& fullKeyID, bool secret) if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } if (m_ttbUids->columnCount() == 1) @@ -355,6 +365,8 @@ void K7Main::DisplayUids(const WString& fullKeyID, bool secret) 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()]); @@ -376,6 +388,8 @@ void K7Main::DisplayUids(const WString& fullKeyID, bool secret) 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()); @@ -398,6 +412,7 @@ void K7Main::DisplaySubKeys(const WString& fullKeyID, bool secret) if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } if (m_ttbSubKeys->columnCount() == 1) @@ -422,13 +437,18 @@ void K7Main::DisplaySubKeys(const WString& fullKeyID, bool secret) 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()))); + 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; diff --git a/KeyEdit.cpp b/KeyEdit.cpp index acfe565..2ca9786 100644 --- a/KeyEdit.cpp +++ b/KeyEdit.cpp @@ -13,6 +13,7 @@ #include #include "GpgMEWorker.h" #include "Tools.h" +#include "GpgMELogger.h" using namespace std; @@ -20,7 +21,7 @@ KeyEdit::KeyEdit(K7Main * owner) : WObject() { m_owner = owner; - m_popupUid = NULL; + m_popupCertifyUid = NULL; m_popupExpiryTime = NULL; m_popupAddUid = NULL; m_targetUidValidityKeyFpr = WString::Empty; @@ -29,7 +30,7 @@ KeyEdit::KeyEdit(K7Main * owner) KeyEdit::~KeyEdit() { - delete m_popupUid; + delete m_popupCertifyUid; } void KeyEdit::OnOwnerTrustDoubleClicked(WTreeTableNode * keyNode, bool keyHasSecret) @@ -91,6 +92,7 @@ void KeyEdit::OnOwnerTrustBlurred(WTreeTableNode* keyNode, bool keyHasSecret) { lblOwnerTrust->setText(previousTrustLevel); m_owner->m_tmwMessage->SetText(TR("OwnerTrustFailure")); + LGE(e); return; } m_owner->m_tmwMessage->SetText(TR("OwnerTrustSuccess")); @@ -130,47 +132,65 @@ void KeyEdit::OnUidValidityClicked(WTreeTableNode* uidNode, vector& pri if (targetKeyFpr != m_targetUidValidityKeyFpr) { bool passwordVisibility = true; - if (m_popupUid) - passwordVisibility = m_popupUid->IsPasswordVisible(); - delete m_popupUid; + if (m_popupCertifyUid) + passwordVisibility = m_popupCertifyUid->IsPasswordVisible(); + delete m_popupCertifyUid; 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_popupCertifyUid = new PopupCertifyUserId(lblUidValidity, m_owner->m_tmwMessage); + m_popupCertifyUid->Create(privateKeys, targetKeyFpr); + m_popupCertifyUid->ShowPassphrase(passwordVisibility); m_targetUidValidityKeyFpr = targetKeyFpr; - m_popupUid->GetCertifyButton()->clicked().connect(this, &KeyEdit::CertifyKey); + m_popupCertifyUid->GetCertifyButton()->clicked().connect(this, &KeyEdit::EditUidValidity); } - m_popupUid->show(); + m_popupCertifyUid->show(); } -void KeyEdit::CertifyKey() +void KeyEdit::EditUidValidity() { - vector& uidsToSign = m_popupUid->GetUidsToSign(); - if (uidsToSign.size() == 0) + if (!m_popupCertifyUid->Validate()) { - m_owner->m_tmwMessage->SetText(TR("NoUidSelected")); + m_owner->m_tmwMessage->SetText(TR("InvalidInput")); return; } - const WString signingKey = m_popupUid->GetSelectedKey(); - const WString keyToSign = m_popupUid->GetKeyToSign(); - int options = m_popupUid->GetCertifyOptions(); + const WString signingKey = m_popupCertifyUid->GetSelectedKey(); + const WString keyToSign = m_popupCertifyUid->GetKeyToSign(); GpgMEWorker gpgWorker; - GpgME::Error e = gpgWorker.CertifyKey(signingKey.toUTF8().c_str(), - keyToSign.toUTF8().c_str(), - uidsToSign, options, - m_popupUid->GetPassphrase()); + GpgME::Error e; + if (m_popupCertifyUid->WhatToDo() == PopupCertifyUserId::CertifyUid) + { + vector& uidsToSign = m_popupCertifyUid->GetUidsToSign(); + int options = m_popupCertifyUid->GetCertifyOptions(); + e = gpgWorker.CertifyKey(signingKey.toUTF8().c_str(), + keyToSign.toUTF8().c_str(), + uidsToSign, options, + m_popupCertifyUid->GetPassphrase()); + } + else + { + vector uidsToRevoke + = m_popupCertifyUid->GetUidsToRevokeCertification(); + e = gpgWorker.RevokeKeyCertifications(signingKey.toUTF8().c_str(), + keyToSign.toUTF8().c_str(), + uidsToRevoke, + m_popupCertifyUid->GetPassphrase()); + } if (e.code() != 0) { - m_owner->m_tmwMessage->SetText(TR("CertificationFailure")); - m_popupUid->ShowPassphrase(true); + m_owner->m_tmwMessage->SetText(e.asString()); + m_popupCertifyUid->ShowPassphrase(true); + LGE(e); return; } - m_owner->m_tmwMessage->SetText(TR("CertificationSuccess")); - m_popupUid->ShowPassphrase(false); + if (m_popupCertifyUid->WhatToDo() == PopupCertifyUserId::CertifyUid) + m_owner->m_tmwMessage->SetText(TR("CertificationSuccess")); + else + m_owner->m_tmwMessage->SetText(TR("RevocationSuccess")); + m_popupCertifyUid->ShowPassphrase(false); m_owner->DisplayUids(keyToSign); } -void KeyEdit::OnExpiryClicked(WTreeTableNode* subkeyNode, const WString& keyFpr) +void KeyEdit::OnExpiryClicked(WTreeTableNode* subkeyNode, const WString& keyFpr, + const WString& subkeyFpr) { if (keyFpr != m_expiryEditedKeyFpr) { @@ -179,21 +199,29 @@ void KeyEdit::OnExpiryClicked(WTreeTableNode* subkeyNode, const WString& keyFpr) m_popupExpiryTime = new PopupExpiryTime(lblExpiry, m_owner->m_tmwMessage); m_popupExpiryTime->Create(keyFpr); m_expiryEditedKeyFpr = keyFpr; - m_popupExpiryTime->GetApplyButton()->clicked().connect(this, &KeyEdit::SetExpiryTime); + m_popupExpiryTime->GetApplyButton()->clicked().connect(this, &KeyEdit::SetKeyExpiryTime); } + m_popupExpiryTime->SetSubkeyFpr(subkeyFpr); m_popupExpiryTime->show(); } -void KeyEdit::SetExpiryTime() +void KeyEdit::SetKeyExpiryTime() { GpgMEWorker gpgWorker; - GpgME::Error e = gpgWorker.SetExpiryTime(m_expiryEditedKeyFpr.toUTF8().c_str(), - m_popupExpiryTime->GetPassphrase(), - m_popupExpiryTime->GetExpiryTime()); + GpgME::Error e; + const WString keyFpr = m_popupExpiryTime->GetKeyFpr(); + WString subkeyFpr = m_popupExpiryTime->GetSubkeyFpr(); + if (keyFpr == subkeyFpr) + subkeyFpr = WString::Empty; + e = gpgWorker.SetKeyExpiryTime(keyFpr.toUTF8().c_str(), + subkeyFpr.toUTF8().c_str(), + m_popupExpiryTime->GetPassphrase(), + m_popupExpiryTime->GetExpiry()); if (e.code() != 0) { m_owner->m_tmwMessage->SetText(TR("SetExpirationTimeFailure")); m_popupExpiryTime->ShowPassphrase(true); + LGE(e); return; } m_owner->m_tmwMessage->SetText(TR("SetExpirationTimeSuccess")); @@ -250,6 +278,7 @@ void KeyEdit::AddOrRevokeUid() { m_popupAddUid->ShowPassphrase(true); m_owner->m_tmwMessage->SetText(e.asString()); + LGE(e); } else { diff --git a/KeyEdit.h b/KeyEdit.h index 62744b2..3973685 100644 --- a/KeyEdit.h +++ b/KeyEdit.h @@ -39,7 +39,7 @@ private: virtual ~KeyEdit(); K7Main * m_owner; - PopupCertifyUserId * m_popupUid; + PopupCertifyUserId * m_popupCertifyUid; WString m_targetUidValidityKeyFpr; PopupExpiryTime * m_popupExpiryTime; @@ -57,8 +57,8 @@ private: * @param keyHasSecret */ void FillOwnerTrustCombo(WComboBox * cmb, bool keyHasSecret); - void CertifyKey(); - void SetExpiryTime(); + void EditUidValidity(); + void SetKeyExpiryTime(); void AddOrRevokeUid(); /** @@ -85,7 +85,8 @@ private: * @param subkeyNode * @param keyFpr */ - void OnExpiryClicked(WTreeTableNode * subkeyNode, const WString& keyFpr); + void OnExpiryClicked(WTreeTableNode * subkeyNode, const WString& keyFpr, + const WString& subkeyFpr); void OnUidEmailClicked(WTreeTableNode * uidNode, const WString& keyFpr); diff --git a/KeyringIO.cpp b/KeyringIO.cpp index 3697014..294e358 100644 --- a/KeyringIO.cpp +++ b/KeyringIO.cpp @@ -13,6 +13,7 @@ #include "Tools.h" #include #include +#include "GpgMELogger.h" using namespace std; @@ -81,6 +82,7 @@ void KeyringIO::DoImportKey() if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } if (fpr.empty()) @@ -93,6 +95,7 @@ void KeyringIO::DoImportKey() if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } m_tmwMessage->SetText(TR("ImportSuccess") + fpr + WString(" - ") + WString(k.userID(0).name())); @@ -111,6 +114,7 @@ bool KeyringIO::CanKeyBeDeleted(const WString& fullKeyID) if (e.code() != 0 && e.code() != 16383) { // 16383 : end of file, when key is not private m_tmwMessage->SetText(e.asString()); + LGE(e); return false; } // k can now be secret or public @@ -168,6 +172,7 @@ void KeyringIO::DoDeleteKey() if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } // Delete the key using the C API @@ -234,6 +239,7 @@ void KeyringIO::DoCreateKey() if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); } else { @@ -337,21 +343,19 @@ void ExportKeyStreamResource::handleRequest(const Http::Request& request, */ string buffer; - if (!request.continuation()) // Needed for WStreamResource ? - { - Error e; - GpgMEWorker gpgw; - e = m_isSecret + Error e; + GpgMEWorker gpgw; + e = m_isSecret ? gpgw.ExportPrivateKey(m_fpr.toUTF8().c_str(), buffer, m_passphrase.toUTF8()) : gpgw.ExportPublicKey(m_fpr.toUTF8().c_str(), buffer); - if (e.code() != 0) - { - m_tmwMessage->SetText(e.asString()); - return; - } - suggestFileName(m_fpr + WString(".asc"), ContentDisposition::Attachment); + if (e.code() != 0) + { + m_tmwMessage->SetText(e.asString()); + LGE(e); + return; } + suggestFileName(m_fpr + WString(".asc"), ContentDisposition::Attachment); istrstream bufStream(buffer.c_str()); handleRequestPiecewise(request, response, bufStream); diff --git a/PopupCertifyUserId.cpp b/PopupCertifyUserId.cpp index d942ca1..8be0ebe 100644 --- a/PopupCertifyUserId.cpp +++ b/PopupCertifyUserId.cpp @@ -12,7 +12,10 @@ #include "Tools.h" #include #include +#include #include +#include +#include "GpgMELogger.h" using namespace std; @@ -26,6 +29,7 @@ PopupCertifyUserId::PopupCertifyUserId(WWidget * anchorWidget, TransientMessageW m_cbOptionExportable = NULL; m_cbOptionNonRevocable = NULL; // m_cbOptionTrust = NULL; + m_cbConfirm = NULL; m_lblPassphrase = NULL; m_lePassphrase = NULL; m_btnApply = NULL; @@ -63,10 +67,10 @@ void PopupCertifyUserId::Create(vector& privateKeys, m_hblPreferences->addLayout(unique_ptr (m_vblEmail)); PresentEmail(); // Column 1 + m_gbOptions = new WGroupBox(TR("Options")); WVBoxLayout * vblOptions = new WVBoxLayout(); - m_hblPreferences->addLayout(unique_ptr (vblOptions)); - WText * lblOptions = new WText(TR("Options")); - vblOptions->addWidget(unique_ptr (lblOptions)); + m_gbOptions->setLayout(unique_ptr (vblOptions)); + m_hblPreferences->addWidget(unique_ptr (m_gbOptions)); m_cbOptionExportable = new WCheckBox(TR("ExportableCertification")); m_cbOptionExportable->setToolTip(TR("OneWayHint")); vblOptions->addWidget(unique_ptr (m_cbOptionExportable)); @@ -74,7 +78,7 @@ void PopupCertifyUserId::Create(vector& privateKeys, m_cbOptionNonRevocable->setToolTip(TR("OneWayHint")); vblOptions->addWidget(unique_ptr (m_cbOptionNonRevocable)); /*m_cbOptionTrust = new WCheckBox(TR("TrustCertification")); - vblOptions->addWidget(unique_ptr (m_cbOptionTrust));*/ + gbOptions->addWidget(unique_ptr (m_cbOptionTrust));*/ WHBoxLayout * hblPassphrase = new WHBoxLayout(); m_lblPassphrase = new WText(TR("Passphrase")); @@ -84,6 +88,21 @@ void PopupCertifyUserId::Create(vector& privateKeys, hblPassphrase->addWidget(unique_ptr (m_lePassphrase), 1); vblMain->addLayout(unique_ptr (hblPassphrase)); + WHBoxLayout * hblWhat = new WHBoxLayout(); + WRadioButton * rbCertifyUid = new WRadioButton(TR("CertifyUid")); + hblWhat->addWidget(unique_ptr (rbCertifyUid)); + WRadioButton * rbRevokeCertification = + new WRadioButton(TR("RevokeUidCertification")); + hblWhat->addWidget(unique_ptr (rbRevokeCertification)); + m_bgWhat = make_shared(); + m_bgWhat->addButton(rbCertifyUid, What::CertifyUid); + m_bgWhat->addButton(rbRevokeCertification, What::RevokeUidCertification); + m_bgWhat->setCheckedButton(rbCertifyUid); + vblMain->addLayout(unique_ptr (hblWhat)); + + m_cbConfirm = new WCheckBox(TR("Confirm")); + vblMain->addWidget(unique_ptr (m_cbConfirm)); + WHBoxLayout * hblButtons = new WHBoxLayout(); WPushButton * btnClose = new WPushButton(TR("Close")); hblButtons->addWidget(unique_ptr (btnClose)); @@ -99,6 +118,7 @@ void PopupCertifyUserId::Create(vector& privateKeys, m_cbOptionNonRevocable->unChecked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionUnChecked, this, 2)); // m_cbOptionTrust->unChecked().connect(std::bind(&PopupCertifyUserId::OnCertifyOptionUnChecked, this, 4)); btnClose->clicked().connect(this, &WPopupWidget::hide); + m_bgWhat->checkedChanged().connect(this, &PopupCertifyUserId::OnButtonGroupWhat); } void PopupCertifyUserId::FillPrivateKeyComboBox(vector& privateKeys) @@ -116,6 +136,7 @@ void PopupCertifyUserId::FillPrivateKeyComboBox(vector& privateKeys) if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } /* @@ -158,6 +179,7 @@ void PopupCertifyUserId::PresentEmail() if (e.code() != 0) { m_tmwMessage->SetText(e.asString()); + LGE(e); return; } if (lst.size() != 1) @@ -175,8 +197,8 @@ void PopupCertifyUserId::PresentEmail() 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)); + cbEmail->checked().connect(std::bind(&PopupCertifyUserId::OnEmailChecked, this, cbEmail, (*it))); + cbEmail->unChecked().connect(std::bind(&PopupCertifyUserId::OnEmailUnChecked, this, cbEmail, (*it))); id++; } } @@ -220,25 +242,32 @@ void PopupCertifyUserId::ShowPassphrase(bool show) } } -void PopupCertifyUserId::OnEmailChecked(WCheckBox* cb) +void PopupCertifyUserId::OnEmailChecked(WCheckBox* cb, GpgME::UserID& uid) { int id = Tools::ToInt(cb->id()); m_uidsToSign.push_back(id); + m_uidsToRevokeCertification.push_back(uid); } -void PopupCertifyUserId::OnEmailUnChecked(WCheckBox* cb) +void PopupCertifyUserId::OnEmailUnChecked(WCheckBox* cb, GpgME::UserID& uid) { - // 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++) + vector::iterator it = + std::find(m_uidsToSign.begin(), m_uidsToSign.end(), id); + if (it != m_uidsToSign.end()) + m_uidsToSign.erase(it); + + vector::iterator uit; + for (uit = m_uidsToRevokeCertification.begin(); + uit != m_uidsToRevokeCertification.end(); uit++) { - if ((*it) == id) + if ((*uit).uidhash() == uid.uidhash()) { - m_uidsToSign.erase(it); - return; + m_uidsToRevokeCertification.erase(uit); + break; } } + } void PopupCertifyUserId::OnCertifyOptionChecked(int id) @@ -250,3 +279,27 @@ void PopupCertifyUserId::OnCertifyOptionUnChecked(int id) { m_certifyOptions -= id; } + +void PopupCertifyUserId::OnButtonGroupWhat(WRadioButton* btn) +{ + m_gbOptions->setDisabled(m_bgWhat->checkedId() + == What::RevokeUidCertification); + +} + +bool PopupCertifyUserId::Validate() const +{ + if (!m_cbConfirm->isChecked() || m_lePassphrase->text().empty()) + return false; + if (m_bgWhat->checkedId() == What::CertifyUid) + { + if (m_uidsToSign.size() == 0) + return false; + } + else + { + if (m_uidsToRevokeCertification.size() == 0) + return false; + } + return true; +} diff --git a/PopupCertifyUserId.h b/PopupCertifyUserId.h index 32a6629..95b4b48 100644 --- a/PopupCertifyUserId.h +++ b/PopupCertifyUserId.h @@ -19,28 +19,32 @@ #include #include #include +#include +#include #include +#include #include "TransientMessageWidget.h" using namespace Wt; /** - * A popup with required parameters to certify a key : + * A popup with required parameters to certify or revoke a key uid : *
    *
  • 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'. + * \n For revocation, GnuPG >= 2.2.24 is required. */ class PopupCertifyUserId : public WPopupWidget { public: + + enum What + { + RevokeUidCertification = 0, CertifyUid + }; PopupCertifyUserId(WWidget * anchorWidget, TransientMessageWidget * txtMessage, const WLength& width = 500); virtual ~PopupCertifyUserId(); @@ -102,6 +106,27 @@ public: return m_uidsToSign; } + /** + * Get the certifications to revoke. + * \n GPGME >= 1.15.0 is required. It expects + * it as a vector of GpgME::UserID, instead of a vector of indices used in + * GetUidsToSign(). + * @return + */ + vector& GetUidsToRevokeCertification() + { + return m_uidsToRevokeCertification; + } + + /** + * Certify selected user identities or revoke certifications. + * @return + */ + const What WhatToDo() const + { + return (What) m_bgWhat->checkedId(); + } + /** * Sum of option values *
    @@ -124,6 +149,11 @@ public: { return m_btnApply; } + /** + * Check required parameters are set and confirmed. + * @return + */ + bool Validate() const; private: TransientMessageWidget * m_tmwMessage; WContainerWidget * m_cwMain; @@ -138,10 +168,14 @@ private: // WCheckBox * m_cbOptionTrust; // Always fails WText * m_lblPassphrase; WLineEdit * m_lePassphrase; + WCheckBox * m_cbConfirm; WPushButton * m_btnApply; vector m_uidsToSign; + vector m_uidsToRevokeCertification; int m_certifyOptions; + WGroupBox * m_gbOptions; + shared_ptr m_bgWhat; /** * Available private fingerprints in a combobox. The selected item is the * signing key. @@ -154,15 +188,17 @@ private: */ void PresentEmail(); /** - * Add the identity index in a vector. + * Add the identity index in a vector for certification. + * \n Add the ::UserID in a vector for signature revocation. * @param cb */ - void OnEmailChecked(WCheckBox * cb); + void OnEmailChecked(WCheckBox * cb, GpgME::UserID& uid); /** - * Removes the identity index in a vector. + * Removes the identity index from a vector for certification. + * \n Removes the ::UserID from a vector for signature revocation. * @param cb */ - void OnEmailUnChecked(WCheckBox * cb); + void OnEmailUnChecked(WCheckBox * cb, GpgME::UserID& uid); /** * Adds the option value in m_certifyOptions. * @param id @@ -173,6 +209,11 @@ private: * @param id */ void OnCertifyOptionUnChecked(int id); + /** + * Show certification options if certifying, else hide them. + * @param btn + */ + void OnButtonGroupWhat(WRadioButton * btn); }; #endif /* POPUPCERTIFYUSERID_H */ diff --git a/PopupExpiryTime.cpp b/PopupExpiryTime.cpp index 7b47893..dfb6331 100644 --- a/PopupExpiryTime.cpp +++ b/PopupExpiryTime.cpp @@ -22,6 +22,7 @@ PopupExpiryTime::PopupExpiryTime(WWidget * anchorWidget, TransientMessageWidget m_tmwMessage = txtMessage; m_cwMain = NULL; m_keyFpr = WString::Empty; + m_subkeyFpr = WString::Empty; /* * Trade-off. * When the calendar of WDateEdit is clicked, this popup gets hidden, @@ -82,6 +83,11 @@ const string PopupExpiryTime::GetExpiryTime() const return m_deExpiry->text().toUTF8(); } +const ulong PopupExpiryTime::GetExpiry() const +{ + return ((WDate::currentDate().daysTo(m_deExpiry->date())) * 24 * 3600); +} + void PopupExpiryTime::ShowPassphrase(bool show) { // See comments in PopupCertifyUserId::ShowPassphrase diff --git a/PopupExpiryTime.h b/PopupExpiryTime.h index 78ec676..dde804b 100644 --- a/PopupExpiryTime.h +++ b/PopupExpiryTime.h @@ -23,9 +23,24 @@ class PopupExpiryTime : public WPopupWidget { public: PopupExpiryTime(WWidget * anchorWidget, TransientMessageWidget * txtMessage, - const WLength& width = 300); + const WLength& width = 300); virtual ~PopupExpiryTime(); void Create(const WString& keyFpr); + + const WString GetKeyFpr() + { + return m_keyFpr; + } + + void SetSubkeyFpr(const WString& subkeyFpr) + { + m_subkeyFpr = subkeyFpr; + } + + const WString GetSubkeyFpr() + { + return m_subkeyFpr; + } /** * Controls visibility of passphrase widgets. * \n Need not be always visible as the passphrase is cached by gpg-agent. @@ -34,6 +49,7 @@ public: * @param show */ void ShowPassphrase(bool show = true); + /** * Used to forward the passphrase to the loopback passphrase provider. * @return @@ -43,9 +59,15 @@ public: return m_lePassphrase->text().toUTF8(); } /** - * Returns the new expiry date. + * Returns the new expiry date, or 0 if date is invalid. */ const std::string GetExpiryTime() const; + /** + * Number of seconds from now. + * @return + */ + const ulong GetExpiry() const; + /** * Caller binds its function here. * @return @@ -54,7 +76,7 @@ public: { return m_btnApply; } - + private: TransientMessageWidget * m_tmwMessage; WContainerWidget * m_cwMain; @@ -63,6 +85,7 @@ private: WPushButton * m_btnApply; WText * m_lblPassphrase; WString m_keyFpr; + WString m_subkeyFpr; }; diff --git a/SensitiveTreeTableNodeText.cpp b/SensitiveTreeTableNodeText.cpp index d56c212..dc9afe9 100644 --- a/SensitiveTreeTableNodeText.cpp +++ b/SensitiveTreeTableNodeText.cpp @@ -51,6 +51,7 @@ void TreeTableNodeText::OnClick() m_lineEdit->setReadOnly(true); m_lineEdit->blurred().connect(m_lineEdit, &TreeTableNodeLineEdit::OnBlurred); m_lineEdit->setSelection(0, text().toUTF8().length()); + m_lineEdit->HoldSourceWidget(removeFromParent()); m_node->setColumnWidget(m_colIndex, unique_ptr (m_lineEdit)); m_lineEdit->setFocus(true); // +++ } @@ -78,13 +79,15 @@ void TreeTableNodeLineEdit::Init(WTreeTableNode* node, uint colIndex) { m_node = node; m_colIndex = colIndex; - m_text = NULL; clicked().connect(this, &TreeTableNodeLineEdit::OnBlurred); } void TreeTableNodeLineEdit::OnBlurred() { - m_text = new TreeTableNodeText(text(), m_node, m_colIndex); - m_text->clicked().connect(m_text, &TreeTableNodeText::OnClick); - m_node->setColumnWidget(m_colIndex, unique_ptr (m_text)); + m_node->setColumnWidget(m_colIndex, std::move(m_sourceWidget)); +} + +void TreeTableNodeLineEdit::HoldSourceWidget(std::unique_ptr sourceWidget) +{ + m_sourceWidget.swap(sourceWidget); } diff --git a/SensitiveTreeTableNodeText.h b/SensitiveTreeTableNodeText.h index f716ad4..24b8078 100644 --- a/SensitiveTreeTableNodeText.h +++ b/SensitiveTreeTableNodeText.h @@ -58,13 +58,19 @@ public: WTreeTableNode * node, uint colIndex); virtual ~TreeTableNodeLineEdit(); /** - * Creates back a TreeTableNodeText replacing this TreeTableNodeLineEdit. + * Move back the original TreeTableNodeText replacing this + * TreeTableNodeLineEdit. */ void OnBlurred(); + /** + * Keep the original TreeTableNodeText verbatim. + * @param text + */ + void HoldSourceWidget(std::unique_ptr sourceWidget); private: WTreeTableNode * m_node; uint m_colIndex; - TreeTableNodeText * m_text; + std::unique_ptr m_sourceWidget; void Init(WTreeTableNode * node, uint colIndex); }; diff --git a/Tools.cpp b/Tools.cpp index b4d5466..e37befc 100644 --- a/Tools.cpp +++ b/Tools.cpp @@ -10,6 +10,7 @@ #include "Tools.h" #include #include +#include "GpgMELogger.h" using namespace std; @@ -101,6 +102,7 @@ bool Tools::KeyHasSecret(const WString& fpr) GpgME::Key k = gpgw.FindKey(fpr.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 + LGE(e); return false; } return (!k.isNull()); diff --git a/WTAPPROOT/K7/K7.css b/WTAPPROOT/K7/K7.css index 2e289cf..c7d6113 100644 --- a/WTAPPROOT/K7/K7.css +++ b/WTAPPROOT/K7/K7.css @@ -6,5 +6,15 @@ * are all 0. */ div.popup { - background-color: lavender; + background-color: floralwhite; } +div.red { + color: red; +} +div.bold { + font-weight: bold; +} +span.title { + font-size: 125%; + font-weight: bold; +} diff --git a/WTAPPROOT/K7/K7.xml b/WTAPPROOT/K7/K7.xml index 63f8c0f..bdc03ea 100644 --- a/WTAPPROOT/K7/K7.xml +++ b/WTAPPROOT/K7/K7.xml @@ -83,8 +83,10 @@ No email address is selected Apply Key succesfully certified - Key certification failed + Identity certification succesfully revoked Once set, this option cannot be removed. Use gpg cli for further edit. + Certify identities + Revoke certifications Click to be able to copy next diff --git a/WTAPPROOT/K7/K7_fr.xml b/WTAPPROOT/K7/K7_fr.xml index 86d65be..4b365c4 100644 --- a/WTAPPROOT/K7/K7_fr.xml +++ b/WTAPPROOT/K7/K7_fr.xml @@ -83,8 +83,10 @@ Aucun courriel n'est sélectionné Appliquer Succès de certification de la clé - Échec de certification de la clé + Succès de révocation de l'identité Une fois appliquée, cette option ne pourra plus être enlevée. Utilisez gpg en ligne de commande pour édition ultérieure. + Certifier les identités + Revoquer les certifications Cliquez pour pouvoir ensuite copier diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 02abdb8..14e08ee 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -6,6 +6,7 @@ projectFiles="true"> AppConfig.h GpgMECWorker.h + GpgMELogger.h GpgMEWorker.h K7Main.h KeyEdit.h @@ -104,6 +105,8 @@ + + @@ -224,6 +227,8 @@ + + @@ -348,6 +353,8 @@ + + diff --git a/nbproject/private/configurations.xml b/nbproject/private/configurations.xml index 1f82c50..2c9b830 100644 --- a/nbproject/private/configurations.xml +++ b/nbproject/private/configurations.xml @@ -13,8 +13,6 @@ - -