# -*- encoding: utf-8; frozen_string_literal: true -*- # #-- # This file is part of HexaPDF. # # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby # Copyright (C) 2014-2023 Thomas Leitner # # HexaPDF is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License version 3 as # published by the Free Software Foundation with the addition of the # following permission added to Section 15 as permitted in Section 7(a): # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON # INFRINGEMENT OF THIRD PARTY RIGHTS. # # HexaPDF is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # License for more details. # # You should have received a copy of the GNU Affero General Public License # along with HexaPDF. If not, see . # # The interactive user interfaces in modified source and object code # versions of HexaPDF must display Appropriate Legal Notices, as required # under Section 5 of the GNU Affero General Public License version 3. # # In accordance with Section 7(b) of the GNU Affero General Public # License, a covered work must retain the producer line in every PDF that # is created or manipulated using HexaPDF. # # If the GNU Affero General Public License doesn't fit your need, # commercial licenses are available at . #++ require 'hexapdf/document' module HexaPDF module DigitalSignature # This module contains everything related to the signing of a PDF document, i.e. signing # handlers and the actual code for signing. module Signing autoload(:DefaultHandler, 'hexapdf/digital_signature/signing/default_handler') autoload(:TimestampHandler, 'hexapdf/digital_signature/signing/timestamp_handler') autoload(:SignedDataCreator, 'hexapdf/digital_signature/signing/signed_data_creator') # Embeds the given +signature+ into the /Contents value of the newest signature dictionary of # the PDF document given by the +io+ argument. # # This functionality can be used together with the support for external signing (see # DefaultHandler and DefaultHandler#external_signing) to implement asynchronous signing. # # Note: This will, most probably, only work on documents prepared for external signing by # HexaPDF and not by other libraries. def self.embed_signature(io, signature) doc = HexaPDF::Document.new(io: io) signature_dict = doc.signatures.find {|sig| doc.revisions.current.object(sig) == sig } signature_dict_offset, signature_dict_length = locate_signature_dict( doc.revisions.current.xref_section, doc.revisions.parser.startxref_offset, signature_dict.oid ) io.pos = signature_dict_offset signature_data = io.read(signature_dict_length) replace_signature_contents(signature_data, signature) io.pos = signature_dict_offset io.write(signature_data) end # Uses the information in the given cross-reference section as well as the byte offset of the # cross-reference section to calculate the offset and length of the signature dictionary with # the given object id. def self.locate_signature_dict(xref_section, start_xref_position, signature_oid) data = xref_section.map {|oid, _gen, entry| [entry.pos, oid] if entry.in_use? }.compact.sort << [start_xref_position, nil] index = data.index {|_pos, oid| oid == signature_oid } [data[index][0], data[index + 1][0] - data[index][0]] end # Replaces the value of the /Contents key in the serialized +signature_data+ with the value of # +contents+. def self.replace_signature_contents(signature_data, contents) signature_data.sub!(/Contents(?:\(.*?\)|<.*?>)/) do |match| length = match.size result = "Contents<#{contents.unpack1('H*')}" if length < result.size raise HexaPDF::Error, "The reserved space for the signature was too small " \ "(#{(length - 10) / 2} vs #{(result.size - 10) / 2}) - use the handlers " \ "#signature_size method to increase the reserved space" end "#{result.ljust(length - 1, '0')}>" end end end end end