Class: HexaPDF::DigitalSignature::Signing::DefaultHandler
- Inherits:
-
Object
- Object
- HexaPDF::DigitalSignature::Signing::DefaultHandler
- Defined in:
- lib/hexapdf/digital_signature/signing/default_handler.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.
Note: Currently only RSA is supported, DSA and ECDSA are not. See the examples below for how to handle them using external signing.
CMS and PAdES Signatures
The handler supports the older standard of CMS signatures as well as the newer PAdES signatures specified in PDF 2.0. By default, CMS signatures are created but this can be changed by setting #signature_type to :pades.
When creating PAdES signatures the following two PAdES baseline signatures are supported: B-B and B-T. The difference between those two is that a timestamp handler was defined for B-T compatibility.
Signing Modes - Internal, External, External/Asynchronous
This handler provides two ways to create the CMS signed-data structure required by Signatures#add:
-
By providing the signing certificate together with the signing key and the certificate chain, HexaPDF itself does the signing internally. 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*, a callable object assigned to #external_signing. Here the actual signing happens “outside” of HexaPDF, for example, in custom code or even asynchronously. This is needed in case the signing key is not directly available but only an interface to it (e.g. when dealing with a HSM).
Depending on whether #certificate is set the signing happens differently:
-
If #certificate is not set, the callable object is used instead of #sign, so it needs to accept the same arguments as #sign and needs to return a complete, DER-serialized CMS signed data object.
-
If #certificate is set, the CMS signed data object is created by HexaPDF. The callable #external_signing object is called with the used digest algorithm and the already digested data which needs to be signed (but not digested) and the signature returned.
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.
-
Optional Data
Besides the required data, some optional attributes can also be specified:
-
Reason, location and contact information
-
Making the signature a certification signature by applying the DocMDP transform method and a DoCMDP permission
Examples
# Signing using certificate + key
document.sign("output.pdf", certificate: my_cert, key: my_key,
certificate_chain: my_chain)
# Signing using an external mechanism without certificate set
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).to_der
end
document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
# Signing using external mechanism with certificate set
signing_proc = lambda do |digest_method, hash|
signing_service.sign_raw(digest_method, hash)
end
document.sign("output.pdf", certificate: my_cert, certificate_chain: my_chain,
external_signing: signing_proc)
# Signing with DSA or ECDSA certificate/keys
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])
OpenSSL::PKCS7.sign(certificate, key, data, certificate_chain,
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
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
-
#certificate ⇒ Object
The certificate with which to sign the PDF.
-
#certificate_chain ⇒ Object
The certificate chain that should be embedded in the PDF; usually contains all certificates up to the root certificate.
-
#contact_info ⇒ Object
The contact information.
-
#digest_algorithm ⇒ Object
The digest algorithm that should be used when creating the signature.
-
#doc_mdp_permissions ⇒ Object
The DocMDP permissions that should be set on the document.
-
#external_signing ⇒ Object
A callable object for custom signing mechanisms.
-
#key ⇒ Object
The private key for the #certificate.
-
#location ⇒ Object
The signing location.
-
#reason ⇒ Object
The reason for signing.
-
#signature_size ⇒ Object
Returns the size of the serialized signature that should be reserved.
-
#signature_type ⇒ Object
The type of signature to be written (i.e. the value of the /SubFilter key).
-
#signing_time ⇒ Object
The custom signing time.
-
#timestamp_handler ⇒ Object
The timestamp handler that should be used for timestamping the signature.
Instance Method Summary collapse
-
#finalize_objects(_signature_field, signature) ⇒ Object
Finalizes the signature field as well as the signature dictionary before writing.
-
#initialize(**arguments) ⇒ DefaultHandler
constructor
Creates a new DefaultHandler instance with the given attributes.
-
#sign(io, byte_range) ⇒ Object
Returns the DER serialized CMS signed data object containing the signature for the given IO byte ranges.
Constructor Details
#initialize(**arguments) ⇒ DefaultHandler
Creates a new DefaultHandler instance with the given attributes.
242 243 244 245 246 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 242 def initialize(**arguments) @signature_size = nil @signature_type = :cms arguments.each {|name, value| send("#{name}=", value) } end |
Instance Attribute Details
#certificate ⇒ Object
The certificate with which to sign the PDF.
If the certificate is provided, HexaPDF creates the signature object. Otherwise the #external_signing callable object has to create it.
See the class documentation section “Signing Modes” on how #certificate, #key and #external_signing play together.
164 165 166 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 164 def certificate @certificate end |
#certificate_chain ⇒ Object
The certificate chain that should be embedded in the PDF; usually contains all certificates up to the root certificate.
177 178 179 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 177 def certificate_chain @certificate_chain end |
#contact_info ⇒ Object
The contact information. If used, will be set on the signature dictionary.
212 213 214 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 212 def contact_info @contact_info end |
#digest_algorithm ⇒ Object
The digest algorithm that should be used when creating the signature.
See SignedDataCreator#digest_algorithm for the default value (if nothing is set) and for the allowed values.
183 184 185 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 183 def digest_algorithm @digest_algorithm end |
#doc_mdp_permissions ⇒ Object
The DocMDP permissions that should be set on the document.
See #doc_mdp_permissions=
239 240 241 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 239 def @doc_mdp_permissions end |
#external_signing ⇒ Object
A callable object for custom signing mechanisms.
The callable object has two different uses depending on whether #certificate is set:
-
If #certificate is not set, it fulfills the same role as the #sign method and needs to conform to that interface.
-
If #certificate is set and #key is not, it is just used for signing. Here it needs to accept the used digest algorithm and the already digested data as arguments and return the signature.
Also dee the class documentation section “Signing Modes” on how #certificate, #key and #external_signing play together.
203 204 205 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 203 def external_signing @external_signing end |
#key ⇒ Object
The private key for the #certificate.
If the key is provided, HexaPDF does the signing. Otherwise the #external_signing callable object has to sign the data.
See the class documentation section “Signing Modes” on how #certificate, #key and #external_signing play together.
173 174 175 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 173 def key @key end |
#location ⇒ Object
The signing location. If used, will be set on the signature dictionary.
209 210 211 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 209 def location @location end |
#reason ⇒ Object
The reason for signing. If used, will be set on the signature dictionary.
206 207 208 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 206 def reason @reason end |
#signature_size ⇒ Object
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.
279 280 281 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 279 def signature_size @signature_size || sign(StringIO.new, [0, 0, 0, 0]).size end |
#signature_type ⇒ Object
The type of signature to be written (i.e. the value of the /SubFilter key).
The value can either be :cms (the default; uses a detached CMS signature) or :pades (uses an ETSI CAdES compatible signature).
234 235 236 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 234 def signature_type @signature_type end |
#signing_time ⇒ Object
The custom signing time.
The signing time is usually the time when signing actually happens. This is also what HexaPDF uses. If it is known that signing happened at a different point in time, that time can be provided using this accessor.
219 220 221 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 219 def signing_time @signing_time end |
#timestamp_handler ⇒ Object
The timestamp handler that should be used for timestamping the signature.
If this attribute is set, a timestamp token is embedded into the CMS object.
188 189 190 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 188 def @timestamp_handler end |
Instance Method Details
#finalize_objects(_signature_field, signature) ⇒ Object
Finalizes the signature field as well as the signature dictionary before writing.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 284 def finalize_objects(_signature_field, signature) signature[:Filter] = :'Adobe.PPKLite' signature[:SubFilter] = (signature_type == :pades ? :'ETSI.CAdES.detached' : :'adbe.pkcs7.detached') signature[:M] = self.signing_time ||= Time.now signature[:Reason] = reason if reason signature[:Location] = location if location signature[:ContactInfo] = contact_info if contact_info signature[:Prop_Build] = {App: {Name: :HexaPDF, REx: HexaPDF::VERSION}} signature.document.version = '2.0' if signature_type == :pades if 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: }) sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, DigestMethod: :SHA256, TransformParams: params}) signature[:Reference] = [sigref] (doc.catalog[:Perms] ||= {})[:DocMDP] = signature end end |
#sign(io, byte_range) ⇒ Object
Returns the DER serialized CMS signed data object 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.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/hexapdf/digital_signature/signing/default_handler.rb', line 313 def sign(io, byte_range) if certificate io.pos = byte_range[0] data = io.read(byte_range[1]) io.pos = byte_range[2] data << io.read(byte_range[3]) SignedDataCreator.create(data, type: signature_type, certificate: certificate, key: key, digest_algorithm: digest_algorithm, signing_time: signing_time, timestamp_handler: , certificates: certificate_chain, &external_signing).to_der else external_signing.call(io, byte_range) end end |