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 530b93e..68adc04 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -443,6 +443,8 @@ FormWidgetSignature::FormWidgetSignature(PDFDoc *docA, Object *aobj, unsigned nu { type = formSignature; file_size = 0; + sig_start = 0; + sig_end = 0; } SignatureInfo *FormWidgetSignature::validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime) @@ -455,6 +457,41 @@ Object* FormWidgetSignature::getByteRange() return static_cast(field)->getByteRange(); } +GBool FormWidgetSignature::signDocument(const char* certNickname, const char* digestName, + const char* password, const char* reason) +{ + GBool ok = gFalse; + if (certNickname != nullptr) + { + time_t now = time(nullptr); + unsigned char tmp_buffer[4]; + memcpy(tmp_buffer, "PDF", 4); + SignatureHandler sigHandler(certNickname, SignatureHandler::getHashOidTag(digestName)); + sigHandler.updateHash(tmp_buffer, 4); + GooString* tmpSignature = sigHandler.signDetached(password, now); + if (tmpSignature != nullptr) + { + FormFieldSignature* signatureField = static_cast(field); + GooString gReason(reason != nullptr ? reason : "I approve this document"); + GooString gName(sigHandler.getSignerName()); + Object vObj; + ok = createSignature(vObj, gName, gReason, tmpSignature, &now); + if (ok) + { + sigHandler.restartHash(); + prepareSignature(&sigHandler); + GooString* signature = sigHandler.signDetached(password, now); + if (signature != nullptr) + { + ok = updateSignature(vObj, signature); + signatureField->setSignature(signature); + } + } + } + } + return ok; +} + GooString* FormWidgetSignature::getCheckedSignature() { Goffset start = 0; @@ -535,7 +572,7 @@ GooString* FormWidgetSignature::getCheckedSignature() break; } } - if (sigLen > 0 && 2*(sigLen+lenBytes) < len-4) + if (sigLen > 0 && 2*(sigLen+lenBytes) <= len-4) { for (int i = 2*(sigLen+lenBytes)+4; i < len; ++i) { @@ -565,6 +602,155 @@ GooString* FormWidgetSignature::getCheckedSignature() return nullptr; } +GBool FormWidgetSignature::prepareSignature(SignatureHandler *handler) +{ + GBool ok = gFalse; + size_t size; + char* membuf; + FILE* mfp = open_memstream(&membuf, &size); + if (mfp != nullptr) + { + FileOutStream* outStr = new FileOutStream(mfp, 0); + doc->saveAs(outStr, writeForceIncremental); + fclose(mfp); + if (membuf != nullptr) + { + for (sig_start = 0; sig_start < size; ++sig_start) + { + if (strncmp(&membuf[sig_start], "/Contents <308", 14) == 0) + { + char* p = index(&membuf[sig_start+14], '>'); + if (p != nullptr) + { + sig_end = ++p - membuf; + p = strstr(&membuf[sig_end], "%%%%EOF\r\n"); + for (file_size = sig_end; file_size < size; ++file_size) + { + if (membuf[file_size] == '%' && membuf[file_size+1] == '%' && + membuf[file_size+2] == 'E' && membuf[file_size+3] == 'O' && + membuf[file_size+4] == 'F' && membuf[file_size+5] == '\r' && + membuf[file_size+6] == '\n') + { + file_size += 7; + ok = gTrue; + break; + } + } + if (ok) + { + sig_start += 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 != nullptr) + { + range_size = (++p - membuf) - range_offset; + ok = gTrue; + break; + } + } + } + } + if (ok) + { + char* range_buf = new char[128]; + Goffset s = range_size; + Goffset last_s = 0; + while (s != last_s) + { + last_s = s; + Goffset end_size = file_size - sig_end + s - range_size; + s = sprintf(range_buf, "/ByteRange [0 %lld %lld %lld ]", + sig_start, sig_end, end_size); + } + char* buffer = new char[file_size-sig_end+64]; + 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::createSignature(Object &vObj, const GooString& name, + const GooString& reason, + const GooString* signature, + const time_t* sTime) +{ + GBool ok = obj.isDict(); + if (ok) + { + file_size = doc->getBaseStream()->getLength(); + Object bObj, obj1; + obj.dictSet("V", vObj.initDict(xref)); + vObj.dictAdd(copyString("Type"), obj1.initName("Sig")); + vObj.dictAdd(copyString("Filter"), obj1.initName("Adobe.PPKLite")); + vObj.dictAdd(copyString("SubFilter"), obj1.initName("adbe.pkcs7.detached")); + vObj.dictAdd(copyString("Name"), obj1.initString(name.copy())); + char buf[24]; + size_t size = strftime(buf, 24, "D:%Y%m%d%H%M%S%z", localtime(sTime)); + if (size >= 2 && size < 22) + { + 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"), obj1.initString(gTime.copy())); + } + vObj.dictAdd(copyString("Reason"), obj1.initString(reason.copy())); + vObj.dictAdd(copyString("Contents"), obj1.initString(signature->copy())); + bObj.initArray(xref); + bObj.arrayAdd(obj1.initInt64(0)); + bObj.arrayAdd(obj1.initInt64(file_size)); + bObj.arrayAdd(obj1.initInt64(file_size+2*signature->getLength()+2)); + bObj.arrayAdd(obj1.initInt64(100)); + vObj.dictAdd(copyString("ByteRange"), &bObj); + xref->setModifiedObject(&obj, ref); + obj1.free(); + } + return ok; +} + +GBool FormWidgetSignature::updateSignature(Object &vObj, const GooString* signature) +{ + GBool ok = vObj.isDict(); + if (ok && signature != nullptr) + { + Object bObj, obj1; + vObj.dictSet("Contents", obj1.initString(signature->copy())); + bObj.initArray(xref); + bObj.arrayAdd(obj1.initInt64(0)); + bObj.arrayAdd(obj1.initInt64(sig_start)); + bObj.arrayAdd(obj1.initInt64(sig_end)); + bObj.arrayAdd(obj1.initInt64(file_size-sig_end)); + vObj.dictSet("ByteRange", &bObj); + obj1.free(); + } + return ok; +} + void FormWidgetSignature::updateWidgetAppearance() { // Unimplemented diff --git a/poppler/Form.h b/poppler/Form.h index c595aa0..e3f1783 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -253,17 +253,34 @@ public: void updateWidgetAppearance() override; SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime = -1); - Object* getByteRange(); + Object *getByteRange(); + + // 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); // checks the length encoding of the signature and returns the hex encoded signature // if the check passed otherwise a nullptr is returned - GooString* getCheckedSignature(); + GooString *getCheckedSignature(); // this method only gives the correct file size if getCheckedSignature() // has been called before Goffset getCheckedFileSize() const { return file_size; } + protected: + GBool prepareSignature(SignatureHandler *handler); + GBool createSignature(Object &vObj, const GooString &name, const GooString &reason, + const GooString *signature, const time_t *sTime); + GBool updateSignature(Object &vObj, const GooString *signature); + Goffset file_size; + Goffset sig_start; + Goffset sig_end; }; //------------------------------------------------------------------------ @@ -502,6 +519,7 @@ protected: //------------------------------------------------------------------------ class FormFieldSignature: public FormField { + friend class FormWidgetSignature; public: FormFieldSignature(PDFDoc *docA, Object *dict, const Ref& ref, FormField *parent, std::set *usedParents); @@ -509,6 +527,8 @@ 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 f02f192..bd132bb 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -1215,6 +1215,14 @@ void PDFDoc::writeString (GooString* s, OutStream* outStr, Guchar *fileKey, outStr->printf("%c", unescaped); } outStr->printf(") "); + } else if (s->hasASN1Marker()) { + 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 456d688..22c7ac8 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 != nullptr && buf != nullptr) + { + 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 == nullptr) + if (signing_cert == nullptr && CMSSignerInfo == nullptr) return nullptr; - CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); - return CERT_GetCommonName(&cert->subject); + if (signing_cert == nullptr) + signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); + return CERT_GetCommonName(&signing_cert->subject); } const char * SignatureHandler::getSignerSubjectDN() { - if (CMSSignerInfo == nullptr) + if (signing_cert == nullptr && CMSSignerInfo == nullptr) return nullptr; - CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); - if (cert == nullptr) + if (signing_cert == nullptr) + signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); + + if (signing_cert == nullptr) return nullptr; - return cert->subjectName; + + return signing_cert->subjectName; } const char *SignatureHandler::getHashAlgorithmName() @@ -129,7 +167,7 @@ GooString *SignatureHandler::getDefaultFirefoxCertDB_Linux() /** * Initialise NSS */ -void SignatureHandler::init_nss() +void SignatureHandler::init_nss() { GooString *certDBPath = getDefaultFirefoxCertDB_Linux(); if (certDBPath == NULL) { @@ -145,11 +183,12 @@ void SignatureHandler::init_nss() SignatureHandler::SignatureHandler(unsigned char *p7, int p7_length) - : hash_context(NULL), - CMSMessage(NULL), - CMSSignedData(NULL), - CMSSignerInfo(NULL), - temp_certs(NULL) + : hash_context(nullptr), + CMSMessage(nullptr), + CMSSignedData(nullptr), + CMSSignerInfo(nullptr), + signing_cert(nullptr), + temp_certs(nullptr) { init_nss(); CMSitem.data = p7; @@ -162,6 +201,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() { @@ -179,6 +235,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); @@ -189,6 +252,8 @@ SignatureHandler::~SignatureHandler() if (CMSMessage) NSS_CMSMessage_Destroy(CMSMessage); + if (signing_cert) + CERT_DestroyCertificate(signing_cert); if (hash_context) HASH_Destroy(hash_context); @@ -251,7 +316,6 @@ NSSCMSSignerInfo *SignatureHandler::CMS_SignerInfoCreate(NSSCMSSignedData * cms_ { NSSCMSSignerInfo *signerInfo = NSS_CMSSignedData_GetSignerInfo(cms_sig_data, 0); if (!signerInfo) { - printf("Error in NSS_CMSSignedData_GetSignerInfo()\n"); return NULL; } else { return signerInfo; @@ -342,6 +406,54 @@ SECErrorCodes SignatureHandler::validateCertificate(time_t validation_time) return retVal; } +GooString* SignatureHandler::signDetached(const char* password, time_t signing_time) +{ + if (hash_context == nullptr) + 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 == nullptr) + { + return nullptr; + } + SEC_PKCS7SignerInfo** signerinfos = p7_info->content.signedData->signerInfos; + if (signerinfos == nullptr) + { + SEC_PKCS7DestroyContentInfo(p7_info); + return nullptr; + } + for (SEC_PKCS7SignerInfo* si = *signerinfos; si != nullptr; si = *++signerinfos) + { + if (si->cert != nullptr) + { + si->certList = CERT_CertChainFromCert(si->cert, certUsageEmailSigner, PR_TRUE); + } + } + SEC_PKCS7AddSigningTime(p7_info); + GooString* signature = new GooString; + PWData pwdata = + { + password != nullptr ? PWData::PW_PLAINTEXT : PWData::PW_NONE, + password + }; + + if (SEC_PKCS7Encode(p7_info, outputCallback, reinterpret_cast(signature), + nullptr, nullptr, &pwdata) != SECSuccess) + { + delete signature; + signature = nullptr; + } + SEC_PKCS7DestroyContentInfo(p7_info); + return signature; +} SignatureValidationStatus SignatureHandler::NSS_SigTranslate(NSSCMSVerificationStatus nss_code) { diff --git a/poppler/SignatureHandler.h b/poppler/SignatureHandler.h index 0b58da3..7ab3e6d 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,14 +42,27 @@ public: const char * getHashAlgorithmName(); void setSignature(unsigned char *, int); void updateHash(unsigned char * data_block, int data_len); + void restartHash(); NSSCMSVerificationStatus validateSignature(); SECErrorCodes validateCertificate(time_t validation_time = -1); + GooString * signDetached(const char * password, time_t signing_time = -1); //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 &); @@ -60,13 +74,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 d829b40..0a95fb9 100644 --- a/qt5/src/poppler-form.cc +++ b/qt5/src/poppler-form.cc @@ -182,7 +182,6 @@ Link *FormField::additionalAction(AdditionalActionType type) const return action; } - FormFieldButton::FormFieldButton(DocumentData *doc, ::Page *p, ::FormWidgetButton *w) : FormField(*new FormFieldData(doc, p, w)) { @@ -564,6 +563,37 @@ FormField::FormType FormFieldSignature::type() const return FormField::FormSignature; } +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(int opt, const QDateTime& validationTime) const { FormWidgetSignature* fws = static_cast(m_formData->fm); @@ -642,9 +672,10 @@ SignatureValidationInfo FormFieldSignature::validate(int opt, const QDateTime& v } } } - if (priv->range_bounds.size() == 4) + GooString* checkedSignature = fws->getCheckedSignature(); + if (priv->range_bounds.size() == 4 && checkedSignature != nullptr) { - priv->signature = QByteArray::fromHex(fws->getCheckedSignature()->getCString()); + priv->signature = QByteArray::fromHex(checkedSignature->getCString()); priv->docLength = fws->getCheckedFileSize(); } diff --git a/qt5/src/poppler-form.h b/qt5/src/poppler-form.h index d568083..c659846 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; @@ -480,6 +481,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 @@ -487,7 +495,18 @@ namespace Poppler { FormType type() const override; - /** + /** + 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. Reset signature validatation info of scoped instance.