# frozen_string_literal: false require "hexapdf" require "tempfile" module Decidim module Initiatives # Example of service to add a signature to a pdf class PdfSignatureExample attr_accessor :pdf # Public: Initializes the service. # pdf - The pdf document to be signed def initialize(args = {}) @pdf = args.fetch(:pdf) end # Public: PDF signed using a new certificate generated by the service def signed_pdf signed_file = Tempfile.new("signed_pdf") doc = HexaPDF::Document.new(io: StringIO.new(pdf)) # Prepare the document for embedding of the digital signature data = nil # Used for storing the to-be-signed data signing_mechanism = lambda do |io, byte_range| # Store the to-be-signed data in the local variable data io.pos = byte_range[0] data = io.read(byte_range[1]) io.pos = byte_range[2] data << io.read(byte_range[3]) "" end doc.sign(signed_file.path, signature: signature_widget(doc), reason: caption, signature_size: 10_000, external_signing: signing_mechanism) signature = OpenSSL::PKCS7.sign(certificate, key, data, # [HexaPDF.demo_cert.sub_ca, HexaPDF.demo_cert.root_ca], [], OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der # Embed the signature HexaPDF::DigitalSignature::Signing.embed_signature(File.open(signed_file.path, "rb+"), signature) File.binread(signed_file.path) ensure signed_file.close signed_file.unlink end private def signature_widget(doc) doc.pages.add.document form = doc.acro_form(create: true) form.signature_flag(:append_only) sig_field = form.create_signature_field("signature") widget = sig_field.create_widget(doc.pages[doc.pages.length - 1], Rect: [60, 50, 250, 620]) widget.flag(:print) xobject = (widget[:AP] ||= {})[:N] ||= doc.add({ Type: :XObject, Subtype: :Form }) # Create the appearance for the widget xobject[:BBox] = [0, 0, widget[:Rect].width, widget[:Rect].height] xobject.canvas .font("Helvetica", size: 10) .text(caption, at: [10, 30]) sig = doc.add({ Type: :Sig }) # set an empty signature and apply it to the field sig_field.field_value = sig sig end def certificate @certificate ||= OpenSSL::X509::Certificate.new.tap do |cert| cert.not_before = Time.current cert.not_after = 10.years.from_now cert.public_key = key.public_key cert.sign(key, OpenSSL::Digest.new("SHA256")) end end def key @key ||= OpenSSL::PKey::RSA.new(2048) end def caption @caption ||= "Digitally Signed By: #{signedby}\nContact: #{contact}\nLocation: #{location}\nDate: #{date.iso8601}" end def signedby "Test" end def location "Barcelona" end def contact "test@example.org" end def issuer "Decidim" end def date Time.current end end end end