From 3ac56cf6f909b741320a0ec0b04d9ef54010d531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Guerreiro?= Date: Thu, 6 Sep 2012 16:07:44 +0100 Subject: [PATCH] Add PDF Signature Validation to poppler The following new functions are available on the glib wrapper: poppler_document_is_signed poppler_document_can_validate poppler_document_signature_validate poppler_document_signature_get_time poppler_document_signature_get_signername The OpenSSL PKCS7 API is used for crypto operations. As the additional dependency can be undesired for some users/developers the feature can be disabled at build time. To build the signature support a new configure option is added: --enable-openssl. --- CMakeLists.txt | 7 + config.h.cmake | 3 + configure.ac | 17 +++ glib/demo/info.cc | 35 ++++++ glib/poppler-document.cc | 127 +++++++++++++++++++++- glib/poppler-document.h | 27 +++++ m4/ax_check_openssl.m4 | 124 ++++++++++++++++++++ poppler/Makefile.am | 8 ++ poppler/PDFDoc.cc | 263 +++++++++++++++++++++++++++++++++++++++++++ poppler/PDFDoc.h | 24 ++++ poppler/SignatureHandler.cc | 159 ++++++++++++++++++++++++++ poppler/SignatureHandler.h | 25 ++++ 12 files changed, 818 insertions(+), 1 deletions(-) create mode 100644 m4/ax_check_openssl.m4 create mode 100644 poppler/SignatureHandler.cc create mode 100644 poppler/SignatureHandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f519639..3192477 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ option(ENABLE_CPP "Compile poppler cpp wrapper." ON) option(ENABLE_LIBOPENJPEG "Use libopenjpeg for JPX streams." ON) set(ENABLE_CMS "auto" CACHE STRING "Use color management system. Possible values: auto, lcms1, lcms2. 'auto' prefers lcms2 over lcms1 if both are available. Unset to disable color management system.") option(ENABLE_LIBCURL "Build libcurl based HTTP support." OFF) +option(ENABLE_OPENSSL "Build OpenSSL-based based cryptography support." OFF) option(ENABLE_ZLIB "Build with zlib (not totally safe)." OFF) option(USE_FIXEDPOINT "Use fixed point arithmetic in the Splash backend" OFF) option(USE_FLOAT "Use single precision arithmetic in the Splash backend" OFF) @@ -158,6 +159,11 @@ if(ENABLE_LIBCURL) set(POPPLER_HAS_CURL_SUPPORT ON) endif(ENABLE_LIBCURL) +if(ENABLE_OPENSSL) + find_package(OpenSSL) + set(OPENSSL_SUPPORT ON) +endif(ENABLE_OPENSSL) + add_definitions(-DHAVE_CONFIG_H=1) if(FONTCONFIG_FOUND) add_definitions(${FONTCONFIG_DEFINITIONS}) @@ -613,6 +619,7 @@ show_end_message_yesno("cpp wrapper" ENABLE_CPP) show_end_message("use gtk-doc" "not supported with this CMake build system") show_end_message_yesno("use libjpeg" ENABLE_LIBJPEG) show_end_message_yesno("use libpng" ENABLE_LIBPNG) +show_end_message_yesno("use openssl" ENABLE_OPENSSL) show_end_message_yesno("use libtiff" ENABLE_LIBTIFF) show_end_message_yesno("use zlib" ENABLE_ZLIB) show_end_message_yesno("use curl" ENABLE_LIBCURL) diff --git a/config.h.cmake b/config.h.cmake index cde219f..8d80e1b 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -3,6 +3,9 @@ /* Build against libcurl. */ #cmakedefine ENABLE_LIBCURL 1 +/* Build against libcurl. */ +#cmakedefine ENABLE_OPENSSL 1 + /* Use libjpeg instead of builtin jpeg decoder. */ #cmakedefine ENABLE_LIBJPEG 1 diff --git a/configure.ac b/configure.ac index 48f71c8..38f1c0f 100644 --- a/configure.ac +++ b/configure.ac @@ -565,6 +565,22 @@ AC_SUBST(POPPLER_GLIB_DISABLE_SINGLE_INCLUDES) GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) +dnl Try OpenSSL +AC_ARG_ENABLE(openssl, + AC_HELP_STRING([--enable-openssl], + [Compile support for OpenSSL crypto operations]), + enable_openssl="yes", enable_openssl="no") + +AS_IF([test x$enable_openssl = "xyes"], [ + AX_CHECK_OPENSSL([] , [enable_openssl="no"]) + ]) + +AM_CONDITIONAL(OPENSSL_SUPPORT, test "x$enable_openssl" = "xyes") + +AS_IF([test x$enable_openssl = "xyes"], [ + AC_DEFINE([OPENSSL_SUPPORT], [1],[Define if building with openssl support]) + ]) + dnl dnl Try Qt4 dnl @@ -812,6 +828,7 @@ echo " qt4 wrapper: $enable_poppler_qt4" echo " glib wrapper: $use_glib" echo " introspection: $found_introspection" echo " cpp wrapper: $enable_poppler_cpp" +echo " openssl support $enable_openssl" echo " use gtk-doc: $enable_gtk_doc" echo " use libjpeg: $enable_libjpeg" echo " use libpng: $enable_libpng" diff --git a/glib/demo/info.cc b/glib/demo/info.cc index 590ddc7..c7ac9a8 100644 --- a/glib/demo/info.cc +++ b/glib/demo/info.cc @@ -20,6 +20,7 @@ #include "config.h" #include "info.h" +#include #include "utils.h" static void @@ -229,6 +230,40 @@ 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_sprintf(number_of_sigs, "%d", signed_doc); + pgd_table_add_property (GTK_TABLE (table), "Signatures:", number_of_sigs, &row); + + if (signed_doc > 0) + { + + + int i=0; + + while (i !=signed_doc ) + { + + gchar *validation = (gchar *)g_malloc(10); + int validation_code = poppler_document_signature_validate(document, i); + + g_sprintf(validation, "%d", validation_code); + + pgd_table_add_property (GTK_TABLE (table), "Validation status:", validation, &row); + + pgd_table_add_property (GTK_TABLE (table), "Signer Name:", + poppler_document_signature_get_signername(document, i), &row); + + pgd_table_add_property (GTK_TABLE (table), "Signing Time:", + poppler_document_signature_get_time(document, i), &row); + + i++; + } + + } + 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 e626a2a..e4bb655 100644 --- a/glib/poppler-document.cc +++ b/glib/poppler-document.cc @@ -35,6 +35,10 @@ #include #endif +#ifdef OPENSSL_SUPPORT +#include +#endif + #include "poppler.h" #include "poppler-private.h" #include "poppler-enums.h" @@ -67,7 +71,8 @@ enum { PROP_PAGE_MODE, PROP_VIEWER_PREFERENCES, PROP_PERMISSIONS, - PROP_METADATA + PROP_METADATA, + PROP_SIGNED }; static void poppler_document_layers_free (PopplerDocument *document); @@ -1101,6 +1106,110 @@ 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 + * + * Since: 0.20 (??) + **/ +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; + +} + +gboolean +poppler_document_can_validate (PopplerDocument *document) +{ +#ifdef OPENSSL_SUPPORT + return true; +#else + return false; +#endif +} + +gchar * +poppler_document_signature_get_time(PopplerDocument *document, int index) +{ +#ifdef OPENSSL_SUPPORT + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL); + return document->doc->getSigningTime(index); +#else + g_return_val_if_reached(NULL); +#endif +} + +gchar * +poppler_document_signature_get_signername(PopplerDocument *document, int index) +{ +#ifdef OPENSSL_SUPPORT + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL); + return document->doc->getSigner(); +#else + + g_return_val_if_reached(NULL); +#endif + +} + +/** + * poppler_document_is_signed: + * @document: A #PopplerDocument + * @index: the signature index within the document starting from 0 + * + * Validates the signature refered to by @index if present in the document using + * the PKCS7 OpenSSL API + * Caveats: No certificate revocation checking is done for now + * + * Return value: see #PopplerSignatureStatus enum + * + * Since: 0.20 (??) + **/ +PopplerSignatureStatus poppler_document_signature_validate(PopplerDocument *document, int index) +{ +#ifdef OPENSSL_SUPPORT + g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_SIGNATURE_GENERIC_ERROR); + int ret = document->doc->validateSignature(index); + + if (ret == -1) + return POPPLER_SIGNATURE_NOT_FOUND; + else if (ret == 1) + return POPPLER_SIGNATURE_VALID; + + /* OpenSSL Error Translation */ + + //The reason error code is encoded in the first 12 bits + //of the openssl return value + int reason = ret & 0xFFF; + switch(reason) + { + case PKCS7_R_CERTIFICATE_VERIFY_ERROR: + return POPPLER_SIGNATURE_UNTRUSTED_SIGNER; + + case PKCS7_R_DIGEST_FAILURE: + return POPPLER_SIGNATURE_INVALID; + + default: + return POPPLER_SIGNATURE_GENERIC_ERROR; + + } +#else + g_return_val_if_reached(POPPLER_SIGNATURE_GENERIC_ERROR); + +#endif +} + +/** * poppler_document_get_page_layout: * @document: A #PopplerDocument * @@ -1267,6 +1376,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; @@ -1451,6 +1563,19 @@ poppler_document_class_init (PopplerDocumentClass *klass) "Is the document optimized for web viewing?", FALSE, G_PARAM_READABLE)); + /** + * PopplerDocument::signed: + * Wheter 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..5ee5aaf 100644 --- a/glib/poppler-document.h +++ b/glib/poppler-document.h @@ -53,6 +53,25 @@ typedef enum } PopplerPageLayout; /** + * PopplerSignatureStatus: + * + * + * TODO: If revocation status was being checked (CRL/OCSP) + * we would define another error code for that + * Also valid for TSA timestamps... + */ +typedef enum +{ + POPPLER_SIGNATURE_VALID, + POPPLER_SIGNATURE_UNTRUSTED_SIGNER, + POPPLER_SIGNATURE_INVALID, + POPPLER_SIGNATURE_DECODING_ERROR, + POPPLER_SIGNATURE_GENERIC_ERROR, + POPPLER_SIGNATURE_NOT_FOUND + +} PopplerSignatureStatus; + +/** * PopplerPageMode: * @POPPLER_PAGE_MODE_UNSET: no specific mode set * @POPPLER_PAGE_MODE_NONE: neither document outline nor thumbnails visible @@ -209,11 +228,19 @@ 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); +gboolean poppler_document_can_validate (PopplerDocument *document); +PopplerSignatureStatus poppler_document_signature_validate (PopplerDocument *document, int index); +gchar *poppler_document_signature_get_time (PopplerDocument *document, int index); +gchar *poppler_document_signature_get_signername(PopplerDocument *document, int index); + /* Attachments */ guint poppler_document_get_n_attachments (PopplerDocument *document); gboolean poppler_document_has_attachments (PopplerDocument *document); diff --git a/m4/ax_check_openssl.m4 b/m4/ax_check_openssl.m4 new file mode 100644 index 0000000..a87c5a6 --- /dev/null +++ b/m4/ax_check_openssl.m4 @@ -0,0 +1,124 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_openssl.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) +# +# DESCRIPTION +# +# Look for OpenSSL in a number of default spots, or in a user-selected +# spot (via --with-openssl). Sets +# +# OPENSSL_INCLUDES to the include directives required +# OPENSSL_LIBS to the -l directives required +# OPENSSL_LDFLAGS to the -L or -R flags required +# +# and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately +# +# This macro sets OPENSSL_INCLUDES such that source files should use the +# openssl/ directory in include directives: +# +# #include +# +# LICENSE +# +# Copyright (c) 2009,2010 Zmanda Inc. +# Copyright (c) 2009,2010 Dustin J. Mitchell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) +AC_DEFUN([AX_CHECK_OPENSSL], [ + found=false + AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl=DIR], + [root of the OpenSSL directory])], + [ + case "$withval" in + "" | y | ye | yes | n | no) + AC_MSG_ERROR([Invalid --with-openssl value]) + ;; + *) ssldirs="$withval" + ;; + esac + ], [ + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + AC_PATH_PROG([PKG_CONFIG], [pkg-config]) + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + ] + ) + + + # note that we #include , so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + AC_MSG_CHECKING([for openssl/ssl.h in $ssldir]) + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + AC_MSG_RESULT([yes]) + break + else + AC_MSG_RESULT([no]) + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ], [SSL_new(NULL)])], + [ + AC_MSG_RESULT([yes]) + $1 + ], [ + AC_MSG_RESULT([no]) + $2 + ]) + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + AC_SUBST([OPENSSL_INCLUDES]) + AC_SUBST([OPENSSL_LIBS]) + AC_SUBST([OPENSSL_LDFLAGS]) +]) diff --git a/poppler/Makefile.am b/poppler/Makefile.am index e9ac9d4..3bc4404 100644 --- a/poppler/Makefile.am +++ b/poppler/Makefile.am @@ -32,6 +32,12 @@ arthur_libs = \ endif +if OPENSSL_SUPPORT +openssl_sources = SignatureHandler.cc +openssl_libs = $(OPENSSL_LIBS) +endif + + if BUILD_CAIRO_OUTPUT @@ -172,6 +178,7 @@ libpoppler_la_LIBADD = \ $(zlib_libs) \ $(libcurl_libs) \ $(libjpeg2000_libs) \ + $(openssl_libs) \ $(FREETYPE_LIBS) \ $(FONTCONFIG_LIBS) \ $(PTHREAD_LIBS) \ @@ -266,6 +273,7 @@ libpoppler_la_SOURCES = \ $(zlib_sources) \ $(libjpeg2000_sources) \ $(curl_sources) \ + $(openssl_sources) \ Annot.cc \ Array.cc \ BuiltinFont.cc \ diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index dd93a64..98bb86a 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -74,6 +74,7 @@ #endif #include "PDFDoc.h" #include "Hints.h" +#include "DateInfo.h" //------------------------------------------------------------------------ @@ -109,6 +110,9 @@ void PDFDoc::init() startXRefPos = ~(Guint)0; secHdlr = NULL; pageCache = NULL; +#ifdef OPENSSL_SUPPORT + m_sig_handler = NULL; +#endif } PDFDoc::PDFDoc() @@ -449,6 +453,265 @@ 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, f, type, obj1; + + if (acro_form->isNull()) + return count; + acro_form->dictLookup("Fields", &fields); + + //Find the Signature Field and retrieve /Contents + for (int i = 0; i != fields.arrayGetLength(); i++) + { + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &obj1); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(obj1.getName(), "Sig") == 0) + { + count++; + } + } + return count; +} + +#ifdef OPENSSL_SUPPORT + +int PDFDoc::validateSignature(int sig_index) +{ + + Object r2, r3, r4; + + unsigned char *signature; + + if (isSigned()) + { + int sig_len = getSignatureContents(&signature, sig_index); + Object *hello = getByteRange(sig_index); + hello->arrayGet(1, &r2); + hello->arrayGet(2, &r3); + hello->arrayGet(3, &r4); + 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()); + m_sig_handler = new SignatureHandler(signature, sig_len); + + return m_sig_handler->Validate(to_check, signed_data_len); + + } + else + return -1; + +} + +char *PDFDoc::getSigner() +{ + 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; + +} + +#endif + +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(); + + 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(); i++) + { + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &obj1); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(obj1.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(); + return formatSigningTimeDate(time_str); + + } + + sig_count++; + } + } + + return NULL; + +} + +int PDFDoc::getSignatureContents(unsigned char **contents, int index) +{ + Object *acro_form = getCatalog()->getAcroForm(); + Object fields, f, sig_dict, contents_obj, type, obj1; + int sig_count=0; + + 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(); i++) + { + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &obj1); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(obj1.getName(), "Sig") == 0) + { + + if (sig_count == index) + { + f.dictLookup("V", &sig_dict); + sig_dict.dictLookup("Contents", &contents_obj); + if (contents_obj.isString()) + { + GooString *str = contents_obj.getString(); + int ret = str->getLength(); + *contents = (unsigned char *)malloc(ret); + memcpy(*contents, str->getCString(), ret); + return ret; + } + } + sig_count++; + + } + + } + + return 0; +} + +Object *PDFDoc::getByteRange(int index) +{ + + Object *acro_form = getCatalog()->getAcroForm(); + Object fields, f, sig_dict, byterange_obj, type, obj1; + int sig_count=0; + + 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(); i++) + { + fields.arrayGet(i, &f); + + f.dictLookup("Type", &type); + f.dictLookup("FT", &obj1); + if (strcmp(type.getName(), "Annot") == 0 + && strcmp(obj1.getName(), "Sig") == 0) + { + + if (sig_count == index) + { + f.dictLookup("V", &sig_dict); + if (!sig_dict.isDict()) + return NULL; + sig_dict.dictLookup("ByteRange", &byterange_obj); + if (byterange_obj.isArray()) + { + return new Object(byterange_obj); + } + } + sig_count++; + } + + } + + return NULL; +} + + 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 328b0c7..963b779 100644 --- a/poppler/PDFDoc.h +++ b/poppler/PDFDoc.h @@ -43,6 +43,10 @@ #include "Page.h" #include "Annot.h" #include "OptionalContent.h" +#include "poppler-config.h" +#ifdef OPENSSL_SUPPORT +#include "SignatureHandler.h" +#endif class GooString; class BaseStream; @@ -192,6 +196,22 @@ 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); + +#ifdef OPENSSL_SUPPORT + int validateSignature(int index); + char *getSigner(); + char *getSigningTime(int index); +#endif + + /* End of Signature Changes */ + // Check various permissions. GBool okToPrint(GBool ignoreOwnerPW = gFalse) { return xref->okToPrint(ignoreOwnerPW); } @@ -305,6 +325,10 @@ private: wchar_t *fileNameU; #endif FILE *file; + +#ifdef OPENSSL_SUPPORT + SignatureHandler * m_sig_handler; +#endif BaseStream *str; void *guiData; int pdfMajorVersion; diff --git a/poppler/SignatureHandler.cc b/poppler/SignatureHandler.cc new file mode 100644 index 0000000..fb7c802 --- /dev/null +++ b/poppler/SignatureHandler.cc @@ -0,0 +1,159 @@ + +#include "SignatureHandler.h" + +//OpenSSL +#include +#include +#include +#include +#include + +/** + * Initialise OpenSSL + */ +void init_openssl() { + + /* call the standard SSL init functions */ + SSL_library_init(); + SSL_load_error_strings(); + + ERR_load_BIO_strings(); + OpenSSL_add_all_algorithms(); + +} + + +SignatureHandler::SignatureHandler(unsigned char *p7, int p7_length) +{ + init_openssl(); + pkcs7_data = p7; + p7_len = p7_length; + +} + + +SignatureHandler::~SignatureHandler() +{ + PKCS7_free(p7); + +} + +char *SignatureHandler::getSigningTime() +{ + ASN1_TYPE *so; + STACK_OF(PKCS7_SIGNER_INFO) *sinfos = PKCS7_get_signer_info(p7); + //NOTE: Assuming just one signer info + PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(sinfos, 0); + + BIO *bio_out = BIO_new(BIO_s_mem()); + + so = PKCS7_get_signed_attribute(si, NID_pkcs9_signingTime); + if (!so) + { + fprintf(stderr, "Signature has no attribute pkcs9_signingTime\n"); + return NULL; + } + if (so->type == V_ASN1_UTCTIME) + { + ASN1_UTCTIME_print(bio_out, so->value.utctime); + //ASN1_UTCTIME_print(bio_out_c, so->value.utctime); + //BIO_printf(bio_out_c, "\n"); + } + + char *signing_time_tmp; + long size = BIO_get_mem_data(bio_out, &signing_time_tmp); + //We shouldnt rely on BIOs internal buffers so + //allocate explicitly our string + char *signing_time = (char *)malloc(size+1); + memcpy(signing_time, signing_time_tmp, size); + signing_time[size] = 0; + + BIO_free(bio_out); + + return signing_time; +} + +char *SignatureHandler::getSignerName() +{ + X509 *signer; + STACK_OF(X509) * signer_certs = PKCS7_get0_signers(p7, NULL, 0); + const int BUFSIZE = 512; + + char * subject = (char *)malloc(BUFSIZE); + + if (!signer_certs) + return NULL; + + //Assume just 1 signer + //for (int k = 0; k < sk_X509_num(signer_certs); k++) { + + signer = sk_X509_value (signer_certs, 0); + X509_NAME_get_text_by_NID(X509_get_subject_name(signer), NID_commonName, subject, BUFSIZE); + + return subject; +} + + + +unsigned long SignatureHandler::Validate(unsigned char *signed_data, int signed_data_len) { + + + //DEBUG + //dumpPKCS7ToFile("/pkcs7_signature_data_", pkcs7_data, p7_len); + //dumpPKCS7ToFile("/signed_data_", signed_data, signed_data_len); + + BIO *mem = BIO_new_mem_buf(pkcs7_data, p7_len); + BIO *mem_signed_data = BIO_new_mem_buf(signed_data, signed_data_len); + + PKCS7 *my_p7 = d2i_PKCS7_bio(mem, NULL); + + if (!my_p7) + return ERR_get_error(); + + p7 = my_p7; + X509_STORE * store = X509_STORE_new(); + + //Use our system CA certificates, in Linux usually in /usr/lib/ssl/certs + X509_STORE_set_default_paths(store); + unsigned long ret = PKCS7_verify(p7, NULL, NULL, mem_signed_data, NULL, PKCS7_NOVERIFY); + if (ret != 1) + { + const char *additional_details = NULL; + ret = ERR_get_error_line_data(NULL, NULL, &additional_details, NULL); + + char * error_openssl = ERR_error_string(ret, NULL); + + fprintf(stderr, "Openssl error code: %lu\n", ret); + fprintf(stderr, "Signature validation failed: %s\n", + error_openssl); + //fprintf(stderr, "Details: %s\n", additional_details); + + } + else + { + //Check cert chain + ret = PKCS7_verify(p7, NULL, store, mem_signed_data, NULL, PKCS7_NOSIGS); + if (ret != 1) + { + const char *additional_details = NULL; + ret = ERR_get_error_line_data(NULL, NULL, &additional_details, NULL); + char * error_openssl = ERR_error_string(ret, NULL); + + fprintf(stderr, "Certificate validation failed: %s\n", + error_openssl); + //fprintf(stderr, "Details: %s\n", additional_details); + + //Little hack to unify certificate verification errors + //for the higher-level interface (glib) + ret = PKCS7_R_CERTIFICATE_VERIFY_ERROR; + } + } + + //Cleanup + BIO_free(mem); + BIO_free(mem_signed_data); + + return ret; +} + + diff --git a/poppler/SignatureHandler.h b/poppler/SignatureHandler.h new file mode 100644 index 0000000..d61b30c --- /dev/null +++ b/poppler/SignatureHandler.h @@ -0,0 +1,25 @@ +#ifndef SIGNATURE_HANDLER_H +#define SIGNATURE_HANDLER_H + +#include + +class SignatureHandler +{ + + public: + SignatureHandler(unsigned char *p7, int p7_length); + ~SignatureHandler(); + char * getSigningTime(); + char * getSignerName(); + unsigned long Validate(unsigned char *signed_data, int signed_data_len); + + private: + //Binary PKCS-7 Signature Data + unsigned char *pkcs7_data; + unsigned int p7_len; + + PKCS7 *p7; + +}; + +#endif -- 1.7.4.1