Class: HexaPDF::DigitalSignature::Signing::TimestampHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/hexapdf/digital_signature/signing/timestamp_handler.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 can optionally use HTTP basic authentication.

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.



106
107
108
109
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 106

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 dictionary.



103
104
105
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 103

def contact_info
  @contact_info
end

#locationObject

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



100
101
102
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 100

def location
  @location
end

#reasonObject

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



97
98
99
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 97

def reason
  @reason
end

#signature_sizeObject

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



112
113
114
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 112

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.



82
83
84
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 82

def tsa_hash_algorithm
  @tsa_hash_algorithm
end

#tsa_passwordObject

The password for basic authentication to the TSA server.

See: #tsa_username



79
80
81
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 79

def tsa_password
  @tsa_password
end

#tsa_policy_idObject

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



85
86
87
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 85

def tsa_policy_id
  @tsa_policy_id
end

#tsa_urlObject

The URL of the timestamp authority server.

This value is required.



67
68
69
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 67

def tsa_url
  @tsa_url
end

#tsa_usernameObject

The username for basic authentication to the TSA server.

If the username is not set, no basic authentication is done.

See: #tsa_password



74
75
76
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 74

def tsa_username
  @tsa_username
end

Instance Method Details

#finalize_objects(_signature_field, signature) ⇒ Object

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



117
118
119
120
121
122
123
124
125
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 117

def finalize_objects(_signature_field, signature)
  signature.document.version = '2.0'
  signature[:Type] = :DocTimeStamp
  signature[:Filter] = :'Adobe.PPKLite'
  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.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/hexapdf/digital_signature/signing/timestamp_handler.rb', line 129

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

  url = URI(tsa_url)
  http_request = Net::HTTP::Post.new(url, 'Content-Type' => 'application/timestamp-query')
  http_request.body = req.to_der
  http_request.basic_auth(tsa_username, tsa_password) if tsa_username
  http_response = Net::HTTP.start(url.hostname, url.port, use_ssl: (url.scheme == 'https')) do |http|
    http.request(http_request)
  end

  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
  elsif http_response.kind_of?(Net::HTTPUnauthorized)
    raise HexaPDF::Error, "Basic authentication to the server failed: #{http_response.body}"
  else
    raise HexaPDF::Error, "Invalid TSA server response: #{http_response.body}"
  end
end