PDF: Allow signing unsigned signature fields

This commit is contained in:
Albert Astals Cid 2022-01-03 15:49:10 +01:00
parent d59967d8e1
commit a8e5f6e9f7
23 changed files with 452 additions and 160 deletions

View file

@ -47,6 +47,11 @@ if(Poppler_Qt5_FOUND)
TEST_NAME "formattest"
LINK_LIBRARIES Qt5::Widgets Qt5::Test okularcore
)
ecm_add_test(signunsignedfieldtest
TEST_NAME "signunsignedfieldtest"
LINK_LIBRARIES Qt5::Widgets Qt5::Test okularcore
)
endif()
ecm_add_test(documenttest.cpp

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,5 @@
library=
name=NSS Internal PKCS #11 Module
parameters=configdir='testest' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})

Binary file not shown.

View file

@ -0,0 +1,123 @@
/*
SPDX-FileCopyrightText: 2022 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QMimeDatabase>
#include <QPushButton>
#include <QTemporaryFile>
#include <QTest>
#include <QTimer>
#include "../core/document.h"
#include "../core/form.h"
#include "../core/page.h"
#include "../settings_core.h"
class EnterPasswordDialogHelper : public QObject
{
Q_OBJECT
public:
EnterPasswordDialogHelper()
{
QTimer::singleShot(0, this, &EnterPasswordDialogHelper::enterPassword);
}
void enterPassword()
{
QWidget *dialog = qApp->activeModalWidget();
if (!dialog) {
QTimer::singleShot(0, this, &EnterPasswordDialogHelper::enterPassword);
return;
}
QLineEdit *lineEdit = dialog->findChild<QLineEdit *>();
lineEdit->setText(QStringLiteral("fakeokular"));
QDialogButtonBox *buttonBox = dialog->findChild<QDialogButtonBox *>();
buttonBox->button(QDialogButtonBox::Ok)->click();
}
};
class SignUnsignedFieldTest : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void init();
void cleanup();
void testSignUnsignedField();
private:
Okular::Document *m_document;
};
void SignUnsignedFieldTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
Okular::SettingsCore::instance(QStringLiteral("signunsignedfieldtest"));
KConfig cfg(QStringLiteral("okular-generator-popplerrc"));
KConfigGroup g = cfg.group(QStringLiteral("Signatures"));
g.writeEntry(QStringLiteral("UseDefaultCertDB"), false);
g.writeEntry(QStringLiteral("DBCertificatePath"), "file://" KDESRCDIR "data/fake_okular_certstore");
m_document = new Okular::Document(nullptr);
}
void SignUnsignedFieldTest::init()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/hello_with_dummy_signature.pdf");
QMimeDatabase db;
const QMimeType mime = db.mimeTypeForFile(testFile);
QCOMPARE(m_document->openDocument(testFile, QUrl(), mime), Okular::Document::OpenSuccess);
}
void SignUnsignedFieldTest::cleanup()
{
m_document->closeDocument();
}
void SignUnsignedFieldTest::testSignUnsignedField()
{
const QLinkedList<Okular::FormField *> forms = m_document->page(0)->formFields();
QCOMPARE(forms.count(), 1);
Okular::FormFieldSignature *ffs = dynamic_cast<Okular::FormFieldSignature *>(forms.first());
// This is a hacky way of doing ifdef HAVE_POPPLER_22_02
if (m_document->metaData(QStringLiteral("CanSignDocumentWithPassword")).toString() == QLatin1String("yes")) {
QCOMPARE(ffs->signatureType(), Okular::FormFieldSignature::UnsignedSignature);
const Okular::CertificateStore *certStore = m_document->certificateStore();
bool userCancelled, nonDateValidCerts;
{
EnterPasswordDialogHelper helper;
const QList<Okular::CertificateInfo *> &certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts);
QCOMPARE(certs.count(), 1);
}
Okular::NewSignatureData data;
data.setCertNickname(QStringLiteral("fake-okular"));
QTemporaryFile f;
f.open();
QVERIFY(ffs->sign(data, f.fileName()));
m_document->closeDocument();
QMimeDatabase db;
const QMimeType mime = db.mimeTypeForFile(f.fileName());
QCOMPARE(m_document->openDocument(f.fileName(), QUrl(), mime), Okular::Document::OpenSuccess);
const QLinkedList<Okular::FormField *> newForms = m_document->page(0)->formFields();
QCOMPARE(newForms.count(), 1);
ffs = dynamic_cast<Okular::FormFieldSignature *>(newForms.first());
QCOMPARE(ffs->signatureType(), Okular::FormFieldSignature::AdbePkcs7detached);
QCOMPARE(ffs->signatureInfo().signerName(), "FakeOkular");
}
}
QTEST_MAIN(SignUnsignedFieldTest)
#include "signunsignedfieldtest.moc"

View file

@ -9,6 +9,7 @@
#include "annotations.h"
#include "area.h"
#include "document.h"
#include "okularcore_export.h"
#include "signatureutils.h"
@ -456,7 +457,13 @@ public:
/**
* The types of signature.
*/
enum SignatureType { AdbePkcs7sha1, AdbePkcs7detached, EtsiCAdESdetached, UnknownType };
enum SignatureType {
AdbePkcs7sha1,
AdbePkcs7detached,
EtsiCAdESdetached,
UnknownType,
UnsignedSignature ///< The signature field has not been signed yet. @since 22.04
};
~FormFieldSignature() override;
@ -470,6 +477,13 @@ public:
*/
virtual const SignatureInfo &signatureInfo() const = 0;
/**
Signs a field of UnsignedSignature type.
@since 22.04
*/
virtual bool sign(const NewSignatureData &data, const QString &newPath) const = 0;
protected:
FormFieldSignature();

View file

@ -60,6 +60,7 @@ check_cxx_source_compiles("
#include <poppler-form.h>
int main()
{
auto us = Poppler::FormFieldSignature::UnsignedSignature;
Poppler::PDFConverter::NewSignatureData pData;
pData.setDocumentOwnerPassword(QByteArray());
}

View file

@ -10,6 +10,7 @@
#include "core/action.h"
#include "generator_pdf.h"
#include "pdfsettings.h"
#include "pdfsignatureutils.h"
@ -453,6 +454,10 @@ PopplerFormFieldSignature::SignatureType PopplerFormFieldSignature::signatureTyp
return Okular::FormFieldSignature::AdbePkcs7detached;
case Poppler::FormFieldSignature::EtsiCAdESdetached:
return Okular::FormFieldSignature::EtsiCAdESdetached;
#ifdef HAVE_POPPLER_22_02
case Poppler::FormFieldSignature::UnsignedSignature:
return Okular::FormFieldSignature::UnsignedSignature;
#endif
default:
return Okular::FormFieldSignature::UnknownType;
}
@ -462,3 +467,16 @@ const Okular::SignatureInfo &PopplerFormFieldSignature::signatureInfo() const
{
return *m_info;
}
bool PopplerFormFieldSignature::sign(const Okular::NewSignatureData &oData, const QString &newPath) const
{
#ifdef HAVE_POPPLER_22_02
Poppler::PDFConverter::NewSignatureData pData;
PDFGenerator::okularToPoppler(oData, &pData);
return m_field->sign(newPath, pData) == Poppler::FormFieldSignature::SigningSuccess;
#else
Q_UNUSED(oData)
Q_UNUSED(newPath)
return false;
#endif
}

View file

@ -139,6 +139,7 @@ public:
// inherited from Okular::FormFieldSignature
SignatureType signatureType() const override;
const Okular::SignatureInfo &signatureInfo() const override;
bool sign(const Okular::NewSignatureData &oData, const QString &newPath) const override;
private:
std::unique_ptr<Poppler::FormFieldSignature> m_field;

View file

@ -1312,6 +1312,28 @@ QByteArray PDFGenerator::requestFontData(const Okular::FontInfo &font)
return pdfdoc->fontData(fi);
}
#ifdef HAVE_POPPLER_SIGNING
void PDFGenerator::okularToPoppler(const Okular::NewSignatureData &oData, Poppler::PDFConverter::NewSignatureData *pData)
{
pData->setCertNickname(oData.certNickname());
pData->setPassword(oData.password());
pData->setPage(oData.page());
const QString datetime = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss t"));
pData->setSignatureText(i18n("Signed by: %1\n\nDate: %2", oData.certSubjectCommonName(), datetime));
#ifdef HAVE_POPPLER_FANCY_SIGNATURE
pData->setSignatureLeftText(oData.certSubjectCommonName());
#endif
const Okular::NormalizedRect bRect = oData.boundingRectangle();
pData->setBoundingRectangle({bRect.left, bRect.top, bRect.width(), bRect.height()});
pData->setFontColor(Qt::black);
pData->setBorderColor(Qt::black);
#ifdef HAVE_POPPLER_22_02
pData->setDocumentOwnerPassword(oData.documentPassword().toLatin1());
pData->setDocumentUserPassword(oData.documentPassword().toLatin1());
#endif
}
#endif
#define DUMMY_QPRINTER_COPY
Okular::Document::PrintError PDFGenerator::print(QPrinter &printer)
{
@ -1954,22 +1976,7 @@ bool PDFGenerator::sign(const Okular::NewSignatureData &oData, const QString &rF
converter->setPDFOptions(converter->pdfOptions() | Poppler::PDFConverter::WithChanges);
Poppler::PDFConverter::NewSignatureData pData;
pData.setCertNickname(oData.certNickname());
pData.setPassword(oData.password());
pData.setPage(oData.page());
const QString datetime = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss t"));
pData.setSignatureText(i18n("Signed by: %1\n\nDate: %2", oData.certSubjectCommonName(), datetime));
#ifdef HAVE_POPPLER_FANCY_SIGNATURE
pData.setSignatureLeftText(oData.certSubjectCommonName());
#endif
const Okular::NormalizedRect bRect = oData.boundingRectangle();
pData.setBoundingRectangle({bRect.left, bRect.top, bRect.width(), bRect.height()});
pData.setFontColor(Qt::black);
pData.setBorderColor(Qt::black);
#ifdef HAVE_POPPLER_22_02
pData.setDocumentOwnerPassword(oData.documentPassword().toLatin1());
pData.setDocumentUserPassword(oData.documentPassword().toLatin1());
#endif
okularToPoppler(oData, &pData);
if (!converter->sign(pData))
return false;

View file

@ -108,6 +108,10 @@ public:
QByteArray requestFontData(const Okular::FontInfo &font) override;
#ifdef HAVE_POPPLER_SIGNING
static void okularToPoppler(const Okular::NewSignatureData &oData, Poppler::PDFConverter::NewSignatureData *pData);
#endif
protected:
SwapBackingFileResult swapBackingFile(QString const &newFileName, QVector<Okular::Page *> &newPagesVector) override;
bool doCloseDocument() override;

View file

@ -9,8 +9,10 @@
*/
#include "formwidgets.h"
#include "pageview.h"
#include "pageviewutils.h"
#include "revisionviewer.h"
#include "signatureguiutils.h"
#include "signaturepropertiesdialog.h"
#include <KLineEdit>
@ -222,7 +224,7 @@ void FormWidgetsController::slotFormButtonsChangedByUndoRedo(int pageNumber, con
emit changed(pageNumber);
}
FormWidgetIface *FormWidgetFactory::createWidget(Okular::FormField *ff, QWidget *parent)
FormWidgetIface *FormWidgetFactory::createWidget(Okular::FormField *ff, PageView *pageView)
{
FormWidgetIface *widget = nullptr;
@ -231,13 +233,13 @@ FormWidgetIface *FormWidgetFactory::createWidget(Okular::FormField *ff, QWidget
Okular::FormFieldButton *ffb = static_cast<Okular::FormFieldButton *>(ff);
switch (ffb->buttonType()) {
case Okular::FormFieldButton::Push:
widget = new PushButtonEdit(ffb, parent);
widget = new PushButtonEdit(ffb, pageView);
break;
case Okular::FormFieldButton::CheckBox:
widget = new CheckBoxEdit(ffb, parent);
widget = new CheckBoxEdit(ffb, pageView);
break;
case Okular::FormFieldButton::Radio:
widget = new RadioButtonEdit(ffb, parent);
widget = new RadioButtonEdit(ffb, pageView);
break;
default:;
}
@ -247,13 +249,13 @@ FormWidgetIface *FormWidgetFactory::createWidget(Okular::FormField *ff, QWidget
Okular::FormFieldText *fft = static_cast<Okular::FormFieldText *>(ff);
switch (fft->textType()) {
case Okular::FormFieldText::Multiline:
widget = new TextAreaEdit(fft, parent);
widget = new TextAreaEdit(fft, pageView);
break;
case Okular::FormFieldText::Normal:
widget = new FormLineEdit(fft, parent);
widget = new FormLineEdit(fft, pageView);
break;
case Okular::FormFieldText::FileSelect:
widget = new FileEdit(fft, parent);
widget = new FileEdit(fft, pageView);
break;
}
break;
@ -262,10 +264,10 @@ FormWidgetIface *FormWidgetFactory::createWidget(Okular::FormField *ff, QWidget
Okular::FormFieldChoice *ffc = static_cast<Okular::FormFieldChoice *>(ff);
switch (ffc->choiceType()) {
case Okular::FormFieldChoice::ListBox:
widget = new ListEdit(ffc, parent);
widget = new ListEdit(ffc, pageView);
break;
case Okular::FormFieldChoice::ComboBox:
widget = new ComboEdit(ffc, parent);
widget = new ComboEdit(ffc, pageView);
break;
}
break;
@ -273,7 +275,7 @@ FormWidgetIface *FormWidgetFactory::createWidget(Okular::FormField *ff, QWidget
case Okular::FormField::FormSignature: {
Okular::FormFieldSignature *ffs = static_cast<Okular::FormFieldSignature *>(ff);
if (ffs->isVisible() && ffs->signatureType() != Okular::FormFieldSignature::UnknownType)
widget = new SignatureEdit(ffs, parent);
widget = new SignatureEdit(ffs, pageView);
break;
}
default:;
@ -363,8 +365,8 @@ void FormWidgetIface::slotRefresh(Okular::FormField *form)
m_widget->setEnabled(!form->isReadOnly());
}
PushButtonEdit::PushButtonEdit(Okular::FormFieldButton *button, QWidget *parent)
: QPushButton(parent)
PushButtonEdit::PushButtonEdit(Okular::FormFieldButton *button, PageView *pageView)
: QPushButton(pageView->viewport())
, FormWidgetIface(this, button)
{
setText(button->caption());
@ -377,8 +379,8 @@ PushButtonEdit::PushButtonEdit(Okular::FormFieldButton *button, QWidget *parent)
setCursor(Qt::ArrowCursor);
}
CheckBoxEdit::CheckBoxEdit(Okular::FormFieldButton *button, QWidget *parent)
: QCheckBox(parent)
CheckBoxEdit::CheckBoxEdit(Okular::FormFieldButton *button, PageView *pageView)
: QCheckBox(pageView->viewport())
, FormWidgetIface(this, button)
{
setText(button->caption());
@ -418,8 +420,8 @@ void CheckBoxEdit::slotRefresh(Okular::FormField *form)
}
}
RadioButtonEdit::RadioButtonEdit(Okular::FormFieldButton *button, QWidget *parent)
: QRadioButton(parent)
RadioButtonEdit::RadioButtonEdit(Okular::FormFieldButton *button, PageView *pageView)
: QRadioButton(pageView->viewport())
, FormWidgetIface(this, button)
{
setText(button->caption());
@ -436,8 +438,8 @@ void RadioButtonEdit::setFormWidgetsController(FormWidgetsController *controller
setChecked(form->state());
}
FormLineEdit::FormLineEdit(Okular::FormFieldText *text, QWidget *parent)
: QLineEdit(parent)
FormLineEdit::FormLineEdit(Okular::FormFieldText *text, PageView *pageView)
: QLineEdit(pageView->viewport())
, FormWidgetIface(this, text)
{
int maxlen = text->maximumLength();
@ -594,8 +596,8 @@ void FormLineEdit::slotRefresh(Okular::FormField *form)
setText(text->text());
}
TextAreaEdit::TextAreaEdit(Okular::FormFieldText *text, QWidget *parent)
: KTextEdit(parent)
TextAreaEdit::TextAreaEdit(Okular::FormFieldText *text, PageView *pageView)
: KTextEdit(pageView->viewport())
, FormWidgetIface(this, text)
{
setAcceptRichText(text->isRichText());
@ -730,8 +732,8 @@ void TextAreaEdit::slotRefresh(Okular::FormField *form)
setPlainText(text->text());
}
FileEdit::FileEdit(Okular::FormFieldText *text, QWidget *parent)
: KUrlRequester(parent)
FileEdit::FileEdit(Okular::FormFieldText *text, PageView *pageView)
: KUrlRequester(pageView->viewport())
, FormWidgetIface(this, text)
{
setMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly);
@ -839,8 +841,8 @@ void FileEdit::slotHandleFileChangedByUndoRedo(int pageNumber, Okular::FormField
setFocus();
}
ListEdit::ListEdit(Okular::FormFieldChoice *choice, QWidget *parent)
: QListWidget(parent)
ListEdit::ListEdit(Okular::FormFieldChoice *choice, PageView *pageView)
: QListWidget(pageView->viewport())
, FormWidgetIface(this, choice)
{
addItems(choice->choices());
@ -900,8 +902,8 @@ void ListEdit::slotHandleFormListChangedByUndoRedo(int pageNumber, Okular::FormF
setFocus();
}
ComboEdit::ComboEdit(Okular::FormFieldChoice *choice, QWidget *parent)
: QComboBox(parent)
ComboEdit::ComboEdit(Okular::FormFieldChoice *choice, PageView *pageView)
: QComboBox(pageView->viewport())
, FormWidgetIface(this, choice)
{
addItems(choice->choices());
@ -1035,15 +1037,20 @@ bool ComboEdit::event(QEvent *e)
return QComboBox::event(e);
}
SignatureEdit::SignatureEdit(Okular::FormFieldSignature *signature, QWidget *parent)
: QAbstractButton(parent)
SignatureEdit::SignatureEdit(Okular::FormFieldSignature *signature, PageView *pageView)
: QAbstractButton(pageView->viewport())
, FormWidgetIface(this, signature)
, m_widgetPressed(false)
, m_dummyMode(false)
, m_wasVisible(false)
{
setCursor(Qt::PointingHandCursor);
connect(this, &SignatureEdit::clicked, this, &SignatureEdit::slotViewProperties);
if (signature->signatureType() == Okular::FormFieldSignature::UnsignedSignature) {
setToolTip(i18n("Unsigned Signature Field (Click to Sign)"));
connect(this, &SignatureEdit::clicked, this, &SignatureEdit::signUnsignedSignature);
} else {
connect(this, &SignatureEdit::clicked, this, &SignatureEdit::slotViewProperties);
}
}
void SignatureEdit::setDummyMode(bool set)
@ -1102,9 +1109,16 @@ bool SignatureEdit::event(QEvent *e)
void SignatureEdit::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = new QMenu(this);
QAction *signatureProperties = new QAction(i18n("Signature Properties"), menu);
connect(signatureProperties, &QAction::triggered, this, &SignatureEdit::slotViewProperties);
menu->addAction(signatureProperties);
Okular::FormFieldSignature *formSignature = static_cast<Okular::FormFieldSignature *>(formField());
if (formSignature->signatureType() == Okular::FormFieldSignature::UnsignedSignature) {
QAction *signAction = new QAction(i18n("&Sign..."), menu);
connect(signAction, &QAction::triggered, this, &SignatureEdit::signUnsignedSignature);
menu->addAction(signAction);
} else {
QAction *signatureProperties = new QAction(i18n("Signature Properties"), menu);
connect(signatureProperties, &QAction::triggered, this, &SignatureEdit::slotViewProperties);
menu->addAction(signatureProperties);
}
menu->exec(event->globalPos());
delete menu;
}
@ -1139,6 +1153,16 @@ void SignatureEdit::slotViewProperties()
propDlg.exec();
}
void SignatureEdit::signUnsignedSignature()
{
if (m_dummyMode)
return;
Okular::FormFieldSignature *formSignature = static_cast<Okular::FormFieldSignature *>(formField());
PageView *pageView = static_cast<PageView *>(parent()->parent());
SignatureGuiUtils::signUnsignedSignature(formSignature, pageView, pageView->document());
}
// Code for additional action handling.
// Challenge: Change preprocessor magic to C++ magic!
//

View file

@ -26,6 +26,7 @@ class ComboEdit;
class QMenu;
class QButtonGroup;
class FormWidgetIface;
class PageView;
class PageViewItem;
class RadioButtonEdit;
class QEvent;
@ -121,7 +122,7 @@ private:
class FormWidgetFactory
{
public:
static FormWidgetIface *createWidget(Okular::FormField *ff, QWidget *parent = nullptr);
static FormWidgetIface *createWidget(Okular::FormField *ff, PageView *pageView);
};
class FormWidgetIface
@ -171,7 +172,7 @@ class PushButtonEdit : public QPushButton, public FormWidgetIface
Q_OBJECT
public:
explicit PushButtonEdit(Okular::FormFieldButton *button, QWidget *parent = nullptr);
explicit PushButtonEdit(Okular::FormFieldButton *button, PageView *pageView);
DECLARE_ADDITIONAL_ACTIONS
};
@ -181,7 +182,7 @@ class CheckBoxEdit : public QCheckBox, public FormWidgetIface
Q_OBJECT
public:
explicit CheckBoxEdit(Okular::FormFieldButton *button, QWidget *parent = nullptr);
explicit CheckBoxEdit(Okular::FormFieldButton *button, PageView *pageView);
// reimplemented from FormWidgetIface
void setFormWidgetsController(FormWidgetsController *controller) override;
@ -198,7 +199,7 @@ class RadioButtonEdit : public QRadioButton, public FormWidgetIface
Q_OBJECT
public:
explicit RadioButtonEdit(Okular::FormFieldButton *button, QWidget *parent = nullptr);
explicit RadioButtonEdit(Okular::FormFieldButton *button, PageView *pageView);
// reimplemented from FormWidgetIface
void setFormWidgetsController(FormWidgetsController *controller) override;
@ -210,7 +211,7 @@ class FormLineEdit : public QLineEdit, public FormWidgetIface
Q_OBJECT
public:
explicit FormLineEdit(Okular::FormFieldText *text, QWidget *parent = nullptr);
explicit FormLineEdit(Okular::FormFieldText *text, PageView *pageView);
void setFormWidgetsController(FormWidgetsController *controller) override;
bool event(QEvent *e) override;
void contextMenuEvent(QContextMenuEvent *event) override;
@ -235,7 +236,7 @@ class TextAreaEdit : public KTextEdit, public FormWidgetIface
Q_OBJECT
public:
explicit TextAreaEdit(Okular::FormFieldText *text, QWidget *parent = nullptr);
explicit TextAreaEdit(Okular::FormFieldText *text, PageView *pageView);
~TextAreaEdit() override;
void setFormWidgetsController(FormWidgetsController *controller) override;
bool event(QEvent *e) override;
@ -262,7 +263,7 @@ class FileEdit : public KUrlRequester, public FormWidgetIface
Q_OBJECT
public:
explicit FileEdit(Okular::FormFieldText *text, QWidget *parent = nullptr);
explicit FileEdit(Okular::FormFieldText *text, PageView *pageView);
void setFormWidgetsController(FormWidgetsController *controller) override;
protected:
@ -283,7 +284,7 @@ class ListEdit : public QListWidget, public FormWidgetIface
Q_OBJECT
public:
explicit ListEdit(Okular::FormFieldChoice *choice, QWidget *parent = nullptr);
explicit ListEdit(Okular::FormFieldChoice *choice, PageView *pageView);
void setFormWidgetsController(FormWidgetsController *controller) override;
private Q_SLOTS:
@ -297,7 +298,7 @@ class ComboEdit : public QComboBox, public FormWidgetIface
Q_OBJECT
public:
explicit ComboEdit(Okular::FormFieldChoice *choice, QWidget *parent = nullptr);
explicit ComboEdit(Okular::FormFieldChoice *choice, PageView *pageView);
void setFormWidgetsController(FormWidgetsController *controller) override;
bool event(QEvent *e) override;
void contextMenuEvent(QContextMenuEvent *event) override;
@ -317,7 +318,7 @@ class SignatureEdit : public QAbstractButton, public FormWidgetIface
Q_OBJECT
public:
explicit SignatureEdit(Okular::FormFieldSignature *signature, QWidget *parent = nullptr);
explicit SignatureEdit(Okular::FormFieldSignature *signature, PageView *pageView);
// This will be called when an item in signature panel is clicked. Calling it changes the
// widget state. If this widget was visible prior to calling this then background
@ -332,6 +333,7 @@ protected:
private Q_SLOTS:
void slotViewProperties();
void signUnsignedSignature();
private:
bool m_widgetPressed;

View file

@ -1252,7 +1252,7 @@ void PageView::notifySetup(const QVector<Okular::Page *> &pageSet, int setupFlag
#endif
const QLinkedList<Okular::FormField *> pageFields = page->formFields();
for (Okular::FormField *ff : pageFields) {
FormWidgetIface *w = FormWidgetFactory::createWidget(ff, viewport());
FormWidgetIface *w = FormWidgetFactory::createWidget(ff, this);
if (w) {
w->setPageItem(item);
w->setFormWidgetsController(d->formWidgetsController());
@ -4894,6 +4894,11 @@ void PageView::showNoSigningCertificatesDialog(bool nonDateValidCerts)
}
}
Okular::Document *PageView::document() const
{
return d->document;
}
void PageView::slotSignature()
{
if (!d->document->isHistoryClean()) {

View file

@ -117,6 +117,8 @@ public:
void showNoSigningCertificatesDialog(bool nonDateValidCerts);
Okular::Document *document() const;
public Q_SLOTS:
void copyTextSelection() const;
void selectAll();

View file

@ -17,7 +17,6 @@
#include <QInputDialog>
#include <QList>
#include <QLoggingCategory>
#include <QMimeDatabase>
#include <QPainter>
#include <QSet>
#include <QVariant>
@ -45,6 +44,7 @@
#include "guiutils.h"
#include "pageview.h"
#include "settings.h"
#include "signatureguiutils.h"
/** @short PickPointEngine */
class PickPointEngine : public AnnotatorEngine
@ -354,75 +354,19 @@ public:
}
}
const Okular::CertificateStore *certStore = m_document->certificateStore();
bool userCancelled, nonDateValidCerts;
const QList<Okular::CertificateInfo *> &certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts);
if (userCancelled) {
const std::unique_ptr<Okular::CertificateInfo> cert = SignatureGuiUtils::getCertificateAndPasswordForSigning(m_pageView, m_document, &passToUse, &documentPassword);
if (!cert) {
m_aborted = true;
return {};
}
if (certs.isEmpty()) {
m_creationCompleted = false;
clicked = false;
m_pageView->showNoSigningCertificatesDialog(nonDateValidCerts);
return {};
}
QStringList items;
QHash<QString, Okular::CertificateInfo *> nickToCert;
for (auto cert : certs) {
items.append(cert->nickName());
nickToCert[cert->nickName()] = cert;
}
bool resok = false;
certNicknameToUse = QInputDialog::getItem(m_pageView, i18n("Select certificate to sign with"), i18n("Certificates:"), items, 0, false, &resok);
if (resok) {
// I could not find any case in which i need to enter a password to use the certificate, seems that once you unlcok the firefox/NSS database
// you don't need a password anymore, but still there's code to do that in NSS so we have code to ask for it if needed. What we do is
// ask if the empty password is fine, if it is we don't ask the user anything, if it's not, we ask for a password
Okular::CertificateInfo *cert = nickToCert.value(certNicknameToUse);
bool passok = cert->checkPassword(QString());
while (!passok) {
const QString title = i18n("Enter password (if any) to unlock certificate: %1", certNicknameToUse);
bool ok;
passToUse = QInputDialog::getText(m_pageView, i18n("Enter certificate password"), title, QLineEdit::Password, QString(), &ok);
if (ok) {
passok = cert->checkPassword(passToUse);
} else {
passok = false;
break;
}
}
if (passok) {
certCommonName = cert->subjectInfo(Okular::CertificateInfo::CommonName);
} else {
certNicknameToUse.clear();
m_aborted = true;
}
passToUse.clear();
documentPassword.clear();
} else {
// The Cancel button has been clicked in the certificate dialog.
certNicknameToUse.clear();
m_aborted = true;
}
if (m_document->metaData(QStringLiteral("DocumentHasPassword")).toString() == QLatin1String("yes")) {
bool ok;
documentPassword = QInputDialog::getText(m_pageView, i18n("Enter document password"), i18n("Enter document password"), QLineEdit::Password, QString(), &ok);
if (!ok) {
passToUse.clear();
m_aborted = true;
}
certNicknameToUse = cert->nickName();
certCommonName = cert->subjectInfo(Okular::CertificateInfo::CommonName);
}
m_creationCompleted = false;
clicked = false;
qDeleteAll(certs);
return {};
}
@ -1041,18 +985,7 @@ QRect PageViewAnnotator::performRouteMouseOrTabletEvent(const AnnotatorEngine::E
if (signatureMode()) {
auto signEngine = static_cast<PickPointEngineSignature *>(m_engine);
if (signEngine->isAccepted()) {
QMimeDatabase db;
const QString typeName = m_document->documentInfo().get(Okular::DocumentInfo::MimeType);
const QMimeType mimeType = db.mimeTypeForName(typeName);
const QString mimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' ')));
const QUrl currentFileUrl = m_document->currentDocument();
const QFileInfo currentFileInfo(currentFileUrl.fileName());
const QString localFilePathIfAny = currentFileUrl.isLocalFile() ? QFileInfo(currentFileUrl.path()).canonicalPath() + QLatin1Char('/') : QString();
const QString newFileName =
localFilePathIfAny + i18nc("Used when suggesting a new name for a digitally signed file. %1 is the old file name and %2 it's extension", "%1_signed.%2", currentFileInfo.baseName(), currentFileInfo.completeSuffix());
const QString newFilePath = QFileDialog::getSaveFileName(m_pageView, i18n("Save Signed File As"), newFileName, mimeTypeFilter);
const QString newFilePath = SignatureGuiUtils::getFileNameForNewSignedFile(m_pageView, m_document);
if (!newFilePath.isEmpty()) {
const bool success = static_cast<PickPointEngineSignature *>(m_engine)->sign(newFilePath);

View file

@ -1589,14 +1589,22 @@ bool Part::openFile()
} else {
const QVector<const Okular::FormFieldSignature *> signatureFormFields = SignatureGuiUtils::getSignatureFormFields(m_document);
bool allSignaturesValid = true;
bool anySignatureUnsigned = false;
for (const Okular::FormFieldSignature *signature : signatureFormFields) {
const Okular::SignatureInfo &info = signature->signatureInfo();
if (info.signatureStatus() != SignatureInfo::SignatureValid) {
allSignaturesValid = false;
if (signature->signatureType() == Okular::FormFieldSignature::UnsignedSignature) {
anySignatureUnsigned = true;
} else {
const Okular::SignatureInfo &info = signature->signatureInfo();
if (info.signatureStatus() != SignatureInfo::SignatureValid) {
allSignaturesValid = false;
}
}
}
if (allSignaturesValid) {
if (anySignatureUnsigned) {
m_signatureMessage->setMessageType(KMessageWidget::Information);
m_signatureMessage->setText(i18n("This document has unsigned signature fields."));
} else if (allSignaturesValid) {
if (signatureFormFields.last()->signatureInfo().signsTotalDocument()) {
m_signatureMessage->setMessageType(KMessageWidget::Information);
m_signatureMessage->setText(i18n("This document is digitally signed."));

View file

@ -6,11 +6,18 @@
#include "signatureguiutils.h"
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QMimeDatabase>
#include <KLocalizedString>
#include <KMessageBox>
#include "core/document.h"
#include "core/form.h"
#include "core/page.h"
#include "pageview.h"
namespace SignatureGuiUtils
{
@ -145,4 +152,106 @@ QString getReadableKeyUsageNewLineSeparated(Okular::CertificateInfo::KeyUsageExt
return getReadableKeyUsage(kuExtensions, QStringLiteral("\n"));
}
std::unique_ptr<Okular::CertificateInfo> getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword)
{
const Okular::CertificateStore *certStore = doc->certificateStore();
bool userCancelled, nonDateValidCerts;
QList<Okular::CertificateInfo *> certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts);
if (userCancelled) {
return nullptr;
}
if (certs.isEmpty()) {
pageView->showNoSigningCertificatesDialog(nonDateValidCerts);
return nullptr;
}
QStringList items;
QHash<QString, Okular::CertificateInfo *> nickToCert;
for (auto cert : qAsConst(certs)) {
items.append(cert->nickName());
nickToCert[cert->nickName()] = cert;
}
bool resok = false;
const QString certNicknameToUse = QInputDialog::getItem(pageView, i18n("Select certificate to sign with"), i18n("Certificates:"), items, 0, false, &resok);
if (!resok) {
qDeleteAll(certs);
return nullptr;
}
// I could not find any case in which i need to enter a password to use the certificate, seems that once you unlcok the firefox/NSS database
// you don't need a password anymore, but still there's code to do that in NSS so we have code to ask for it if needed. What we do is
// ask if the empty password is fine, if it is we don't ask the user anything, if it's not, we ask for a password
Okular::CertificateInfo *cert = nickToCert.value(certNicknameToUse);
bool passok = cert->checkPassword(*password);
while (!passok) {
const QString title = i18n("Enter password (if any) to unlock certificate: %1", certNicknameToUse);
bool ok;
*password = QInputDialog::getText(pageView, i18n("Enter certificate password"), title, QLineEdit::Password, QString(), &ok);
if (ok) {
passok = cert->checkPassword(*password);
} else {
passok = false;
break;
}
}
if (doc->metaData(QStringLiteral("DocumentHasPassword")).toString() == QLatin1String("yes")) {
*documentPassword = QInputDialog::getText(pageView, i18n("Enter document password"), i18n("Enter document password"), QLineEdit::Password, QString(), &passok);
}
if (passok) {
certs.removeOne(cert);
}
qDeleteAll(certs);
return passok ? std::unique_ptr<Okular::CertificateInfo>(cert) : std::unique_ptr<Okular::CertificateInfo>();
}
QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc)
{
QMimeDatabase db;
const QString typeName = doc->documentInfo().get(Okular::DocumentInfo::MimeType);
const QMimeType mimeType = db.mimeTypeForName(typeName);
const QString mimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' ')));
const QUrl currentFileUrl = doc->currentDocument();
const QFileInfo currentFileInfo(currentFileUrl.fileName());
const QString localFilePathIfAny = currentFileUrl.isLocalFile() ? QFileInfo(currentFileUrl.path()).canonicalPath() + QLatin1Char('/') : QString();
const QString newFileName =
localFilePathIfAny + i18nc("Used when suggesting a new name for a digitally signed file. %1 is the old file name and %2 it's extension", "%1_signed.%2", currentFileInfo.baseName(), currentFileInfo.completeSuffix());
return QFileDialog::getSaveFileName(pageView, i18n("Save Signed File As"), newFileName, mimeTypeFilter);
}
void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc)
{
Q_ASSERT(form && form->signatureType() == Okular::FormFieldSignature::UnsignedSignature);
QString password, documentPassword;
const std::unique_ptr<Okular::CertificateInfo> cert = SignatureGuiUtils::getCertificateAndPasswordForSigning(pageView, doc, &password, &documentPassword);
if (!cert) {
return;
}
Okular::NewSignatureData data;
data.setCertNickname(cert->nickName());
data.setCertSubjectCommonName(cert->subjectInfo(Okular::CertificateInfo::CommonName));
data.setPassword(password);
data.setDocumentPassword(documentPassword);
password.clear();
documentPassword.clear();
const QString newFilePath = SignatureGuiUtils::getFileNameForNewSignedFile(pageView, doc);
if (!newFilePath.isEmpty()) {
const bool success = form->sign(data, newFilePath);
if (success) {
emit pageView->requestOpenFile(newFilePath, form->page()->number() + 1);
} else {
KMessageBox::error(pageView, i18nc("%1 is a file path", "Could not sign. Invalid certificate password or could not write to '%1'", newFilePath));
}
}
}
}

View file

@ -11,6 +11,10 @@
#include "core/signatureutils.h"
#include <memory>
class PageView;
namespace Okular
{
class Document;
@ -30,6 +34,9 @@ QString getReadablePublicKeyType(Okular::CertificateInfo::PublicKeyType type);
QString getReadableKeyUsageCommaSeparated(Okular::CertificateInfo::KeyUsageExtensions kuExtensions);
QString getReadableKeyUsageNewLineSeparated(Okular::CertificateInfo::KeyUsageExtensions kuExtensions);
std::unique_ptr<Okular::CertificateInfo> getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword);
QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc);
void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc);
}
#endif

View file

@ -124,32 +124,43 @@ void SignatureModelPrivate::notifySetup(const QVector<Okular::Page *> &pages, in
}
int revNumber = 1;
int unsignedSignatureNumber = 1;
const QVector<const Okular::FormFieldSignature *> signatureFormFields = SignatureGuiUtils::getSignatureFormFields(document);
for (const Okular::FormFieldSignature *sf : signatureFormFields) {
const Okular::SignatureInfo &info = sf->signatureInfo();
const int pageNumber = sf->page()->number();
// based on whether or not signature form is a nullptr it is decided if clicking on an item should change the viewport.
auto *parentItem = new SignatureItem(root, sf, SignatureItem::RevisionInfo, pageNumber);
parentItem->displayString = i18n("Rev. %1: Signed By %2", revNumber, info.signerName());
if (sf->signatureType() == Okular::FormFieldSignature::UnsignedSignature) {
auto *parentItem = new SignatureItem(root, sf, SignatureItem::RevisionInfo, pageNumber);
parentItem->displayString = i18n("Unsigned Signature %1", unsignedSignatureNumber);
auto childItem1 = new SignatureItem(parentItem, nullptr, SignatureItem::ValidityStatus, pageNumber);
childItem1->displayString = SignatureGuiUtils::getReadableSignatureStatus(info.signatureStatus());
auto childItem = new SignatureItem(parentItem, sf, SignatureItem::FieldInfo, pageNumber);
childItem->displayString = i18n("Field: %1 on page %2", sf->name(), pageNumber + 1);
auto childItem2 = new SignatureItem(parentItem, nullptr, SignatureItem::SigningTime, pageNumber);
childItem2->displayString = i18n("Signing Time: %1", info.signingTime().toString(Qt::DefaultLocaleLongDate));
++unsignedSignatureNumber;
} else {
const Okular::SignatureInfo &info = sf->signatureInfo();
const QString reason = info.reason();
if (!reason.isEmpty()) {
auto childItem3 = new SignatureItem(parentItem, nullptr, SignatureItem::Reason, pageNumber);
childItem3->displayString = i18n("Reason: %1", reason);
// based on whether or not signature form is a nullptr it is decided if clicking on an item should change the viewport.
auto *parentItem = new SignatureItem(root, sf, SignatureItem::RevisionInfo, pageNumber);
parentItem->displayString = i18n("Rev. %1: Signed By %2", revNumber, info.signerName());
auto childItem1 = new SignatureItem(parentItem, nullptr, SignatureItem::ValidityStatus, pageNumber);
childItem1->displayString = SignatureGuiUtils::getReadableSignatureStatus(info.signatureStatus());
auto childItem2 = new SignatureItem(parentItem, nullptr, SignatureItem::SigningTime, pageNumber);
childItem2->displayString = i18n("Signing Time: %1", info.signingTime().toString(Qt::DefaultLocaleLongDate));
const QString reason = info.reason();
if (!reason.isEmpty()) {
auto childItem3 = new SignatureItem(parentItem, nullptr, SignatureItem::Reason, pageNumber);
childItem3->displayString = i18n("Reason: %1", reason);
}
auto childItem4 = new SignatureItem(parentItem, sf, SignatureItem::FieldInfo, pageNumber);
childItem4->displayString = i18n("Field: %1 on page %2", sf->name(), pageNumber + 1);
++revNumber;
}
auto childItem4 = new SignatureItem(parentItem, sf, SignatureItem::FieldInfo, pageNumber);
childItem4->displayString = i18n("Field: %1 on page %2", sf->name(), pageNumber + 1);
++revNumber;
}
q->endResetModel();
}

View file

@ -91,9 +91,15 @@ void SignaturePanel::slotShowContextMenu()
return;
QMenu *menu = new QMenu(this);
QAction *sigProp = new QAction(i18n("Properties"), menu);
connect(sigProp, &QAction::triggered, this, &SignaturePanel::slotViewProperties);
menu->addAction(sigProp);
if (d->m_currentForm->signatureType() == Okular::FormFieldSignature::UnsignedSignature) {
QAction *signAction = new QAction(i18n("&Sign..."), menu);
connect(signAction, &QAction::triggered, this, &SignaturePanel::signUnsignedSignature);
menu->addAction(signAction);
} else {
QAction *sigProp = new QAction(i18n("Properties"), menu);
connect(sigProp, &QAction::triggered, this, &SignaturePanel::slotViewProperties);
menu->addAction(sigProp);
}
menu->exec(QCursor::pos());
delete menu;
}
@ -105,6 +111,12 @@ void SignaturePanel::slotViewProperties()
propDlg.exec();
}
void SignaturePanel::signUnsignedSignature()
{
Q_D(SignaturePanel);
SignatureGuiUtils::signUnsignedSignature(d->m_currentForm, d->m_pageView, d->m_document);
}
void SignaturePanel::notifySetup(const QVector<Okular::Page *> & /*pages*/, int setupFlags)
{
if (!(setupFlags & Okular::DocumentObserver::UrlChanged))

View file

@ -40,6 +40,7 @@ private Q_SLOTS:
void activated(const QModelIndex &);
void slotShowContextMenu();
void slotViewProperties();
void signUnsignedSignature();
private:
Q_DECLARE_PRIVATE(SignaturePanel)