diff --git a/Dockerfile b/Dockerfile index 93c2836e..ee883b70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,10 @@ RUN apk --no-cache add wget unzip && \ wget https://raw.githubusercontent.com/impallari/DancingScript/master/OFL.txt && \ wget https://raw.githubusercontent.com/notofonts/noto-fonts/refs/heads/main/LICENSE && \ wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \ - wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/download/20260813/pdfium-musl-$(uname -m).zip" && \ + wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/download/20260920/pdfium-musl-$(uname -m).zip" && \ case "$(uname -m)" in \ - x86_64) echo "c5c7dde243ecb66ab0819c8193515ef38ad53549fe260f3c2dfd93ea56eda2e7 pdfium-linux.zip" ;; \ - aarch64) echo "64c4483449b1b4dccc696ad0c5c96e0b7f74dcc57b4f23c676b7a70671b0bbb5 pdfium-linux.zip" ;; \ + x86_64) echo "bbca8a648dbd1ba81f9c6c223f21dc394cf1ad381c6c17d60c6dcc4a7d3a2ae0 pdfium-linux.zip" ;; \ + aarch64) echo "73b72ed79b9bfdd494dee7e863482c24971a8d4b11f6cd1289aa170d31304a1f pdfium-linux.zip" ;; \ esac | sha256sum -c - && \ mkdir -p /pdfium-linux && \ unzip -q pdfium-linux.zip -d /pdfium-linux diff --git a/lib/pdfium.rb b/lib/pdfium.rb index 7227e7bc..ce7b0975 100644 --- a/lib/pdfium.rb +++ b/lib/pdfium.rb @@ -109,6 +109,7 @@ class Pdfium attach_function :FPDF_GetLastError, [], :ulong attach_function :FPDF_GetTrailerEnds, %i[FPDF_DOCUMENT pointer ulong], :ulong attach_function :FPDF_DocumentHasValidCrossReferenceTable, [:FPDF_DOCUMENT], :int + attach_function :FPDF_HasOnlyDSSChanges, %i[FPDF_DOCUMENT FPDF_DOCUMENT], :int attach_function :FPDF_GetSecurityHandlerRevision, [:FPDF_DOCUMENT], :int attach_function :FPDF_GetFormType, [:FPDF_DOCUMENT], :int @@ -775,6 +776,10 @@ class Pdfium Pdfium.FPDF_DocumentHasValidCrossReferenceTable(@document_ptr) == 1 end + def only_dss_changes?(signed_document) + Pdfium.FPDF_HasOnlyDSSChanges(@document_ptr, signed_document.document_ptr) == 1 + end + def annot_count(page_index) @annot_counts[page_index] ||= Pdfium.FPDFPage_GetAnnotCountRaw(@document_ptr, page_index) end diff --git a/lib/verify_pdf_signature.rb b/lib/verify_pdf_signature.rb index f8da1408..b6bda335 100644 --- a/lib/verify_pdf_signature.rb +++ b/lib/verify_pdf_signature.rb @@ -10,20 +10,22 @@ module VerifyPdfSignature module_function def call(io, trusted_certs) - Pdfium::Document.open_io(io) do |document| - signatures = document.signatures.select { |e| e.byte_range.any?(&:positive?) && e.contents.present? } - - next [] if signatures.blank? - - verified_signatures = signatures.select { |e| verified_signature?(e, io, trusted_certs) } - trusted_signatures = verified_signatures.select { |e| trusted_signature?(e, trusted_certs) } - last_signature = (trusted_signatures.presence || verified_signatures).max_by(&:signed_end) - has_unsigned_changes = last_signature && unsigned_changes?(document, io, last_signature.signed_end) - - signatures.map do |signature| - build_signature(signature, trusted_certs, - verified: verified_signatures.include?(signature), - has_unsigned_changes: has_unsigned_changes && signature == last_signature) + Pdfium.with_instance do + Pdfium::Document.open_io(io) do |document| + signatures = document.signatures.select { |e| e.byte_range.any?(&:positive?) && e.contents.present? } + + next [] if signatures.blank? + + verified_signatures = signatures.select { |e| verified_signature?(e, io, trusted_certs) } + trusted_signatures = verified_signatures.select { |e| trusted_signature?(e, trusted_certs) } + last_signature = (trusted_signatures.presence || verified_signatures).max_by(&:signed_end) + has_unsigned_changes = last_signature && unsigned_changes?(document, io, last_signature.signed_end) + + signatures.map do |signature| + build_signature(signature, trusted_certs, + verified: verified_signatures.include?(signature), + has_unsigned_changes: has_unsigned_changes && signature == last_signature) + end end end end @@ -31,11 +33,26 @@ module VerifyPdfSignature def verified_signature?(signature, io, trusted_certs) return false unless covers_signed_revision?(signature, io) - verify_contents(OpenSSL::PKCS7.new(signature.contents), signed_data(io, signature.byte_range), trusted_certs) - rescue OpenSSL::PKCS7::PKCS7Error + pkcs7 = OpenSSL::PKCS7.new(signature.contents) + + if signature.sub_filter == 'ETSI.RFC3161' + verify_timestamp(pkcs7, signed_data(io, signature.byte_range)) + else + verify_contents(pkcs7, signed_data(io, signature.byte_range), trusted_certs) + end + rescue OpenSSL::PKCS7::PKCS7Error, OpenSSL::Timestamp::TimestampError false end + def verify_timestamp(pkcs7, signed_data) + return false unless pkcs7.verify(pkcs7.certificates, OpenSSL::X509::Store.new, nil, + OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::BINARY) + + token_info = OpenSSL::Timestamp::TokenInfo.new(pkcs7.data) + + token_info.message_imprint == OpenSSL::Digest.digest(token_info.algorithm, signed_data) + end + def build_signature(signature, trusted_certs, verified:, has_unsigned_changes:) pkcs7 = OpenSSL::PKCS7.new(signature.contents) @@ -173,27 +190,8 @@ module VerifyPdfSignature io.seek(0) Pdfium::Document.open_bytes(io.read(signed_end)) do |signed_document| - next true unless signed_document.valid_cross_reference_table? - - serialized_document(signed_document) != serialized_document(document) - end - end - - def serialized_document(document) - pages = (0...document.page_count).map do |index| - page = document.get_page(index) - objects = page.objects.map { |object| [*object.to_a, image_digest(page, object)] } - - [page.rotation, objects, page.annotations, page.text] + !document.only_dss_changes?(signed_document) end - - [pages, document.bookmarks] - end - - def image_digest(page, object) - return unless object.image? - - Digest::SHA256.hexdigest(page.extract_image_bitmap(object.object_ptr)[:data]) end def signed_data(io, byte_range)