diff --git a/CMakeLists.txt b/CMakeLists.txt index c587ea6..e9e7d0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ endif(WITH_FONTCONFIGURATION_FONTCONFIG) macro_optional_find_package(JPEG) macro_optional_find_package(PNG) macro_optional_find_package(TIFF) +find_package(NSS3 REQUIRED) if(JPEG_FOUND) set(ENABLE_LIBJPEG ${JPEG_FOUND}) endif(JPEG_FOUND) @@ -200,7 +201,9 @@ if(ENABLE_LIBCURL) include_directories(${CURL_INCLUDE_DIR}) set(POPPLER_HAS_CURL_SUPPORT ON) endif(ENABLE_LIBCURL) - +if (NSS3_FOUND) + add_definitions(${NSS3_CFLAGS}) +endif(NSS3_FOUND) add_definitions(-DHAVE_CONFIG_H=1) if(MINGW) # Use mingw's ansi stdio extensions @@ -363,6 +366,8 @@ set(poppler_SRCS poppler/ProfileData.cc poppler/PreScanOutputDev.cc poppler/PSTokenizer.cc + poppler/SignatureHandler.cc + poppler/SignatureInfo.cc poppler/Stream.cc poppler/StructTreeRoot.cc poppler/StructElement.cc @@ -411,6 +416,10 @@ endif(ENABLE_SPLASH) if(FONTCONFIG_FOUND) set(poppler_LIBS ${poppler_LIBS} ${FONTCONFIG_LIBRARIES}) endif(FONTCONFIG_FOUND) + +if (NSS3_FOUND) + set(poppler_LIBS ${poppler_LIBS} ${NSS3_LIBRARIES}) +endif(NSS3_FOUND) if(JPEG_FOUND) set(poppler_SRCS ${poppler_SRCS} poppler/DCTStream.cc diff --git a/cmake/modules/FindNSS3.cmake b/cmake/modules/FindNSS3.cmake new file mode 100644 index 0000000..657cad8 --- /dev/null +++ b/cmake/modules/FindNSS3.cmake @@ -0,0 +1,22 @@ +# - try to find NSS3 libraries +# Once done this will define +# +# NSS_FOUND - system has NSS3 +# NSS3_CFLAGS - the NSS CFlags +# NSS3_LIBRARIES - Link these to use NSS +# +# Copyright 2015 André Guerreiro, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +if (NOT WIN32) + find_package(PkgConfig REQUIRED) + + pkg_check_modules(NSS3 "nss>=3.19") + + find_package_handle_standard_args(NSS3 DEFAULT_MSG NSS3_LIBRARIES NSS3_CFLAGS) + +endif(NOT WIN32) diff --git a/configure.ac b/configure.ac index bd0fcc2..9fceeac 100644 --- a/configure.ac +++ b/configure.ac @@ -264,6 +264,8 @@ else enable_libopenjpeg=no fi +PKG_CHECK_MODULES(NSS3, nss, [], []) + AM_CONDITIONAL(BUILD_LIBOPENJPEG, test x$openjpeg1 = xyes || test x$openjpeg2 = xyes) AH_TEMPLATE([ENABLE_LIBOPENJPEG], [Use libopenjpeg instead of builtin jpeg2000 decoder.]) diff --git a/poppler/DateInfo.cc b/poppler/DateInfo.cc index 563204a..82aa33f 100644 --- a/poppler/DateInfo.cc +++ b/poppler/DateInfo.cc @@ -116,3 +116,27 @@ GooString *timeToDateString(time_t *timet) { return dateString; } +time_t pdfTimeToInteger(GooString *time_str) +{ + int year, mon, day, hour, min, sec, tz_hour, tz_minute; + char tz; + struct tm time_struct; + + if (!parseDateString (time_str->getCString(), &year, + &mon, &day, &hour, &min, &sec, &tz, &tz_hour, &tz_minute)) + return 0; + + time_struct.tm_year = year - 1900; + time_struct.tm_mon = mon - 1; + time_struct.tm_mday = day; + time_struct.tm_hour = hour; + time_struct.tm_min = min; + time_struct.tm_sec = sec; + time_struct.tm_wday = -1; + time_struct.tm_yday = -1; + time_struct.tm_isdst = -1; + + time_t unix_time = mktime(&time_struct); + + return unix_time; +} diff --git a/poppler/DateInfo.h b/poppler/DateInfo.h index 116350f..0e04171 100644 --- a/poppler/DateInfo.h +++ b/poppler/DateInfo.h @@ -31,5 +31,5 @@ GBool parseDateString(const char *string, int *year, int *month, int *day, int * * If timet is NULL, current time is used. */ GooString *timeToDateString(time_t *timet); - +time_t pdfTimeToInteger(GooString *time_str); #endif diff --git a/poppler/Form.cc b/poppler/Form.cc index 661ed89..ace3223 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -36,6 +36,8 @@ #include "Gfx.h" #include "Form.h" #include "PDFDoc.h" +#include "DateInfo.h" +#include "SignatureHandler.h" #include "XRef.h" #include "PDFDocEncoding.h" #include "Annot.h" @@ -441,6 +443,11 @@ FormWidgetSignature::FormWidgetSignature(PDFDoc *docA, Object *aobj, unsigned nu parent = static_cast(field); } +SignatureInfo *FormWidgetSignature::validateSignature(bool doVerifyCert, bool forceRevalidation) +{ + return parent->validateSignature(doVerifyCert, forceRevalidation); +} + void FormWidgetSignature::updateWidgetAppearance() { // Unimplemented @@ -1370,11 +1377,114 @@ 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 = NULL; + signature_info = new SignatureInfo(); + parseInfo(); } FormFieldSignature::~FormFieldSignature() { + if (byte_range) + { + byte_range->free(); + delete byte_range; + } +} + +void FormFieldSignature::parseInfo() +{ + if(obj.isDict()) + { + Object sig_dict, contents_obj, byterange_obj, time_of_signing, subfilterName; + + // retrieve PKCS#7 + obj.dictLookup("V", &sig_dict); + sig_dict.dictLookup("Contents", &contents_obj); + if (contents_obj.isString()) + { + GooString *str = contents_obj.getString(); + signature_len = str->getLength(); + signature = (unsigned char *)gmalloc(signature_len); + memcpy(signature, str->getCString(), signature_len); + } + contents_obj.free(); + + sig_dict.dictLookup("ByteRange", &byterange_obj); + + if (byterange_obj.isArray()) + byte_range = new Object(byterange_obj); + + // retrieve SigningTime + sig_dict.dictLookup("M", &time_of_signing); + if(!time_of_signing.isNull()) + { + GooString *time_str = time_of_signing.getString(); + signature_info->setSigningTime(pdfTimeToInteger(time_str)); // Put this information directly in SignatureInfo object + time_of_signing.free(); + } + + // check if subfilter is supported for signature validation, only detached signatures work for now + sig_dict.dictLookup("SubFilter", &subfilterName); + if(subfilterName.isName() && strcmp(subfilterName.getName(), "adbe.pkcs7.detached") == 0) + signature_info->setSubFilterSupport(true); + + subfilterName.free(); + sig_dict.free(); + } + +} + +SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool forceRevalidation) +{ + + if(!signature_info->isSubfilterSupported()){ + error(errUnimplemented, 0, "Unable to validate this type of signature"); + return signature_info; + } + + if(signature_info->getSignatureValStatus() != SIGNATURE_NOT_VERIFIED && !forceRevalidation) + return signature_info; + + if (byte_range == NULL || !byte_range->isArray() || signature == NULL) + { + return signature_info; + } + + Object r2, r3, r4; + NSSCMSVerificationStatus sig_val_state; + SECErrorCodes cert_val_state; + + byte_range->arrayGet(1, &r2); + byte_range->arrayGet(2, &r3); + byte_range->arrayGet(3, &r4); + + unsigned int signed_data_len = r2.getInt()+r4.getInt(); + unsigned char *to_check = (unsigned char *)gmalloc(signed_data_len); + + //Read the 2 slices of data that are signed + doc->getBaseStream()->setPos(0); + doc->getBaseStream()->doGetChars(r2.getInt(), to_check); + doc->getBaseStream()->setPos(r3.getInt()); + doc->getBaseStream()->doGetChars(r4.getInt(), to_check+r2.getInt()); + + SignatureHandler signature_handler(signature, signature_len); + + sig_val_state = signature_handler.ValidateSignature(to_check, signed_data_len); + signature_info->setSignatureValStatus(SignatureHandler::NSS_SigTranslate(sig_val_state)); + signature_info->setSignerName(signature_handler.getSignerName()); + + // verify if signature contains a 'signing time' attribute + if(signature_handler.getSigningTime() != 0) + signature_info->setSigningTime(signature_handler.getSigningTime()); + + if(sig_val_state != NSSCMSVS_GoodSignature || !doVerifyCert) + return signature_info; + + cert_val_state = signature_handler.ValidateCertificate(); + signature_info->setCertificateValStatus(SignatureHandler::NSS_CertTranslate(cert_val_state)); + free(to_check); + return signature_info; } #ifdef DEBUG_FORMS @@ -1475,7 +1585,7 @@ Form::Form(PDFDoc *docA, Object* acroFormA) Form::~Form() { int i; - for(i = 0; i< numFields; ++i) + for(i = 0; i < numFields; ++i) delete rootFields[i]; gfree (rootFields); delete defaultAppearance; diff --git a/poppler/Form.h b/poppler/Form.h index 3778ff6..181af0c 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -35,6 +35,8 @@ class Annots; class LinkAction; class GfxResources; class PDFDoc; +class SignatureInfo; +class SignatureHandler; enum FormFieldType { formButton, @@ -245,6 +247,9 @@ class FormWidgetSignature: public FormWidget { public: FormWidgetSignature(PDFDoc *docA, Object *dict, unsigned num, Ref ref, FormField *p); void updateWidgetAppearance(); + + SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation); + protected: FormFieldSignature *parent; }; @@ -487,8 +492,17 @@ class FormFieldSignature: public FormField { public: FormFieldSignature(PDFDoc *docA, Object *dict, const Ref& ref, FormField *parent, std::set *usedParents); + SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation); + virtual ~FormFieldSignature(); +private: + void parseInfo(); + Object *byte_range; + unsigned char *signature; + unsigned int signature_len; + SignatureInfo *signature_info; + #ifdef DEBUG_FORMS void print(int indent = 0); #endif diff --git a/poppler/Makefile.am b/poppler/Makefile.am index 3c943ba..b27f390 100644 --- a/poppler/Makefile.am +++ b/poppler/Makefile.am @@ -166,6 +166,8 @@ poppler_include_HEADERS = \ PreScanOutputDev.h \ PSTokenizer.h \ Rendition.h \ + SignatureHandler.h \ + SignatureInfo.h \ StdinCachedFile.h \ StdinPDFDocBuilder.h \ Stream-CCITT.h \ @@ -247,6 +249,8 @@ libpoppler_la_SOURCES = \ PreScanOutputDev.cc \ PSTokenizer.cc \ Rendition.cc \ + SignatureHandler.cc \ + SignatureInfo.cc \ StdinCachedFile.cc \ StdinPDFDocBuilder.cc \ Stream.cc \ @@ -275,6 +279,7 @@ libpoppler_la_CPPFLAGS = \ $(libjpeg2000_includes) \ $(libpng_includes) \ $(libcurl_includes) \ + $(NSS3_CFLAGS) \ $(FREETYPE_CFLAGS) \ $(FONTCONFIG_CFLAGS) \ $(AM_CPPFLAGS) @@ -290,6 +295,7 @@ libpoppler_la_LIBADD = \ $(zlib_libs) \ $(libcurl_libs) \ $(libjpeg2000_libs) \ + $(NSS3_LIBS) \ $(FREETYPE_LIBS) \ $(FONTCONFIG_LIBS) \ $(PTHREAD_LIBS) \ diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index da95ff0..9e54fb3 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -44,6 +44,7 @@ #pragma implementation #endif + #include #include #include @@ -76,6 +77,9 @@ #endif #include "PDFDoc.h" #include "Hints.h" +#include "DateInfo.h" +#include "SignatureHandler.h" + #if MULTITHREADED # define pdfdocLocker() MutexLocker locker(&mutex) @@ -452,6 +456,31 @@ GBool PDFDoc::checkEncryption(GooString *ownerPassword, GooString *userPassword) return ret; } +unsigned int PDFDoc::countSignatures() +{ + return getSignatureWidgets().size(); +} + +std::vector PDFDoc::getSignatureWidgets() +{ + int num_pages = getNumPages(); + FormPageWidgets *page_widgets = NULL; + std::vector widget_vector; + + for(int i = 1; i <= num_pages; i++) + { + page_widgets = getCatalog()->getPage(i)->getFormWidgets(); + for(int j = 0; page_widgets != NULL && j < page_widgets->getNumWidgets(); j++) + { + if(page_widgets->getWidget(j)->getType() == formSignature) + widget_vector.push_back(static_cast(page_widgets->getWidget(j))); + + } + delete page_widgets; + } + return widget_vector; +} + void PDFDoc::displayPage(OutputDev *out, int page, double hDPI, double vDPI, int rotate, GBool useMediaBox, GBool crop, GBool printing, diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h index 6c40f7b..40eeea7 100644 --- a/poppler/PDFDoc.h +++ b/poppler/PDFDoc.h @@ -47,8 +47,12 @@ #include "Catalog.h" #include "Page.h" #include "Annot.h" +#include "Form.h" #include "OptionalContent.h" #include "Stream.h" +#include "poppler-config.h" +#include "SignatureInfo.h" + class GooString; class GooFile; @@ -62,6 +66,7 @@ class Linearization; class SecurityHandler; class Hints; class StructTreeRoot; +class SignatureHandler; enum PDFWriteMode { writeStandard, @@ -199,6 +204,13 @@ public: // Is the file encrypted? GBool isEncrypted() { return xref->isEncrypted(); } + /* Signature Changes */ + unsigned int countSignatures(); + std::vector getSignatureWidgets(); + + + /* End of Signature Changes */ + // Check various permissions. GBool okToPrint(GBool ignoreOwnerPW = gFalse) { return xref->okToPrint(ignoreOwnerPW); } @@ -327,7 +339,6 @@ private: Outline *outline; #endif Page **pageCache; - GBool ok; int errCode; //If there is an error opening the PDF file with fopen() in the constructor, diff --git a/poppler/SignatureHandler.cc b/poppler/SignatureHandler.cc new file mode 100644 index 0000000..1aa4831 --- /dev/null +++ b/poppler/SignatureHandler.cc @@ -0,0 +1,297 @@ +#include "SignatureHandler.h" +#include "goo/GooString.h" +#include "goo/gmem.h" +#include +#include + + +void SignatureHandler::digestFile(unsigned char *digest_buffer, unsigned char *input_data, int input_data_len, SECOidTag hashOIDTag) +{ + HASH_HashType hashType; + hashType = HASH_GetHashTypeByOidTag(hashOIDTag); + HASH_HashBuf(hashType, digest_buffer, input_data, input_data_len); + +} + +unsigned int SignatureHandler::digestLength(SECOidTag digestAlgId) +{ + switch(digestAlgId){ + case SEC_OID_SHA1: + return 20; + case SEC_OID_SHA256: + return 32; + case SEC_OID_SHA384: + return 48; + case SEC_OID_SHA512: + return 64; + default: + printf("ERROR: Unrecognized Hash ID\n"); + return 0; + } +} + +char * SignatureHandler::getSignerName() +{ + if(!CMSSignerInfo) + return NULL; + + CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); + return CERT_GetCommonName(&cert->subject); + +} + +time_t SignatureHandler::getSigningTime() +{ + PRTime sTime; // time in microseconds since the epoch + + if( NSS_CMSSignerInfo_GetSigningTime (CMSSignerInfo, &sTime) != SECSuccess) + return 0; + + return (time_t) sTime/1000000; +} + + +GooString * SignatureHandler::getDefaultFirefoxCertDB_Linux() +{ + GooString * finalPath = NULL; + DIR *toSearchIn; + struct dirent *subFolder; + + GooString * homePath = new GooString(getenv("HOME")); + homePath = homePath->append("/.mozilla/firefox/"); + + if ((toSearchIn = opendir(homePath->getCString())) == NULL){ + error(errInternal, 0, "couldn't find default Firefox Folder"); + return NULL; + } + do { + if ((subFolder = readdir(toSearchIn)) != NULL) { + if (strstr(subFolder->d_name, "default") != NULL) + { + finalPath = homePath->append(subFolder->d_name); + closedir(toSearchIn); + return finalPath; + } + } + } while (subFolder != NULL); + + return NULL; +} + +/** + * Initialise NSS + */ +void SignatureHandler::init_nss() +{ + + GooString *certDBPath = getDefaultFirefoxCertDB_Linux(); + if(certDBPath == NULL) + NSS_Init("sql:/etc/pki/nssdb"); + else + NSS_Init(certDBPath->getCString()); + + if (certDBPath) + delete certDBPath; +} + + +SignatureHandler::SignatureHandler(unsigned char *p7, int p7_length) +{ + init_nss(); + CMSitem.data = p7; + CMSitem.len = p7_length; + CMSMessage = CMS_MessageCreate(&CMSitem); + CMSSignedData = CMS_SignedDataCreate(CMSMessage); + CMSSignerInfo = CMS_SignerInfoCreate(CMSSignedData); +} + + +SignatureHandler::~SignatureHandler() +{ + SECITEM_FreeItem(&CMSitem, PR_FALSE); + if (CMSSignerInfo) + NSS_CMSSignerInfo_Destroy(CMSSignerInfo); + if (CMSSignedData) + NSS_CMSSignedData_Destroy(CMSSignedData); + if (CMSMessage) + NSS_CMSMessage_Destroy(CMSMessage); + + free(temp_certs); + + if(NSS_Shutdown()!=SECSuccess) + fprintf(stderr, "Detail: %s\n", PR_ErrorToString(PORT_GetError(), PR_LANGUAGE_I_DEFAULT)); +} + +NSSCMSMessage *SignatureHandler::CMS_MessageCreate(SECItem * cms_item) +{ + if(cms_item->data){ + return NSS_CMSMessage_CreateFromDER(cms_item, NULL, NULL /* Content callback */ + , NULL, NULL /*Password callback*/ + , NULL, NULL /*Decrypt callback*/); + } + else + return NULL; +} + +NSSCMSSignedData *SignatureHandler::CMS_SignedDataCreate(NSSCMSMessage * cms_msg) +{ + if (!NSS_CMSMessage_IsSigned(cms_msg)) { + error(errInternal, 0, "Input couldn't be parsed as a CMS signature"); + return NULL; + } + + NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cms_msg, 0); + if (!cinfo) { + error(errInternal, 0, "Error in NSS_CMSMessage_ContentLevel"); + return NULL; + } + + NSSCMSSignedData *signedData = (NSSCMSSignedData*) NSS_CMSContentInfo_GetContent(cinfo); + if (!signedData) { + error(errInternal, 0, "CError in NSS_CMSContentInfo_GetContent()"); + return NULL; + } + + if (signedData->rawCerts) + { + size_t i; + for (i = 0; signedData->rawCerts[i]; ++i); // just count the length of the certificate chain + + // tempCerts field needs to be filled for complete memory release by NSSCMSSignedData_Destroy + signedData->tempCerts = (CERTCertificate **) gmallocn( i+1, sizeof(CERTCertificate *)); + memset(signedData->tempCerts, 0, (i+1) * sizeof(CERTCertificate *)); + // store the adresses of these temporary certificates for future release + for (i = 0; signedData->rawCerts[i]; ++i) + signedData->tempCerts[i] = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), signedData->rawCerts[i], NULL, 0, 0); + + temp_certs = signedData->tempCerts; + return signedData; + } + else + return NULL; + +} + +NSSCMSSignerInfo *SignatureHandler::CMS_SignerInfoCreate(NSSCMSSignedData * cms_sig_data) +{ + NSSCMSSignerInfo *signerInfo = NSS_CMSSignedData_GetSignerInfo(cms_sig_data, 0); + if (!signerInfo) + { + printf("Error in NSS_CMSSignedData_GetSignerInfo()\n"); + return NULL; + } + else + return signerInfo; +} + +NSSCMSVerificationStatus SignatureHandler::ValidateSignature(unsigned char *signed_data, int signed_data_len) +{ + + unsigned char *digest_buffer = NULL; + + if (!CMSSignedData) + return NSSCMSVS_MalformedSignature; + + SECItem usedAlgorithm = NSS_CMSSignedData_GetDigestAlgs(CMSSignedData)[0]->algorithm; + unsigned int hash_length = digestLength(SECOID_FindOIDTag(&usedAlgorithm)); + + digest_buffer = (unsigned char *)PORT_Alloc(hash_length); + + digestFile(digest_buffer, signed_data, signed_data_len, SECOID_FindOIDTag(&usedAlgorithm)); + + SECItem digest; + digest.data = digest_buffer; + digest.len = hash_length; + + if ((NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB())) == NULL) + CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; + + if (NSS_CMSSignerInfo_Verify(CMSSignerInfo, &digest, NULL) != SECSuccess) + { + PORT_Free(digest_buffer); + return CMSSignerInfo->verificationStatus; + } + else + { + PORT_Free(digest_buffer); + return NSSCMSVS_GoodSignature; + } + +} + +SECErrorCodes SignatureHandler::ValidateCertificate() +{ + + SECErrorCodes retVal; + CERTCertificate *cert; + + if (!CMSSignerInfo) + return (SECErrorCodes) -1; //error code to avoid matching error codes defined in SECErrorCodes + + if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB())) == NULL) + CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; + + CERTValInParam inParams[2]; + inParams[0].type = cert_pi_revocationFlags; + inParams[0].value.pointer.revocation = CERT_GetClassicOCSPEnabledSoftFailurePolicy(); + inParams[1].type = cert_pi_end; + + if (CERT_PKIXVerifyCert(cert, certificateUsageEmailSigner, inParams, NULL, + CMSSignerInfo->cmsg->pwfn_arg) != SECSuccess) { + retVal = (SECErrorCodes) PORT_GetError(); + } else { + // PORT_GetError() will return 0 if everything was fine, + // there are other possible outcomes even if the previous return was SECSuccess. + retVal = (SECErrorCodes) PORT_GetError(); + } + + + if (cert) + CERT_DestroyCertificate(cert); + + return retVal; +} + + +SignatureValidationStatus SignatureHandler::NSS_SigTranslate(NSSCMSVerificationStatus nss_code) +{ + switch(nss_code) + { + case NSSCMSVS_GoodSignature: + return SIGNATURE_VALID; + + case NSSCMSVS_BadSignature: + return SIGNATURE_INVALID; + + case NSSCMSVS_DigestMismatch: + return SIGNATURE_DIGEST_MISMATCH; + + case NSSCMSVS_ProcessingError: + return SIGNATURE_DECODING_ERROR; + + default: + return SIGNATURE_GENERIC_ERROR; + } +} + +CertificateValidationStatus SignatureHandler::NSS_CertTranslate(SECErrorCodes nss_code) +{ + // 0 not defined in SECErrorCodes, it means success for this purpose. + if (nss_code == (SECErrorCodes) 0) + return CERTIFICATE_TRUSTED; + + switch(nss_code) + { + case SEC_ERROR_UNKNOWN_ISSUER: + return CERTIFICATE_UNTRUSTED; + + case SEC_ERROR_REVOKED_CERTIFICATE: + return CERTIFICATE_REVOKED; + + case SEC_ERROR_EXPIRED_CERTIFICATE: + return CERTIFICATE_EXPIRED; + + default: + return CERTIFICATE_GENERIC_ERROR; + } +} diff --git a/poppler/SignatureHandler.h b/poppler/SignatureHandler.h new file mode 100644 index 0000000..126fbc7 --- /dev/null +++ b/poppler/SignatureHandler.h @@ -0,0 +1,59 @@ +#ifndef SIGNATURE_HANDLER_H +#define SIGNATURE_HANDLER_H + + +#include "goo/GooString.h" +#include "SignatureInfo.h" + +/* NSPR Headers */ +#include +#include +#include +#include +#include + +/* NSS headers */ +#include +#include +#include +#include +#include +#include +#include +#include + + +class SignatureHandler +{ + + public: + SignatureHandler(unsigned char *p7, int p7_length); + ~SignatureHandler(); + time_t getSigningTime(); + char * getSignerName(); + void setSignature(unsigned char *, int); + NSSCMSVerificationStatus ValidateSignature(unsigned char *signed_data, int signed_data_len); + SECErrorCodes ValidateCertificate(); + //Translate NSS error codes + static SignatureValidationStatus NSS_SigTranslate(NSSCMSVerificationStatus nss_code); + static CertificateValidationStatus NSS_CertTranslate(SECErrorCodes nss_code); + + + private: + SECItem CMSitem; + NSSCMSMessage *CMSMessage = NULL; + NSSCMSSignedData *CMSSignedData = NULL; + NSSCMSSignerInfo *CMSSignerInfo = NULL; + CERTCertificate **temp_certs = NULL; + + void init_nss(); + GooString * getDefaultFirefoxCertDB_Linux(); + unsigned int digestLength(SECOidTag digestAlgId); + NSSCMSMessage *CMS_MessageCreate(SECItem * cms_item); + NSSCMSSignedData *CMS_SignedDataCreate(NSSCMSMessage * cms_msg); + NSSCMSSignerInfo *CMS_SignerInfoCreate(NSSCMSSignedData * cms_sig_data); + void digestFile(unsigned char *digest_buffer, unsigned char *input_data, int input_data_len, SECOidTag hashOIDTag); + +}; + +#endif diff --git a/poppler/SignatureInfo.cc b/poppler/SignatureInfo.cc new file mode 100644 index 0000000..c51a839 --- /dev/null +++ b/poppler/SignatureInfo.cc @@ -0,0 +1,73 @@ +#include "SignatureInfo.h" +#include "goo/gmem.h" +#include +#include + +/* Constructor & Destructor */ + +SignatureInfo::SignatureInfo() +{ + sig_status = SIGNATURE_NOT_VERIFIED; + cert_status = CERTIFICATE_NOT_VERIFIED; + signer_name = NULL; + signing_time = 0; + sig_subfilter_supported = false; +} + +SignatureInfo::SignatureInfo(SignatureValidationStatus sig_val_status, CertificateValidationStatus cert_val_status) +{ + sig_status = sig_val_status; + cert_status = cert_val_status; + signer_name = NULL; + signing_time = 0; + sig_subfilter_supported = false; +} + +SignatureInfo::~SignatureInfo() +{ + free(signer_name); +} + +/* GETTERS */ + +SignatureValidationStatus SignatureInfo::getSignatureValStatus() +{ + return sig_status; +} + +CertificateValidationStatus SignatureInfo::getCertificateValStatus() +{ + return cert_status; +} + +char *SignatureInfo::getSignerName() +{ + return signer_name; +} + +time_t SignatureInfo::getSigningTime() +{ + return signing_time; +} + +/* SETTERS */ + +void SignatureInfo::setSignatureValStatus(enum SignatureValidationStatus sig_val_status) +{ + sig_status = sig_val_status; +} + +void SignatureInfo::setCertificateValStatus(enum CertificateValidationStatus cert_val_status) +{ + cert_status = cert_val_status; +} + +void SignatureInfo::setSignerName(char *signerName) +{ + signer_name = signerName; +} + +void SignatureInfo::setSigningTime(time_t signingTime) +{ + signing_time = signingTime; +} \ No newline at end of file diff --git a/poppler/SignatureInfo.h b/poppler/SignatureInfo.h new file mode 100644 index 0000000..0ae6207 --- /dev/null +++ b/poppler/SignatureInfo.h @@ -0,0 +1,57 @@ +#ifndef SIGNATUREINFO_H +#define SIGNATUREINFO_H + +#include + +enum SignatureValidationStatus +{ + SIGNATURE_VALID, + SIGNATURE_INVALID, + SIGNATURE_DIGEST_MISMATCH, + SIGNATURE_DECODING_ERROR, + SIGNATURE_GENERIC_ERROR, + SIGNATURE_NOT_FOUND, + SIGNATURE_NOT_VERIFIED +}; + +enum CertificateValidationStatus +{ + CERTIFICATE_TRUSTED, + CERTIFICATE_UNTRUSTED, + CERTIFICATE_REVOKED, + CERTIFICATE_EXPIRED, + CERTIFICATE_GENERIC_ERROR, + CERTIFICATE_NOT_VERIFIED +}; + +class SignatureInfo { + +public: + SignatureInfo(); + SignatureInfo(SignatureValidationStatus, CertificateValidationStatus); + ~SignatureInfo(); + + /* GETTERS */ + SignatureValidationStatus getSignatureValStatus(); + CertificateValidationStatus getCertificateValStatus(); + char *getSignerName(); + time_t getSigningTime(); + bool isSubfilterSupported() { return sig_subfilter_supported; } + + /* SETTERS */ + void setSignatureValStatus(enum SignatureValidationStatus ); + void setCertificateValStatus(enum CertificateValidationStatus ); + void setSignerName(char *); + void setSigningTime(time_t); + void setSubFilterSupport(bool isSupported) { sig_subfilter_supported = isSupported; } + +private: + SignatureValidationStatus sig_status; + CertificateValidationStatus cert_status; + char *signer_name; + time_t signing_time; + bool sig_subfilter_supported; + +}; + +#endif \ No newline at end of file diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 9c16018..582dec9 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -89,6 +89,14 @@ target_link_libraries(pdfinfo ${common_libs}) install(TARGETS pdfinfo DESTINATION bin) install(FILES pdfinfo.1 DESTINATION share/man/man1) +# pdfsigverify +set(pdfsigverify_SOURCES ${common_srcs} + pdfsigverify.cc +) +add_executable(pdfsigverify ${pdfsigverify_SOURCES}) +target_link_libraries(pdfsigverify ${common_libs}) +install(TARGETS pdfsigverify DESTINATION bin) + # pdftops set(pdftops_SOURCES ${common_srcs} pdftops.cc diff --git a/utils/Makefile.am b/utils/Makefile.am index 376d021..318cfb6 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -23,6 +23,7 @@ bin_PROGRAMS = \ pdftotext \ pdftohtml \ pdfseparate \ + pdfsigverify \ pdfunite if BUILD_SPLASH_OUTPUT @@ -60,6 +61,9 @@ pdfdetach_SOURCES = \ pdffonts_SOURCES = \ pdffonts.cc +pdfsigverify_SOURCES = \ + pdfsigverify.cc + pdfimages_SOURCES = \ pdfimages.cc \ ImageOutputDev.cc \ diff --git a/utils/pdfsigverify.cc b/utils/pdfsigverify.cc new file mode 100644 index 0000000..038ea73 --- /dev/null +++ b/utils/pdfsigverify.cc @@ -0,0 +1,172 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include "parseargs.h" +#include "Object.h" +#include "Array.h" +#include "Page.h" +#include "PDFDoc.h" +#include "PDFDocFactory.h" +#include "Error.h" +#include "GlobalParams.h" + + +const char * getReadableSigState(SignatureValidationStatus sig_vs) +{ + + switch(sig_vs) + { + case SIGNATURE_VALID: + return "Signature is Valid."; + + case SIGNATURE_INVALID: + return "Signature is Invalid."; + + case SIGNATURE_DIGEST_MISMATCH: + return "Digest Mismatch."; + + case SIGNATURE_DECODING_ERROR: + return "Document isn't signed or corrupted data."; + + case SIGNATURE_NOT_VERIFIED: + return "Signature has not yet been verified."; + + default: + return "Unknown Validation Failure."; + } +} + +const char * getReadableCertState(CertificateValidationStatus cert_vs) +{ + switch(cert_vs) + { + case CERTIFICATE_TRUSTED: + return "Certificate is Trusted."; + + case CERTIFICATE_UNTRUSTED: + return "Certificate isn't Trusted."; + + case CERTIFICATE_REVOKED: + return "Certificate has been Revoked."; + + case CERTIFICATE_EXPIRED: + return "Certificate has Expired"; + + case CERTIFICATE_NOT_VERIFIED: + return "Certificate has not yet been verified."; + + default: + return "Unknown issue with Certificate or corrupted data."; + } +} + +char *getReadableTime(time_t unix_time) +{ + char * time_str = (char *) gmalloc(64); + strftime(time_str, 64, "%b %d %Y %H:%M:%S", localtime(&unix_time)); + return time_str; +} + + +static GBool printVersion = gFalse; +static GBool printHelp = gFalse; +static GBool dontVerifyCert = gFalse; + +static const ArgDesc argDesc[] = { + {"-v", argFlag, &printVersion, 0, + "print copyright and version info"}, + {"-h", argFlag, &printHelp, 0, + "print usage information"}, + {"-help", argFlag, &printHelp, 0, + "print usage information"}, + {"-c", argFlag, &dontVerifyCert, 0, + "don't perform certificate validation"}, + {NULL} +}; + + +int main(int argc, char *argv[]) +{ + PDFDoc *doc = NULL; + unsigned int sigCount; + GooString * fileName = NULL; + SignatureInfo *sig_info = NULL; + char *time_str = NULL; + std::vector sig_widgets; + globalParams = new GlobalParams(); + + + int exitCode = 99; + GBool ok; + + ok = parseArgs(argDesc, &argc, argv); + + if (!ok || argc != 2 || printVersion || printHelp) { + fprintf(stderr, "pdfsigverify version %s\n", PACKAGE_VERSION); + fprintf(stderr, "%s\n", popplerCopyright); + fprintf(stderr, "%s\n", xpdfCopyright); + if (!printVersion) { + printUsage("pdfsigverify", "", argDesc); + } + if (printVersion || printHelp) + exitCode = 0; + goto end; + } + + fileName = new GooString(argv[argc - 1]); + + // open PDF file + doc = PDFDocFactory().createPDFDoc(*fileName, NULL, NULL); + + if (!doc->isOk()) + { + goto end; + exitCode = 1; + } + + sigCount = doc->countSignatures(); + + if(sigCount >= 1) + printf("Digital Signature Info of: %s\n", fileName->getCString()); + else + { + printf("File '%s' does not contain any signatures\n", fileName->getCString()); + exitCode = 2; + goto end; + } + + sig_widgets = doc->getSignatureWidgets(); + if(sig_widgets.size() == 0) + goto end; + + for(unsigned int i = 0; i < sigCount; i++) + { + sig_info = sig_widgets.at(i)->validateSignature(!dontVerifyCert, false); + printf("Signature #%d:\n", i+1); + printf(" - Signer Certificate Common Name: %s\n", sig_info->getSignerName()); + printf(" - Signing Time: %s\n", time_str = getReadableTime(sig_info->getSigningTime())); + printf(" - Signature Validation: %s\n", getReadableSigState(sig_info->getSignatureValStatus())); + if (sig_info->getSignatureValStatus() != SIGNATURE_VALID || dontVerifyCert) + { + continue; + } + printf(" - Certificate Validation: %s\n", getReadableCertState(sig_info->getCertificateValStatus())); + gfree(time_str); + delete sig_info; + } + + exitCode = 0; + + end: + delete globalParams; + if (fileName) + delete fileName; + if (doc) + delete doc; + + return exitCode; +}