Class: HexaPDF::Document::Signatures::TimestampHandler

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

Overview

This is a signing handler for adding a timestamp signature (a PDF2.0 feature) to a PDF document. It is registered under the :timestamp name.

The timestamp is provided by a timestamp authority and establishes the document contents at the time indicated in the timestamp. Timestamping a PDF document is usually done in context of long term validation but can also be done standalone.

Usage

It is necessary to provide at least the URL of the timestamp authority server (TSA) via #tsa_url, everything else is optional and uses default values. The TSA server must not use authentication to be usable.

Example:

document.sign("output.pdf", handler: :timestamp, tsa_url: 'https://freetsa.org/tsr')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**arguments) ⇒ TimestampHandler

Creates a new TimestampHandler with the given attributes.



283
284
285
286
# File 'lib/hexapdf/document/signatures.rb', line 283

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

Instance Attribute Details

#contact_infoObject

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



280
281
282
# File 'lib/hexapdf/document/signatures.rb', line 280

def contact_info
  @contact_info
end

#locationObject

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



277
278
279
# File 'lib/hexapdf/document/signatures.rb', line 277

def location
  @location
end

#reasonObject

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



274
275
276
# File 'lib/hexapdf/document/signatures.rb', line 274

def reason
  @reason
end

#signature_sizeObject

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



289
290
291
# File 'lib/hexapdf/document/signatures.rb', line 289

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

#tsa_hash_algorithmObject

The hash algorithm to use for timestamping. Defaults to SHA512.



259
260
261
# File 'lib/hexapdf/document/signatures.rb', line 259

def tsa_hash_algorithm
  @tsa_hash_algorithm
end

#tsa_policy_idObject

The policy OID to use for timestamping. Defaults to nil.



262
263
264
# File 'lib/hexapdf/document/signatures.rb', line 262

def tsa_policy_id
  @tsa_policy_id
end

#tsa_urlObject

The URL of the timestamp authority server.

This value is required.



256
257
258
# File 'lib/hexapdf/document/signatures.rb', line 256

def tsa_url
  @tsa_url
end

Instance Method Details

#finalize_objects(_signature_field, signature) ⇒ Object

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



294
295
296
297
298
299
300
301
# File 'lib/hexapdf/document/signatures.rb', line 294

def finalize_objects(_signature_field, signature)
  signature.document.version = '2.0'
  signature[:Type] = :DocTimeStamp
  signature[:SubFilter] = :'ETSI.RFC3161'
  signature[:Reason] = reason if reason
  signature[:Location] = location if location
  signature[:ContactInfo] = contact_info if contact_info
end

#sign(io, byte_range) ⇒ Object

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



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/hexapdf/document/signatures.rb', line 305

def sign(io, byte_range)
  hash_algorithm = tsa_hash_algorithm || 'SHA512'
  digest = OpenSSL::Digest.new(hash_algorithm)
  io.pos = byte_range[0]
  digest << io.read(byte_range[1])
  io.pos = byte_range[2]
  digest << io.read(byte_range[3])

  req = OpenSSL::Timestamp::Request.new
  req.algorithm = hash_algorithm
  req.message_imprint = digest.digest
  req.policy_id = tsa_policy_id if tsa_policy_id

  http_response = Net::HTTP.post(URI(tsa_url), req.to_der,
                                 'content-type' => 'application/timestamp-query')
  if http_response.kind_of?(Net::HTTPOK)
    response = OpenSSL::Timestamp::Response.new(http_response.body)
    if response.status == 0
      response.token.to_der
    else
      raise HexaPDF::Error, "Timestamp token could not be created: #{response.failure_info}"
    end
  else
    raise HexaPDF::Error, "Invalid TSA server response: #{http_response.body}"
  end
end