Allow exporting secret keys.

Requests the passphrase with a popup.

As from GPGME 1.15.0, the loopback pinentry is functional when exporting secret keys. It works fine when the exact passphrase is provided. If it's a wrong passphrase, GPGME does not generate an ::Error, but the app crashes with 'free(): double free detected in tcache 2'.

Hence, this patch cannot be committed to master.

Status : dangerous
Result : works and works not
Reason : a wrong passphrase means a crash
This commit is contained in:
SET
2020-11-20 22:41:29 +01:00
parent bb174075df
commit 82b8810f62
15 changed files with 241 additions and 142 deletions

View File

@@ -9,37 +9,6 @@
#include "GpgMECWorker.h"
#ifdef DEVTIME
/**
* This callback is never called when exporting private keys.
* From gpgme-1.14.0/tests/run-support.h
*/
gpgme_error_t passphrase_cb(void *opaque, const char *uid_hint, const char *passphrase_info,
int last_was_bad, int fd)
{
int res;
char pass[] = "test_pwd\n";
int passlen = strlen(pass);
int off = 0;
(void) opaque;
(void) uid_hint;
(void) passphrase_info;
(void) last_was_bad;
do
{
res = gpgme_io_write(fd, &pass[off], passlen - off);
if (res > 0)
off += res;
}
while (res > 0 && off != passlen);
return off == passlen ? 0 : gpgme_error_from_errno(errno);
}
#endif
GpgMECWorker::GpgMECWorker()
{
gpgme_error_t c_err = gpgme_new(&c_ctx);
@@ -68,66 +37,3 @@ bool GpgMECWorker::DeleteKey(const char * fpr, bool secret, GpgME::Error& e)
}
return true;
}
#ifdef DEVTIME
/**
* Much code here is obtained from gpgme-1.14.0/tests/run-export.c
* @param fpr
* @param e
* @return
*/
bool GpgMECWorker::ExportPrivateKey(const char* fpr, GpgME::Error& e)
{
gpgme_key_t c_key;
gpgme_data_t out;
gpgme_set_armor(c_ctx, 1);
gpgme_error_t c_err = gpgme_get_key(c_ctx, fpr, &c_key, true);
if (c_key == NULL)
{
e = GpgME::Error::fromCode(c_err);
return false;
}
FILE * kFp = fopen("/tmp/data", "wb");
if (kFp == NULL)
return false;
c_err = gpgme_data_new_from_stream(&out, kFp);
if (c_err != 0)
{
fclose(kFp);
e = GpgME::Error::fromCode(c_err);
return false;
}
gpgme_key_t aKeys[2];
aKeys[0] = c_key;
aKeys[1] = NULL;
c_err = gpgme_set_pinentry_mode(c_ctx, GPGME_PINENTRY_MODE_LOOPBACK);
if (c_err != 0)
{
fclose(kFp);
e = GpgME::Error::fromCode(c_err);
return false;
}
gpgme_set_passphrase_cb(c_ctx, passphrase_cb, NULL);
gpgme_export_mode_t flags = GPGME_EXPORT_MODE_SECRET;
c_err = gpgme_op_export_keys(c_ctx, aKeys, flags, out);
if (c_err != 0)
{
fclose(kFp);
gpgme_data_release(out);
e = GpgME::Error::fromCode(c_err);
return false;
}
fflush(NULL);
fclose(kFp);
gpgme_data_release(out);
return true;
}
#endif

View File

@@ -31,20 +31,6 @@ public:
*/
bool DeleteKey(const char * fpr, bool secret, GpgME::Error& e);
#ifdef DEVTIME
/**
* Status : testing
* \n Result : fails to export a private key
* \n Reason : passphrase callback is never called
* \n With default pinentry mode, the password is requested normally
* and the private key is exported. But this can't be done on a web server.
* @param fpr
* @param e
* @return
*/
bool ExportPrivateKey(const char* fpr, GpgME::Error& e);
#endif
private:
gpgme_ctx_t c_ctx;
};

View File

@@ -308,8 +308,6 @@ const Error GpgMEWorker::CreateSubKey(GpgME::Key& k,
return e;
}
#ifdef DEVTIME
const Error GpgMEWorker::ExportPrivateKey(const char * pattern, string& buffer,
const string& passphrase)
{
@@ -324,14 +322,13 @@ const Error GpgMEWorker::ExportPrivateKey(const char * pattern, string& buffer,
uint flags = Context::ExportSecret;
Error e = ctx->exportPublicKeys(pattern, kData, flags);
buffer = kData.toString(); // Empty
buffer = kData.toString();
delete ppp;
delete ctx;
return e;
}
#endif
const Error GpgMEWorker::ExportPublicKey(const char* pattern, string& buffer)
{

View File

@@ -162,21 +162,14 @@ public:
const char * algo,
const string& passphrase,
ulong expires = 63072000);
#ifdef DEVTIME
/**
* Status : testing
* \n Result : fails to export a private key
* \n Reason : loopback passphrase provider is never called
* \n With default pinentry mode, the password is requested normally
* and the private key is exported. But this can't be done on a web server.
* @param fpr
* @param e
* Export a secret key.
* @param pattern : a key fingerprint
* @param buffer : returns data in armor mode
* @return
*/
const Error ExportPrivateKey(const char * pattern, string& buffer,
const string& passphrase = "");
#endif
/**
* Export a public key.
* @param pattern : a key fingerprint

View File

@@ -142,7 +142,6 @@ K7Main::Create()
vblButtons->addStretch(1);
// Everyone can export a key
m_btnExport = new WPushButton(TR("Export"));
m_btnExport->setToolTip(TR("TTTExport"));
m_btnExport->hide();
vblButtons->addWidget(unique_ptr<WPushButton> (m_btnExport));
if (m_config->CanCreateKeys())
@@ -314,7 +313,8 @@ void K7Main::OnKeyAnchorClicked(WAnchor * source)
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);
}

View File

@@ -22,6 +22,7 @@ KeyringIO::KeyringIO(K7Main * owner)
m_popupUpload = NULL;
m_popupDelete = NULL;
m_popupCreate = NULL;
m_popupExportSecretKey = NULL;
m_config = m_owner->m_config;
m_tmwMessage = m_owner->m_tmwMessage;
m_btnUpload = m_owner->m_btnUpload;
@@ -250,18 +251,54 @@ void KeyringIO::DoCreateKey()
}
}
void KeyringIO::ShowPopupExportSecretKey(const WString& fpr)
{
m_popupExportSecretKey = new PopupExportSecretKey(m_btnExport,
m_owner->m_tmwMessage);
m_popupExportSecretKey->Create();
m_popupExportSecretKey->GetPreApplyButton()
->clicked().connect(std::bind(&KeyringIO::OnPreExportSecretKey,
this, fpr));
m_popupExportSecretKey->show();
}
void KeyringIO::PrepareExport(const WString& fpr, bool isSecret)
{
// K7Main::m_btnExport
m_btnExport->setLink(WLink());
m_btnExport->clicked().disconnect(m_exportSecretConnection);
if (isSecret)
{
m_exportSecretConnection = m_btnExport->clicked().connect
(std::bind(&KeyringIO::ShowPopupExportSecretKey,
this, fpr));
}
else
{
WLink link;
shared_ptr<ExportKeyStreamResource> shResource =
make_shared<ExportKeyStreamResource>
(fpr, isSecret, "appliation/pgp-keys", m_tmwMessage);
link.setResource(shResource);
m_btnExport->setLink(link);
}
// Never reuse when selecting keys.
delete m_popupExportSecretKey;
m_popupExportSecretKey = NULL;
}
void KeyringIO::OnPreExportSecretKey(const WString& fpr)
{
// On preExport button of popup
WLink link;
shared_ptr<ExportKeyStreamResource> shResource =
make_shared<ExportKeyStreamResource>
(fpr, isSecret, "appliation/pgp-keys", m_tmwMessage);
(fpr, true, "appliation/pgp-keys", m_tmwMessage);
link.setResource(shResource);
m_btnExport->setLink(link);
if (isSecret)
m_btnExport->hide();
else
m_btnExport->show();
shResource->SetPassphrase(m_popupExportSecretKey->GetPassphrase());
m_popupExportSecretKey->GetApplyButton()->setLink(link);
m_popupExportSecretKey->GetApplyButton()->enable();
}
ExportKeyStreamResource::ExportKeyStreamResource(const WString& fpr,
@@ -271,6 +308,7 @@ ExportKeyStreamResource::ExportKeyStreamResource(const WString& fpr,
{
m_fpr = fpr;
m_isSecret = isSecret;
m_passphrase = WString::Empty;
m_tmwMessage = tmw;
}
@@ -282,6 +320,7 @@ ExportKeyStreamResource::ExportKeyStreamResource(const WString& fpr,
{
m_fpr = fpr;
m_isSecret = isSecret;
m_passphrase = WString::Empty;
m_tmwMessage = tmw;
}
@@ -294,20 +333,18 @@ void ExportKeyStreamResource::handleRequest(const Http::Request& request,
Http::Response& response)
{
/*
* Private keys cannot be exported with loopback pinentry.
* Let's hope it gets better someday.
* Private keys can be exported as from GPGME 1.15.0.
*/
if (m_isSecret)
{
m_tmwMessage->SetText(TR("TTTExport"));
return;
}
string buffer;
if (!request.continuation()) // Needed for WStreamResource ?
{
Error e;
GpgMEWorker gpgw;
e = gpgw.ExportPublicKey(m_fpr.toUTF8().c_str(), buffer);
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());

View File

@@ -16,6 +16,7 @@
#include "PopupUploader.h"
#include "PopupDeleter.h"
#include "PopupCreate.h"
#include "PopupExportSecretKey.h"
using namespace Wt;
@@ -41,6 +42,7 @@ private:
PopupUpload * m_popupUpload;
PopupDelete * m_popupDelete;
PopupCreate * m_popupCreate;
PopupExportSecretKey * m_popupExportSecretKey;
// Variables from m_owner mapped here.
AppConfig * m_config;
@@ -51,7 +53,9 @@ private:
WPushButton * m_btnCreate;
WPushButton * m_btnExport;
WLineEdit * m_leSearch;
// Used to disconnect m_btnExport from previous slot.
Signals::connection m_exportSecretConnection;
/**
* Shows a non-blocking popup to upload a key,
@@ -66,6 +70,10 @@ private:
* Shows a non-blocking popup to delete a key
*/
void ShowPopupDelete();
/**
* Shows a non-blocking popup to export a secret key
*/
void ShowPopupExportSecretKey(const WString& fpr);
/**
* All public keys can be deleted.
* Private keys can be deleted only if the user
@@ -91,8 +99,17 @@ private:
* @param spool
*/
void OnUploadCompleted(const WString& spool);
/**
* If isSecret is true, shows a popup for passphrase input befort exporting.
* Else, adds a link with a shared resource to the export button.
* @param fpr
* @param isSecret
*/
void PrepareExport(const WString& fpr, bool isSecret);
/**
* Adds a link with a shared resource to the export button of the popup.
*/
void OnPreExportSecretKey(const WString& fpr);
};
/**
@@ -118,10 +135,15 @@ public:
*/
void handleRequest(const Http::Request& request,
Http::Response& response) override;
void SetPassphrase(const WString& passphrase)
{
m_passphrase = passphrase;
}
private:
WString m_fpr;
bool m_isSecret;
WString m_passphrase;
TransientMessageWidget * m_tmwMessage;
};

58
PopupExportSecretKey.cpp Normal file
View File

@@ -0,0 +1,58 @@
/*
* File: PopupExportSecretKey.cpp
* Author: SET - nmset@yandex.com
* License : GPL v2
* Copyright SET - © 2019
*
* Created on November 19, 2020, 9:01 PM
*/
#include "PopupExportSecretKey.h"
#include <Wt/WVBoxLayout.h>
#include <Wt/WHBoxLayout.h>
#include "global.h"
PopupExportSecretKey::PopupExportSecretKey(WWidget * anchorWidget,
TransientMessageWidget * txtMessage,
const WLength& width)
: WPopupWidget(cpp14::make_unique<WContainerWidget>())
{
m_tmwMessage = txtMessage;
m_cwMain = NULL;
m_lblPassphrase = NULL;
m_lePassphrase = NULL;
m_btnPreApply = NULL;
m_btnApply = NULL;
setTransient(true);
setAnchorWidget(anchorWidget);
setWidth(width);
}
PopupExportSecretKey::~PopupExportSecretKey()
{
}
void PopupExportSecretKey::Create()
{
m_cwMain = static_cast<WContainerWidget*> (implementation());
m_cwMain->setStyleClass("popup");
WVBoxLayout * vblMain = new WVBoxLayout();
m_cwMain->setLayout(unique_ptr<WVBoxLayout> (vblMain));
WHBoxLayout * hblPassphrase = new WHBoxLayout();
m_lblPassphrase = new WText(TR("Passphrase"));
hblPassphrase->addWidget(unique_ptr<WText> (m_lblPassphrase));
m_lePassphrase = new WLineEdit();
m_lePassphrase->setEchoMode(EchoMode::Password);
hblPassphrase->addWidget(unique_ptr<WLineEdit> (m_lePassphrase));
vblMain->addLayout(unique_ptr<WHBoxLayout> (hblPassphrase));
m_btnPreApply = new WPushButton(TR("PreExportSecretKey"));
vblMain->addWidget(unique_ptr<WPushButton> (m_btnPreApply));
m_btnApply = new WPushButton(TR("ExportSecretKey"));
m_btnApply->setToolTip(TR("TTTExportSecretKey"));
vblMain->addWidget(unique_ptr<WPushButton> (m_btnApply));
m_lePassphrase->keyPressed().connect(m_btnApply, &WPushButton::disable);
}

64
PopupExportSecretKey.h Normal file
View File

@@ -0,0 +1,64 @@
/*
* File: PopupExportSecretKey.h
* Author: SET - nmset@yandex.com
* License : GPL v2
* Copyright SET - © 2019
*
* Created on November 19, 2020, 9:01 PM
*/
#ifndef POPUPEXPORTSECRETKEY_H
#define POPUPEXPORTSECRETKEY_H
#include <Wt/WPopupWidget.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
#include "TransientMessageWidget.h"
using namespace Wt;
class PopupExportSecretKey : public WPopupWidget
{
public:
PopupExportSecretKey(WWidget * anchorWidget,
TransientMessageWidget * txtMessage,
const WLength& width = 300);
virtual ~PopupExportSecretKey();
void Create();
/**
* Used to forward the passphrase to the loopback passphrase provider.
* @return
*/
const string GetPassphrase()
{
return m_lePassphrase->text().toUTF8();
}
/**
* Caller binds its function here to add a link to the apply button.
* @return
*/
WPushButton* GetPreApplyButton()
{
return m_btnPreApply;
}
/**
* Expects a link with a shared resource that generates the secret key
* and forwards it to the browser.
* @return
*/
WPushButton* GetApplyButton()
{
return m_btnApply;
}
private:
TransientMessageWidget * m_tmwMessage;
WContainerWidget * m_cwMain;
WLineEdit * m_lePassphrase;
WPushButton * m_btnPreApply;
WPushButton * m_btnApply;
WText * m_lblPassphrase;
};
#endif /* POPUPEXPORTSECRETKEY_H */

View File

@@ -131,7 +131,9 @@
<message id='CreateSuccess'>Create success : </message>
<message id='Export'>Export</message>
<message id='TTTExport'>Exporting secret keys is technically impossible</message>
<message id='PreExportSecretKey'>Prepare download link</message>
<message id='ExportSecretKey'>Export secret key</message>
<message id='TTTExportSecretKey'>Requires GPGME >= 1.15.0</message>
<message id='AddUid'>Add an identity</message>
<message id='RevokeUid'>Revoke identity</message>

View File

@@ -131,7 +131,9 @@
<message id='CreateSuccess'>Création réussie : </message>
<message id='Export'>Exporter</message>
<message id='TTTExport'>L'export des clés secrètes est techniquement impossible</message>
<message id='PreExportSecretKey'>Préparer le téléchargement</message>
<message id='ExportSecretKey'>Exporter la secrète</message>
<message id='TTTExportSecretKey'>Requiert GPGME >= 1.15.0</message>
<message id='AddUid'>Ajouter une identité</message>
<message id='RevokeUid'>Revoquer une identité</message>

View File

@@ -47,6 +47,7 @@ OBJECTFILES= \
${OBJECTDIR}/PopupCreate.o \
${OBJECTDIR}/PopupDeleter.o \
${OBJECTDIR}/PopupExpiryTime.o \
${OBJECTDIR}/PopupExportSecretKey.o \
${OBJECTDIR}/PopupUploader.o \
${OBJECTDIR}/SensitiveTreeTableNodeText.o \
${OBJECTDIR}/Tools.o \
@@ -138,6 +139,11 @@ ${OBJECTDIR}/PopupExpiryTime.o: PopupExpiryTime.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}/PopupExpiryTime.o PopupExpiryTime.cpp
${OBJECTDIR}/PopupExportSecretKey.o: PopupExportSecretKey.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}/PopupExportSecretKey.o PopupExportSecretKey.cpp
${OBJECTDIR}/PopupUploader.o: PopupUploader.cpp
${MKDIR} -p ${OBJECTDIR}
${RM} "$@.d"

View File

@@ -47,6 +47,7 @@ OBJECTFILES= \
${OBJECTDIR}/PopupCreate.o \
${OBJECTDIR}/PopupDeleter.o \
${OBJECTDIR}/PopupExpiryTime.o \
${OBJECTDIR}/PopupExportSecretKey.o \
${OBJECTDIR}/PopupUploader.o \
${OBJECTDIR}/SensitiveTreeTableNodeText.o \
${OBJECTDIR}/Tools.o \
@@ -138,6 +139,11 @@ ${OBJECTDIR}/PopupExpiryTime.o: PopupExpiryTime.cpp
${RM} "$@.d"
$(COMPILE.cc) -g -DDEVTIME -I/usr/local/Wt-Debug/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupExpiryTime.o PopupExpiryTime.cpp
${OBJECTDIR}/PopupExportSecretKey.o: PopupExportSecretKey.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}/PopupExportSecretKey.o PopupExportSecretKey.cpp
${OBJECTDIR}/PopupUploader.o: PopupUploader.cpp
${MKDIR} -p ${OBJECTDIR}
${RM} "$@.d"

View File

@@ -47,6 +47,7 @@ OBJECTFILES= \
${OBJECTDIR}/PopupCreate.o \
${OBJECTDIR}/PopupDeleter.o \
${OBJECTDIR}/PopupExpiryTime.o \
${OBJECTDIR}/PopupExportSecretKey.o \
${OBJECTDIR}/PopupUploader.o \
${OBJECTDIR}/SensitiveTreeTableNodeText.o \
${OBJECTDIR}/Tools.o \
@@ -138,6 +139,11 @@ ${OBJECTDIR}/PopupExpiryTime.o: PopupExpiryTime.cpp
${RM} "$@.d"
$(COMPILE.cc) -O2 -s -I/usr/local/Wt/include -I/usr/include/gpgme++ -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/PopupExpiryTime.o PopupExpiryTime.cpp
${OBJECTDIR}/PopupExportSecretKey.o: PopupExportSecretKey.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}/PopupExportSecretKey.o PopupExportSecretKey.cpp
${OBJECTDIR}/PopupUploader.o: PopupUploader.cpp
${MKDIR} -p ${OBJECTDIR}
${RM} "$@.d"

View File

@@ -16,6 +16,7 @@
<itemPath>PopupCreate.h</itemPath>
<itemPath>PopupDeleter.h</itemPath>
<itemPath>PopupExpiryTime.h</itemPath>
<itemPath>PopupExportSecretKey.h</itemPath>
<itemPath>PopupUploader.h</itemPath>
<itemPath>SensitiveTreeTableNodeText.h</itemPath>
<itemPath>Tools.h</itemPath>
@@ -45,6 +46,7 @@
<itemPath>PopupCreate.cpp</itemPath>
<itemPath>PopupDeleter.cpp</itemPath>
<itemPath>PopupExpiryTime.cpp</itemPath>
<itemPath>PopupExportSecretKey.cpp</itemPath>
<itemPath>PopupUploader.cpp</itemPath>
<itemPath>SensitiveTreeTableNodeText.cpp</itemPath>
<itemPath>Tools.cpp</itemPath>
@@ -142,6 +144,10 @@
</item>
<item path="PopupExpiryTime.h" ex="false" tool="3" flavor2="0">
</item>
<item path="PopupExportSecretKey.cpp" ex="false" tool="1" flavor2="0">
</item>
<item path="PopupExportSecretKey.h" ex="false" tool="3" flavor2="0">
</item>
<item path="PopupUploader.cpp" ex="false" tool="1" flavor2="0">
</item>
<item path="PopupUploader.h" ex="false" tool="3" flavor2="0">
@@ -258,6 +264,10 @@
</item>
<item path="PopupExpiryTime.h" ex="false" tool="3" flavor2="0">
</item>
<item path="PopupExportSecretKey.cpp" ex="false" tool="1" flavor2="0">
</item>
<item path="PopupExportSecretKey.h" ex="false" tool="3" flavor2="0">
</item>
<item path="PopupUploader.cpp" ex="false" tool="1" flavor2="0">
</item>
<item path="PopupUploader.h" ex="false" tool="3" flavor2="0">
@@ -378,6 +388,10 @@
</item>
<item path="PopupExpiryTime.h" ex="false" tool="3" flavor2="0">
</item>
<item path="PopupExportSecretKey.cpp" ex="false" tool="1" flavor2="0">
</item>
<item path="PopupExportSecretKey.h" ex="false" tool="3" flavor2="0">
</item>
<item path="PopupUploader.cpp" ex="false" tool="1" flavor2="0">
</item>
<item path="PopupUploader.h" ex="false" tool="3" flavor2="0">