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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/configuration.rb +12 -12
  5. data/lib/hexapdf/dictionary_fields.rb +6 -2
  6. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  7. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  8. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  9. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  10. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  11. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  12. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  13. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  14. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  15. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  16. data/lib/hexapdf/digital_signature.rb +56 -0
  17. data/lib/hexapdf/document.rb +21 -14
  18. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  19. data/lib/hexapdf/type.rb +0 -1
  20. data/lib/hexapdf/version.rb +1 -1
  21. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  22. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  23. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  24. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  25. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  26. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  27. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  28. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  29. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  30. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  31. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  32. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  33. data/test/hexapdf/test_document.rb +1 -1
  34. data/test/hexapdf/test_writer.rb +3 -3
  35. metadata +25 -15
  36. data/lib/hexapdf/document/signatures.rb +0 -546
  37. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  38. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  39. data/lib/hexapdf/type/signature/handler.rb +0 -140
  40. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -1,95 +0,0 @@
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/type/signature'
39
-
40
- module HexaPDF
41
- module Type
42
- class Signature
43
-
44
- # The signature handler for the adbe.x509.rsa_sha1 sub-filter.
45
- #
46
- # Since this handler is deprecated with PDF 2.0 it only provides the implementation for
47
- # reading and verifying signatures.
48
- class AdbeX509RsaSha1 < Handler
49
-
50
- # Returns the certificate chain.
51
- def certificate_chain
52
- return [] unless signature_dict.key?(:Cert)
53
- [signature_dict[:Cert]].flatten.map {|str| OpenSSL::X509::Certificate.new(str) }
54
- end
55
-
56
- # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
57
- def signer_certificate
58
- certificate_chain.first
59
- end
60
-
61
- # Verifies the signature using the provided OpenSSL::X509::Store object.
62
- def verify(store, allow_self_signed: false)
63
- result = super
64
-
65
- signer_certificate = self.signer_certificate
66
- certificate_chain = self.certificate_chain
67
-
68
- if certificate_chain.empty?
69
- result.log(:error, "No certificates for verification found")
70
- return result
71
- end
72
-
73
- signature = OpenSSL::ASN1.decode(signature_dict.contents)
74
- if signature.tag != OpenSSL::ASN1::OCTET_STRING
75
- result.log(:error, "PKCS1 signature object invalid, octet string expected")
76
- return result
77
- end
78
-
79
- store.verify(signer_certificate, certificate_chain)
80
-
81
- if signer_certificate.public_key.verify(OpenSSL::Digest.new('SHA1'),
82
- signature.value, signature_dict.signed_data)
83
- result.log(:info, "Signature valid")
84
- else
85
- result.log(:error, "Signature verification failed")
86
- end
87
-
88
- result
89
- end
90
-
91
- end
92
-
93
- end
94
- end
95
- end
@@ -1,140 +0,0 @@
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 'hexapdf/type/signature'
38
-
39
- module HexaPDF
40
- module Type
41
- class Signature
42
-
43
- # The base signature handler providing common functionality.
44
- #
45
- # Specific signature handler need to override methods if necessary and implement the needed
46
- # ones that don't have a default implementation.
47
- class Handler
48
-
49
- # The signature dictionary used by the handler.
50
- attr_reader :signature_dict
51
-
52
- # Creates a new signature handler for the given signature dictionary.
53
- def initialize(signature_dict)
54
- @signature_dict = signature_dict
55
- end
56
-
57
- # Returns the common name of the signer (/Name field of the signature dictionary).
58
- def signer_name
59
- @signature_dict[:Name]
60
- end
61
-
62
- # Returns the time of signing (/M field of the signature dictionary).
63
- def signing_time
64
- @signature_dict[:M]
65
- end
66
-
67
- # Returns the certificate chain.
68
- #
69
- # Needs to be implemented by specific handlers.
70
- def certificate_chain
71
- raise "Needs to be implemented by specific handlers"
72
- end
73
-
74
- # Returns the certificate used for signing.
75
- #
76
- # Needs to be implemented by specific handlers.
77
- def signer_certificate
78
- raise "Needs to be implemented by specific handlers"
79
- end
80
-
81
- # Verifies general signature properties and prepares the provided OpenSSL::X509::Store
82
- # object for use by concrete implementations.
83
- #
84
- # Needs to be called by specific handlers.
85
- def verify(store, allow_self_signed: false)
86
- result = VerificationResult.new
87
- check_certified_signature(result)
88
- verify_signing_time(result)
89
- store.verify_callback =
90
- store_verification_callback(result, allow_self_signed: allow_self_signed)
91
- result
92
- end
93
-
94
- protected
95
-
96
- # Verifies that the signing time was within the validity period of the signer certificate.
97
- def verify_signing_time(result)
98
- time = signing_time
99
- cert = signer_certificate
100
- if time && cert && (time < cert.not_before || time > cert.not_after)
101
- result.log(:error, "Signer certificate not valid at signing time")
102
- end
103
- end
104
-
105
- DOCMDP_PERMS_MESSAGE_MAP = { # :nodoc:
106
- 1 => "No changes allowed",
107
- 2 => "Form filling and signing allowed",
108
- 3 => "Form filling, signing and annotation manipulation allowed",
109
- }
110
-
111
- # Sets an informational message on +result+ whether the signature is a certified signature.
112
- def check_certified_signature(result)
113
- sigref = signature_dict[:Reference]&.find {|ref| ref[:TransformMethod] == :DocMDP }
114
- if sigref && signature_dict.document.catalog[:Perms]&.[](:DocMDP) == signature_dict
115
- perms = sigref[:TransformParams]&.[](:P) || 2
116
- result.log(:info, "Certified signature (#{DOCMDP_PERMS_MESSAGE_MAP[perms]})")
117
- end
118
- end
119
-
120
- # Returns the block that should be used as the OpenSSL::X509::Store verification callback.
121
- #
122
- # +result+:: The VerificationResult object that should be updated if problems are found.
123
- #
124
- # +allow_self_signed+:: Specifies whether self-signed certificates are allowed.
125
- def store_verification_callback(result, allow_self_signed: false)
126
- lambda do |_success, context|
127
- if context.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
128
- context.error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
129
- result.log(allow_self_signed ? :info : :error, "Self-signed certificate found")
130
- end
131
-
132
- true
133
- end
134
- end
135
-
136
- end
137
-
138
- end
139
- end
140
- end
@@ -1,352 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- require 'test_helper'
4
- require 'stringio'
5
- require 'tempfile'
6
- require 'hexapdf/document'
7
- require_relative '../type/signature/common'
8
-
9
- describe HexaPDF::Document::Signatures do
10
- before do
11
- @doc = HexaPDF::Document.new
12
- @form = @doc.acro_form(create: true)
13
- @sig1 = @form.create_signature_field("test1")
14
- @sig2 = @form.create_signature_field("test2")
15
- @handler = HexaPDF::Document::Signatures::DefaultHandler.new(
16
- certificate: CERTIFICATES.signer_certificate,
17
- key: CERTIFICATES.signer_key,
18
- certificate_chain: [CERTIFICATES.ca_certificate]
19
- )
20
- end
21
-
22
- it "allows embedding an external signature value" do
23
- doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
24
- io = StringIO.new(''.b)
25
- doc.signatures.add(io, @handler)
26
- doc = HexaPDF::Document.new(io: io)
27
- io = StringIO.new(''.b)
28
-
29
- byte_range = nil
30
- @handler.signature_size = 5000
31
- @handler.external_signing = proc {|_, br| byte_range = br; "" }
32
- doc.signatures.add(io, @handler)
33
-
34
- io.pos = byte_range[0]
35
- data = io.read(byte_range[1])
36
- io.pos = byte_range[2]
37
- data << io.read(byte_range[3])
38
- contents = OpenSSL::PKCS7.sign(@handler.certificate, @handler.key, data, @handler.certificate_chain,
39
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
40
- HexaPDF::Document::Signatures.embed_signature(io, contents)
41
- doc = HexaPDF::Document.new(io: io)
42
- assert_equal(2, doc.signatures.each.count)
43
- doc.signatures.each do |signature|
44
- assert(signature.verify(allow_self_signed: true).messages.find {|m| m.content == 'Signature valid' })
45
- end
46
- end
47
-
48
- describe "DefaultHandler" do
49
- it "returns the size of serialized signature" do
50
- assert_equal(1310, @handler.signature_size)
51
- @handler.signature_size = 100
52
- assert_equal(100, @handler.signature_size)
53
- end
54
-
55
- it "allows setting the DocMDP permissions" do
56
- assert_nil(@handler.doc_mdp_permissions)
57
-
58
- @handler.doc_mdp_permissions = :no_changes
59
- assert_equal(1, @handler.doc_mdp_permissions)
60
- @handler.doc_mdp_permissions = 1
61
- assert_equal(1, @handler.doc_mdp_permissions)
62
-
63
- @handler.doc_mdp_permissions = :form_filling
64
- assert_equal(2, @handler.doc_mdp_permissions)
65
- @handler.doc_mdp_permissions = 2
66
- assert_equal(2, @handler.doc_mdp_permissions)
67
-
68
- @handler.doc_mdp_permissions = :form_filling_and_annotations
69
- assert_equal(3, @handler.doc_mdp_permissions)
70
- @handler.doc_mdp_permissions = 3
71
- assert_equal(3, @handler.doc_mdp_permissions)
72
-
73
- @handler.doc_mdp_permissions = nil
74
- assert_nil(@handler.doc_mdp_permissions)
75
-
76
- assert_raises(ArgumentError) { @handler.doc_mdp_permissions = :other }
77
- end
78
-
79
- describe "sign" do
80
- it "can sign the data using PKCS7" do
81
- data = StringIO.new("data")
82
- store = OpenSSL::X509::Store.new
83
- store.add_cert(CERTIFICATES.ca_certificate)
84
-
85
- pkcs7 = OpenSSL::PKCS7.new(@handler.sign(data, [0, 4, 0, 0]))
86
- assert(pkcs7.detached?)
87
- assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
88
- pkcs7.certificates)
89
- assert(pkcs7.verify([], store, data.string, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY))
90
- end
91
-
92
- it "can use external signing" do
93
- @handler.external_signing = proc { "hallo" }
94
- assert_equal("hallo", @handler.sign(StringIO.new, [0, 0, 0, 0]))
95
- end
96
- end
97
-
98
- describe "finalize_objects" do
99
- before do
100
- @field = @doc.wrap({})
101
- @obj = @doc.wrap({})
102
- end
103
-
104
- it "does nothing if no finalization tasks need to be done" do
105
- @handler.finalize_objects(@field, @obj)
106
- assert(@field.empty?)
107
- assert(@obj.empty?)
108
- end
109
-
110
- it "adjust the /SubFilter if signature type is etsi" do
111
- @handler.signature_type = :etsi
112
- @handler.finalize_objects(@field, @obj)
113
- assert_equal(:'ETSI.CAdES.detached', @obj[:SubFilter])
114
- end
115
-
116
- it "sets the reason, location and contact info fields" do
117
- @handler.reason = 'Reason'
118
- @handler.location = 'Location'
119
- @handler.contact_info = 'Contact'
120
- @handler.finalize_objects(@field, @obj)
121
- assert(@field.empty?)
122
- assert_equal({Reason: 'Reason', Location: 'Location', ContactInfo: 'Contact'}, @obj.value)
123
- end
124
-
125
- it "applies the specified DocMDP permissions" do
126
- @handler.doc_mdp_permissions = :no_changes
127
- @handler.finalize_objects(@field, @obj)
128
- ref = @obj[:Reference][0]
129
- assert_equal(:DocMDP, ref[:TransformMethod])
130
- assert_equal(:SHA1, ref[:DigestMethod])
131
- assert_equal(1, ref[:TransformParams][:P])
132
- assert_equal(:'1.2', ref[:TransformParams][:V])
133
- assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
134
- end
135
-
136
- it "fails if DocMDP should be set but there is already a signature" do
137
- @handler.doc_mdp_permissions = :no_changes
138
- 2.times do
139
- field = @doc.acro_form(create: true).create_signature_field('test')
140
- field.field_value = :something
141
- end
142
- assert_raises(HexaPDF::Error) { @handler.finalize_objects(@field, @obj) }
143
- end
144
- end
145
- end
146
-
147
- describe "TimestampHandler" do
148
- before do
149
- @handler = HexaPDF::Document::Signatures::TimestampHandler.new
150
- end
151
-
152
- it "allows setting the attributes in the constructor" do
153
- handler = HexaPDF::Document::Signatures::TimestampHandler.new(
154
- tsa_url: "url", tsa_hash_algorithm: "MD5", tsa_policy_id: "5",
155
- reason: "Reason", location: "Location", contact_info: "Contact",
156
- signature_size: 1_000
157
- )
158
- assert_equal("url", handler.tsa_url)
159
- assert_equal("MD5", handler.tsa_hash_algorithm)
160
- assert_equal("5", handler.tsa_policy_id)
161
- assert_equal("Reason", handler.reason)
162
- assert_equal("Location", handler.location)
163
- assert_equal("Contact", handler.contact_info)
164
- assert_equal(1_000, handler.signature_size)
165
- end
166
-
167
- it "finalizes the signature field and signature objects" do
168
- @field = @doc.wrap({})
169
- @sig = @doc.wrap({})
170
- @handler.reason = 'Reason'
171
- @handler.location = 'Location'
172
- @handler.contact_info = 'Contact'
173
-
174
- @handler.finalize_objects(@field, @sig)
175
- assert_equal('2.0', @doc.version)
176
- assert_equal(:DocTimeStamp, @sig[:Type])
177
- assert_equal(:'ETSI.RFC3161', @sig[:SubFilter])
178
- assert_equal('Reason', @sig[:Reason])
179
- assert_equal('Location', @sig[:Location])
180
- assert_equal('Contact', @sig[:ContactInfo])
181
- end
182
-
183
- it "returns the size of serialized signature" do
184
- @handler.tsa_url = "http://127.0.0.1:34567"
185
- CERTIFICATES.start_tsa_server
186
- assert_equal(1420, @handler.signature_size)
187
- end
188
-
189
- describe "sign" do
190
- before do
191
- @data = StringIO.new("data")
192
- @range = [0, 4, 0, 0]
193
- @handler.tsa_url = "http://127.0.0.1:34567"
194
- CERTIFICATES.start_tsa_server
195
- end
196
-
197
- it "respects the set hash algorithm and policy id" do
198
- @handler.tsa_hash_algorithm = 'SHA256'
199
- @handler.tsa_policy_id = '1.2.3.4.2'
200
- token = OpenSSL::ASN1.decode(@handler.sign(@data, @range))
201
- content = OpenSSL::ASN1.decode(token.value[1].value[0].value[2].value[1].value[0].value)
202
- policy_id = content.value[1].value
203
- digest_algorithm = content.value[2].value[0].value[0].value
204
- assert_equal('SHA256', digest_algorithm)
205
- assert_equal("1.2.3.4.2", policy_id)
206
- end
207
-
208
- it "returns the serialized timestamp token" do
209
- token = OpenSSL::PKCS7.new(@handler.sign(@data, @range))
210
- assert_equal(CERTIFICATES.ca_certificate.subject, token.signers[0].issuer)
211
- assert_equal(CERTIFICATES.timestamp_certificate.serial, token.signers[0].serial)
212
- end
213
-
214
- it "fails if the timestamp token could not be created" do
215
- @handler.tsa_hash_algorithm = 'SHA1'
216
- msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
217
- assert_match(/BAD_ALG/, msg.message)
218
- end
219
-
220
- it "fails if the timestamp server couldn't process the request" do
221
- @handler.tsa_policy_id = '1.2.3.4.1'
222
- msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
223
- assert_match(/Invalid TSA server response/, msg.message)
224
- end
225
- end
226
- end
227
-
228
- it "iterates over all signature dictionaries" do
229
- assert_equal([], @doc.signatures.to_a)
230
- @sig1.field_value = :sig1
231
- @sig2.field_value = :sig2
232
- assert_equal([:sig1, :sig2], @doc.signatures.to_a)
233
- end
234
-
235
- it "returns the number of signature dictionaries" do
236
- @sig1.field_value = :sig1
237
- assert_equal(1, @doc.signatures.count)
238
- end
239
-
240
- describe "handler" do
241
- it "return the initialized handler" do
242
- handler = @doc.signatures.handler(certificate: 'cert', reason: 'reason')
243
- assert_equal('cert', handler.certificate)
244
- assert_equal('reason', handler.reason)
245
- end
246
-
247
- it "fails if the given task is not available" do
248
- assert_raises(HexaPDF::Error) { @doc.signatures.handler(name: :unknown) }
249
- end
250
- end
251
-
252
- describe "add" do
253
- before do
254
- @doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
255
- @io = StringIO.new(''.b)
256
- end
257
-
258
- it "uses the provided signature dictionary" do
259
- sig = @doc.add({Type: :Sig, Key: :value})
260
- @doc.signatures.add(@io, @handler, signature: sig)
261
- assert_equal(1, @doc.signatures.to_a.compact.size)
262
- assert_equal(:value, @doc.signatures.to_a[0][:Key])
263
- refute_equal(:value, @doc.acro_form.each_field.first[:Key])
264
- end
265
-
266
- it "creates the signature dictionary if none is provided" do
267
- @doc.signatures.add(@io, @handler)
268
- assert_equal(1, @doc.signatures.to_a.compact.size)
269
- refute(@doc.acro_form.each_field.first.key?(:Contents))
270
- end
271
-
272
- it "sets the needed information on the signature dictionary" do
273
- def @handler.finalize_objects(sigfield, sig)
274
- sig[:key] = :sig
275
- sigfield[:key] = :sig_field
276
- end
277
- @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
278
- sig = @doc.signatures.first
279
- assert_equal(:'Adobe.PPKLite', sig[:Filter])
280
- assert_equal(:'adbe.pkcs7.detached', sig[:SubFilter])
281
- assert_equal([0, 996, 3618, 2501], sig[:ByteRange].value)
282
- assert_equal(:sig, sig[:key])
283
- assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
284
- assert(sig.key?(:Contents))
285
- assert(sig.key?(:M))
286
- end
287
-
288
- it "creates the main form dictionary if necessary" do
289
- @doc.signatures.add(@io, @handler)
290
- assert(@doc.acro_form)
291
- assert_equal([:signatures_exist, :append_only], @doc.acro_form.signature_flags)
292
- end
293
-
294
- it "uses the provided signature field" do
295
- field = @doc.acro_form(create: true).create_signature_field('Signature2')
296
- @doc.signatures.add(@io, @handler, signature: field)
297
- assert_nil(@doc.acro_form.field_by_name("Signature3"))
298
- refute_nil(field.field_value)
299
- assert_nil(@doc.signatures.first[:T])
300
- end
301
-
302
- it "uses an existing signature field if possible" do
303
- field = @doc.acro_form(create: true).create_signature_field('Signature2')
304
- field.field_value = sig = @doc.add({Type: :Sig, key: :value})
305
- @doc.signatures.add(@io, @handler, signature: sig)
306
- assert_nil(@doc.acro_form.field_by_name("Signature3"))
307
- assert_same(sig, @doc.signatures.first)
308
- end
309
-
310
- it "creates the signature field if necessary" do
311
- @doc.acro_form(create: true).create_text_field('Signature2')
312
- @doc.signatures.add(@io, @handler)
313
- field = @doc.acro_form.field_by_name("Signature3")
314
- assert_equal(:Sig, field.field_type)
315
- refute_nil(field.field_value)
316
- assert_equal(1, field.each_widget.count)
317
- end
318
-
319
- it "handles different xref section types correctly when determing the offsets" do
320
- @doc.delete(7)
321
- sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
322
- assert_equal([0, 988, 3610, 2483], sig[:ByteRange].value)
323
- end
324
-
325
- it "works if the signature object is the last object of the xref section" do
326
- field = @doc.acro_form(create: true).create_signature_field('Signature2')
327
- field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
328
- sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
329
- assert_equal([0, 3095, 5717, 380], sig[:ByteRange].value)
330
- end
331
-
332
- it "allows writing to a file in addition to writing to an IO" do
333
- tempfile = Tempfile.new('hexapdf-signature')
334
- tempfile.close
335
- @doc.signatures.add(tempfile.path, @handler)
336
- doc = HexaPDF::Document.open(tempfile.path)
337
- assert(doc.signatures.first.verify(allow_self_signed: true).success?)
338
- end
339
-
340
- it "adds a new revision with the signature" do
341
- @doc.signatures.add(@io, @handler)
342
- signed_doc = HexaPDF::Document.new(io: @io)
343
- assert(signed_doc.signatures.first.verify)
344
- end
345
-
346
- it "fails if the reserved signature space is too small" do
347
- def @handler.signature_size; 200; end
348
- msg = assert_raises(HexaPDF::Error) { @doc.signatures.add(@io, @handler) }
349
- assert_match(/space.*too small.*200 vs/, msg.message)
350
- end
351
- end
352
- end