hexapdf 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -10
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/configuration.rb +12 -12
- data/lib/hexapdf/dictionary_fields.rb +6 -2
- data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
- data/lib/hexapdf/digital_signature/handler.rb +138 -0
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
- data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
- data/lib/hexapdf/digital_signature/signatures.rb +210 -0
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
- data/lib/hexapdf/digital_signature/signing.rb +101 -0
- data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
- data/lib/hexapdf/digital_signature.rb +56 -0
- data/lib/hexapdf/document.rb +21 -14
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
- data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
- data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
- data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
- data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
- data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
- data/test/hexapdf/digital_signature/test_signing.rb +53 -0
- data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +1 -1
- data/test/hexapdf/test_writer.rb +3 -3
- metadata +25 -15
- data/lib/hexapdf/document/signatures.rb +0 -546
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
- data/lib/hexapdf/type/signature/handler.rb +0 -140
- data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -0,0 +1,210 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2022 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'openssl'
|
38
|
+
require 'stringio'
|
39
|
+
require 'hexapdf/digital_signature'
|
40
|
+
require 'hexapdf/error'
|
41
|
+
|
42
|
+
module HexaPDF
|
43
|
+
module DigitalSignature
|
44
|
+
|
45
|
+
# This class provides methods for interacting with digital signatures of a PDF file. Use it
|
46
|
+
# through HexaPDF::Document#signatures.
|
47
|
+
class Signatures
|
48
|
+
|
49
|
+
include Enumerable
|
50
|
+
|
51
|
+
# Creates a new Signatures object for the given PDF document.
|
52
|
+
def initialize(document)
|
53
|
+
@document = document
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates a signing handler with the given attributes and returns it.
|
57
|
+
#
|
58
|
+
# A signing handler name is mapped to a class via the 'signature.signing_handler'
|
59
|
+
# configuration option. The default signing handler is DefaultHandler.
|
60
|
+
def signing_handler(name: :default, **attributes)
|
61
|
+
handler = @document.config.constantize('signature.signing_handler', name) do
|
62
|
+
raise HexaPDF::Error, "No signing handler named '#{name}' is available"
|
63
|
+
end
|
64
|
+
handler.new(**attributes)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Adds a signature to the document and returns the corresponding signature object.
|
68
|
+
#
|
69
|
+
# This method will add a new signature to the document and write the updated document to the
|
70
|
+
# given file or IO stream. Afterwards the document can't be modified anymore and still retain
|
71
|
+
# a correct digital signature. To modify the signed document (e.g. for adding another
|
72
|
+
# signature) create a new document based on the given file or IO stream instead.
|
73
|
+
#
|
74
|
+
# +signature+::
|
75
|
+
# Can either be a signature object (determined via the /Type key), a signature field or
|
76
|
+
# +nil+. Providing a signature object or signature field provides for more control, e.g.:
|
77
|
+
#
|
78
|
+
# * Setting values for optional signature object fields like /Reason and /Location.
|
79
|
+
# * (In)directly specifying which signature field should be used.
|
80
|
+
#
|
81
|
+
# If a signature object is provided and it is not associated with an AcroForm signature
|
82
|
+
# field, a new signature field is created and added to the main AcroForm object, creating
|
83
|
+
# that if necessary.
|
84
|
+
#
|
85
|
+
# If a signature field is provided and it already has a signature object as field value,
|
86
|
+
# that signature object is discarded.
|
87
|
+
#
|
88
|
+
# If the signature field doesn't have a widget, a non-visible one is created on the first
|
89
|
+
# page.
|
90
|
+
#
|
91
|
+
# +handler+::
|
92
|
+
# The signing handler that provides the necessary methods for signing and adjusting the
|
93
|
+
# signature and signature field objects to one's liking, see #handler and DefaultHandler.
|
94
|
+
#
|
95
|
+
# +write_options+::
|
96
|
+
# The key-value pairs of this hash will be passed on to the HexaPDF::Document#write
|
97
|
+
# method. Note that +incremental+ will be automatically set to ensure proper behaviour.
|
98
|
+
#
|
99
|
+
# The used signature object will have the following default values set:
|
100
|
+
#
|
101
|
+
# /Filter:: /Adobe.PPKLite
|
102
|
+
# /SubFilter:: /adbe.pkcs7.detached
|
103
|
+
# /M:: The current time.
|
104
|
+
#
|
105
|
+
# These values can be overridden in the #finalize_objects method of the signature handler.
|
106
|
+
def add(file_or_io, handler, signature: nil, write_options: {})
|
107
|
+
if signature && signature.type != :Sig
|
108
|
+
signature_field = signature
|
109
|
+
signature = signature_field.field_value
|
110
|
+
end
|
111
|
+
signature ||= @document.add({Type: :Sig})
|
112
|
+
|
113
|
+
# Prepare AcroForm
|
114
|
+
form = @document.acro_form(create: true)
|
115
|
+
form.signature_flag(:signatures_exist, :append_only)
|
116
|
+
|
117
|
+
# Prepare signature field
|
118
|
+
signature_field ||= form.each_field.find {|field| field.field_value == signature } ||
|
119
|
+
form.create_signature_field(generate_field_name)
|
120
|
+
signature_field.field_value = signature
|
121
|
+
|
122
|
+
if signature_field.each_widget.to_a.empty?
|
123
|
+
signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
|
124
|
+
end
|
125
|
+
|
126
|
+
# Prepare signature object
|
127
|
+
handler.finalize_objects(signature_field, signature)
|
128
|
+
signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
|
129
|
+
signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
|
130
|
+
|
131
|
+
io = if file_or_io.kind_of?(String)
|
132
|
+
File.open(file_or_io, 'wb+')
|
133
|
+
else
|
134
|
+
file_or_io
|
135
|
+
end
|
136
|
+
|
137
|
+
# Save the current state so that we can determine the correct /ByteRange value and set the
|
138
|
+
# values
|
139
|
+
start_xref, section = @document.write(io, incremental: true, **write_options)
|
140
|
+
signature_offset, signature_length = Signing.locate_signature_dict(section, start_xref,
|
141
|
+
signature.oid)
|
142
|
+
io.pos = signature_offset
|
143
|
+
signature_data = io.read(signature_length)
|
144
|
+
|
145
|
+
io.seek(0, IO::SEEK_END)
|
146
|
+
file_size = io.pos
|
147
|
+
|
148
|
+
# Calculate the offsets for the /ByteRange
|
149
|
+
contents_offset = signature_offset + signature_data.index('Contents(') + 8
|
150
|
+
offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
|
151
|
+
length2 = file_size - offset2
|
152
|
+
signature[:ByteRange] = [0, contents_offset, offset2, length2]
|
153
|
+
|
154
|
+
# Set the correct /ByteRange value
|
155
|
+
signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
|
156
|
+
length = match.size
|
157
|
+
result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
|
158
|
+
result.ljust(length)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Now everything besides the /Contents value is correct, so we can read the contents for
|
162
|
+
# signing
|
163
|
+
io.pos = signature_offset
|
164
|
+
io.write(signature_data)
|
165
|
+
signature[:Contents] = handler.sign(io, signature[:ByteRange].value)
|
166
|
+
|
167
|
+
# And now replace the /Contents value
|
168
|
+
Signing.replace_signature_contents(signature_data, signature[:Contents])
|
169
|
+
io.pos = signature_offset
|
170
|
+
io.write(signature_data)
|
171
|
+
|
172
|
+
signature
|
173
|
+
ensure
|
174
|
+
io.close if io && io != file_or_io
|
175
|
+
end
|
176
|
+
|
177
|
+
# :call-seq:
|
178
|
+
# signatures.each {|signature| block } -> signatures
|
179
|
+
# signatures.each -> Enumerator
|
180
|
+
#
|
181
|
+
# Iterates over all signatures in the order they are found.
|
182
|
+
def each
|
183
|
+
return to_enum(__method__) unless block_given?
|
184
|
+
|
185
|
+
return [] unless (form = @document.acro_form)
|
186
|
+
form.each_field do |field|
|
187
|
+
yield(field.field_value) if field.field_type == :Sig && field.field_value
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns the number of signatures in the PDF document. May be zero if the document has no
|
192
|
+
# signatures.
|
193
|
+
def count
|
194
|
+
each.to_a.size
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
# Generates a field name for a signature field.
|
200
|
+
def generate_field_name
|
201
|
+
index = (@document.acro_form.each_field.
|
202
|
+
map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
|
203
|
+
max || 0) + 1
|
204
|
+
"Signature#{index}"
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2022 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'openssl'
|
38
|
+
require 'hexapdf/error'
|
39
|
+
require 'hexapdf/version'
|
40
|
+
require 'stringio'
|
41
|
+
|
42
|
+
module HexaPDF
|
43
|
+
module DigitalSignature
|
44
|
+
module Signing
|
45
|
+
|
46
|
+
# This is the default signing handler which provides the ability to sign a document with the
|
47
|
+
# adbe.pkcs7.detached or ETSI.CAdES.detached algorithms. It is registered under the :default
|
48
|
+
# name.
|
49
|
+
#
|
50
|
+
# == Usage
|
51
|
+
#
|
52
|
+
# The signing handler is used by default by all methods that need a signing handler. Therefore
|
53
|
+
# it is usually only necessary to provide the actual attribute values.
|
54
|
+
#
|
55
|
+
# *Note*: Currently only RSA is supported, DSA and ECDSA are not. See the examples below for
|
56
|
+
# how to handle them using external signing.
|
57
|
+
#
|
58
|
+
#
|
59
|
+
# == CMS and PAdES Signatures
|
60
|
+
#
|
61
|
+
# The handler supports the older standard of CMS signatures as well as the newer PAdES
|
62
|
+
# signatures specified in PDF 2.0. By default, CMS signatures are created but this can be
|
63
|
+
# changed by setting #signature_type to :pades.
|
64
|
+
#
|
65
|
+
# When creating PAdES signatures the following two PAdES baseline signatures are supported:
|
66
|
+
# B-B and B-T. The difference between those two is that a timestamp handler was defined for
|
67
|
+
# B-T compatibility.
|
68
|
+
#
|
69
|
+
#
|
70
|
+
# == Signing Modes - Internal, External, External/Asynchronous
|
71
|
+
#
|
72
|
+
# This handler provides two ways to create the CMS signed-data structure required by
|
73
|
+
# Signatures#add:
|
74
|
+
#
|
75
|
+
# * By providing the signing certificate together with the signing key and the certificate
|
76
|
+
# chain, HexaPDF itself does the signing *internally*. It is the preferred way if all the
|
77
|
+
# needed information is available.
|
78
|
+
#
|
79
|
+
# Assign the respective data to the #certificate, #key and #certificate_chain attributes.
|
80
|
+
#
|
81
|
+
# * By using an *external signing mechanism*, a callable object assigned to #external_signing.
|
82
|
+
# Here the actual signing happens "outside" of HexaPDF, for example, in custom code or even
|
83
|
+
# asynchronously. This is needed in case the signing key is not directly available but only
|
84
|
+
# an interface to it (e.g. when dealing with a HSM).
|
85
|
+
#
|
86
|
+
# Depending on whether #certificate is set the signing happens differently:
|
87
|
+
#
|
88
|
+
# * If #certificate is not set, the callable object is used instead of #sign, so it needs to
|
89
|
+
# accept the same arguments as #sign and needs to return a complete, DER-serialized CMS
|
90
|
+
# signed data object.
|
91
|
+
#
|
92
|
+
# * If #certificate is set, the CMS signed data object is created by HexaPDF. The
|
93
|
+
# callable #external_signing object is called with the used digest algorithm and the
|
94
|
+
# already digested data which needs to be signed (but *not* digested) and the signature
|
95
|
+
# returned.
|
96
|
+
#
|
97
|
+
# If the signing process needs to be *asynchronous*, make sure to set the #signature_size
|
98
|
+
# appropriately, return an empty string during signing and later use
|
99
|
+
# Signatures.embed_signature to embed the actual signature.
|
100
|
+
#
|
101
|
+
#
|
102
|
+
# == Optional Data
|
103
|
+
#
|
104
|
+
# Besides the required data, some optional attributes can also be specified:
|
105
|
+
#
|
106
|
+
# * Reason, location and contact information
|
107
|
+
# * Making the signature a certification signature by applying the DocMDP transform method and
|
108
|
+
# a DoCMDP permission
|
109
|
+
#
|
110
|
+
#
|
111
|
+
# == Examples
|
112
|
+
#
|
113
|
+
# # Signing using certificate + key
|
114
|
+
# document.sign("output.pdf", certificate: my_cert, key: my_key,
|
115
|
+
# certificate_chain: my_chain)
|
116
|
+
#
|
117
|
+
# # Signing using an external mechanism without certificate set
|
118
|
+
# signing_proc = lambda do |io, byte_range|
|
119
|
+
# io.pos = byte_range[0]
|
120
|
+
# data = io.read(byte_range[1])
|
121
|
+
# io.pos = byte_range[2]
|
122
|
+
# data << io.read(byte_range[3])
|
123
|
+
# signing_service.pkcs7_sign(data).to_der
|
124
|
+
# end
|
125
|
+
# document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
|
126
|
+
#
|
127
|
+
# # Signing using external mechanism with certificate set
|
128
|
+
# signing_proc = lambda do |digest_method, hash|
|
129
|
+
# signing_service.sign_raw(digest_method, hash)
|
130
|
+
# end
|
131
|
+
# document.sign("output.pdf", certificate: my_cert, certificate_chain: my_chain,
|
132
|
+
# external_signing: signing_proc)
|
133
|
+
#
|
134
|
+
# # Signing with DSA or ECDSA certificate/keys
|
135
|
+
# signing_proc = lambda do |io, byte_range|
|
136
|
+
# io.pos = byte_range[0]
|
137
|
+
# data = io.read(byte_range[1])
|
138
|
+
# io.pos = byte_range[2]
|
139
|
+
# data << io.read(byte_range[3])
|
140
|
+
# OpenSSL::PKCS7.sign(certificate, key, data, certificate_chain,
|
141
|
+
# OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
|
142
|
+
# end
|
143
|
+
# document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
|
144
|
+
#
|
145
|
+
#
|
146
|
+
# == Implementing a Signing Handler
|
147
|
+
#
|
148
|
+
# This class also serves as an example on how to create a custom handler: The public methods
|
149
|
+
# #signature_size, #finalize_objects and #sign are used by the digital signature algorithm.
|
150
|
+
# See their descriptions for details.
|
151
|
+
#
|
152
|
+
# Once a custom signing handler has been created, it can be registered under the
|
153
|
+
# 'signature.signing_handler' configuration option for easy use. It has to take keyword
|
154
|
+
# arguments in its initialize method to be compatible with the Signatures#handler method.
|
155
|
+
class DefaultHandler
|
156
|
+
|
157
|
+
# The certificate with which to sign the PDF.
|
158
|
+
#
|
159
|
+
# If the certificate is provided, HexaPDF creates the signature object. Otherwise the
|
160
|
+
# #external_signing callable object has to create it.
|
161
|
+
attr_accessor :certificate
|
162
|
+
|
163
|
+
# The private key for the #certificate.
|
164
|
+
#
|
165
|
+
# If the key is provided, HexaPDF does the signing. Otherwise the #external_signing callable
|
166
|
+
# object has to sign the data.
|
167
|
+
attr_accessor :key
|
168
|
+
|
169
|
+
# The certificate chain that should be embedded in the PDF; usually contains all
|
170
|
+
# certificates up to the root certificate.
|
171
|
+
attr_accessor :certificate_chain
|
172
|
+
|
173
|
+
# The digest algorithm that should be used when creating the signature.
|
174
|
+
#
|
175
|
+
# See SignedDataCreator#digest_algorithm for the default value (if nothing is set) and for
|
176
|
+
# the allowed values.
|
177
|
+
attr_accessor :digest_algorithm
|
178
|
+
|
179
|
+
# The timestamp handler that should be used for timestamping the signature.
|
180
|
+
#
|
181
|
+
# If this attribute is set, a timestamp token is embedded into the CMS object.
|
182
|
+
attr_accessor :timestamp_handler
|
183
|
+
|
184
|
+
# A callable object for custom signing mechanisms.
|
185
|
+
#
|
186
|
+
# The callable object has two different uses depending on whether #certificate is set:
|
187
|
+
#
|
188
|
+
# * If #certificate is not set, it fulfills the same role as the #sign method and needs to
|
189
|
+
# conform to that interface.
|
190
|
+
#
|
191
|
+
# * If #certificate is set and #key is not, it is just used for signing. Here it needs to
|
192
|
+
# accept the used digest algorithm and the already digested data as arguments and return
|
193
|
+
# the signature.
|
194
|
+
attr_accessor :external_signing
|
195
|
+
|
196
|
+
# The reason for signing. If used, will be set on the signature object.
|
197
|
+
attr_accessor :reason
|
198
|
+
|
199
|
+
# The signing location. If used, will be set on the signature object.
|
200
|
+
attr_accessor :location
|
201
|
+
|
202
|
+
# The contact information. If used, will be set on the signature object.
|
203
|
+
attr_accessor :contact_info
|
204
|
+
|
205
|
+
# The size of the serialized signature that should be reserved.
|
206
|
+
#
|
207
|
+
# If this attribute has not been set, an empty string will be signed using #sign to
|
208
|
+
# determine the signature size.
|
209
|
+
#
|
210
|
+
# The size needs to be at least as big as the final signature, otherwise signing results in
|
211
|
+
# an error.
|
212
|
+
attr_writer :signature_size
|
213
|
+
|
214
|
+
# The type of signature to be written (i.e. the value of the /SubFilter key).
|
215
|
+
#
|
216
|
+
# The value can either be :cms (the default; uses a detached CMS signature) or :pades
|
217
|
+
# (uses an ETSI CAdES compatible signature).
|
218
|
+
attr_accessor :signature_type
|
219
|
+
|
220
|
+
# The DocMDP permissions that should be set on the document.
|
221
|
+
#
|
222
|
+
# See #doc_mdp_permissions=
|
223
|
+
attr_reader :doc_mdp_permissions
|
224
|
+
|
225
|
+
# Creates a new DefaultHandler instance with the given attributes.
|
226
|
+
def initialize(**arguments)
|
227
|
+
@signature_size = nil
|
228
|
+
@signature_type = :cms
|
229
|
+
arguments.each {|name, value| send("#{name}=", value) }
|
230
|
+
end
|
231
|
+
|
232
|
+
# Sets the DocMDP permissions that should be applied to the document.
|
233
|
+
#
|
234
|
+
# Valid values for +permissions+ are:
|
235
|
+
#
|
236
|
+
# +nil+::
|
237
|
+
# Don't set any DocMDP permissions (default).
|
238
|
+
#
|
239
|
+
# +:no_changes+ or 1::
|
240
|
+
# No changes whatsoever are allowed.
|
241
|
+
#
|
242
|
+
# +:form_filling+ or 2::
|
243
|
+
# Only filling in forms and signing are allowed.
|
244
|
+
#
|
245
|
+
# +:form_filling_and_annotations+ or 3::
|
246
|
+
# Only filling in forms, signing and annotation creation/deletion/modification are
|
247
|
+
# allowed.
|
248
|
+
def doc_mdp_permissions=(permissions)
|
249
|
+
case permissions
|
250
|
+
when :no_changes, 1 then @doc_mdp_permissions = 1
|
251
|
+
when :form_filling, 2 then @doc_mdp_permissions = 2
|
252
|
+
when :form_filling_and_annotations, 3 then @doc_mdp_permissions = 3
|
253
|
+
when nil then @doc_mdp_permissions = nil
|
254
|
+
else
|
255
|
+
raise ArgumentError, "Invalid permissions value '#{permissions.inspect}'"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns the size of the serialized signature that should be reserved.
|
260
|
+
#
|
261
|
+
# If a custom size is set using #signature_size=, it used. Otherwise the size is determined
|
262
|
+
# by using #sign to sign an empty string.
|
263
|
+
def signature_size
|
264
|
+
@signature_size || sign(StringIO.new, [0, 0, 0, 0]).size
|
265
|
+
end
|
266
|
+
|
267
|
+
# Finalizes the signature field as well as the signature dictionary before writing.
|
268
|
+
def finalize_objects(_signature_field, signature)
|
269
|
+
signature[:Filter] = :'Adobe.PPKLite'
|
270
|
+
signature[:SubFilter] = (signature_type == :pades ? :'ETSI.CAdES.detached' : :'adbe.pkcs7.detached')
|
271
|
+
signature[:M] = Time.now
|
272
|
+
signature[:Reason] = reason if reason
|
273
|
+
signature[:Location] = location if location
|
274
|
+
signature[:ContactInfo] = contact_info if contact_info
|
275
|
+
signature[:Prop_Build] = {App: {Name: :HexaPDF, REx: HexaPDF::VERSION}}
|
276
|
+
|
277
|
+
if doc_mdp_permissions
|
278
|
+
doc = signature.document
|
279
|
+
if doc.signatures.count > 1
|
280
|
+
raise HexaPDF::Error, "Can set DocMDP access permissions only on first signature"
|
281
|
+
end
|
282
|
+
params = doc.add({Type: :TransformParams, V: :'1.2', P: doc_mdp_permissions})
|
283
|
+
sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, DigestMethod: :SHA256,
|
284
|
+
TransformParams: params})
|
285
|
+
signature[:Reference] = [sigref]
|
286
|
+
(doc.catalog[:Perms] ||= {})[:DocMDP] = signature
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Returns the DER serialized CMS signed data object containing the signature for the given
|
291
|
+
# IO byte ranges.
|
292
|
+
#
|
293
|
+
# The +byte_range+ argument is an array containing four numbers [offset1, length1, offset2,
|
294
|
+
# length2]. The offset numbers are byte positions in the +io+ argument and the to-be-signed
|
295
|
+
# data can be determined by reading length bytes at the offsets.
|
296
|
+
def sign(io, byte_range)
|
297
|
+
if certificate
|
298
|
+
io.pos = byte_range[0]
|
299
|
+
data = io.read(byte_range[1])
|
300
|
+
io.pos = byte_range[2]
|
301
|
+
data << io.read(byte_range[3])
|
302
|
+
SignedDataCreator.create(data,
|
303
|
+
type: signature_type,
|
304
|
+
certificate: certificate, key: key,
|
305
|
+
digest_algorithm: digest_algorithm,
|
306
|
+
timestamp_handler: timestamp_handler,
|
307
|
+
certificates: certificate_chain, &external_signing).to_der
|
308
|
+
else
|
309
|
+
external_signing.call(io, byte_range)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|