From a8e5f6e9f75fc5101608364a3e62495b77fc0818 Mon Sep 17 00:00:00 2001 From: Albert Astals Cid Date: Mon, 3 Jan 2022 15:49:10 +0100 Subject: [PATCH] PDF: Allow signing unsigned signature fields --- autotests/CMakeLists.txt | 5 + autotests/data/fake_okular_certstore/cert9.db | Bin 0 -> 28672 bytes autotests/data/fake_okular_certstore/key4.db | Bin 0 -> 36864 bytes .../data/fake_okular_certstore/pkcs11.txt | 5 + autotests/data/hello_with_dummy_signature.pdf | Bin 0 -> 11379 bytes autotests/signunsignedfieldtest.cpp | 123 ++++++++++++++++++ core/form.h | 16 ++- generators/poppler/CMakeLists.txt | 1 + generators/poppler/formfields.cpp | 18 +++ generators/poppler/formfields.h | 1 + generators/poppler/generator_pdf.cpp | 39 +++--- generators/poppler/generator_pdf.h | 4 + part/formwidgets.cpp | 88 ++++++++----- part/formwidgets.h | 22 ++-- part/pageview.cpp | 7 +- part/pageview.h | 2 + part/pageviewannotator.cpp | 83 ++---------- part/part.cpp | 16 ++- part/signatureguiutils.cpp | 109 ++++++++++++++++ part/signatureguiutils.h | 7 + part/signaturemodel.cpp | 45 ++++--- part/signaturepanel.cpp | 18 ++- part/signaturepanel.h | 1 + 23 files changed, 451 insertions(+), 159 deletions(-) create mode 100644 autotests/data/fake_okular_certstore/cert9.db create mode 100644 autotests/data/fake_okular_certstore/key4.db create mode 100644 autotests/data/fake_okular_certstore/pkcs11.txt create mode 100644 autotests/data/hello_with_dummy_signature.pdf create mode 100644 autotests/signunsignedfieldtest.cpp diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 6825ce6c0..fd9872cee 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -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 diff --git a/autotests/data/fake_okular_certstore/cert9.db b/autotests/data/fake_okular_certstore/cert9.db new file mode 100644 index 0000000000000000000000000000000000000000..04959dcbb53360181b5e1f4f7d6e251e8f4605e1 GIT binary patch literal 28672 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 MhpdHDjW??JH*-U&I{*Lx literal 0 HcmV?d00001 diff --git a/autotests/data/fake_okular_certstore/key4.db b/autotests/data/fake_okular_certstore/key4.db new file mode 100644 index 0000000000000000000000000000000000000000..4d990e8dda64a8d383af55d83255bf51096f68a6 GIT binary patch literal 36864 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 literal 0 HcmV?d00001 diff --git a/autotests/data/fake_okular_certstore/pkcs11.txt b/autotests/data/fake_okular_certstore/pkcs11.txt new file mode 100644 index 000000000..17374ec6b --- /dev/null +++ b/autotests/data/fake_okular_certstore/pkcs11.txt @@ -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}) + diff --git a/autotests/data/hello_with_dummy_signature.pdf b/autotests/data/hello_with_dummy_signature.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c4d222e4d7ad087b2d3a9cf6f123469aec8984fa GIT binary patch literal 11379 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 literal 0 HcmV?d00001 diff --git a/autotests/signunsignedfieldtest.cpp b/autotests/signunsignedfieldtest.cpp new file mode 100644 index 000000000..7e3de19bf --- /dev/null +++ b/autotests/signunsignedfieldtest.cpp @@ -0,0 +1,123 @@ +/* + SPDX-FileCopyrightText: 2022 Albert Astals Cid + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include +#include +#include +#include +#include +#include + +#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(); + lineEdit->setText(QStringLiteral("fakeokular")); + + QDialogButtonBox *buttonBox = dialog->findChild(); + 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 forms = m_document->page(0)->formFields(); + QCOMPARE(forms.count(), 1); + Okular::FormFieldSignature *ffs = dynamic_cast(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 &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 newForms = m_document->page(0)->formFields(); + QCOMPARE(newForms.count(), 1); + ffs = dynamic_cast(newForms.first()); + QCOMPARE(ffs->signatureType(), Okular::FormFieldSignature::AdbePkcs7detached); + QCOMPARE(ffs->signatureInfo().signerName(), "FakeOkular"); + } +} + +QTEST_MAIN(SignUnsignedFieldTest) +#include "signunsignedfieldtest.moc" diff --git a/core/form.h b/core/form.h index d977cc38d..b841e6cfa 100644 --- a/core/form.h +++ b/core/form.h @@ -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(); diff --git a/generators/poppler/CMakeLists.txt b/generators/poppler/CMakeLists.txt index 82fb63aab..23efdd06b 100644 --- a/generators/poppler/CMakeLists.txt +++ b/generators/poppler/CMakeLists.txt @@ -60,6 +60,7 @@ check_cxx_source_compiles(" #include int main() { + auto us = Poppler::FormFieldSignature::UnsignedSignature; Poppler::PDFConverter::NewSignatureData pData; pData.setDocumentOwnerPassword(QByteArray()); } diff --git a/generators/poppler/formfields.cpp b/generators/poppler/formfields.cpp index da9de82bc..6b9d20a8e 100644 --- a/generators/poppler/formfields.cpp +++ b/generators/poppler/formfields.cpp @@ -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 +} diff --git a/generators/poppler/formfields.h b/generators/poppler/formfields.h index ec0cfaaa2..c9cee593b 100644 --- a/generators/poppler/formfields.h +++ b/generators/poppler/formfields.h @@ -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 m_field; diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp index eac223045..7ba5a0c9e 100644 --- a/generators/poppler/generator_pdf.cpp +++ b/generators/poppler/generator_pdf.cpp @@ -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; diff --git a/generators/poppler/generator_pdf.h b/generators/poppler/generator_pdf.h index 1b7bcc1a6..d58b4a333 100644 --- a/generators/poppler/generator_pdf.h +++ b/generators/poppler/generator_pdf.h @@ -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 &newPagesVector) override; bool doCloseDocument() override; diff --git a/part/formwidgets.cpp b/part/formwidgets.cpp index c29400691..4a911f51b 100644 --- a/part/formwidgets.cpp +++ b/part/formwidgets.cpp @@ -9,8 +9,10 @@ */ #include "formwidgets.h" +#include "pageview.h" #include "pageviewutils.h" #include "revisionviewer.h" +#include "signatureguiutils.h" #include "signaturepropertiesdialog.h" #include @@ -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(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(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(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(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(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(formField()); + PageView *pageView = static_cast(parent()->parent()); + SignatureGuiUtils::signUnsignedSignature(formSignature, pageView, pageView->document()); +} + // Code for additional action handling. // Challenge: Change preprocessor magic to C++ magic! // diff --git a/part/formwidgets.h b/part/formwidgets.h index 89ccb1b57..9e54581a6 100644 --- a/part/formwidgets.h +++ b/part/formwidgets.h @@ -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; diff --git a/part/pageview.cpp b/part/pageview.cpp index ac73069c3..c677711e1 100644 --- a/part/pageview.cpp +++ b/part/pageview.cpp @@ -1252,7 +1252,7 @@ void PageView::notifySetup(const QVector &pageSet, int setupFlag #endif const QLinkedList 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()) { diff --git a/part/pageview.h b/part/pageview.h index 0297ea581..2a942b721 100644 --- a/part/pageview.h +++ b/part/pageview.h @@ -117,6 +117,8 @@ public: void showNoSigningCertificatesDialog(bool nonDateValidCerts); + Okular::Document *document() const; + public Q_SLOTS: void copyTextSelection() const; void selectAll(); diff --git a/part/pageviewannotator.cpp b/part/pageviewannotator.cpp index 4a076e3bf..e15d63d54 100644 --- a/part/pageviewannotator.cpp +++ b/part/pageviewannotator.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -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 &certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts); - if (userCancelled) { + const std::unique_ptr 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 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(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(m_engine)->sign(newFilePath); diff --git a/part/part.cpp b/part/part.cpp index 2c53feaee..57882ef04 100644 --- a/part/part.cpp +++ b/part/part.cpp @@ -1589,14 +1589,22 @@ bool Part::openFile() } else { const QVector 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.")); diff --git a/part/signatureguiutils.cpp b/part/signatureguiutils.cpp index e497038ac..4206e8390 100644 --- a/part/signatureguiutils.cpp +++ b/part/signatureguiutils.cpp @@ -6,11 +6,18 @@ #include "signatureguiutils.h" +#include +#include +#include +#include + #include +#include #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 getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword) +{ + const Okular::CertificateStore *certStore = doc->certificateStore(); + bool userCancelled, nonDateValidCerts; + QList certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts); + if (userCancelled) { + return nullptr; + } + + if (certs.isEmpty()) { + pageView->showNoSigningCertificatesDialog(nonDateValidCerts); + return nullptr; + } + + QStringList items; + QHash 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(cert) : std::unique_ptr(); +} + +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 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)); + } + } +} } diff --git a/part/signatureguiutils.h b/part/signatureguiutils.h index eba06a5db..95fa0bba0 100644 --- a/part/signatureguiutils.h +++ b/part/signatureguiutils.h @@ -11,6 +11,10 @@ #include "core/signatureutils.h" +#include + +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 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 diff --git a/part/signaturemodel.cpp b/part/signaturemodel.cpp index 324b2c7b7..98c54643e 100644 --- a/part/signaturemodel.cpp +++ b/part/signaturemodel.cpp @@ -124,32 +124,43 @@ void SignatureModelPrivate::notifySetup(const QVector &pages, in } int revNumber = 1; + int unsignedSignatureNumber = 1; const QVector 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 childItem4 = new SignatureItem(parentItem, sf, SignatureItem::FieldInfo, pageNumber); - childItem4->displayString = i18n("Field: %1 on page %2", sf->name(), pageNumber + 1); + auto childItem1 = new SignatureItem(parentItem, nullptr, SignatureItem::ValidityStatus, pageNumber); + childItem1->displayString = SignatureGuiUtils::getReadableSignatureStatus(info.signatureStatus()); - ++revNumber; + 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; + } } q->endResetModel(); } diff --git a/part/signaturepanel.cpp b/part/signaturepanel.cpp index 06a779d74..89955be18 100644 --- a/part/signaturepanel.cpp +++ b/part/signaturepanel.cpp @@ -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 & /*pages*/, int setupFlags) { if (!(setupFlags & Okular::DocumentObserver::UrlChanged)) diff --git a/part/signaturepanel.h b/part/signaturepanel.h index 457a50adf..a904a4401 100644 --- a/part/signaturepanel.h +++ b/part/signaturepanel.h @@ -40,6 +40,7 @@ private Q_SLOTS: void activated(const QModelIndex &); void slotShowContextMenu(); void slotViewProperties(); + void signUnsignedSignature(); private: Q_DECLARE_PRIVATE(SignaturePanel)