Class: HexaPDF::Document::Signatures::DefaultHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/hexapdf/document/signatures.rb

Overview

This is the default signing handler which provides the ability to sign a document with the adbe.pkcs7.detached or ETSI.CAdES.detached algorithms. It is registered under the :default name.

Usage

The signing handler is used by default by all methods that need a signing handler. Therefore it is usually only necessary to provide the actual attribute values.

This handler provides two ways to create the PKCS#7 signed-data structure required by Signatures#add:

  • By providing the signing certificate together with the signing key and the certificate chain. This way HexaPDF itself does the signing. It is the preferred way if all the needed information is available.

    Assign the respective data to the #certificate, #key and #certificate_chain attributes.

  • By using an external signing mechanism. Here the actual signing happens “outside” of HexaPDF, for example, in custom code or even asynchronously. This is needed in case the signing certificate plus key are not directly available but only an interface to them (e.g. when dealing with a HSM).

    Assign a callable object to #external_signing. If the signing process needs to be asynchronous, make sure to set the #signature_size appropriately, return an empty string during signing and later use Signatures.embed_signature to embed the actual signature.

Additional functionality:

  • Optionally setting the reason, location and contact information.

  • Making the signature a certification signature by applying the DocMDP transform method.

Example:

# Signing using certificate + key
document.sign("output.pdf", certificate: my_cert, key: my_key,
              certificate_chain: my_chain)

# Signing using an external mechanism:
signing_proc = lambda do |io, byte_range|
  io.pos = byte_range[0]
  data = io.read(byte_range[1])
  io.pos = byte_range[2]
  data << io.read(byte_range[3])
  signing_service.pkcs7_sign(data)
end
document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)

Implementing a Signing Handler

This class also serves as an example on how to create a custom handler: The public methods #signature_size, #finalize_objects and #sign are used by the digital signature algorithm. See their descriptions for details.

Once a custom signing handler has been created, it can be registered under the 'signature.signing_handler' configuration option for easy use. It has to take keyword arguments in its initialize method to be compatible with the Signatures#handler method.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**arguments) ⇒ DefaultHandler

Creates a new DefaultHandler with the given attributes.



154
155
156
157
# File 'lib/hexapdf/document/signatures.rb', line 154

def initialize(**arguments)
  @signature_size = nil
  arguments.each {|name, value| send("#{name}=", value) }
end

Instance Attribute Details

#certificateObject

The certificate with which to sign the PDF.



108
109
110
# File 'lib/hexapdf/document/signatures.rb', line 108

def certificate
  @certificate
end

#certificate_chainObject

The certificate chain that should be embedded in the PDF; normally contains all certificates up to the root certificate.



115
116
117
# File 'lib/hexapdf/document/signatures.rb', line 115

def certificate_chain
  @certificate_chain
end

#contact_infoObject

The contact information. If used, will be set on the signature object.



131
132
133
# File 'lib/hexapdf/document/signatures.rb', line 131

def contact_info
  @contact_info
end

#doc_mdp_permissionsObject

The DocMDP permissions that should be set on the document.

See #doc_mdp_permissions=



151
152
153
# File 'lib/hexapdf/document/signatures.rb', line 151

def doc_mdp_permissions
  @doc_mdp_permissions
end

#external_signingObject

A callable object fulfilling the same role as the #sign method that is used instead of the default mechanism for signing.

If this attribute is set, the attributes #certificate, #key and #certificate_chain are not used.



122
123
124
# File 'lib/hexapdf/document/signatures.rb', line 122

def external_signing
  @external_signing
end

#keyObject

The private key for the #certificate.



111
112
113
# File 'lib/hexapdf/document/signatures.rb', line 111

def key
  @key
end

#locationObject

The signing location. If used, will be set on the signature object.



128
129
130
# File 'lib/hexapdf/document/signatures.rb', line 128

def location
  @location
end

#reasonObject

The reason for signing. If used, will be set on the signature object.



125
126
127
# File 'lib/hexapdf/document/signatures.rb', line 125

def reason
  @reason
end

#signature_sizeObject

Returns the size of the serialized signature that should be reserved.

If a custom size is set using #signature_size=, it used. Otherwise the size is determined by using #sign to sign an empty string.



190
191
192
# File 'lib/hexapdf/document/signatures.rb', line 190

def signature_size
  @signature_size || sign(StringIO.new, [0, 0, 0, 0]).size
end

#signature_typeObject

The type of signature to be written (i.e. the value of the /SubFilter key).

The value can either be :adobe (the default; uses a detached PKCS7 signature) or :etsi (uses an ETSI CAdES compatible signature).



146
147
148
# File 'lib/hexapdf/document/signatures.rb', line 146

def signature_type
  @signature_type
end

Instance Method Details

#finalize_objects(_signature_field, signature) ⇒ Object

Finalizes the signature field as well as the signature dictionary before writing.



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/hexapdf/document/signatures.rb', line 195

def finalize_objects(_signature_field, signature)
  signature[:SubFilter] = :'ETSI.CAdES.detached' if signature_type == :etsi
  signature[:Reason] = reason if reason
  signature[:Location] = location if location
  signature[:ContactInfo] = contact_info if contact_info

  if doc_mdp_permissions
    doc = signature.document
    if doc.signatures.count > 1
      raise HexaPDF::Error, "Can set DocMDP access permissions only on first signature"
    end
    params = doc.add({Type: :TransformParams, V: :'1.2', P: doc_mdp_permissions})
    sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, DigestMethod: :SHA1,
                      TransformParams: params})
    signature[:Reference] = [sigref]
    (doc.catalog[:Perms] ||= {})[:DocMDP] = signature
  end
end

#sign(io, byte_range) ⇒ Object

Returns the DER serialized OpenSSL::PKCS7 structure containing the signature for the given IO byte ranges.

The byte_range argument is an array containing four numbers [offset1, length1, offset2, length2]. The offset numbers are byte positions in the io argument and the to-be-signed data can be determined by reading length bytes at the offsets.



220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/hexapdf/document/signatures.rb', line 220

def sign(io, byte_range)
  if external_signing
    external_signing.call(io, byte_range)
  else
    io.pos = byte_range[0]
    data = io.read(byte_range[1])
    io.pos = byte_range[2]
    data << io.read(byte_range[3])
    OpenSSL::PKCS7.sign(@certificate, @key, data, @certificate_chain,
                        OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
  end
end