diff --git a/CMakeLists.txt b/CMakeLists.txt index c587ea6..570e6d5 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/SigStatusStruct.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/glib/Makefile.am b/glib/Makefile.am index 52669e2..9b46974 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -70,6 +70,7 @@ libpoppler_glib_la_CPPFLAGS = \ -I$(top_srcdir) \ -I$(top_srcdir)/poppler \ $(POPPLER_GLIB_CFLAGS) \ + $(NSS3_CFLAGS) \ $(FREETYPE_CFLAGS) \ $(POPPLER_GLIB_DISABLE_DEPRECATED) \ $(POPPLER_GLIB_DISABLE_SINGLE_INCLUDES) diff --git a/glib/demo/info.cc b/glib/demo/info.cc index a396ba1..b562ebd 100644 --- a/glib/demo/info.cc +++ b/glib/demo/info.cc @@ -17,6 +17,7 @@ */ #include +#include #include "config.h" #include "info.h" @@ -233,6 +234,13 @@ pgd_info_create_widget (PopplerDocument *document) pgd_table_add_property (GTK_GRID (table), "Linearized:", linearized ? "Yes" : "No", &row); + guint signed_doc = poppler_document_is_signed(document); + + gchar *number_of_sigs= (gchar *)g_malloc(10); + + g_snprintf(number_of_sigs, 10, "%d", signed_doc); + pgd_table_add_property (GTK_GRID (table), "Signatures:", number_of_sigs, &row); + str = pgd_format_date (creation_date); pgd_table_add_property (GTK_GRID (table), "Creation Date:", str, &row); g_free (str); diff --git a/glib/poppler-document.cc b/glib/poppler-document.cc index 61d92e8..3ce0252 100644 --- a/glib/poppler-document.cc +++ b/glib/poppler-document.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,8 @@ #include "poppler-input-stream.h" #include "poppler-cached-file-loader.h" + + /** * SECTION:poppler-document * @short_description: Information about a document @@ -67,7 +70,8 @@ enum { PROP_PAGE_MODE, PROP_VIEWER_PREFERENCES, PROP_PERMISSIONS, - PROP_METADATA + PROP_METADATA, + PROP_SIGNED }; static void poppler_document_layers_free (PopplerDocument *document); @@ -1103,6 +1107,164 @@ poppler_document_is_linearized (PopplerDocument *document) } /** + * poppler_document_is_signed: + * @document: A #PopplerDocument + * + * Returns whether @document is signed or not. PDF Signatures ensure + * that the content hash not been altered since last edit and + * that it was produced by someone the user can trust + * + * Return value: %TRUE if @document is signed, %FALSE otherwhise + * + **/ +guint +poppler_document_is_signed(PopplerDocument *document) +{ + + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), 0); + if (document->doc->isSigned()) + return document->doc->countSignatures(); + else return 0; + +} + +/** + * poppler_document_signature_get_time + * @document: A #PopplerDocument + * @index: the signature index within the document starting from 0 + * + * Returns a human-readable string representing the signing time + * of the signature number @index of @document + * + **/ +gchar * +poppler_document_signature_get_time(PopplerDocument *document, int index) +{ + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL); + return document->doc->getSigningTime(index); +} + +/** + * poppler_document_signature_get_signername + * @document: A #PopplerDocument + * @index: the signature index within the document starting from 0 + * + * Returns a string representing the CommonName field of the signing certificate + * of the signature number @index of @document + * + **/ +gchar * +poppler_document_signature_get_signername(PopplerDocument *document, int index) +{ + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL); + return document->doc->getSignerName(); +} + + + + +POPPLER_DEFINE_BOXED_TYPE (PopplerSignatureInfo, poppler_signature_info, + poppler_signature_info_copy, + poppler_signature_info_free) + +PopplerSignatureInfo * +poppler_signature_info_copy (PopplerSignatureInfo *sig_info) +{ + PopplerSignatureInfo *new_info; + + g_return_val_if_fail (sig_info != NULL, NULL); + + new_info = g_new0(PopplerSignatureInfo, 1); + new_info->sig_status = sig_info->sig_status; + new_info->cert_status = sig_info->cert_status; + + return new_info; +} + +void +poppler_signature_info_free (PopplerSignatureInfo *siginfo) +{ + g_free(siginfo); +} + +/** + * poppler_document_signature_validate: + * @document: A #PopplerDocument + * @index: the signature index within the document starting from 0 + * @verifyCert: flag that indicates if the signing certificate validity and trust should be checked + * + * Validates the signature refered to by @index if present in the document using + * the NSS cryptographic libraries + * Certificate Revocation checks are performed which usually require Internet access for OCSP requests + * or CRL downloads + * + * Return value: (transfer full): A #PopplerSignatureInfo structure containing the 2 components of the + * signature validation output + * + **/ +PopplerSignatureInfo * + poppler_document_signature_validate(PopplerDocument *document, int index, gboolean verifyCert) +{ + PopplerSignatureInfo *sig_info; + + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL); + sig_info = (PopplerSignatureInfo*) gmalloc(sizeof(PopplerSignatureInfo)); + SigStatus * _sig = document->doc->validateSignature(index, verifyCert); + + switch(_sig->sig_vs) + { + case SIGNATURE_VALID: + sig_info->sig_status = POPPLER_SIGNATURE_VALID; + break; + case SIGNATURE_INVALID: + sig_info->sig_status = POPPLER_SIGNATURE_INVALID; + break; + case SIGNATURE_DIGEST_MISMATCH: + sig_info->sig_status = POPPLER_SIGNATURE_DIGEST_MISMATCH; + break; + case SIGNATURE_DECODING_ERROR: + sig_info->sig_status = POPPLER_SIGNATURE_DECODING_ERROR; + break; + case SIGNATURE_GENERIC_ERROR: + sig_info->sig_status = POPPLER_SIGNATURE_GENERIC_ERROR; + break; + case SIGNATURE_NOT_FOUND: + sig_info->sig_status = POPPLER_SIGNATURE_NOT_FOUND; + break; + case SIGNATURE_NOT_VERIFIED: + sig_info->sig_status = POPPLER_SIGNATURE_NOT_VERIFIED; + break; + } + + + switch(_sig->cert_vs) + { + case CERTIFICATE_TRUSTED: + sig_info->cert_status = POPPLER_CERTIFICATE_TRUSTED; + break; + case CERTIFICATE_UNTRUSTED: + sig_info->cert_status = POPPLER_CERTIFICATE_UNTRUSTED; + break; + case CERTIFICATE_REVOKED: + sig_info->cert_status = POPPLER_CERTIFICATE_REVOKED; + break; + case CERTIFICATE_EXPIRED: + sig_info->cert_status = POPPLER_CERTIFICATE_EXPIRED; + break; + case CERTIFICATE_GENERIC_ERROR: + sig_info->cert_status = POPPLER_CERTIFICATE_GENERIC_ERROR; + break; + case CERTIFICATE_NOT_VERIFIED: + sig_info->cert_status = POPPLER_CERTIFICATE_NOT_VERIFIED; + break; + } + + SigStatus_destroy(_sig); + + return sig_info; +} + +/** * poppler_document_get_page_layout: * @document: A #PopplerDocument * @@ -1269,6 +1431,9 @@ poppler_document_get_property (GObject *object, case PROP_LINEARIZED: g_value_set_boolean (value, poppler_document_is_linearized (document)); break; + case PROP_SIGNED: + g_value_set_boolean(value, poppler_document_is_signed (document)); + break; case PROP_PAGE_LAYOUT: g_value_set_enum (value, poppler_document_get_page_layout (document)); break; @@ -1453,6 +1618,19 @@ poppler_document_class_init (PopplerDocumentClass *klass) "Is the document optimized for web viewing?", FALSE, G_PARAM_READABLE)); + /** + * PopplerDocument::signed: + * Whether the document is signed. + * + */ + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_SIGNED, + g_param_spec_boolean ("signed", + "Digital Signature Present", + "Is the document signed?", + FALSE, + G_PARAM_READABLE)); /** * PopplerDocument:page-layout: diff --git a/glib/poppler-document.h b/glib/poppler-document.h index a34e88c..aafbe83 100644 --- a/glib/poppler-document.h +++ b/glib/poppler-document.h @@ -23,6 +23,7 @@ #include #include "poppler.h" + G_BEGIN_DECLS #define POPPLER_TYPE_DOCUMENT (poppler_document_get_type ()) @@ -41,6 +42,7 @@ G_BEGIN_DECLS * * Page layout types */ + typedef enum { POPPLER_PAGE_LAYOUT_UNSET, @@ -164,6 +166,57 @@ typedef enum /*< flags >*/ } PopplerPermissions; +/** + * PopplerSignatureStatus + * @POPPLER_SIGNATURE_VALID: signature is cryptographically valid + * @POPPLER_SIGNATURE_INVALID: signature is cryptographically invalid + * @POPPLER_SIGNATURE_DIGEST_MISMATCH: document content was changed after the signature was applied + * @POPPLER_SIGNATURE_DECODING_ERROR: signature CMS/PKCS7 structure is malformed + * @POPPLER_SIGNATURE_GENERIC_ERROR: failed to verify signature + * @POPPLER_SIGNATURE_NOT_FOUND: requested signature is not present in the document + * @POPPLER_SIGNATURE_NOT_VERIFIED: signature not yet verified + * + * Signature verification results + */ +typedef enum +{ + POPPLER_SIGNATURE_VALID, + POPPLER_SIGNATURE_INVALID, + POPPLER_SIGNATURE_DIGEST_MISMATCH, + POPPLER_SIGNATURE_DECODING_ERROR, + POPPLER_SIGNATURE_GENERIC_ERROR, + POPPLER_SIGNATURE_NOT_FOUND, + POPPLER_SIGNATURE_NOT_VERIFIED +} PopplerSignatureStatus; + +/** + * PopplerSignatureCertificateStatus + * @POPPLER_CERTIFICATE_TRUSTED: certificate is considered trusted + * @POPPLER_CERTIFICATE_UNTRUSTED: could not build trust chain for this certificate + * @POPPLER_CERTIFICATE_REVOKED: certificate was revoked by the issuing certificate authority + * @POPPLER_CERTIFICATE_EXPIRED: signing time is outside the validity bounds of this certificate + * @POPPLER_CERTIFICATE_GENERIC_ERROR: failed to verify certificate + * @POPPLER_CERTIFICATE_NOT_VERIFIED: certificate not yet verified + * + * Signature certificate verification results + */ + +typedef enum +{ + POPPLER_CERTIFICATE_TRUSTED, + POPPLER_CERTIFICATE_UNTRUSTED, + POPPLER_CERTIFICATE_REVOKED, + POPPLER_CERTIFICATE_EXPIRED, + POPPLER_CERTIFICATE_GENERIC_ERROR, + POPPLER_CERTIFICATE_NOT_VERIFIED +} PopplerSignatureCertificateStatus; + +struct _PopplerSignatureInfo +{ + PopplerSignatureStatus sig_status; + PopplerSignatureCertificateStatus cert_status; +}; + GType poppler_document_get_type (void) G_GNUC_CONST; PopplerDocument *poppler_document_new_from_file (const char *uri, @@ -209,11 +262,23 @@ gchar *poppler_document_get_producer (PopplerDocument *doc time_t poppler_document_get_creation_date (PopplerDocument *document); time_t poppler_document_get_modification_date (PopplerDocument *document); gboolean poppler_document_is_linearized (PopplerDocument *document); + PopplerPageLayout poppler_document_get_page_layout (PopplerDocument *document); PopplerPageMode poppler_document_get_page_mode (PopplerDocument *document); PopplerPermissions poppler_document_get_permissions (PopplerDocument *document); gchar *poppler_document_get_metadata (PopplerDocument *document); +/* Signatures */ + +guint poppler_document_is_signed (PopplerDocument *document); +PopplerSignatureInfo *poppler_document_signature_validate (PopplerDocument *document, int index, gboolean verifyCert); +gchar *poppler_document_signature_get_time (PopplerDocument *document, int index); +gchar *poppler_document_signature_get_signername(PopplerDocument *document, int index); +#define POPPLER_TYPE_SIGNATURE_INFO (poppler_signature_info_get_type ()) +GType poppler_signature_info_get_type (void) G_GNUC_CONST; +PopplerSignatureInfo * poppler_signature_info_copy (PopplerSignatureInfo *siginfo); +void poppler_signature_info_free(PopplerSignatureInfo *siginfo); + /* Attachments */ guint poppler_document_get_n_attachments (PopplerDocument *document); gboolean poppler_document_has_attachments (PopplerDocument *document); diff --git a/glib/poppler.h b/glib/poppler.h index 4df9c0e..0f860c6 100644 --- a/glib/poppler.h +++ b/glib/poppler.h @@ -212,6 +212,7 @@ typedef struct _PopplerQuadrilateral PopplerQuadrilateral; typedef struct _PopplerStructureElement PopplerStructureElement; typedef struct _PopplerStructureElementIter PopplerStructureElementIter; typedef struct _PopplerTextSpan PopplerTextSpan; +typedef struct _PopplerSignatureInfo PopplerSignatureInfo; /** * PopplerBackend: diff --git a/glib/reference/poppler-sections.txt b/glib/reference/poppler-sections.txt index bbac42d..34e6d49 100644 --- a/glib/reference/poppler-sections.txt +++ b/glib/reference/poppler-sections.txt @@ -124,6 +124,9 @@ PopplerFontType PopplerPSFile PopplerViewerPreferences PopplerPermissions +PopplerSignatureCertificateStatus +PopplerSignatureInfo +PopplerSignatureStatus poppler_document_new_from_file poppler_document_new_from_data poppler_document_new_from_stream @@ -154,6 +157,11 @@ poppler_document_get_n_attachments poppler_document_has_attachments poppler_document_get_attachments poppler_document_get_form_field +poppler_document_is_signed +poppler_document_signature_get_signername +poppler_document_signature_get_time +poppler_document_signature_validate +poppler_orientation_get_type poppler_index_iter_new poppler_index_iter_copy poppler_index_iter_free diff --git a/poppler/Makefile.am b/poppler/Makefile.am index 3c943ba..8357d77 100644 --- a/poppler/Makefile.am +++ b/poppler/Makefile.am @@ -166,6 +166,8 @@ poppler_include_HEADERS = \ PreScanOutputDev.h \ PSTokenizer.h \ Rendition.h \ + SignatureHandler.h \ + SigStatusStruct.h \ StdinCachedFile.h \ StdinPDFDocBuilder.h \ Stream-CCITT.h \ @@ -247,6 +249,8 @@ libpoppler_la_SOURCES = \ PreScanOutputDev.cc \ PSTokenizer.cc \ Rendition.cc \ + SignatureHandler.cc \ + SigStatusStruct.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..bc90885 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) @@ -120,6 +124,7 @@ void PDFDoc::init() startXRefPos = -1; secHdlr = NULL; pageCache = NULL; + m_sig_handler = NULL; } PDFDoc::PDFDoc() @@ -337,6 +342,9 @@ PDFDoc::~PDFDoc() { if (fileName) { delete fileName; } + if(m_sig_handler) { + delete m_sig_handler; + } #ifdef _WIN32 if (fileNameU) { gfree(fileNameU); @@ -452,11 +460,334 @@ GBool PDFDoc::checkEncryption(GooString *ownerPassword, GooString *userPassword) return ret; } +#define SIGFLAGS_SIGNATURES_EXIST 0x1 +#define SIGFLAGS_APPEND_ONLY 0x2 + +GBool PDFDoc::isSigned() { + GBool ret; + Object sigFlags; + if (getCatalog()->getAcroForm()->isNull()) + { + return gFalse; + } + getCatalog()->getAcroForm()->dictLookup("SigFlags", &sigFlags); + + if (sigFlags.isInt()) { + ret = sigFlags.getInt() & SIGFLAGS_SIGNATURES_EXIST; + } else { + ret = gFalse; + } + sigFlags.free(); + + return ret; +} + +unsigned int PDFDoc::countSignatures() +{ + unsigned int count = 0; + Object *acro_form = getCatalog()->getAcroForm(); + Object fields; + + if (acro_form->isNull()) + return count; + acro_form->dictLookup("Fields", &fields); + if(fields.isNull()) + return count; + + //Find the Signature Field and retrieve /Contents + for (int i = 0; i != fields.arrayGetLength(); i++) + { + Object field, type, field_type; + fields.arrayGet(i, &field); + + field.dictLookup("Type", &type); + field.dictLookup("FT", &field_type); + if (type.isNull() || field_type.isNull()) + { + field.free(); + type.free(); + field_type.free(); + continue; + } + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(field_type.getName(), "Sig") == 0) + { + count++; + } + + type.free(); + field_type.free(); + field.free(); + } + + fields.free(); + + return count; +} + +SigStatus * PDFDoc::validateSignature(int sig_index, GBool verifyCertBool) +{ + + Object r2, r3, r4; + NSSCMSVerificationStatus sig_val_state; + SECErrorCodes cert_val_state; + SigStatus * sig_status_struct = SigStatus_create(); + + unsigned char *signature; + + if (isSigned()) + { + int sig_len = getSignatureContents(&signature, sig_index); + Object *byterange = getByteRange(sig_index); + if(!byterange) + return sig_status_struct; + + byterange->arrayGet(1, &r2); + byterange->arrayGet(2, &r3); + byterange->arrayGet(3, &r4); + byterange->free(); + delete byterange; + unsigned int signed_data_len = r2.getInt()+r4.getInt(); + unsigned char *to_check = (unsigned char *)malloc(signed_data_len); + + //Read the 2 slices of data that are signed + str->setPos(0); + str->doGetChars(r2.getInt(), to_check); + str->setPos(r3.getInt()); + str->doGetChars(r4.getInt(), to_check+r2.getInt()); + + if(!m_sig_handler) + m_sig_handler = new SignatureHandler(signature, sig_len); + else { + delete m_sig_handler; // avoid memory leak for documents with multiple signatures. restarts nss. + m_sig_handler = new SignatureHandler(signature, sig_len); + } + + sig_val_state = (m_sig_handler->ValidateSignature(to_check, signed_data_len)); + setSignatureValStatus(sig_status_struct, SignatureHandler::NSS_SigTranslate(sig_val_state)); + + if(verifyCertBool) + { + cert_val_state = (m_sig_handler->ValidateCertificate()); + setCertificateValStatus(sig_status_struct, SignatureHandler::NSS_CertTranslate(cert_val_state)); + } + else + { + setCertificateValStatus(sig_status_struct, CERTIFICATE_NOT_VERIFIED); + } + free(to_check); + return sig_status_struct; + } + else + { + setSignatureValStatus(sig_status_struct, SIGNATURE_NOT_FOUND); + return sig_status_struct; + } + +} + +char *PDFDoc::getSignerName() +{ + if (m_sig_handler != NULL) + return m_sig_handler->getSignerName(); + else + return NULL; +} + +char *PDFDoc::getSigningTime(int index) +{ + if (m_sig_handler != NULL) + { + char * sig_time = m_sig_handler->getSigningTime(); + if (sig_time != NULL) + return sig_time; + else + return getTimestampFromSigDict(index); + } + else + return NULL; + +} + +char * formatSigningTimeDate(GooString *time_str) +{ + int year, mon, day, hour, min, sec, tz_hour, tz_minute; + char tz; + struct tm time; + const size_t date_len = 50; + char *date_string = (char *)malloc(date_len); + + if (!parseDateString (time_str->getCString(), &year, + &mon, &day, &hour, &min, &sec, &tz, &tz_hour, &tz_minute)) + return NULL; + + time.tm_year = year - 1900; + time.tm_mon = mon - 1; + time.tm_mday = day; + time.tm_hour = hour; + time.tm_min = min; + time.tm_sec = sec; + time.tm_wday = -1; + time.tm_yday = -1; + time.tm_isdst = -1; + strftime(date_string, date_len, "%b %d %Y %H:%M:%S", &time); + + return date_string; + +} + + +char *PDFDoc::getTimestampFromSigDict(int index) +{ + Object fields, f, sig_dict, time_of_signing, type, obj1; + Object *acro_form = getCatalog()->getAcroForm(); + bool timestamp_found = false; + char * formatted_ts = NULL; + + int sig_count=0; + + if (acro_form->isNull()) + return 0; + acro_form->dictLookup("Fields", &fields); + + //Find the Signature Field and retrieve /M attribute + for (int i = 0; i != fields.arrayGetLength() && !timestamp_found; i++) + { + Object type, field_type; + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &field_type); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(field_type.getName(), "Sig") == 0) + { + if (sig_count == index) + { + f.dictLookup("V", &sig_dict); + sig_dict.dictLookup("M", &time_of_signing); + GooString *time_str = time_of_signing.getString(); + formatted_ts = formatSigningTimeDate(time_str); + timestamp_found = true; + time_of_signing.free(); + } + + sig_count++; + } + f.free(); + type.free(); + field_type.free(); + sig_dict.free(); + } + + return formatted_ts; + +} + +int PDFDoc::getSignatureContents(unsigned char **contents, int index) +{ + Object *acro_form = getCatalog()->getAcroForm(); + Object fields, f, sig_dict, contents_obj; + int sig_count=0; + int sizeof_contents = 0; + bool contents_found = false; + + if (acro_form->isNull()) + return 0; + acro_form->dictLookup("Fields", &fields); + + //Find the Signature Field and retrieve /Contents + for (int i = 0; i != fields.arrayGetLength() && !contents_found; i++) + { + Object type, field_type; + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &field_type); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(field_type.getName(), "Sig") == 0) + { + + if (sig_count == index) + { + f.dictLookup("V", &sig_dict); + f.free(); + sig_dict.dictLookup("Contents", &contents_obj); + if (contents_obj.isString()) + { + GooString *str = contents_obj.getString(); + sizeof_contents = str->getLength(); + *contents = (unsigned char *)malloc(sizeof_contents); + memcpy(*contents, str->getCString(), sizeof_contents); + contents_found = true; + } + sig_dict.free(); + contents_obj.free(); + } + sig_count++; + + } + type.free(); + field_type.free(); + } + + return sizeof_contents; +} + +Object *PDFDoc::getByteRange(int index) +{ + + Object *acro_form = getCatalog()->getAcroForm(); + Object fields, f, sig_dict, byterange_obj, type, field_type; + int sig_count=0; + bool byte_range_found = false; + + if (acro_form->isNull()) + return 0; + acro_form->dictLookup("Fields", &fields); + + + //Find the Signature Field and retrieve /ByteRange + for (int i = 0; i != fields.arrayGetLength() && !byte_range_found; i++) + { + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &field_type); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(field_type.getName(), "Sig") == 0) + { + + if (sig_count == index) + { + f.dictLookup("V", &sig_dict); + f.free(); + if (!sig_dict.isDict()) + return NULL; + sig_dict.dictLookup("ByteRange", &byterange_obj); + if (byterange_obj.isArray()) + { + byte_range_found = true; + } + } + type.free(); + field_type.free(); + sig_dict.free(); + sig_count++; + } + + } + if (!byterange_obj.isNull()) + return new Object(byterange_obj); + else + return NULL; +} + + void PDFDoc::displayPage(OutputDev *out, int page, - double hDPI, double vDPI, int rotate, - GBool useMediaBox, GBool crop, GBool printing, - GBool (*abortCheckCbk)(void *data), - void *abortCheckCbkData, + double hDPI, double vDPI, int rotate, + GBool useMediaBox, GBool crop, GBool printing, + GBool (*abortCheckCbk)(void *data), + void *abortCheckCbkData, GBool (*annotDisplayDecideCbk)(Annot *annot, void *user_data), void *annotDisplayDecideCbkData, GBool copyXRef) { if (globalParams->getPrintCommands()) { diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h index 6c40f7b..62f556a 100644 --- a/poppler/PDFDoc.h +++ b/poppler/PDFDoc.h @@ -49,6 +49,9 @@ #include "Annot.h" #include "OptionalContent.h" #include "Stream.h" +#include "poppler-config.h" +#include "SigStatusStruct.h" + class GooString; class GooFile; @@ -62,6 +65,7 @@ class Linearization; class SecurityHandler; class Hints; class StructTreeRoot; +class SignatureHandler; enum PDFWriteMode { writeStandard, @@ -199,6 +203,20 @@ public: // Is the file encrypted? GBool isEncrypted() { return xref->isEncrypted(); } + /* Signature Changes */ + GBool isSigned(); + int getSignatureContents(unsigned char **, int index); + unsigned int countSignatures(); + + Object *getByteRange(int index); + char *getTimestampFromSigDict(int index); + + SigStatus * validateSignature(int index, GBool verifyCertBool); + char *getSignerName(); + char *getSigningTime(int index); + + /* End of Signature Changes */ + // Check various permissions. GBool okToPrint(GBool ignoreOwnerPW = gFalse) { return xref->okToPrint(ignoreOwnerPW); } @@ -327,7 +345,7 @@ private: Outline *outline; #endif Page **pageCache; - + SignatureHandler * m_sig_handler; GBool ok; int errCode; //If there is an error opening the PDF file with fopen() in the constructor, diff --git a/poppler/SigStatusStruct.cc b/poppler/SigStatusStruct.cc new file mode 100644 index 0000000..1802df8 --- /dev/null +++ b/poppler/SigStatusStruct.cc @@ -0,0 +1,46 @@ +#include "SigStatusStruct.h" +#include "goo/gmem.h" +#include +#include +#include + +SigStatus *SigStatus_create(){ + return SigStatus_create(SIGNATURE_NOT_VERIFIED, CERTIFICATE_NOT_VERIFIED); +} + +SigStatus *SigStatus_create(SignatureValidationStatus sig_vs, CertificateValidationStatus cert_vs) +{ + SigStatus *status = (SigStatus *) gmalloc(sizeof(SigStatus)); + assert(status != NULL); + + status->sig_vs = sig_vs; + status->cert_vs = cert_vs; + + return status; +} + +void SigStatus_destroy(SigStatus *status) +{ + assert(status != NULL); + free(status); +} + +SignatureValidationStatus getSignatureValStatus(SigStatus * sss) +{ + return sss->sig_vs; +} + +CertificateValidationStatus getCertificateValStatus(SigStatus * sss) +{ + return sss->cert_vs; +} + +void setSignatureValStatus(SigStatus * sss, SignatureValidationStatus sigStatus) +{ + sss->sig_vs = sigStatus; +} + +void setCertificateValStatus(SigStatus * sss, CertificateValidationStatus certStatus) +{ + sss->cert_vs = certStatus; +} \ No newline at end of file diff --git a/poppler/SigStatusStruct.h b/poppler/SigStatusStruct.h new file mode 100644 index 0000000..1abaecd --- /dev/null +++ b/poppler/SigStatusStruct.h @@ -0,0 +1,40 @@ +#ifndef SIGSTRUCT_H +#define SIGSTRUCT_H + +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 +}; + +typedef struct +{ + enum SignatureValidationStatus sig_vs; + enum CertificateValidationStatus cert_vs; +} SigStatus; + +SigStatus *SigStatus_create(); +SigStatus *SigStatus_create(enum SignatureValidationStatus sig_vs, enum CertificateValidationStatus cert_vs); +void SigStatus_destroy(SigStatus *status); +void setSignatureValStatus(SigStatus * sigstatus_struct, enum SignatureValidationStatus sigState); +void setCertificateValStatus(SigStatus * sigstatus_struct, enum CertificateValidationStatus certState); +enum SignatureValidationStatus getSignatureValStatus(); +enum CertificateValidationStatus getCertificateValStatus(); + + +#endif \ No newline at end of file diff --git a/poppler/SignatureHandler.cc b/poppler/SignatureHandler.cc new file mode 100644 index 0000000..b46c287 --- /dev/null +++ b/poppler/SignatureHandler.cc @@ -0,0 +1,311 @@ +#include "SignatureHandler.h" +#include "goo/GooString.h" +#include +#include + + +/* NSPR Headers */ +#include +#include +#include +#include +#include + + +/* NSS headers */ +#include +#include +#include +#include +#include +#include +#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); + +} + +char * SignatureHandler::getSigningTime(){ + char * timeString = NULL; + PRExplodedTime exTime; + PRTime sTime; + + if( CERT_CertTimesValid( NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB())) != SECSuccess) + return NULL; + else + NSS_CMSSignerInfo_GetSigningTime (CMSSignerInfo, &sTime); + + timeString = (char *) PORT_Alloc(256); + PR_ExplodeTime(sTime, PR_GMTParameters, &exTime); + PR_FormatTime(timeString, 256, "%F %T", &exTime); + + return timeString; +} + + +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); + + 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)){ + printf("CMS Message isn't a signature!\n"); + return NULL; + } + + NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cms_msg, 0); + if (!cinfo) { + printf("Error in NSS_CMSMessage_ContentLevel\n"); + return NULL; + } + + NSSCMSSignedData *signedData = (NSSCMSSignedData*) NSS_CMSContentInfo_GetContent(cinfo); + if (!signedData) { + printf("Error in NSS_CMSContentInfo_GetContent()\n"); + 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 **) calloc(sizeof(CERTCertificate *), i+1); + for (i = 0; signedData->rawCerts[i]; ++i) + signedData->tempCerts[i] = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), signedData->rawCerts[i], NULL, 0, 0); // store the adresses of these tempporary certificates for future release + + 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; //random error code meant to 'switch' to default error translation. + + 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..73f635a --- /dev/null +++ b/poppler/SignatureHandler.h @@ -0,0 +1,51 @@ +#ifndef SIGNATURE_HANDLER_H +#define SIGNATURE_HANDLER_H + +/* NSPR Headers */ +#include + +#include "goo/GooString.h" + + +/* NSS headers */ +#include +#include +#include +#include +#include + +#include "SigStatusStruct.h" + +class SignatureHandler +{ + + public: + SignatureHandler(unsigned char *p7, int p7_length); + ~SignatureHandler(); + char * 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; + + 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/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..1740e58 --- /dev/null +++ b/utils/pdfsigverify.cc @@ -0,0 +1,160 @@ +#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" + + +const char * getReadableSigState(SigStatus * svs) +{ + + switch(svs->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."; + + default: + return "Unkown Validation Failure."; + } +} + +const char * getReadableCertState(SigStatus * cvs) +{ + switch(cvs->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 "Unkown issue with Certificate or corrupted data."; + } +} + +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; + SigStatus *sig_status = NULL; + GooString * fileName = NULL; + char * signing_time = NULL; + char * signer_name = NULL; + + int exitCode = 99; + unsigned int i = 0; + 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; + } + + do + { + sig_status = doc->validateSignature(i, !dontVerifyCert); + signing_time = doc->getSigningTime(i); + signer_name = doc->getSignerName(); + printf("Signature #%d:\n", i+1); + printf(" - Signer Certificate Common Name: %s\n", signer_name); + printf(" - Signing Time: %s\n", signing_time); + printf(" - Signature Validation: %s\n", getReadableSigState(sig_status)); + if (sig_status->sig_vs != SIGNATURE_VALID || dontVerifyCert) + { + i++; + continue; + } + + printf(" - Certificate Validation: %s\n", getReadableCertState(sig_status)); + + free(sig_status); + free(signing_time); + free(signer_name); + i++; + } while( i < sigCount); + + exitCode = 0; + + end: + if (fileName) + delete fileName; + if (doc) + delete doc; + + return exitCode; +}