diff --git a/PdfForQtLib/sources/pdfsignaturehandler.cpp b/PdfForQtLib/sources/pdfsignaturehandler.cpp index eaf6244..829f339 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.cpp +++ b/PdfForQtLib/sources/pdfsignaturehandler.cpp @@ -312,8 +312,29 @@ void PDFSignatureVerificationResult::addSignatureDataCoveredBySignatureMissingEr void PDFSignatureVerificationResult::addSignatureNotCoveredBytesWarning(PDFInteger count) { - m_flags.setFlag(Warning_Signature_NotCoveredBytes); - m_warnings << PDFTranslationContext::tr("%1 bytes are not covered by signature.").arg(count); + if (!m_flags.testFlag(Warning_Signature_NotCoveredBytes)) + { + m_flags.setFlag(Warning_Signature_NotCoveredBytes); + m_warnings << PDFTranslationContext::tr("%1 bytes are not covered by signature.").arg(count); + } +} + +void PDFSignatureVerificationResult::addCertificateCRLValidityTimeExpiredWarning() +{ + if (!m_flags.testFlag(Warning_Certificate_CRLValidityTimeExpired)) + { + m_flags.setFlag(Warning_Certificate_CRLValidityTimeExpired); + m_warnings << PDFTranslationContext::tr("Certificate revocation list (CRL) not checked, validity time has expired."); + } +} + +void PDFSignatureVerificationResult::addCertificateQualifiedStatementNotVerifiedWarning() +{ + if (!m_flags.testFlag(Warning_Certificate_QualifiedStatement)) + { + m_flags.setFlag(Warning_Certificate_QualifiedStatement); + m_warnings << PDFTranslationContext::tr("Qualified certificate statement not verified."); + } } void PDFSignatureVerificationResult::setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName) @@ -694,10 +715,79 @@ PDFSignatureVerificationResult PDFSignatureHandler_ETSI_CAdES_detached::verify() return result; } +// This is protected by global mutex, but it is ugly +static PDFSignatureVerificationResult* s_ETSI_CAdES_detached_currentResult = nullptr; + +int PDFSignatureHandler_ETSI_CAdES_detached::verifyCallback(int ok, X509_STORE_CTX* context) +{ + const int errorCode = X509_STORE_CTX_get_error(context); + + switch (errorCode) + { + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + { + // We will treat this as only warning + s_ETSI_CAdES_detached_currentResult->addCertificateCRLValidityTimeExpiredWarning(); + X509_STORE_CTX_set_error(context, X509_V_OK); + return 1; + } + + case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: + { + // We must handle all critical extensions manually + X509* certificate = X509_STORE_CTX_get_current_cert(context); + const STACK_OF(X509_EXTENSION)* extensions = X509_get0_extensions(certificate); + for (int i = 0, extensionsCount = sk_X509_EXTENSION_num(extensions); i < extensionsCount; ++i) + { + X509_EXTENSION* extension = sk_X509_EXTENSION_value(extensions, i); + + // Skip non-critical extensions + if (!X509_EXTENSION_get_critical(extension)) + { + continue; + } + + const ASN1_OBJECT* object = X509_EXTENSION_get_object(extension); + const int nid = OBJ_obj2nid(object); + + switch (nid) + { + case NID_basic_constraints: + case NID_key_usage: + // These are handled by OpenSSL + continue; + + case NID_qcStatements: + { + // We will treat this as only warning + s_ETSI_CAdES_detached_currentResult->addCertificateQualifiedStatementNotVerifiedWarning(); + X509_STORE_CTX_set_error(context, X509_V_OK); + continue; + } + + default: + return ok; + } + } + + X509_STORE_CTX_set_error(context, X509_V_OK); + return 1; + } + + default: + break; + } + + return ok; +} + void PDFSignatureHandler_ETSI_CAdES_detached::verifyCertificateCAdES(PDFSignatureVerificationResult& result) const { PDFOpenSSLGlobalLock lock; + s_ETSI_CAdES_detached_currentResult = &result; + OpenSSL_add_all_algorithms(); const PDFSignature& signature = m_signatureField->getSignature(); @@ -746,6 +836,20 @@ void PDFSignatureHandler_ETSI_CAdES_detached::verifyCertificateCAdES(PDFSignatur } STACK_OF(X509)* usedCertificates = allCertificates ? allCertificates : certificates; + // Jakub Melka: add certificate revocation lists + if (m_parameters.dss && !m_parameters.dss->getMasterItem()->CRL.empty()) + { + for (const QByteArray& crlData : m_parameters.dss->getMasterItem()->CRL) + { + const unsigned char* crlDataBuffer = convertByteArrayToUcharPtr(crlData); + if (X509_CRL* crl = d2i_X509_CRL(nullptr, &crlDataBuffer, crlData.size())) + { + X509_STORE_add_crl(store, crl); + X509_CRL_free(crl); + } + } + } + for (int i = 0; i < signerInfoCount; ++i) { PKCS7_SIGNER_INFO* signerInfoValue = sk_PKCS7_SIGNER_INFO_value(signerInfo, i); @@ -770,12 +874,13 @@ void PDFSignatureHandler_ETSI_CAdES_detached::verifyCertificateCAdES(PDFSignatur break; } - unsigned long flags = X509_V_FLAG_TRUSTED_FIRST; + unsigned long flags = X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL | X509_V_FLAG_EXTENDED_CRL_SUPPORT; if (m_parameters.ignoreExpirationDate) { flags |= X509_V_FLAG_NO_CHECK_TIME; } X509_STORE_CTX_set_flags(context, flags); + X509_STORE_CTX_set_verify_cb(context, &PDFSignatureHandler_ETSI_CAdES_detached::verifyCallback); int verificationResult = X509_verify_cert(context); if (verificationResult <= 0) @@ -1435,7 +1540,14 @@ QString PDFPublicKeySignatureHandler::getStringFromX509Name(X509_NAME* name, int const int stringLocation = X509_NAME_get_index_by_NID(name, nid, -1); X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, stringLocation); - if (ASN1_STRING* string = X509_NAME_ENTRY_get_data(entry)) + return getStringFromASN1_STRING(X509_NAME_ENTRY_get_data(entry)); +} + +QString pdf::PDFPublicKeySignatureHandler::getStringFromASN1_STRING(ASN1_STRING* string) +{ + QString result; + + if (string) { // Jakub Melka: we must convert entry to UTF8 encoding using function ASN1_STRING_to_UTF8 unsigned char* utf8Buffer = nullptr; diff --git a/PdfForQtLib/sources/pdfsignaturehandler.h b/PdfForQtLib/sources/pdfsignaturehandler.h index 1547174..937408f 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.h +++ b/PdfForQtLib/sources/pdfsignaturehandler.h @@ -297,6 +297,8 @@ public: Error_Signature_DataCoveredBySignatureMissing = 0x00100000, ///< Data covered by signature are not present Warning_Signature_NotCoveredBytes = 0x00200000, ///< Some bytes in source data are not covered by signature + Warning_Certificate_CRLValidityTimeExpired = 0x00400000, ///< Certificate revocation list was not checked, because it's validity expired + Warning_Certificate_QualifiedStatement = 0x00800000, ///< Qualified certificate statement not verified Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic | Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound | @@ -305,7 +307,7 @@ public: Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound | Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing, - Warnings_Mask = Warning_Signature_NotCoveredBytes + Warnings_Mask = Warning_Signature_NotCoveredBytes | Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement }; Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag) @@ -329,7 +331,10 @@ public: void addSignatureDigestFailureError(); void addSignatureDataOtherError(); void addSignatureDataCoveredBySignatureMissingError(); + void addSignatureNotCoveredBytesWarning(PDFInteger count); + void addCertificateCRLValidityTimeExpiredWarning(); + void addCertificateQualifiedStatementNotVerifiedWarning(); bool isValid() const { return hasFlag(OK); } bool isCertificateValid() const { return hasFlag(Certificate_OK); } diff --git a/PdfForQtLib/sources/pdfsignaturehandler_impl.h b/PdfForQtLib/sources/pdfsignaturehandler_impl.h index 5509742..802a254 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler_impl.h +++ b/PdfForQtLib/sources/pdfsignaturehandler_impl.h @@ -56,6 +56,9 @@ public: /// Returns name converted to QString static QString getStringFromX509Name(X509_NAME* name, int nid); + /// Returns ASN1 string converted to QString + static QString getStringFromASN1_STRING(ASN1_STRING* string); + /// Converts ASN time to QDateTime. If conversion fails, then invalid /// datetime is returned. static QDateTime getDateTimeFromASN(const ASN1_TIME* time); @@ -126,6 +129,7 @@ public: private: void verifyCertificateCAdES(PDFSignatureVerificationResult& result) const; + static int verifyCallback(int ok, X509_STORE_CTX* context); }; } // namespace pdf