From 86c7faa67841b2e0fe065d98e6c73058c7fe597d Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Thu, 7 Sep 2017 08:39:22 +0930 Subject: [PATCH 2/2] write document then update byte offsets and sig on disk --- poppler/Form.cc | 320 +++++++++++++++++++++++------------------------- poppler/Form.h | 9 +- qt5/src/poppler-form.cc | 5 +- qt5/src/poppler-form.h | 2 +- utils/pdfsig.cc | 6 +- 5 files changed, 161 insertions(+), 181 deletions(-) diff --git a/poppler/Form.cc b/poppler/Form.cc index 2a9c3cf7..00bcf482 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -29,9 +29,11 @@ #include #include +#include #include #include #include "goo/gmem.h" +#include "goo/gfile.h" #include "goo/GooString.h" #include "Error.h" #include "Object.h" @@ -445,8 +447,8 @@ SignatureInfo *FormWidgetSignature::validateSignature(const char *nssDir, bool d return static_cast(field)->validateSignature(nssDir, doVerifyCert, forceRevalidation, validationTime); } -GBool FormWidgetSignature::signDocument(const char *nssDir, const char* certNickname, const char* digestName, - const char* password, const char* reason) +GBool FormWidgetSignature::signDocument(const char *nssDir, const char*filename, const char* certNickname, + const char* digestName, const char* password, const char* reason) { GBool ok = gFalse; if (certNickname) @@ -471,34 +473,162 @@ GBool FormWidgetSignature::signDocument(const char *nssDir, const char* certNick 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 + GooString *fname = new GooString(filename); + doc->saveAs(fname, writeForceIncremental); + + PDFDoc *newDoc = new PDFDoc(fname); + XRefEntry *entry = newDoc->getXRef()->getEntry(ref.num); + Goffset objOffset = entry->offset; + delete newDoc; + + Goffset sigStart, sigEnd, fileSize; + FILE *file = openFile(filename, "r+b"); + updateOffsets(file, objOffset, &sigStart, &sigEnd, &fileSize); sigHandler.restartHash(); - prepareSignature(&sigHandler, file_size, sig_start, sig_end); + hashFileRange(file, &sigHandler, 0LL, sigStart); + hashFileRange(file, &sigHandler, sigEnd, fileSize); GooString* signature = sigHandler.signDetached(password); - if (signature) - { - ok = updateSignature(vObj, signature, file_size, sig_start, sig_end); - if (ok) - signatureField->setSignature(signature); + if (signature) { + updateSignature(file, sigStart, sigEnd, signature); + signatureField->setSignature(signature); delete signature; } + fclose(file); } } return ok; } +static char *setNextOffset(char *start, Goffset offset) +{ + char buf[50]; + sprintf(buf, "%lld", offset); + strcat(buf, " "); + + char *p = strstr(start, "9999999999"); + if (p) { + memcpy(p, buf, 10); + p += 10; + } + return p; +} + +GBool FormWidgetSignature::updateOffsets(FILE *f, Goffset objOffset, Goffset *sigStart, Goffset *sigEnd, Goffset *fileSize) +{ + Gfseek(f, 0, SEEK_END); + *fileSize = Gftell(f); + + Gfseek(f, objOffset, SEEK_SET); + int bufSize = static_cast(*fileSize - objOffset); // objOffset will be near end of file + if (bufSize == 0) + return gFalse; + char *buf = new char[bufSize]; + fread(buf, bufSize, 1, f); + + // search for the Contents field which contains the signature + // which always must start with hex digits 308 + for (int i = 0; i < bufSize; i++) { + if (buf[i] == '/' && strncmp(&buf[i], "/Contents <308", 14) == 0) { + *sigStart = objOffset + i + 10; + char *p = strchr(&buf[i], '>'); + if (p) { + *sigEnd = objOffset + (p - buf) + 1; + } else { + return gFalse; + } + break; + } + } + + // Search for ByteRange array + for (int i = 0; i < bufSize; i++) { + if (buf[i] == '/' && strncmp(&buf[i], "/ByteRange", 10) == 0) { + // update range + char *p = setNextOffset(&buf[i], *sigStart); + if (!p) + return gFalse; + p = setNextOffset(p, *sigEnd); + if (!p) + return gFalse; + p = setNextOffset(p, *fileSize - *sigEnd); + if (!p) + return gFalse; + break; + } + } + + Gfseek(f, objOffset, SEEK_SET); + fwrite(buf, bufSize, 1, f); + delete buf; + return gTrue; +} + +void FormWidgetSignature::hashFileRange(FILE *f, SignatureHandler *handler, Goffset start, Goffset end) +{ + const int BUF_SIZE = 65536; + + unsigned char *buf = new unsigned char[BUF_SIZE]; + + while (start < end) { + Gfseek(f, start, SEEK_SET); + int len = BUF_SIZE; + if (end - start < len) + len = end - start; + fread(buf, len, 1, f); + handler->updateHash(buf, len); + start += len; + } + delete buf; +} + +void FormWidgetSignature::updateSignature(FILE *f, Goffset sigStart, Goffset sigEnd, const GooString* signature) +{ + Gfseek(f, sigStart, SEEK_SET); + const char* c = signature->getCString(); + fprintf(f, "<"); + for (int i = 0; i < signature->getLength(); i++) { + unsigned char value = *(c+i)&0x000000ff; + fprintf(f, "%2.2x", value); + } + fprintf(f, "> "); +} + +GBool FormWidgetSignature::createSignature(Object &vObj, const GooString& name, + const GooString& reason, + const GooString* signature) +{ + 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())); + GooString *date = timeToDateString(nullptr); + vObj.dictAdd(copyString("M"), Object(date)); + if (reason.getLength() > 0) + vObj.dictAdd(copyString("Reason"), Object(reason.copy())); + vObj.dictAdd(copyString("Contents"), Object(signature->copy())); + Object bObj(new Array(xref)); + // reserve space in byte range for maximum number of bytes + bObj.arrayAdd(Object(static_cast(0LL))); + bObj.arrayAdd(Object(static_cast(9999999999LL))); + bObj.arrayAdd(Object(static_cast(9999999999LL))); + bObj.arrayAdd(Object(static_cast(9999999999LL))); + vObj.dictAdd(copyString("ByteRange"), bObj.copy()); + obj.dictSet("V", vObj.copy()); + xref->setModifiedObject(&obj, ref); + return gTrue; +} + std::vector FormWidgetSignature::getSignedRangeBounds() { Object* obj = static_cast(field)->getByteRange(); @@ -638,154 +768,6 @@ 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())); - } - if (reason.getLength() > 0) - 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 diff --git a/poppler/Form.h b/poppler/Form.h index b1edfbf5..00b1d592 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -272,7 +272,7 @@ public: // 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 *nssDir, + GBool signDocument(const char *nssDir, const char*filename, const char *certNickname, const char *digestName, const char *password, const char *reason = nullptr); @@ -288,10 +288,9 @@ public: 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); + GBool updateOffsets(FILE *f, Goffset objOffset, Goffset *sigStart, Goffset *sigEnd, Goffset *fileSize); + void hashFileRange(FILE *f, SignatureHandler *handler, Goffset start, Goffset end); + void updateSignature(FILE *f, Goffset sigStart, Goffset sigEnd, const GooString* signature); }; //------------------------------------------------------------------------ diff --git a/qt5/src/poppler-form.cc b/qt5/src/poppler-form.cc index afb0b02f..509cd730 100644 --- a/qt5/src/poppler-form.cc +++ b/qt5/src/poppler-form.cc @@ -624,7 +624,7 @@ void FormFieldSignature::setSignatureType(SignatureType type) } } -bool FormFieldSignature::sign(const QString& certNickname, const QString& password, + bool FormFieldSignature::sign(const QString& saveFilename, const QString& certNickname, const QString& password, DigestAlgorithm digestAlg, const QString& reason) { FormWidgetSignature* fws = static_cast(m_formData->fm); @@ -632,6 +632,7 @@ bool FormFieldSignature::sign(const QString& certNickname, const QString& passwo const char* rs = nullptr; char* pw = password.isEmpty() ? nullptr : strdup(password.toUtf8().constData()); char* name = strdup(certNickname.toUtf8().constData()); + char* filename = strdup(saveFilename.toUtf8().constData()); switch (digestAlg) { case SHA1: @@ -652,7 +653,7 @@ bool FormFieldSignature::sign(const QString& certNickname, const QString& passwo } if (!reason.isEmpty()) rs = reason.toUtf8().constData(); - GBool ok = fws->signDocument(nullptr, name, digest, pw, rs); + GBool ok = fws->signDocument(nullptr, filename, name, digest, pw, rs); free(name); free(pw); return ok; diff --git a/qt5/src/poppler-form.h b/qt5/src/poppler-form.h index 97108212..1643669c 100644 --- a/qt5/src/poppler-form.h +++ b/qt5/src/poppler-form.h @@ -545,7 +545,7 @@ namespace Poppler { @param digestAlg digest algorithm to be used in the signature @param reason a reason for the signature written to the signature field */ - bool sign(const QString& certNickname, const QString& password, + bool sign(const QString& saveFilename, const QString& certNickname, const QString& password, DigestAlgorithm digestAlg = SHA256, const QString& reason = QString()); /** diff --git a/utils/pdfsig.cc b/utils/pdfsig.cc index a4d83e63..58dd256e 100644 --- a/utils/pdfsig.cc +++ b/utils/pdfsig.cc @@ -197,10 +197,8 @@ int main(int argc, char *argv[]) fws->setSignatureType(ETSI_CAdES_detached); const char* pw = (strlen(password) == 0) ? nullptr : password; const char* rs = (strlen(reason) == 0) ? nullptr : reason; - if (fws->signDocument(nssDir.getCString(), certNickname, digestName, pw, rs)) { - GooString* out = new GooString(argv[2]); - exitCode = doc->saveAs(out); - delete out; + if (fws->signDocument(nssDir.getCString(), argv[2], certNickname, digestName, pw, rs)) { + exitCode = 0; goto end; } else { exitCode = 3; -- 2.11.0