hexapdf icon indicating copy to clipboard operation
hexapdf copied to clipboard

always get the invalid signature

Open heruwaspodov opened this issue 1 month ago • 3 comments

Hi mas @gettalong. i have this repository for signing pdf using hexapdf, below the repository: https://github.com/heruwaspodov/signing-envelopes

But i have an issue when already signing. They always get an error like the picture below

Image

Below is a script console if you want try.

e = Envelope.where(is_certified: true).first
r = e.recipients.first
ann1 = r.annotations.first

image = File.open('public/mekari-sign.png', 'rb')
temp_doc = [Tempfile.new](http://tempfile.new/)([[e.id](http://e.id/), '.pdf'])
temp_doc.binmode
temp_doc.write(e.doc.download)
temp_doc.rewind
temp_doc

x = Annotations::AnnotationSignature.new(temp_doc, image, ann1['page'])
x.recipient = r
x.setup_dss(e.id)
x.setup_attributes(ann1)
x.process!

i already set to

# frozen_string_literal: true

module Annotations
  class AnnotationSignature < Annotation
    require 'hexapdf'
    require "#{HexaPDF.data_dir}/cert/demo_cert.rb"
    require_relative '../app_config'

    include Certifications::Helper
    include Annotations::SignatureRotations::NoRotation
    include Annotations::SignatureRotations::Rotation180
    include Annotations::SignatureRotations::Rotation90
    include Annotations::SignatureRotations::Rotation270

    attr_accessor :recipient
    attr_reader :dss

    MAX_RETRY_ATTEMPT = 3
    DELAY = 0.2

    def initialize(document_tempfile, image_tempfile, page_number)
      @document = document_tempfile
      @image_path = image_tempfile

      @doc = HexaPDF::Document.open(@document.path)
      @page = @doc.pages[page_number.to_i - 1]
      @page_number = page_number

      @cert_location = AppConfig.cert_location
      @cert_contact  = AppConfig.cert_contact
      @attempts = 1

      @config = Rails.application.config.cloud_hsm.current_config
    end

    def rotate_value
      @rotate_value ||= @page.value[:Rotate].to_i
    rescue StandardError
      0
    end

    def setup_image_rotation
      case rotate_value
      when 90, -270 then rotation90
      when 180, -180 then rotation180
      when 270, -90 then rotation270
      else
        no_rotation
      end
    end

    def setup_dss(envelope_id)
      @dss = Certifications::DocumentSecurityStore.new(envelope_id)
    end

    def process!
      setup_image_rotation
      signing
      add_ltv!
      @document
    rescue StandardError => e
      if (@attempts += 1) <= MAX_RETRY_ATTEMPT
        sleep(DELAY)
        retry
      end

      raise e
    end

    def signing
      time_start = Time.now
      Log.info(">> start signing (rec: #{@recipient.id}) at #{time_start}", {})
      sign
      time_end = Time.now
      Log.info(">> finish signing (rec: #{@recipient.id}) at #{time_end}", {})
      Log.info(
        ">> success signing (rec: #{@recipient.id}) in #{(time_end - time_start).to_f} seconds", {}
      )
    end

    def sign
      @doc.sign(@document.path,
                reason: reason,
                location: @cert_location,
                contact_info: @cert_contact,
                signature: field_signature,
                certificate: msign_cert,
                key: private_key,
                certificate_chain: [intermediate1_cert, intermediate2_cert],
                write_options: write_options,
                signature_type: :pades,
                signature_size: AppConfig.cert_signature_size,
                timestamp_handler: ts_handler)
    end

    def image_signature
      @image_signature ||= @doc.images.add(@image_path)
    end

    def field_signature
      field = create_field(name)
      init_widget = create_widget(field)
      widget = rotate_content_widget(init_widget)
      widget.xobject(image_signature,
                     at: [0, 0],
                     width: @widget_width,
                     height: @widget_height)
      field
    end

    def add_ltv!
      @dss.add_ltv(@document.path)
    end

    def name
      "#{@recipient.email}#{SecureRandom.hex(5)}"
    end

    def create_field(name)
      @doc.acro_form(create: true).create_signature_field(name)
    end

    def create_widget(field)
      field.create_widget(@page, defaults: false, Rect: @matrix_coordinate)
    end

    def rotate_content_widget(widget)
      widget_obj = widget.create_appearance.canvas

      rotate_options(widget_obj)
    end

    def rotate_options(widget)
      case rotate_value
      when 90, -270 then rotate_content90(widget)
      when 180, -180 then rotate_content180(widget)
      when 270, -90 then rotate_content270(widget)
      else
        widget
      end
    end

    def private_key
      if Rails.env.test? || Rails.env.development?
        @private_key ||= HexaPDF.demo_cert.key
        return @private_key
      end

      @private_key ||= OpenSSL::PKey::RSA.new(File.read(private_key_path))
    end

    def private_key_path
      @config.private_key
    end

    def msign_cert
      if Rails.env.test? || Rails.env.development?
        @msign_cert ||= HexaPDF.demo_cert.cert
        return @msign_cert
      end

      @msign_cert ||= x509_certificate(public_key_path)
    end

    def public_key_path
      @config.cert
    end

    def intermediate1_cert
      if Rails.env.test? || Rails.env.development?
        @intermediate1_cert ||= HexaPDF.demo_cert.sub_ca
        return @intermediate1_cert
      end

      @intermediate1_cert ||= x509_certificate(intermediate1_cert_path)
    end

    def intermediate1_cert_path
      @config.intermediate1
    end

    def intermediate2_cert
      if Rails.env.test? || Rails.env.development?
        @intermediate2_cert ||= HexaPDF.demo_cert.root_ca
        return @intermediate2_cert
      end

      @intermediate2_cert ||= x509_certificate(intermediate2_cert_path)
    end

    def intermediate2_cert_path
      @config.intermediate2
    end

    def write_options
      {
        validate: false
      }
    end

    def ts_handler
      return nil if Rails.env.test? || Rails.env.development?

      @doc.signatures.signing_handler(
        name: :timestamp,
        tsa_url: AppConfig.tsa_url
      )
    end

    def reason
      "#{AppConfig.cert_reason}\n\nSigners :\n#{@recipient.email}"
    end
  end
end

i already set to the big signature size

def self.cert_signature_size
    20_000
  end

And I skip the LTV. But still got the same error. i need your advice mas @gettalong . thank you

heruwaspodov avatar Dec 02 '25 08:12 heruwaspodov

Can you please provide a self-contained (including any input files/PDF), run-able script/repo with minimal dependencies that shows the problem?

And/or an example PDF that exhibits the problem?

gettalong avatar Dec 02 '25 21:12 gettalong

Below is the script for execute this repo (https://github.com/heruwaspodov/signing-envelopes), via console:

e = Envelope.where(is_certified: true).first
r = e.recipients.first
ann1 = r.annotations.first

image = File.open('public/mekari-sign.png', 'rb')
temp_doc = Tempfile.new([e.id, '.pdf'])
temp_doc.binmode
temp_doc.write(e.doc.download)
temp_doc.rewind
temp_doc

x = Annotations::AnnotationSignature.new(temp_doc, image, ann1['page'])
x.recipient = r
x.setup_dss(e.id)
x.setup_attributes(ann1)
x.process!

and below the file result

signed-Certified-Document-20251202-150503.pdf

heruwaspodov avatar Dec 02 '25 23:12 heruwaspodov

@heruwaspodov I'm not sure what the problem is here. The file you provided doesn't seem to have any problems (tested with hexapdf info as well as a view PDF readers on Linux.

gettalong avatar Dec 08 '25 09:12 gettalong