hexapdf 0.28.0 → 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -10
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/configuration.rb +12 -12
- data/lib/hexapdf/dictionary_fields.rb +13 -4
- 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/pages.rb +35 -18
- data/lib/hexapdf/document.rb +21 -14
- data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
- data/lib/hexapdf/type/font_simple.rb +14 -2
- 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/document/test_pages.rb +25 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
- data/test/hexapdf/test_dictionary_fields.rb +9 -3
- data/test/hexapdf/test_document.rb +1 -1
- data/test/hexapdf/test_writer.rb +6 -6
- data/test/hexapdf/type/test_font_simple.rb +18 -6
- 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
|