From 3abf72177b8e66816ac365c1a56f473ce1227075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-Ulrich=20J=C3=BCttner?= Date: Wed, 16 Aug 2017 16:16:16 +0200 Subject: [PATCH] Added signing of PDF documents with digital signatures via Qt5 interface. Also enhanced pdfsig to sign PDF documents with parameter -s. Fixes bug #99416 --- goo/GooString.cc | 5 ++ goo/GooString.h | 1 + poppler/Form.cc | 210 +++++++++++++++++++++++++++++++++++++++++++- poppler/Form.h | 20 +++++ poppler/PDFDoc.cc | 9 ++ poppler/SignatureHandler.cc | 131 +++++++++++++++++++++++++-- poppler/SignatureHandler.h | 17 ++++ qt5/src/poppler-form.cc | 48 ++++++++++ qt5/src/poppler-form.h | 21 +++++ utils/pdfsig.1 | 23 +++++ utils/pdfsig.cc | 67 ++++++++++++++ 11 files changed, 542 insertions(+), 10 deletions(-) diff --git a/goo/GooString.cc b/goo/GooString.cc index 12592e4..b6dbda4 100644 --- a/goo/GooString.cc +++ b/goo/GooString.cc @@ -917,6 +917,11 @@ GBool GooString::hasUnicodeMarker(void) const return length > 1 && (s[0] & 0xff) == 0xfe && (s[1] & 0xff) == 0xff; } +GBool GooString::hasASN1Marker(void) const +{ + return length > 1 && (s[0] & 0xff) == 0x30 && (s[1] & 0xf0) == 0x80; +} + GooString *GooString::sanitizedName(GBool psmode) { GooString *name; diff --git a/goo/GooString.h b/goo/GooString.h index de70497..02c9a7e 100644 --- a/goo/GooString.h +++ b/goo/GooString.h @@ -162,6 +162,7 @@ public: GBool hasUnicodeMarker(void) const; GBool hasJustUnicodeMarker(void) const { return length == 2 && hasUnicodeMarker(); } + GBool hasASN1Marker(void) const; // Sanitizes the string so that it does // not contain any ( ) < > [ ] { } / % diff --git a/poppler/Form.cc b/poppler/Form.cc index 0f4718e..073df30 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -445,6 +445,60 @@ SignatureInfo *FormWidgetSignature::validateSignature(bool doVerifyCert, bool fo return static_cast(field)->validateSignature(doVerifyCert, forceRevalidation, validationTime); } +GBool FormWidgetSignature::signDocument(const char* certNickname, const char* digestName, + const char* password, const char* reason) +{ + GBool ok = gFalse; + if (certNickname) + { + unsigned char tmp_buffer[4]; + memcpy(tmp_buffer, "PDF", 4); + SignatureHandler sigHandler(certNickname, SignatureHandler::getHashOidTag(digestName)); + sigHandler.updateHash(tmp_buffer, 4); + // calculate a signature over tmp_buffer with the certificate to get its size + GooString* tmpSignature = sigHandler.signDetached(password); + if (tmpSignature) + { + FormFieldSignature* signatureField = static_cast(field); + GooString gReason(reason ? reason : "I approve this document"); + char* signerName = sigHandler.getSignerName(); + GooString gName(signerName); + free(signerName); + Object vObj(new Dict(xref)); + ok = createSignature(vObj, gName, gReason, tmpSignature); + if (!ok) + { + delete tmpSignature; + return ok; + } + // calculate hash with preliminary values for ranges + sigHandler.restartHash(); + Goffset file_size = 0; + Goffset sig_start = 0; + Goffset sig_end = 0; + prepareSignature(&sigHandler, file_size, sig_start, sig_end); + ok = updateSignature(vObj, tmpSignature, file_size, sig_start, sig_end); + delete tmpSignature; + if (!ok) + { + return ok; + } + // recalculate hash with the correct ranges + sigHandler.restartHash(); + prepareSignature(&sigHandler, file_size, sig_start, sig_end); + GooString* signature = sigHandler.signDetached(password); + if (signature) + { + ok = updateSignature(vObj, signature, file_size, sig_start, sig_end); + if (ok) + signatureField->setSignature(signature); + delete signature; + } + } + } + return ok; +} + std::vector FormWidgetSignature::getSignedRangeBounds() { Object* obj = static_cast(field)->getByteRange(); @@ -584,6 +638,153 @@ GooString* FormWidgetSignature::getCheckedSignature(Goffset *checkedFileSize) return nullptr; } +GBool FormWidgetSignature::createSignature(Object &vObj, const GooString& name, + const GooString& reason, + const GooString* signature) +{ + Goffset file_size = doc->getBaseStream()->getLength(); + vObj.dictAdd(copyString("Type"), Object(objName, "Sig")); + vObj.dictAdd(copyString("Filter"), Object(objName, "Adobe.PPKLite")); + switch (signatureType()) { + case adbe_pkcs7_sha1: + // we don't support signing with SubFilter "adbe.pkcs7.sha1" + error(errUnimplemented, -1, "adbe.pkcs7.sha1 not supported\n"); + return gFalse; + case adbe_pkcs7_detached: + vObj.dictAdd(copyString("SubFilter"), Object(objName, "adbe.pkcs7.detached")); + break; + case ETSI_CAdES_detached: + vObj.dictAdd(copyString("SubFilter"), Object(objName, "ETSI.CAdES.detached")); + break; + } + vObj.dictAdd(copyString("Name"), Object(name.copy())); + char buf[24]; + time_t now = time(nullptr); + size_t size = strftime(buf, 24, "D:%Y%m%d%H%M%S%z", localtime(&now)); + if (size >= 2 && size < 22) + { + // put timezone info into single quotes + buf[size] = buf[size-1]; + buf[size-1] = buf[size-2]; + buf[size-2] = '\''; + buf[++size] = '\''; + buf[++size] = '\0'; + GooString gTime(buf, size); + vObj.dictAdd(copyString("M"), Object(gTime.copy())); + } + vObj.dictAdd(copyString("Reason"), Object(reason.copy())); + vObj.dictAdd(copyString("Contents"), Object(signature->copy())); + Object bObj(new Array(xref)); + bObj.arrayAdd(Object(static_cast(0))); + bObj.arrayAdd(Object(file_size)); + bObj.arrayAdd(Object(file_size+2*signature->getLength()+2)); + bObj.arrayAdd(Object(static_cast(1000))); + vObj.dictAdd(copyString("ByteRange"), bObj.copy()); + obj.dictSet("V", vObj.copy()); + xref->setModifiedObject(&obj, ref); + return gTrue; +} + +GBool FormWidgetSignature::prepareSignature(SignatureHandler *handler, Goffset& file_size, + Goffset& sig_start, Goffset& sig_end) +{ + GBool ok = gFalse; + size_t size; + char* membuf; + FILE* mfp = open_memstream(&membuf, &size); + if (mfp) + { + FileOutStream* outStr = new FileOutStream(mfp, 0); + doc->saveAs(outStr, writeForceIncremental); + fclose(mfp); + delete outStr; + if (membuf) + { + // search for the Contents field which contains the signature + // which always must start with hex digits 308 + for (Goffset i = 0; i < size; ++i) + { + if (strncmp(&membuf[i], "/Contents <308", 14) == 0) + { + char* p = index(&membuf[i+14], '>'); + if (p) + { + sig_end = ++p - membuf; + for (Goffset j = sig_end; j < size; ++j) + { + if (strncmp(&membuf[j], "%%EOF\r\n", 7) == 0) + { + file_size = j + 7; + ok = gTrue; + } + } + if (ok) + { + sig_start = i + 10; + break; + } + } + } + } + Goffset range_offset = sig_end; + Goffset range_size = 0; + if (ok) + { + ok = gFalse; + for (range_offset = sig_end; range_offset < file_size; ++range_offset) + { + if (strncmp(&membuf[range_offset], "/ByteRange [0 ", 14) == 0) + { + char* p = index(&membuf[range_offset+14], ']'); + if (p) + { + range_size = (++p - membuf) - range_offset; + ok = gTrue; + break; + } + } + } + } + if (ok) + { + // the length of the string printed into range_buf is guaranteed to be less than 128 + char* range_buf = new char[128]; + Goffset end_size = file_size - sig_end; + Goffset s = snprintf(range_buf, 128, "/ByteRange [0 %lld %lld %lld ]", + sig_start, sig_end, end_size); + char* buffer = new char[end_size+128]; + memcpy(buffer, &membuf[sig_end], range_offset-sig_end); + memcpy(&buffer[range_offset-sig_end], range_buf, s); + memcpy(&buffer[range_offset-sig_end+s], &membuf[range_offset+range_size], + file_size-range_offset-range_size); + file_size += s - range_size; + handler->updateHash(reinterpret_cast(membuf), sig_start); + handler->updateHash(reinterpret_cast(buffer), file_size-sig_end); + delete[] range_buf; + delete[] buffer; + } + free(membuf); + } + } + return ok; +} + +GBool FormWidgetSignature::updateSignature(Object &vObj, const GooString* signature, + Goffset file_size, Goffset sig_start, Goffset sig_end) +{ + if (!vObj.isDict() || !signature) + return gFalse; + vObj.dictSet("Contents", Object(signature->copy())); + Object bObj(new Array(xref)); + bObj.arrayAdd(Object(static_cast(0))); + bObj.arrayAdd(Object(sig_start)); + bObj.arrayAdd(Object(sig_end)); + bObj.arrayAdd(Object(file_size-sig_end)); + vObj.dictSet("ByteRange", bObj.copy()); + obj.dictSet("V", vObj.copy()); + return gTrue; +} + void FormWidgetSignature::updateWidgetAppearance() { // Unimplemented @@ -1486,11 +1687,9 @@ GooString *FormFieldChoice::getSelectedChoice() { //------------------------------------------------------------------------ FormFieldSignature::FormFieldSignature(PDFDoc *docA, Object *dict, const Ref& ref, FormField *parent, std::set *usedParents) : FormField(docA, dict, ref, parent, usedParents, formSignature), - signature_type(adbe_pkcs7_detached), + signature_type(adbe_pkcs7_detached), byte_range(), signature(nullptr), signature_info(nullptr) { - signature = NULL; - signature_info = new SignatureInfo(); parseInfo(); } @@ -1573,6 +1772,11 @@ FormSignatureType FormWidgetSignature::signatureType() return static_cast(field)->signature_type; } +void FormWidgetSignature::setSignatureType(FormSignatureType type) +{ + static_cast(field)->signature_type = type; +} + SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime) { #ifdef ENABLE_NSS3 diff --git a/poppler/Form.h b/poppler/Form.h index 8498752..60e5622 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -261,9 +261,20 @@ public: void updateWidgetAppearance() override; FormSignatureType signatureType(); + void setSignatureType(FormSignatureType type); + // Use -1 for now as validationTime SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime); + // creates or replaces the dictionary name "V" in the signature dictionary and + // fills it with the fields of the signature; the field "Contents" is the signature + // in PKCS#7 format, which is calculated over the byte range encompassing the whole + // document except for the signature itself; this byte range is specified in the + // field "ByteRange" in the dictionary "V" + // return success + GBool signDocument(const char *certNickname, const char *digestName, + const char *password, const char *reason = nullptr); + // returns a list with the boundaries of the signed ranges // the elements of the list are of type Goffset std::vector getSignedRangeBounds(); @@ -272,6 +283,14 @@ public: // if the check passed (and the checked file size as output parameter in checkedFileSize) // otherwise a nullptr is returned GooString* getCheckedSignature(Goffset *checkedFileSize); + +private: + GBool createSignature(Object &vObj, const GooString &name, const GooString &reason, + const GooString *signature); + GBool prepareSignature(SignatureHandler *handler, + Goffset& file_size, Goffset& sig_start, Goffset& sig_end); + GBool updateSignature(Object &vObj, const GooString *signature, + Goffset file_size, Goffset sig_start, Goffset sig_end); }; //------------------------------------------------------------------------ @@ -520,6 +539,7 @@ public: ~FormFieldSignature(); Object* getByteRange() { return &byte_range; } GooString* getSignature() { return signature; } + void setSignature(const GooString* sig) { if (sig != nullptr) signature = sig->copy(); } private: void parseInfo(); diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index 35b3bc0..549dd8f 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -1176,6 +1176,15 @@ void PDFDoc::writeString (GooString* s, OutStream* outStr, Guchar *fileKey, outStr->printf("%c", unescaped); } outStr->printf(") "); + } else if (s->hasASN1Marker()) { + //format ASN1 strings hex encoded and enclosed in <> + const char* c = s->getCString(); + outStr->printf("<"); + for(int i=0; igetLength(); i++) { + unsigned char value = *(c+i)&0x000000ff; + outStr->printf("%2.2x", value); + } + outStr->printf("> "); } else { const char* c = s->getCString(); outStr->printf("("); diff --git a/poppler/SignatureHandler.cc b/poppler/SignatureHandler.cc index bddc45f..c2f3ef8 100644 --- a/poppler/SignatureHandler.cc +++ b/poppler/SignatureHandler.cc @@ -22,6 +22,18 @@ #include #include +/* NSS headers */ +#include + +void SignatureHandler::outputCallback(void* arg, const char* buf, unsigned long len) +{ + if (arg && buf) + { + GooString* gSignature = reinterpret_cast(arg); + gSignature->append(buf, len); + } +} + unsigned int SignatureHandler::digestLength(SECOidTag digestAlgId) { switch(digestAlgId){ @@ -39,24 +51,50 @@ unsigned int SignatureHandler::digestLength(SECOidTag digestAlgId) } } +SECOidTag SignatureHandler::getHashOidTag(const char* digestName) +{ + SECOidTag tag = SEC_OID_UNKNOWN; + if (strcmp(digestName, "SHA1") == 0) + { + tag = SEC_OID_SHA1; + } + else if (strcmp(digestName, "SHA256") == 0) + { + tag = SEC_OID_SHA256; + } + else if (strcmp(digestName, "SHA384") == 0) + { + tag = SEC_OID_SHA384; + } + else if (strcmp(digestName, "SHA512") == 0) + { + tag = SEC_OID_SHA512; + } + return tag; +} + char *SignatureHandler::getSignerName() { - if (!CMSSignerInfo) + if (!signing_cert && !CMSSignerInfo) return NULL; - CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); - return CERT_GetCommonName(&cert->subject); + if (!signing_cert) + signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); + return CERT_GetCommonName(&signing_cert->subject); } const char * SignatureHandler::getSignerSubjectDN() { - if (!CMSSignerInfo) + if (!signing_cert && !CMSSignerInfo) return nullptr; - CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); - if (!cert) + if (!signing_cert) + signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); + + if (!signing_cert) return nullptr; - return cert->subjectName; + + return signing_cert->subjectName; } HASH_HashType SignatureHandler::getHashAlgorithm() @@ -131,6 +169,7 @@ SignatureHandler::SignatureHandler(unsigned char *p7, int p7_length) CMSMessage(NULL), CMSSignedData(NULL), CMSSignerInfo(NULL), + signing_cert(nullptr), temp_certs(NULL) { init_nss(); @@ -144,6 +183,23 @@ SignatureHandler::SignatureHandler(unsigned char *p7, int p7_length) } } +SignatureHandler::SignatureHandler(const char *certNickname, SECOidTag digestAlgTag) + : hash_length(digestLength(digestAlgTag)), + digest_alg_tag(digestAlgTag), + CMSitem(), + hash_context(nullptr), + CMSMessage(nullptr), + CMSSignedData(nullptr), + CMSSignerInfo(nullptr), + signing_cert(nullptr), + temp_certs(nullptr) +{ + init_nss(); + CMSMessage = NSS_CMSMessage_Create(nullptr); + signing_cert = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), certNickname); + hash_context = HASH_Create(HASH_GetHashTypeByOidTag(digestAlgTag)); +} + HASHContext * SignatureHandler::initHashContext() { @@ -161,6 +217,13 @@ void SignatureHandler::updateHash(unsigned char * data_block, int data_len) } } +void SignatureHandler::restartHash() +{ + if (hash_context) + HASH_Destroy(hash_context); + hash_context = HASH_Create(HASH_GetHashTypeByOidTag(digest_alg_tag)); +} + SignatureHandler::~SignatureHandler() { SECITEM_FreeItem(&CMSitem, PR_FALSE); @@ -171,6 +234,8 @@ SignatureHandler::~SignatureHandler() if (CMSMessage) NSS_CMSMessage_Destroy(CMSMessage); + if (signing_cert) + CERT_DestroyCertificate(signing_cert); if (hash_context) HASH_Destroy(hash_context); @@ -324,6 +389,58 @@ SECErrorCodes SignatureHandler::validateCertificate(time_t validation_time) return retVal; } +GooString* SignatureHandler::signDetached(const char* password) +{ + if (!hash_context) + return nullptr; + unsigned char* digest_buffer = reinterpret_cast(PORT_Alloc(hash_length)); + unsigned int result_len = 0; + HASH_End(hash_context, digest_buffer, &result_len, hash_length); + SECItem digest; + digest.data = digest_buffer; + digest.len = result_len; + SEC_PKCS7ContentInfo* p7_info = + SEC_PKCS7CreateSignedData(signing_cert, certUsageEmailSigner, + CERT_GetDefaultCertDB(), digest_alg_tag, &digest, + nullptr, nullptr); + if (!p7_info) + { + PORT_Free(reinterpret_cast(digest_buffer)); + return nullptr; + } + SEC_PKCS7SignerInfo** signerinfos = p7_info->content.signedData->signerInfos; + if (!signerinfos) + { + PORT_Free(reinterpret_cast(digest_buffer)); + SEC_PKCS7DestroyContentInfo(p7_info); + return nullptr; + } + for (SEC_PKCS7SignerInfo* si = *signerinfos; si; si = *++signerinfos) + { + if (si->cert) + { + si->certList = CERT_CertChainFromCert(si->cert, certUsageEmailSigner, PR_TRUE); + } + } + SEC_PKCS7AddSigningTime(p7_info); + GooString* signature = new GooString; + PWData pwdata = + { + password ? PWData::PW_PLAINTEXT : PWData::PW_NONE, + password + }; + + if (SEC_PKCS7Encode(p7_info, outputCallback, reinterpret_cast(signature), + nullptr, nullptr, &pwdata) != SECSuccess) + { + PORT_Free(reinterpret_cast(digest_buffer)); + delete signature; + signature = nullptr; + } + SEC_PKCS7DestroyContentInfo(p7_info); + PORT_Free(reinterpret_cast(digest_buffer)); + return signature; +} SignatureValidationStatus SignatureHandler::NSS_SigTranslate(NSSCMSVerificationStatus nss_code) { diff --git a/poppler/SignatureHandler.h b/poppler/SignatureHandler.h index 3b2f62b..0f4e3b1 100644 --- a/poppler/SignatureHandler.h +++ b/poppler/SignatureHandler.h @@ -34,6 +34,7 @@ class SignatureHandler { public: SignatureHandler(unsigned char *p7, int p7_length); + SignatureHandler(const char *certNickname, SECOidTag digestAlgTag); ~SignatureHandler(); time_t getSigningTime(); char * getSignerName(); @@ -41,15 +42,28 @@ public: HASH_HashType getHashAlgorithm(); void setSignature(unsigned char *, int); void updateHash(unsigned char * data_block, int data_len); + void restartHash(); NSSCMSVerificationStatus validateSignature(); // Use -1 as validation_time for now SECErrorCodes validateCertificate(time_t validation_time); + GooString * signDetached(const char * password); //Translate NSS error codes static SignatureValidationStatus NSS_SigTranslate(NSSCMSVerificationStatus nss_code); static CertificateValidationStatus NSS_CertTranslate(SECErrorCodes nss_code); + static SECOidTag getHashOidTag(const char* digestName); private: + typedef struct { + enum { + PW_NONE = 0, + PW_FROMFILE = 1, + PW_PLAINTEXT = 2, + PW_EXTERNAL = 3 + } source; + const char * data; + } PWData; + SignatureHandler(const SignatureHandler &); SignatureHandler& operator=(const SignatureHandler &); @@ -61,13 +75,16 @@ private: NSSCMSSignedData *CMS_SignedDataCreate(NSSCMSMessage * cms_msg); NSSCMSSignerInfo *CMS_SignerInfoCreate(NSSCMSSignedData * cms_sig_data); HASHContext * initHashContext(); + static void outputCallback(void* arg, const char* buf, unsigned long len); unsigned int hash_length; + SECOidTag digest_alg_tag; SECItem CMSitem; HASHContext *hash_context; NSSCMSMessage *CMSMessage; NSSCMSSignedData *CMSSignedData; NSSCMSSignerInfo *CMSSignerInfo; + CERTCertificate *signing_cert; CERTCertificate **temp_certs; }; diff --git a/qt5/src/poppler-form.cc b/qt5/src/poppler-form.cc index 02c3a5d..d17130c 100644 --- a/qt5/src/poppler-form.cc +++ b/qt5/src/poppler-form.cc @@ -607,6 +607,54 @@ FormFieldSignature::SignatureType FormFieldSignature::signatureType() const return sigType; } +void FormFieldSignature::setSignatureType(SignatureType type) +{ + FormWidgetSignature* fws = static_cast(m_formData->fm); + switch (type) + { + case AdbePkcs7sha1: + fws->setSignatureType(adbe_pkcs7_sha1); + break; + case AdbePkcs7detached: + fws->setSignatureType(adbe_pkcs7_detached); + break; + case EtsiCAdESdetached: + fws->setSignatureType(ETSI_CAdES_detached); + break; + } +} + +bool FormFieldSignature::sign(const QString& certNickname, const QString& password, + DigestAlgorithm digestAlg) +{ + FormWidgetSignature* fws = static_cast(m_formData->fm); + const char* digest = nullptr; + char* pw = password.isEmpty() ? nullptr : strdup(password.toUtf8().constData()); + char* name = strdup(certNickname.toUtf8().constData()); + switch (digestAlg) + { + case SHA1: + digest = "SHA1"; + break; + case SHA256: + digest = "SHA256"; + break; + case SHA384: + digest = "SHA384"; + break; + case SHA512: + digest = "SHA512"; + break; + default: + digest = "SHA256"; + break; + } + GBool ok = fws->signDocument(name, digest, pw, nullptr); + free(name); + free(pw); + return ok; +} + SignatureValidationInfo FormFieldSignature::validate(ValidateOptions opt) const { return validate(opt, QDateTime()); diff --git a/qt5/src/poppler-form.h b/qt5/src/poppler-form.h index e39d6a2..05410be 100644 --- a/qt5/src/poppler-form.h +++ b/qt5/src/poppler-form.h @@ -30,6 +30,7 @@ #include #include "poppler-export.h" +class Object; class Page; class FormWidget; class FormWidgetButton; @@ -511,6 +512,13 @@ namespace Poppler { ValidateForceRevalidation = 2, ///< Force revalidation of the certificate. }; + enum DigestAlgorithm { + SHA1 = 1, + SHA256 = 2, + SHA384 = 3, + SHA512 = 4, + }; + /// \cond PRIVATE FormFieldSignature(DocumentData *doc, ::Page *p, ::FormWidgetSignature *w); /// \endcond @@ -524,6 +532,19 @@ namespace Poppler { */ SignatureType signatureType() const; + void setSignatureType(SignatureType type); + + /** + Sign the whole document in this signature field. + + @param certNickname nickname of the signing certificate in the mozilla database + @param password password to unlock the private key + (may be empty if no password is set) + @param digestAlg digest algorithm to be used in the signature + */ + bool sign(const QString& certNickname, const QString& password, + DigestAlgorithm digestAlg = SHA256); + /** Validate the signature with now as validation time. diff --git a/utils/pdfsig.1 b/utils/pdfsig.1 index 99ca056..0cda408 100644 --- a/utils/pdfsig.1 +++ b/utils/pdfsig.1 @@ -14,6 +14,7 @@ It also displays the identity of each signer the time and date of the signature, the hash algorithm used for signing, the type of the signature as stated in the PDF and the signed ranges with a statement wether the total document is signed. +Moreover, with option -s it can sign a PDF document. .PP The signer certificate validation uses the trusted certificates stored in the following locations: .IP \(bu @@ -25,6 +26,28 @@ The NSS Certificate database in /etc/pki/nssdb. .B \-nocert Do not validate the certificate. .TP +.B \-s " number" +Sign the document in the signature field with the given number. +.TP +.B \-n " nickname" +Use the certificate with the given nickname for signing. +.TP +.B \-p " password" +Use the given password for the signing key +(this might be missing if the key isn't password protected). +.TP +.B \-d " algorithm" +Use the given digest algorithm for signing (default: SHA256). +.TP +.B \-r " reason" +Set the given reason string for the signature (default: "I approve this document"). +.TP +.B \-o " file" +Write signed output to the given file. +.TP +.B \-etsi +Create a signature of type ETSI.CAdES.detached instead of adbe.pkcs7.detached. +.TP .B \-v Print copyright and version information. .TP diff --git a/utils/pdfsig.cc b/utils/pdfsig.cc index 8b5182d..4aec77c 100644 --- a/utils/pdfsig.cc +++ b/utils/pdfsig.cc @@ -89,10 +89,31 @@ char *getReadableTime(time_t unix_time) static GBool printVersion = gFalse; static GBool printHelp = gFalse; static GBool dontVerifyCert = gFalse; +static GBool etsiCAdESdetached = gFalse; +static int signatureNumber = 0; +static char certNickname[256]; +static char password[256]; +static char digestName[256]; +static char reason[256]; +static char output[256]; static const ArgDesc argDesc[] = { {"-nocert", argFlag, &dontVerifyCert, 0, "don't perform certificate validation"}, + {"-s", argInt, &signatureNumber, 0, + "sign the document in the signature field with the given number"}, + {"-etsi", argFlag, &etsiCAdESdetached , 0, + "create a signature of type ETSI.CAdES.detached instead of adbe.pkcs7.detached"}, + {"-n", argString, &certNickname, 256, + "use the certificate with the given nickname for signing"}, + {"-p", argString, &password, 256, + "password for the signing key (might be missing if the key isn't password protected)"}, + {"-d", argString, &digestName, 256, + "name of the digest algorithm (default: SHA256)"}, + {"-r", argString, &reason, 256, + "reason for signing (default: \"I approve this document\")"}, + {"-o", argString, &output, 256, + "output filename of the signed document"}, {"-v", argFlag, &printVersion, 0, "print copyright and version info"}, @@ -119,6 +140,12 @@ int main(int argc, char *argv[]) int exitCode = 99; GBool ok; + certNickname[0] = '\0'; + password[0] = '\0'; + strcpy(digestName, "SHA256"); + reason[0] = '\0'; + output[0] = '\0'; + ok = parseArgs(argDesc, &argc, argv); if (!ok || argc != 2 || printVersion || printHelp) { @@ -146,6 +173,46 @@ int main(int argc, char *argv[]) sig_widgets = doc->getSignatureWidgets(); sigCount = sig_widgets.size(); + if (signatureNumber > static_cast(sigCount)) { + printf("File '%s' does not contain a signature with number %d\n", + fileName->getCString(), signatureNumber); + exitCode = 2; + goto end; + } else if (signatureNumber > 0) { + if (strlen(certNickname) == 0) { + printf("A nickname of the signing certificate must be given\n"); + exitCode = 2; + goto end; + } + if (strlen(output) == 0) { + printf("An output filename for the signed document must be given\n"); + exitCode = 2; + goto end; + } + FormWidgetSignature* fws = sig_widgets.at(signatureNumber-1); + Goffset file_size = 0; + GooString* sig = fws->getCheckedSignature(&file_size); + if (sig) { + delete sig; + printf("Signature number %d is already signed\n", signatureNumber); + exitCode = 2; + goto end; + } + if (etsiCAdESdetached) + fws->setSignatureType(ETSI_CAdES_detached); + const char* pw = (strlen(password) == 0) ? nullptr : password; + const char* rs = (strlen(reason) == 0) ? nullptr : reason; + if (fws->signDocument(certNickname, digestName, pw, rs)) { + GooString* out = new GooString(output); + exitCode = doc->saveAs(out); + delete out; + goto end; + } else { + exitCode = 3; + goto end; + } + } + if (sigCount >= 1) { printf("Digital Signature Info of: %s\n", fileName->getCString()); } else { -- 1.9.1