poppler/Annot.cc | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ poppler/Annot.h | 7 +++++ poppler/Form.cc | 53 ++++++++++++++++++++++++++++++++++++++++- poppler/Form.h | 10 +++++++ qt5/src/poppler-form.cc | 34 +++++++++++++++++++++++++- qt5/src/poppler-form.h | 25 ++++++++++++++++++- qt6/src/poppler-form.cc | 34 +++++++++++++++++++++++++- qt6/src/poppler-form.h | 25 ++++++++++++++++++- utils/pdfsig.cc | 13 +++++++--- 9 files changed, 249 insertions(+), 13 deletions(-)
New commits: commit 277f5de9684b3392f0d585bd36ad1a5e9e9e9ed7 Author: Albert Astals Cid <[email protected]> Date: Tue Jan 4 15:21:10 2022 +0100 [qt] Add FormFieldSignature::sign diff --git a/poppler/Annot.cc b/poppler/Annot.cc index 686d560f..8596e54e 100644 --- a/poppler/Annot.cc +++ b/poppler/Annot.cc @@ -598,6 +598,22 @@ AnnotBorderArray::AnnotBorderArray(Array *array) } } +std::unique_ptr<AnnotBorder> AnnotBorderArray::copy() const +{ + AnnotBorderArray *res = new AnnotBorderArray(); + res->type = type; + res->width = width; + res->dashLength = dashLength; + if (dashLength > 0) { + res->dash = (double *)gmallocn(dashLength, sizeof(double)); + memcpy(res->dash, dash, dashLength * sizeof(double)); + } + res->style = style; + res->horizontalCorner = horizontalCorner; + res->verticalCorner = verticalCorner; + return std::unique_ptr<AnnotBorder>(res); +} + Object AnnotBorderArray::writeToObject(XRef *xref) const { Array *borderArray = new Array(xref); @@ -683,6 +699,20 @@ const char *AnnotBorderBS::getStyleName() const return "S"; } +std::unique_ptr<AnnotBorder> AnnotBorderBS::copy() const +{ + AnnotBorderBS *res = new AnnotBorderBS(); + res->type = type; + res->width = width; + res->dashLength = dashLength; + if (dashLength > 0) { + res->dash = (double *)gmallocn(dashLength, sizeof(double)); + memcpy(res->dash, dash, dashLength * sizeof(double)); + } + res->style = style; + return std::unique_ptr<AnnotBorder>(res); +} + Object AnnotBorderBS::writeToObject(XRef *xref) const { Dict *dict = new Dict(xref); @@ -1072,6 +1102,11 @@ AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) { Object obj1; + if (!dict) { + rotation = 0; + return; + } + obj1 = dict->lookup("R"); if (obj1.isInt()) { rotation = obj1.getInt(); @@ -1125,6 +1160,32 @@ AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) AnnotAppearanceCharacs::~AnnotAppearanceCharacs() = default; +std::unique_ptr<AnnotAppearanceCharacs> AnnotAppearanceCharacs::copy() const +{ + AnnotAppearanceCharacs *res = new AnnotAppearanceCharacs(nullptr); + res->rotation = rotation; + if (borderColor) { + res->borderColor = std::make_unique<AnnotColor>(*borderColor); + } + if (backColor) { + res->backColor = std::make_unique<AnnotColor>(*backColor); + } + if (normalCaption) { + res->normalCaption = std::unique_ptr<GooString>(normalCaption->copy()); + } + if (rolloverCaption) { + res->rolloverCaption = std::unique_ptr<GooString>(rolloverCaption->copy()); + } + if (alternateCaption) { + res->alternateCaption = std::unique_ptr<GooString>(alternateCaption->copy()); + } + if (iconFit) { + res->iconFit = std::make_unique<AnnotIconFit>(*iconFit); + } + res->position = position; + return std::unique_ptr<AnnotAppearanceCharacs>(res); +} + //------------------------------------------------------------------------ // AnnotAppearanceBBox //------------------------------------------------------------------------ diff --git a/poppler/Annot.h b/poppler/Annot.h index e1d211c4..b7a34bb3 100644 --- a/poppler/Annot.h +++ b/poppler/Annot.h @@ -279,6 +279,7 @@ public: virtual AnnotBorderStyle getStyle() const { return style; } virtual Object writeToObject(XRef *xref) const = 0; + virtual std::unique_ptr<AnnotBorder> copy() const = 0; protected: AnnotBorder(); @@ -309,6 +310,8 @@ public: double getHorizontalCorner() const { return horizontalCorner; } double getVerticalCorner() const { return verticalCorner; } + std::unique_ptr<AnnotBorder> copy() const override; + private: AnnotBorderType getType() const override { return typeArray; } Object writeToObject(XRef *xref) const override; @@ -334,6 +337,8 @@ private: const char *getStyleName() const; + std::unique_ptr<AnnotBorder> copy() const override; + // double width; // W (Default 1) (inherited from AnnotBorder) // AnnotBorderStyle style; // S (Default S) (inherited from AnnotBorder) // double *dash; // D (Default [3]) (inherited from AnnotBorder) @@ -511,6 +516,8 @@ public: const AnnotIconFit *getIconFit() { return iconFit.get(); } AnnotAppearanceCharacsTextPos getPosition() const { return position; } + std::unique_ptr<AnnotAppearanceCharacs> copy() const; + protected: int rotation; // R (Default 0) std::unique_ptr<AnnotColor> borderColor; // BC diff --git a/poppler/Form.cc b/poppler/Form.cc index 7dd6f53f..c3624240 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -660,6 +660,49 @@ bool FormWidgetSignature::signDocument(const char *saveFilename, const char *cer #endif } +bool FormWidgetSignature::signDocumentWithAppearance(const char *saveFilename, const char *certNickname, const char *digestName, const char *password, const GooString *reason, const GooString *location, const GooString *ownerPassword, + const GooString *userPassword, const GooString &signatureText, const GooString &signatureTextLeft, double fontSize, std::unique_ptr<AnnotColor> &&fontColor, double borderWidth, + std::unique_ptr<AnnotColor> &&borderColor, std::unique_ptr<AnnotColor> &&backgroundColor) +{ + // Set the appearance + GooString *aux = getField()->getDefaultAppearance(); + std::string originalDefaultAppearance = aux ? aux->toStr() : std::string(); + + const DefaultAppearance da { { objName, "SigFont" }, fontSize, std::move(fontColor) }; + getField()->setDefaultAppearance(da.toAppearanceString()); + + std::unique_ptr<AnnotAppearanceCharacs> origAppearCharacs = getWidgetAnnotation()->getAppearCharacs() ? getWidgetAnnotation()->getAppearCharacs()->copy() : nullptr; + auto appearCharacs = std::make_unique<AnnotAppearanceCharacs>(nullptr); + appearCharacs->setBorderColor(std::move(borderColor)); + appearCharacs->setBackColor(std::move(backgroundColor)); + getWidgetAnnotation()->setAppearCharacs(std::move(appearCharacs)); + + std::unique_ptr<AnnotBorder> origBorderCopy = getWidgetAnnotation()->getBorder() ? getWidgetAnnotation()->getBorder()->copy() : nullptr; + std::unique_ptr<AnnotBorder> border(new AnnotBorderArray()); + border->setWidth(borderWidth); + getWidgetAnnotation()->setBorder(std::move(border)); + + getWidgetAnnotation()->generateFieldAppearance(); + getWidgetAnnotation()->updateAppearanceStream(); + + ::FormFieldSignature *ffs = static_cast<::FormFieldSignature *>(getField()); + ffs->setCustomAppearanceContent(signatureText); + ffs->setCustomAppearanceLeftContent(signatureTextLeft); + + const bool success = signDocument(saveFilename, certNickname, digestName, password, reason, location, ownerPassword, userPassword); + + // Now bring back the annotation appearance back to what it was + ffs->setDefaultAppearance(originalDefaultAppearance); + ffs->setCustomAppearanceContent({}); + ffs->setCustomAppearanceLeftContent({}); + getWidgetAnnotation()->setAppearCharacs(std::move(origAppearCharacs)); + getWidgetAnnotation()->setBorder(std::move(origBorderCopy)); + getWidgetAnnotation()->generateFieldAppearance(); + getWidgetAnnotation()->updateAppearanceStream(); + + return success; +} + // Get start and end file position of objNum in the PDF named filename. bool FormWidgetSignature::getObjectStartEnd(GooString *filename, int objNum, Goffset *objStart, Goffset *objEnd, const GooString *ownerPassword, const GooString *userPassword) { @@ -956,6 +999,12 @@ FormField::FormField(PDFDoc *docA, Object &&aobj, const Ref aref, FormField *par } } +void FormField::setDefaultAppearance(const std::string &appearance) +{ + delete defaultAppearance; + defaultAppearance = new GooString(appearance); +} + void FormField::setPartialName(const GooString &name) { delete partialName; diff --git a/poppler/Form.h b/poppler/Form.h index 0aaf1933..cef04150 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -315,6 +315,11 @@ public: bool signDocument(const char *filename, const char *certNickname, const char *digestName, const char *password, const GooString *reason = nullptr, const GooString *location = nullptr, const GooString *ownerPassword = nullptr, const GooString *userPassword = nullptr); + // Same as above but adds text, font color, etc. + bool signDocumentWithAppearance(const char *filename, const char *certNickname, const char *digestName, const char *password, const GooString *reason = nullptr, const GooString *location = nullptr, + const GooString *ownerPassword = nullptr, const GooString *userPassword = nullptr, const GooString &signatureText = {}, const GooString &signatureTextLeft = {}, double fontSize = {}, + std::unique_ptr<AnnotColor> &&fontColor = {}, double borderWidth = {}, std::unique_ptr<AnnotColor> &&borderColor = {}, std::unique_ptr<AnnotColor> &&backgroundColor = {}); + // checks the length encoding of the signature and returns the hex encoded signature // if the check passed (and the checked file size as output parameter in checkedFileSize) // otherwise a nullptr is returned @@ -355,6 +360,8 @@ public: bool isStandAlone() const { return standAlone; } GooString *getDefaultAppearance() const { return defaultAppearance; } + void setDefaultAppearance(const std::string &appearance); + bool hasTextQuadding() const { return hasQuadding; } VariableTextQuadding getTextQuadding() const { return quadding; } diff --git a/qt5/src/poppler-form.cc b/qt5/src/poppler-form.cc index 9c6042dc..620663fe 100644 --- a/qt5/src/poppler-form.cc +++ b/qt5/src/poppler-form.cc @@ -31,7 +31,7 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "poppler-qt5.h" +#include "poppler-form.h" #include <config.h> @@ -47,7 +47,6 @@ # include <SignatureHandler.h> #endif -#include "poppler-form.h" #include "poppler-page-private.h" #include "poppler-private.h" #include "poppler-annotation-helper.h" @@ -1060,6 +1059,34 @@ SignatureValidationInfo FormFieldSignature::validate(int opt, const QDateTime &v return SignatureValidationInfo(priv); } +FormFieldSignature::SigningResult FormFieldSignature::sign(const QString &outputFileName, const PDFConverter::NewSignatureData &data) const +{ + FormWidgetSignature *fws = static_cast<FormWidgetSignature *>(m_formData->fm); + if (fws->signatureType() != unsigned_signature_field) { + return FieldAlreadySigned; + } + + Goffset file_size = 0; + const std::optional<GooString> sig = fws->getCheckedSignature(&file_size); + if (sig) { + // the above unsigned_signature_field check + // should already catch this, but double check + return FieldAlreadySigned; + } + const auto reason = std::unique_ptr<GooString>(data.reason().isEmpty() ? nullptr : QStringToUnicodeGooString(data.reason())); + const auto location = std::unique_ptr<GooString>(data.location().isEmpty() ? nullptr : QStringToUnicodeGooString(data.location())); + const auto ownerPwd = std::make_unique<GooString>(data.documentOwnerPassword().constData()); + const auto userPwd = std::make_unique<GooString>(data.documentUserPassword().constData()); + const auto gSignatureText = std::unique_ptr<GooString>(QStringToUnicodeGooString(data.signatureText())); + const auto gSignatureLeftText = std::unique_ptr<GooString>(QStringToUnicodeGooString(data.signatureLeftText())); + + const bool success = + fws->signDocumentWithAppearance(outputFileName.toUtf8().constData(), data.certNickname().toUtf8().constData(), "SHA256", data.password().toUtf8().constData(), reason.get(), location.get(), ownerPwd.get(), userPwd.get(), + *gSignatureText, *gSignatureLeftText, data.fontSize(), convertQColor(data.fontColor()), data.borderWidth(), convertQColor(data.borderColor()), convertQColor(data.backgroundColor())); + + return success ? SigningSuccess : GenericSigningError; +} + bool hasNSSSupport() { #ifdef ENABLE_NSS3 diff --git a/qt5/src/poppler-form.h b/qt5/src/poppler-form.h index 600a604b..0e64f2ee 100644 --- a/qt5/src/poppler-form.h +++ b/qt5/src/poppler-form.h @@ -1,6 +1,6 @@ /* poppler-form.h: qt interface to poppler * Copyright (C) 2007-2008, Pino Toscano <[email protected]> - * Copyright (C) 2008, 2011, 2016, 2017, 2019-2021, Albert Astals Cid <[email protected]> + * Copyright (C) 2008, 2011, 2016, 2017, 2019-2022, Albert Astals Cid <[email protected]> * Copyright (C) 2012, Adam Reichold <[email protected]> * Copyright (C) 2016, Hanno Meyer-Thurow <[email protected]> * Copyright (C) 2017, Hans-Ulrich Jüttner <[email protected]> @@ -43,6 +43,7 @@ #include <QtCore/QSharedPointer> #include "poppler-export.h" #include "poppler-annotation.h" +#include "poppler-qt5.h" class Object; class Page; @@ -822,6 +823,25 @@ public: */ SignatureValidationInfo validate(int opt, const QDateTime &validationTime) const; + /** + * \since 22.02 + */ + enum SigningResult + { + FieldAlreadySigned, ///< Trying to sign a field that is already signed + GenericSigningError, + SigningSuccess + }; + + /** + Signs a field of UnsignedSignature type. + + Ignores data.page(), data.fieldPartialName() and data.boundingRectangle() + + \since 22.02 + */ + SigningResult sign(const QString &outputFileName, const PDFConverter::NewSignatureData &data) const; + private: Q_DISABLE_COPY(FormFieldSignature) }; diff --git a/qt6/src/poppler-form.cc b/qt6/src/poppler-form.cc index 2604cfd0..28a1e565 100644 --- a/qt6/src/poppler-form.cc +++ b/qt6/src/poppler-form.cc @@ -31,7 +31,7 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "poppler-qt6.h" +#include "poppler-form.h" #include <config.h> @@ -47,7 +47,6 @@ # include <SignatureHandler.h> #endif -#include "poppler-form.h" #include "poppler-page-private.h" #include "poppler-private.h" #include "poppler-annotation-helper.h" @@ -1060,6 +1059,34 @@ SignatureValidationInfo FormFieldSignature::validate(int opt, const QDateTime &v return SignatureValidationInfo(priv); } +FormFieldSignature::SigningResult FormFieldSignature::sign(const QString &outputFileName, const PDFConverter::NewSignatureData &data) const +{ + FormWidgetSignature *fws = static_cast<FormWidgetSignature *>(m_formData->fm); + if (fws->signatureType() != unsigned_signature_field) { + return FieldAlreadySigned; + } + + Goffset file_size = 0; + const std::optional<GooString> sig = fws->getCheckedSignature(&file_size); + if (sig) { + // the above unsigned_signature_field check + // should already catch this, but double check + return FieldAlreadySigned; + } + const auto reason = std::unique_ptr<GooString>(data.reason().isEmpty() ? nullptr : QStringToUnicodeGooString(data.reason())); + const auto location = std::unique_ptr<GooString>(data.location().isEmpty() ? nullptr : QStringToUnicodeGooString(data.location())); + const auto ownerPwd = std::make_unique<GooString>(data.documentOwnerPassword().constData()); + const auto userPwd = std::make_unique<GooString>(data.documentUserPassword().constData()); + const auto gSignatureText = std::unique_ptr<GooString>(QStringToUnicodeGooString(data.signatureText())); + const auto gSignatureLeftText = std::unique_ptr<GooString>(QStringToUnicodeGooString(data.signatureLeftText())); + + const bool success = + fws->signDocumentWithAppearance(outputFileName.toUtf8().constData(), data.certNickname().toUtf8().constData(), "SHA256", data.password().toUtf8().constData(), reason.get(), location.get(), ownerPwd.get(), userPwd.get(), + *gSignatureText, *gSignatureLeftText, data.fontSize(), convertQColor(data.fontColor()), data.borderWidth(), convertQColor(data.borderColor()), convertQColor(data.backgroundColor())); + + return success ? SigningSuccess : GenericSigningError; +} + bool hasNSSSupport() { #ifdef ENABLE_NSS3 diff --git a/qt6/src/poppler-form.h b/qt6/src/poppler-form.h index a5f53c48..beba9fca 100644 --- a/qt6/src/poppler-form.h +++ b/qt6/src/poppler-form.h @@ -1,6 +1,6 @@ /* poppler-form.h: qt interface to poppler * Copyright (C) 2007-2008, Pino Toscano <[email protected]> - * Copyright (C) 2008, 2011, 2016, 2017, 2019-2021, Albert Astals Cid <[email protected]> + * Copyright (C) 2008, 2011, 2016, 2017, 2019-2022, Albert Astals Cid <[email protected]> * Copyright (C) 2012, Adam Reichold <[email protected]> * Copyright (C) 2016, Hanno Meyer-Thurow <[email protected]> * Copyright (C) 2017, Hans-Ulrich Jüttner <[email protected]> @@ -43,6 +43,7 @@ #include <QtCore/QSharedPointer> #include "poppler-export.h" #include "poppler-annotation.h" +#include "poppler-qt6.h" class Object; class Page; @@ -771,6 +772,25 @@ public: */ SignatureValidationInfo validate(int opt, const QDateTime &validationTime) const; + /** + * \since 22.02 + */ + enum SigningResult + { + FieldAlreadySigned, ///< Trying to sign a field that is already signed + GenericSigningError, + SigningSuccess + }; + + /** + Signs a field of UnsignedSignature type. + + Ignores data.page(), data.fieldPartialName() and data.boundingRectangle() + + \since 22.02 + */ + SigningResult sign(const QString &outputFileName, const PDFConverter::NewSignatureData &data) const; + private: Q_DISABLE_COPY(FormFieldSignature) }; commit 5e7f4bd726f82e632f14c21261277f9f944fd918 Author: Albert Astals Cid <[email protected]> Date: Mon Jan 3 14:40:10 2022 +0100 Add a way to detect unsigned FormFieldSignature Adobe Acrobat creates those when you're creating a field to ask someone else to sign the document diff --git a/poppler/Form.cc b/poppler/Form.cc index f2b4815a..7dd6f53f 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -1986,7 +1986,7 @@ void FormFieldChoice::reset(const std::vector<std::string> &excludedFields) // FormFieldSignature //------------------------------------------------------------------------ FormFieldSignature::FormFieldSignature(PDFDoc *docA, Object &&dict, const Ref refA, FormField *parentA, std::set<int> *usedParents) - : FormField(docA, std::move(dict), refA, parentA, usedParents, formSignature), signature_type(unknown_signature_type), signature(nullptr) + : FormField(docA, std::move(dict), refA, parentA, usedParents, formSignature), signature_type(unsigned_signature_field), signature(nullptr) { signature_info = new SignatureInfo(); parseInfo(); @@ -2107,6 +2107,8 @@ void FormFieldSignature::parseInfo() } else if (subfilterName.isName("ETSI.CAdES.detached")) { signature_type = ETSI_CAdES_detached; signature_info->setSubFilterSupport(true); + } else { + signature_type = unknown_signature_type; } } diff --git a/poppler/Form.h b/poppler/Form.h index a5ca8fdf..0aaf1933 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -83,7 +83,8 @@ enum FormSignatureType adbe_pkcs7_sha1, adbe_pkcs7_detached, ETSI_CAdES_detached, - unknown_signature_type + unknown_signature_type, + unsigned_signature_field }; enum FillValueType diff --git a/qt5/src/poppler-form.cc b/qt5/src/poppler-form.cc index 36f7b451..9c6042dc 100644 --- a/qt5/src/poppler-form.cc +++ b/qt5/src/poppler-form.cc @@ -925,6 +925,9 @@ FormFieldSignature::SignatureType FormFieldSignature::signatureType() const case unknown_signature_type: sigType = UnknownSignatureType; break; + case unsigned_signature_field: + sigType = UnsignedSignature; + break; } return sigType; } diff --git a/qt5/src/poppler-form.h b/qt5/src/poppler-form.h index b7197f0f..600a604b 100644 --- a/qt5/src/poppler-form.h +++ b/qt5/src/poppler-form.h @@ -778,7 +778,8 @@ public: AdbePkcs7sha1, AdbePkcs7detached, EtsiCAdESdetached, - UnknownSignatureType ///< \since 0.90 + UnknownSignatureType, ///< \since 0.90 + UnsignedSignature ///< \since 22.02 }; /** diff --git a/qt6/src/poppler-form.cc b/qt6/src/poppler-form.cc index d24e3892..2604cfd0 100644 --- a/qt6/src/poppler-form.cc +++ b/qt6/src/poppler-form.cc @@ -925,6 +925,9 @@ FormFieldSignature::SignatureType FormFieldSignature::signatureType() const case unknown_signature_type: sigType = UnknownSignatureType; break; + case unsigned_signature_field: + sigType = UnsignedSignature; + break; } return sigType; } diff --git a/qt6/src/poppler-form.h b/qt6/src/poppler-form.h index c77b0879..a5f53c48 100644 --- a/qt6/src/poppler-form.h +++ b/qt6/src/poppler-form.h @@ -730,7 +730,8 @@ public: UnknownSignatureType, AdbePkcs7sha1, AdbePkcs7detached, - EtsiCAdESdetached + EtsiCAdESdetached, + UnsignedSignature ///< \since 22.02 }; /** diff --git a/utils/pdfsig.cc b/utils/pdfsig.cc index 7908ac05..fbba2505 100644 --- a/utils/pdfsig.cc +++ b/utils/pdfsig.cc @@ -420,8 +420,15 @@ int main(int argc, char *argv[]) } for (unsigned int i = 0; i < sigCount; i++) { - const SignatureInfo *sig_info = signatures.at(i)->validateSignature(!dontVerifyCert, false, -1 /* now */, !noOCSPRevocationCheck, useAIACertFetch); + FormFieldSignature *ffs = signatures.at(i); printf("Signature #%u:\n", i + 1); + + if (ffs->getSignatureType() == unsigned_signature_field) { + printf(" The signature form field is not signed.\n"); + continue; + } + + const SignatureInfo *sig_info = ffs->validateSignature(!dontVerifyCert, false, -1 /* now */, !noOCSPRevocationCheck, useAIACertFetch); printf(" - Signer Certificate Common Name: %s\n", sig_info->getSignerName()); printf(" - Signer full Distinguished Name: %s\n", sig_info->getSubjectDN()); printf(" - Signing Time: %s\n", time_str = getReadableTime(sig_info->getSigningTime())); @@ -452,7 +459,7 @@ int main(int argc, char *argv[]) printf("unknown\n"); } printf(" - Signature Type: "); - switch (signatures.at(i)->getSignatureType()) { + switch (ffs->getSignatureType()) { case adbe_pkcs7_sha1: printf("adbe.pkcs7.sha1\n"); break; @@ -465,7 +472,7 @@ int main(int argc, char *argv[]) default: printf("unknown\n"); } - std::vector<Goffset> ranges = signatures.at(i)->getSignedRangeBounds(); + const std::vector<Goffset> ranges = ffs->getSignedRangeBounds(); if (ranges.size() == 4) { printf(" - Signed Ranges: [%lld - %lld], [%lld - %lld]\n", ranges[0], ranges[1], ranges[2], ranges[3]); Goffset checked_file_size;
