diff --git a/poppler/Form.cc b/poppler/Form.cc index 4627a43..247024e 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -442,11 +442,128 @@ FormWidgetSignature::FormWidgetSignature(PDFDoc *docA, Object *aobj, unsigned nu FormWidget(docA, aobj, num, ref, p) { type = formSignature; + file_size = 0; } -SignatureInfo *FormWidgetSignature::validateSignature(bool doVerifyCert, bool forceRevalidation) +SignatureInfo *FormWidgetSignature::validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime) { - return static_cast(field)->validateSignature(doVerifyCert, forceRevalidation); + return static_cast(field)->validateSignature(doVerifyCert, forceRevalidation, validationTime); +} + +Object* FormWidgetSignature::getByteRange() +{ + return static_cast(field)->getByteRange(); +} + +GooString* FormWidgetSignature::getCheckedSignature() +{ + int start = 0; + int end = 0; + Object* obj = getByteRange(); + if (obj->isArray() && obj->arrayGetLength() == 4) + { + Object offsetObj1, lenObj, offsetObj2; + obj->arrayGet(0, &offsetObj1); + obj->arrayGet(1, &lenObj); + obj->arrayGet(2, &offsetObj2); + if (offsetObj1.isIntOrInt64() && offsetObj2.isIntOrInt64() && lenObj.isIntOrInt64()) + { + start = offsetObj1.getIntOrInt64() + lenObj.getIntOrInt64(); + end = offsetObj2.getIntOrInt64(); + } + } + if (end >= start+6) + { + BaseStream* stream = doc->getBaseStream(); + file_size = stream->getLength(); + GooString gstr; + stream->fillGooString(&gstr); + // PDF signatures are first ASN1 DER, then hex encoded PKCS#7 structures, + // possibly padded with 0 characters and enclosed in '<' and '>'. + // The ASN1 DER encoding of a PKCS#7 structure must start with the tag 0x30 + // for SEQUENCE. The next byte must be 0x80 for ASN1 DER indefinite length + // encoding or (0x80 + n) for ASN1 DER definite length encoding + // where n is the number of subsequent "length bytes" which big-endian + // encode the length of the content of the SEQUENCE following them. + if (gstr.getLength() >= end && gstr.getChar(start) == '<' && gstr.getChar(end-1) == '>') + { + ++start; + --end; + int len = end-start; + if (gstr.getChar(start) == '3' && gstr.getChar(start+1) == '0') + { + if (gstr.getChar(start+2) == '8' && gstr.getChar(start+3) == '0') + { + // ASN1 DER indefinite length encoding: + // We only check that all characters up to the enclosing '>' + // are hex characters and that there are two hex encoded 0 bytes + // just before the enclosing '>' marking the end of the indefinite + // length encoding. + int paddingCount = 0; + while (gstr.getChar(end-1) == '0' && gstr.getChar(end-2) == '0') + { + ++paddingCount; + end -= 2; + } + end += 4; + len = end-start; + if (paddingCount < 2 || len%2 == 1) + len = 0; + } + else if (gstr.getChar(start+2) == '8') + { + // ASN1 DER definite length encoding: + // We calculate the length of the following bytes from the length bytes and + // check that after the length bytes and the following calculated number of + // bytes all bytes up to the enclosing '>' character are hex encoded 0 bytes. + int lenBytes = gstr.getChar(start+3) - '0'; + if (lenBytes > 0 && lenBytes <= 4) + { + int sigLen = 0; + for (int i = 0; i < 2*lenBytes; ++i) + { + sigLen <<= 4; + char c = gstr.getChar(start+4+i); + if (isdigit(c)) + sigLen += c - '0'; + else if (isxdigit(c) && c >= 'a') + sigLen += c - 'a' + 10; + else if (isxdigit(c) && c >= 'A') + sigLen += c - 'A' + 10; + else + { + len = 0; + break; + } + } + if (sigLen > 0 && 2*(sigLen+lenBytes) <= len-4) + { + for (int i = 2*(sigLen+lenBytes)+4; i < len; ++i) + { + if (gstr.getChar(start+i) != '0') + { + len = 0; + break; + } + } + } + else + len = 0; + } + } + for (int i = 0; i < len; ++i) + { + if (!isxdigit(gstr.getChar(start+i))) + len = 0; + } + if (len > 0) + { + return new GooString(&gstr, start, len); + } + } + } + } + return nullptr; } void FormWidgetSignature::updateWidgetAppearance() @@ -1468,7 +1585,7 @@ void FormFieldSignature::hashSignedDataBlock(SignatureHandler *handler, Goffset } -SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool forceRevalidation) +SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime) { #ifdef ENABLE_NSS3 if (!signature_info->isSubfilterSupported()) { @@ -1529,6 +1646,8 @@ SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool for sig_val_state = signature_handler.validateSignature(); signature_info->setSignatureValStatus(SignatureHandler::NSS_SigTranslate(sig_val_state)); signature_info->setSignerName(signature_handler.getSignerName()); + signature_info->setSubjectDN(signature_handler.getSignerSubjectDN()); + signature_info->setHashAlgorithmName(signature_handler.getHashAlgorithmName()); // verify if signature contains a 'signing time' attribute if (signature_handler.getSigningTime() != 0) { @@ -1539,7 +1658,7 @@ SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool for return signature_info; } - cert_val_state = signature_handler.validateCertificate(); + cert_val_state = signature_handler.validateCertificate(validationTime); signature_info->setCertificateValStatus(SignatureHandler::NSS_CertTranslate(cert_val_state)); #endif diff --git a/poppler/Form.h b/poppler/Form.h index 8ddb6fe..c595aa0 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -13,6 +13,7 @@ // Copyright 2013 Adrian Johnson // Copyright 2015 André Guerreiro // Copyright 2015 André Esser +// Copyright 2017 Hans-Ulrich Jüttner // //======================================================================== @@ -251,7 +252,18 @@ public: FormWidgetSignature(PDFDoc *docA, Object *dict, unsigned num, Ref ref, FormField *p); void updateWidgetAppearance() override; - SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation); + SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime = -1); + Object* getByteRange(); + + // checks the length encoding of the signature and returns the hex encoded signature + // if the check passed otherwise a nullptr is returned + GooString* getCheckedSignature(); + + // this method only gives the correct file size if getCheckedSignature() + // has been called before + Goffset getCheckedFileSize() const { return file_size; } +protected: + Goffset file_size; }; //------------------------------------------------------------------------ @@ -493,9 +505,10 @@ class FormFieldSignature: public FormField { public: FormFieldSignature(PDFDoc *docA, Object *dict, const Ref& ref, FormField *parent, std::set *usedParents); - SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation); + SignatureInfo *validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime = -1); ~FormFieldSignature(); + Object* getByteRange() { return &byte_range; } private: void parseInfo(); diff --git a/poppler/SignatureHandler.cc b/poppler/SignatureHandler.cc index 71644e5..0157c92 100644 --- a/poppler/SignatureHandler.cc +++ b/poppler/SignatureHandler.cc @@ -9,6 +9,7 @@ // Copyright 2015, 2016 Albert Astals Cid // Copyright 2015 Markus Kilås // Copyright 2017 Sebastian Rasmussen +// Copyright 2017 Hans-Ulrich Jüttner // //======================================================================== @@ -47,6 +48,46 @@ char *SignatureHandler::getSignerName() return CERT_GetCommonName(&cert->subject); } +const char * SignatureHandler::getSignerSubjectDN() +{ + if (!CMSSignerInfo) + return nullptr; + + CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); + if (!cert) + return nullptr; + return cert->subjectName; +} + +const char *SignatureHandler::getHashAlgorithmName() +{ + if (hash_context && hash_context->hashobj) + { + switch(hash_context->hashobj->type) + { + case HASH_AlgMD2: + return "MD2"; + case HASH_AlgMD5: + return "MD5"; + case HASH_AlgSHA1: + return "SHA1"; + case HASH_AlgSHA256: + return "SHA-256"; + case HASH_AlgSHA384: + return "SHA-384"; + case HASH_AlgSHA512: + return "SHA-512"; + case HASH_AlgSHA224: + return "SHA-224"; + default: + error(errUnimplemented, -1, "Hash algorithm number {0:d} not implemented", + hash_context->hashobj->type); + return nullptr; + } + } + return nullptr; +} + time_t SignatureHandler::getSigningTime() { PRTime sTime; // time in microseconds since the epoch @@ -54,7 +95,7 @@ time_t SignatureHandler::getSigningTime() if (NSS_CMSSignerInfo_GetSigningTime (CMSSignerInfo, &sTime) != SECSuccess) return 0; - return (time_t) sTime/1000000; + return static_cast(sTime/1000000); } @@ -271,7 +312,7 @@ NSSCMSVerificationStatus SignatureHandler::validateSignature() } } -SECErrorCodes SignatureHandler::validateCertificate() +SECErrorCodes SignatureHandler::validateCertificate(time_t validation_time) { SECErrorCodes retVal; CERTCertificate *cert; @@ -282,10 +323,15 @@ SECErrorCodes SignatureHandler::validateCertificate() if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB())) == NULL) CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; - CERTValInParam inParams[2]; + PRTime vTime = 0; // time in microseconds since the epoch, special value 0 means now + if (validation_time > 0) + vTime = 1000000*(PRTime)validation_time; + CERTValInParam inParams[3]; inParams[0].type = cert_pi_revocationFlags; inParams[0].value.pointer.revocation = CERT_GetClassicOCSPEnabledSoftFailurePolicy(); - inParams[1].type = cert_pi_end; + inParams[1].type = cert_pi_date; + inParams[1].value.scalar.time = vTime; + inParams[2].type = cert_pi_end; CERT_PKIXVerifyCert(cert, certificateUsageEmailSigner, inParams, NULL, CMSSignerInfo->cmsg->pwfn_arg); diff --git a/poppler/SignatureHandler.h b/poppler/SignatureHandler.h index 8e2a4da..0b58da3 100644 --- a/poppler/SignatureHandler.h +++ b/poppler/SignatureHandler.h @@ -7,6 +7,7 @@ // Copyright 2015 André Guerreiro // Copyright 2015 André Esser // Copyright 2015 Albert Astals Cid +// Copyright 2017 Hans-Ulrich Jüttner // //======================================================================== @@ -36,10 +37,12 @@ public: ~SignatureHandler(); time_t getSigningTime(); char * getSignerName(); + const char * getSignerSubjectDN(); + const char * getHashAlgorithmName(); void setSignature(unsigned char *, int); void updateHash(unsigned char * data_block, int data_len); NSSCMSVerificationStatus validateSignature(); - SECErrorCodes validateCertificate(); + SECErrorCodes validateCertificate(time_t validation_time = -1); //Translate NSS error codes static SignatureValidationStatus NSS_SigTranslate(NSSCMSVerificationStatus nss_code); diff --git a/poppler/SignatureInfo.cc b/poppler/SignatureInfo.cc index 8f7ec45..3c3668b 100644 --- a/poppler/SignatureInfo.cc +++ b/poppler/SignatureInfo.cc @@ -6,6 +6,7 @@ // // Copyright 2015 André Guerreiro // Copyright 2015 André Esser +// Copyright 2017 Hans-Ulrich Jüttner // //======================================================================== @@ -22,7 +23,9 @@ SignatureInfo::SignatureInfo() { sig_status = SIGNATURE_NOT_VERIFIED; cert_status = CERTIFICATE_NOT_VERIFIED; - signer_name = NULL; + signer_name = nullptr; + subject_dn = nullptr; + hash_name = nullptr; signing_time = 0; sig_subfilter_supported = false; } @@ -31,7 +34,9 @@ SignatureInfo::SignatureInfo(SignatureValidationStatus sig_val_status, Certifica { sig_status = sig_val_status; cert_status = cert_val_status; - signer_name = NULL; + signer_name = nullptr; + subject_dn = nullptr; + hash_name = nullptr; signing_time = 0; sig_subfilter_supported = false; } @@ -53,11 +58,21 @@ CertificateValidationStatus SignatureInfo::getCertificateValStatus() return cert_status; } -char *SignatureInfo::getSignerName() +const char *SignatureInfo::getSignerName() { return signer_name; } +const char *SignatureInfo::getSubjectDN() +{ + return subject_dn; +} + +const char *SignatureInfo::getHashAlgorithmName() +{ + return hash_name; +} + time_t SignatureInfo::getSigningTime() { return signing_time; @@ -81,6 +96,16 @@ void SignatureInfo::setSignerName(char *signerName) signer_name = signerName; } +void SignatureInfo::setSubjectDN(const char *subjectDN) +{ + subject_dn = subjectDN; +} + +void SignatureInfo::setHashAlgorithmName(const char *hashName) +{ + hash_name = hashName; +} + void SignatureInfo::setSigningTime(time_t signingTime) { signing_time = signingTime; diff --git a/poppler/SignatureInfo.h b/poppler/SignatureInfo.h index 82b4ec4..37a4e5e 100644 --- a/poppler/SignatureInfo.h +++ b/poppler/SignatureInfo.h @@ -7,6 +7,7 @@ // Copyright 2015 André Guerreiro // Copyright 2015 André Esser // Copyright 2015 Albert Astals Cid +// Copyright 2017 Hans-Ulrich Jüttner // //======================================================================== @@ -46,7 +47,9 @@ public: /* GETTERS */ SignatureValidationStatus getSignatureValStatus(); CertificateValidationStatus getCertificateValStatus(); - char *getSignerName(); + const char *getSignerName(); + const char *getSubjectDN(); + const char *getHashAlgorithmName(); time_t getSigningTime(); bool isSubfilterSupported() { return sig_subfilter_supported; } @@ -54,6 +57,8 @@ public: void setSignatureValStatus(enum SignatureValidationStatus ); void setCertificateValStatus(enum CertificateValidationStatus ); void setSignerName(char *); + void setSubjectDN(const char *); + void setHashAlgorithmName(const char *); void setSigningTime(time_t); void setSubFilterSupport(bool isSupported) { sig_subfilter_supported = isSupported; } @@ -64,6 +69,8 @@ private: SignatureValidationStatus sig_status; CertificateValidationStatus cert_status; char *signer_name; + const char *subject_dn; + const char *hash_name; time_t signing_time; bool sig_subfilter_supported; }; diff --git a/qt5/src/poppler-form.cc b/qt5/src/poppler-form.cc index 1ccb26c..18dc543 100644 --- a/qt5/src/poppler-form.cc +++ b/qt5/src/poppler-form.cc @@ -4,6 +4,7 @@ * Copyright (C) 2011 Carlos Garcia Campos * Copyright (C) 2012, Adam Reichold * Copyright (C) 2016, Hanno Meyer-Thurow + * Copyright (C) 2017, Hans-Ulrich Jüttner * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +36,7 @@ #include "poppler-annotation-helper.h" #include +#include namespace { @@ -448,8 +450,13 @@ struct SignatureValidationInfoPrivate { SignatureValidationInfo::SignatureStatus signature_status; SignatureValidationInfo::CertificateStatus certificate_status; + QByteArray signature; QString signer_name; + QString signer_subject_dn; + QString hash_name; time_t signing_time; + QList range_bounds; + qint64 docLength; }; @@ -485,12 +492,57 @@ QString SignatureValidationInfo::signerName() const return d->signer_name; } +QString SignatureValidationInfo::signerSubjectDN() const +{ + Q_D(const SignatureValidationInfo); + return d->signer_subject_dn; +} + +QString SignatureValidationInfo::hashAlgorithmName() const +{ + Q_D(const SignatureValidationInfo); + return d->hash_name; +} + time_t SignatureValidationInfo::signingTime() const { Q_D(const SignatureValidationInfo); return d->signing_time; } +QByteArray SignatureValidationInfo::signature() const +{ + Q_D(const SignatureValidationInfo); + return d->signature; +} + +QList SignatureValidationInfo::signedRangeBounds() const +{ + Q_D(const SignatureValidationInfo); + return d->range_bounds; +} + +bool SignatureValidationInfo::signsTotalDocument() const +{ + Q_D(const SignatureValidationInfo); + if (d->range_bounds.size() == 4 && d->range_bounds.value(0) == 0 && + d->range_bounds.value(1) >= 0 && + d->range_bounds.value(2) > d->range_bounds.value(1) && + d->range_bounds.value(3) >= d->range_bounds.value(2)) + { + // The range from d->range_bounds.value(1) to d->range_bounds.value(2) is + // not authenticated by the signature and should only contain the signature + // itself padded with 0 bytes. This has been checked in readSignature(). + // If it failed, d->signature is empty. + // A potential range after d->range_bounds.value(3) would be also not + // authenticated. Therefore d->range_bounds.value(3) should coincide with + // the end of the document. + if (d->docLength == d->range_bounds.value(3) && !d->signature.isEmpty()) + return true; + } + return false; +} + SignatureValidationInfo &SignatureValidationInfo::operator=(const SignatureValidationInfo &other) { if ( this != &other ) @@ -513,7 +565,7 @@ FormField::FormType FormFieldSignature::type() const return FormField::FormSignature; } -SignatureValidationInfo FormFieldSignature::validate(ValidateOptions opt) const +SignatureValidationInfo FormFieldSignature::validate(int opt, const QDateTime& validationTime) const { FormWidgetSignature* fws = static_cast(m_formData->fm); SignatureInfo* si = fws->validateSignature(opt & ValidateVerifyCertificate, opt & ValidateForceRevalidation); @@ -567,7 +619,35 @@ SignatureValidationInfo FormFieldSignature::validate(ValidateOptions opt) const break; } priv->signer_name = si->getSignerName(); + priv->signer_subject_dn = si->getSubjectDN(); + priv->hash_name = si->getHashAlgorithmName(); priv->signing_time = si->getSigningTime(); + Object* obj = fws->getByteRange(); + if (obj != nullptr && obj->isArray()) + { + int arrayLen = obj->arrayGetLength(); + if (arrayLen%2 == 0) + { + for (int i = 0; i < arrayLen/2; ++i) + { + Object offsetObj, lenObj; + obj->arrayGet(2*i, &offsetObj); + obj->arrayGet(2*i+1, &lenObj); + if (offsetObj.isIntOrInt64() && lenObj.isIntOrInt64()) + { + qint64 offset = offsetObj.getIntOrInt64(); + qint64 len = lenObj.getIntOrInt64(); + priv->range_bounds.append(offset); + priv->range_bounds.append(offset+len); + } + } + } + } + if (priv->range_bounds.size() == 4) + { + priv->signature = QByteArray::fromHex(fws->getCheckedSignature()->getCString()); + priv->docLength = fws->getCheckedFileSize(); + } return SignatureValidationInfo(priv); } diff --git a/qt5/src/poppler-form.h b/qt5/src/poppler-form.h index 44928b3..6ebfd6d 100644 --- a/qt5/src/poppler-form.h +++ b/qt5/src/poppler-form.h @@ -3,6 +3,7 @@ * Copyright (C) 2008, 2011, 2016, 2017, Albert Astals Cid * Copyright (C) 2012, Adam Reichold * Copyright (C) 2016, Hanno Meyer-Thurow + * Copyright (C) 2017, Hans-Ulrich Jüttner * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +23,8 @@ #ifndef _POPPLER_QT5_FORM_H_ #define _POPPLER_QT5_FORM_H_ +#include +#include #include #include #include @@ -422,10 +425,36 @@ namespace Poppler { QString signerName() const; /** + The signer subject distinguished name associated with the signature. + */ + QString signerSubjectDN() const; + + /** + The name of the hash algorithm used for the signature. + */ + QString hashAlgorithmName() const; + + /** The signing time associated with the signature. */ time_t signingTime() const; + /** + Get the signature binary data. + */ + QByteArray signature() const; + + /** + Get the bounds of the ranges of the document which are signed. + */ + QList signedRangeBounds() const; + + /** + Checks whether the signature authenticates the total document + except for the signature itself. + */ + bool signsTotalDocument() const; + SignatureValidationInfo(const SignatureValidationInfo &other); SignatureValidationInfo &operator=(const SignatureValidationInfo &other); @@ -463,7 +492,7 @@ namespace Poppler { Reset signature validatation info of scoped instance. */ - SignatureValidationInfo validate(ValidateOptions opt) const; + SignatureValidationInfo validate(int opt, const QDateTime& validationTime = QDateTime()) const; private: Q_DISABLE_COPY(FormFieldSignature) diff --git a/utils/pdfsig.1 b/utils/pdfsig.1 index 8029ff0..bcc3949 100644 --- a/utils/pdfsig.1 +++ b/utils/pdfsig.1 @@ -9,7 +9,9 @@ pdfsig \- Portable Document Format (PDF) digital signatures tool .SH DESCRIPTION .B pdfsig verifies the digital signatures in a PDF document. -It also displays the identity of each signer (commonName field of the signer certificate) and the time and date of the signature. +It also displays the identity of each signer +(commonName field and full distinguished name of the signer certificate), +the time and date of the signature and the hash algorithm used for signing. .PP The signer certificate validation uses the trusted certificates stored in the following locations: .IP \(bu diff --git a/utils/pdfsig.cc b/utils/pdfsig.cc index 2190fea..0e974f9 100644 --- a/utils/pdfsig.cc +++ b/utils/pdfsig.cc @@ -157,7 +157,9 @@ int main(int argc, char *argv[]) sig_info = sig_widgets.at(i)->validateSignature(!dontVerifyCert, false); printf("Signature #%u:\n", i+1); printf(" - Signer Certificate Common Name: %s\n", sig_info->getSignerName()); + printf(" - Signer full Distinguished Name: %s\n", sig_info->getSubjectDN()); printf(" - Signing Time: %s\n", time_str = getReadableTime(sig_info->getSigningTime())); + printf(" - Signing Hash Algorithm: %s\n", sig_info->getHashAlgorithmName()); printf(" - Signature Validation: %s\n", getReadableSigState(sig_info->getSignatureValStatus())); gfree(time_str); if (sig_info->getSignatureValStatus() != SIGNATURE_VALID || dontVerifyCert) {