|
|
|
@ -10,6 +10,7 @@ module VerifyPdfSignature
|
|
|
|
module_function
|
|
|
|
module_function
|
|
|
|
|
|
|
|
|
|
|
|
def call(io, trusted_certs)
|
|
|
|
def call(io, trusted_certs)
|
|
|
|
|
|
|
|
Pdfium.with_instance do
|
|
|
|
Pdfium::Document.open_io(io) do |document|
|
|
|
|
Pdfium::Document.open_io(io) do |document|
|
|
|
|
signatures = document.signatures.select { |e| e.byte_range.any?(&:positive?) && e.contents.present? }
|
|
|
|
signatures = document.signatures.select { |e| e.byte_range.any?(&:positive?) && e.contents.present? }
|
|
|
|
|
|
|
|
|
|
|
|
@ -27,15 +28,31 @@ module VerifyPdfSignature
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def verified_signature?(signature, io, trusted_certs)
|
|
|
|
def verified_signature?(signature, io, trusted_certs)
|
|
|
|
return false unless covers_signed_revision?(signature, io)
|
|
|
|
return false unless covers_signed_revision?(signature, io)
|
|
|
|
|
|
|
|
|
|
|
|
verify_contents(OpenSSL::PKCS7.new(signature.contents), signed_data(io, signature.byte_range), trusted_certs)
|
|
|
|
pkcs7 = OpenSSL::PKCS7.new(signature.contents)
|
|
|
|
rescue OpenSSL::PKCS7::PKCS7Error
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
false
|
|
|
|
end
|
|
|
|
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:)
|
|
|
|
def build_signature(signature, trusted_certs, verified:, has_unsigned_changes:)
|
|
|
|
pkcs7 = OpenSSL::PKCS7.new(signature.contents)
|
|
|
|
pkcs7 = OpenSSL::PKCS7.new(signature.contents)
|
|
|
|
|
|
|
|
|
|
|
|
@ -173,27 +190,8 @@ module VerifyPdfSignature
|
|
|
|
io.seek(0)
|
|
|
|
io.seek(0)
|
|
|
|
|
|
|
|
|
|
|
|
Pdfium::Document.open_bytes(io.read(signed_end)) do |signed_document|
|
|
|
|
Pdfium::Document.open_bytes(io.read(signed_end)) do |signed_document|
|
|
|
|
next true unless signed_document.valid_cross_reference_table?
|
|
|
|
!document.only_dss_changes?(signed_document)
|
|
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
|
end
|
|
|
|
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
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def signed_data(io, byte_range)
|
|
|
|
def signed_data(io, byte_range)
|
|
|
|
|