hexapdf 0.28.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -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 +13 -4
  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/pages.rb +35 -18
  18. data/lib/hexapdf/document.rb +21 -14
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  20. data/lib/hexapdf/type/font_simple.rb +14 -2
  21. data/lib/hexapdf/type.rb +0 -1
  22. data/lib/hexapdf/version.rb +1 -1
  23. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  24. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  25. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  26. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  27. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  28. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  29. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  30. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  31. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  32. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  33. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  34. data/test/hexapdf/document/test_pages.rb +25 -0
  35. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  36. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  37. data/test/hexapdf/test_document.rb +1 -1
  38. data/test/hexapdf/test_writer.rb +6 -6
  39. data/test/hexapdf/type/test_font_simple.rb +18 -6
  40. metadata +25 -15
  41. data/lib/hexapdf/document/signatures.rb +0 -546
  42. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  43. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  44. data/lib/hexapdf/type/signature/handler.rb +0 -140
  45. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -0,0 +1,56 @@
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
+ module HexaPDF
38
+
39
+ # PDF documents can be signed using digital signatures. Such a signature can be used to
40
+ # authenticate the identity of the signer and the contents of the documents.
41
+ #
42
+ # This module contains all code related to digital signatures in PDF.
43
+ #
44
+ # See: PDF1.7/2.0 s12.8
45
+ module DigitalSignature
46
+
47
+ autoload(:Signatures, 'hexapdf/digital_signature/signatures')
48
+ autoload(:Signature, "hexapdf/digital_signature/signature")
49
+ autoload(:Handler, 'hexapdf/digital_signature/handler')
50
+ autoload(:CMSHandler, "hexapdf/digital_signature/cms_handler")
51
+ autoload(:PKCS1Handler, "hexapdf/digital_signature/pkcs1_handler")
52
+ autoload(:VerificationResult, 'hexapdf/digital_signature/verification_result')
53
+ autoload(:Signing, 'hexapdf/digital_signature/signing')
54
+
55
+ end
56
+ end
@@ -75,28 +75,45 @@ module HexaPDF
75
75
  @document.catalog.pages
76
76
  end
77
77
 
78
- # :call-seq:
79
- # pages.add -> new_page
80
- # pages.add(media_box, orientation: :portrait) -> new_page
81
- # pages.add(page) -> page
78
+ # Creates a page object and returns it *without* adding it to the page tree.
79
+ #
80
+ # +media_box+::
81
+ # If this argument is +nil+/not specified, the value is taken from the configuration
82
+ # option 'page.default_media_box'.
83
+ #
84
+ # If the resulting value is an array with four numbers (specifying the media box), the new
85
+ # page will have these exact dimensions.
82
86
  #
83
- # Adds the page or a new empty page at the end and returns it.
87
+ # If the value is a symbol, it is taken as a reference to a pre-defined media box in
88
+ # HexaPDF::Type::Page::PAPER_SIZE. The +orientation+ can then be used to specify the page
89
+ # orientation.
84
90
  #
85
- # If no argument is given, a new page with the default dimensions (see configuration option
86
- # 'page.default_media_box') is used.
91
+ # +orientation+::
92
+ # If this argument is not specified, it is taken from 'page.default_media_orientation'. It
93
+ # is only used if +media_box+ is a symbol and not an array.
94
+ def create(media_box: nil, orientation: nil)
95
+ media_box ||= @document.config['page.default_media_box']
96
+ orientation ||= @document.config['page.default_media_orientation']
97
+ box = if media_box.kind_of?(Array)
98
+ media_box
99
+ else
100
+ Type::Page.media_box(media_box, orientation: orientation)
101
+ end
102
+ @document.add({Type: :Page, MediaBox: box})
103
+ end
104
+
105
+ # :call-seq:
106
+ # pages.add -> new_page
107
+ # pages.add(page) -> page
108
+ # pages.add(media_box, orientation: nil) -> new_page
87
109
  #
88
- # If the single argument is an array with four numbers (specifying the media box), the new
89
- # page will have these dimensions.
110
+ # Adds the given page or a new empty page at the end and returns it.
90
111
  #
91
- # If the single argument is a symbol, it is taken as referencing a pre-defined media box in
92
- # HexaPDF::Type::Page::PAPER_SIZE for the new page. The optional argument +orientation+ can be
93
- # used to change the orientation to :landscape if needed.
94
- def add(page = nil, orientation: :portrait)
95
- if page.kind_of?(Array)
96
- page = @document.add({Type: :Page, MediaBox: page})
97
- elsif page.kind_of?(Symbol)
98
- box = Type::Page.media_box(page, orientation: orientation)
99
- page = @document.add({Type: :Page, MediaBox: box})
112
+ # If called with a page object as argument, that page object is used. Otherwise #create is
113
+ # called with the arguments +media_box+ and +orientation+ to create a new page.
114
+ def add(page = nil, orientation: nil)
115
+ unless page.kind_of?(HexaPDF::Type::Page)
116
+ page = create(media_box: page, orientation: orientation)
100
117
  end
101
118
  @document.catalog.pages.add_page(page)
102
119
  end
@@ -52,6 +52,7 @@ require 'hexapdf/importer'
52
52
  require 'hexapdf/image_loader'
53
53
  require 'hexapdf/font_loader'
54
54
  require 'hexapdf/layout'
55
+ require 'hexapdf/digital_signature'
55
56
 
56
57
  begin
57
58
  require 'hexapdf/cext'
@@ -105,7 +106,6 @@ module HexaPDF
105
106
  autoload(:Fonts, 'hexapdf/document/fonts')
106
107
  autoload(:Images, 'hexapdf/document/images')
107
108
  autoload(:Files, 'hexapdf/document/files')
108
- autoload(:Signatures, 'hexapdf/document/signatures')
109
109
  autoload(:Destinations, 'hexapdf/document/destinations')
110
110
  autoload(:Layout, 'hexapdf/document/layout')
111
111
 
@@ -152,15 +152,19 @@ module HexaPDF
152
152
  #
153
153
  # Options:
154
154
  #
155
- # io:: If an IO object is provided, then this document can read PDF objects from this IO
156
- # object, otherwise it can only contain created PDF objects.
155
+ # io::
156
+ # If an IO object is provided, then this document can read PDF objects from this IO object,
157
+ # otherwise it can only contain created PDF objects.
157
158
  #
158
- # decryption_opts:: A hash with options for decrypting the PDF objects loaded from the IO.
159
+ # decryption_opts::
160
+ # A hash with options for decrypting the PDF objects loaded from the IO. The PDF standard
161
+ # security handler expects a :password key to be set to either the user or owner password of
162
+ # the PDF file.
159
163
  #
160
- # config:: A hash with configuration options that is deep-merged into the default configuration
161
- # (see
162
- # HexaPDF::DefaultDocumentConfiguration[../index.html#DefaultDocumentConfiguration],
163
- # meaning that direct sub-hashes are merged instead of overwritten.
164
+ # config::
165
+ # A hash with configuration options that is deep-merged into the default configuration (see
166
+ # HexaPDF::DefaultDocumentConfiguration[../index.html#DefaultDocumentConfiguration], meaning
167
+ # that direct sub-hashes are merged instead of overwritten.
164
168
  def initialize(io: nil, decryption_opts: {}, config: {})
165
169
  @config = Configuration.with_defaults(config)
166
170
  @version = '1.2'
@@ -585,25 +589,28 @@ module HexaPDF
585
589
  acro_form&.signature_flag?(:signatures_exist)
586
590
  end
587
591
 
588
- # Returns an array with the digital signatures of this document.
592
+ # Returns a DigitalSignature::Signatures object that allows working with the digital signatures
593
+ # of this document.
589
594
  def signatures
590
- @signatures ||= Signatures.new(self)
595
+ @signatures ||= DigitalSignature::Signatures.new(self)
591
596
  end
592
597
 
593
598
  # Signs the document and writes it to the given file or IO object.
594
599
  #
595
600
  # For details on the arguments +file_or_io+, +signature+ and +write_options+ see
596
- # HexaPDF::Document::Signatures#add.
601
+ # HexaPDF::DigitalSignature::Signatures#add.
597
602
  #
598
603
  # The signing handler to be used is determined by the +handler+ argument together with the rest
599
- # of the keyword arguments (see HexaPDF::Document::Signatures#handler for details).
604
+ # of the keyword arguments (see HexaPDF::DigitalSignature::Signatures#signing_handler for
605
+ # details).
600
606
  #
601
- # If not changed, the default signing handler is HexaPDF::Document::Signatures::DefaultHandler.
607
+ # If not changed, the default signing handler is
608
+ # HexaPDF::DigitalSignature::Signing::DefaultHandler.
602
609
  #
603
610
  # *Note*: Once signing is done the document cannot be changed anymore since it was written. If a
604
611
  # document needs to be signed multiple times, it needs to be loaded again after writing.
605
612
  def sign(file_or_io, handler: :default, signature: nil, write_options: {}, **handler_options)
606
- handler = signatures.handler(name: handler, **handler_options)
613
+ handler = signatures.signing_handler(name: handler, **handler_options)
607
614
  signatures.add(file_or_io, handler, signature: signature, write_options: write_options)
608
615
  end
609
616
 
@@ -97,7 +97,8 @@ module HexaPDF
97
97
  # a user is allowed to do with a PDF file.
98
98
  #
99
99
  # When a user or owner password is specified, a PDF file can only be opened when the correct
100
- # password is supplied.
100
+ # password is supplied. To open such an encrypted PDF file, the +decryption_opts+ provided to
101
+ # HexaPDF::Document.new needs to contain a :password key with the password.
101
102
  #
102
103
  # See: PDF1.7 s7.6.3, PDF2.0 s7.6.3
103
104
  class StandardSecurityHandler < SecurityHandler
@@ -323,10 +324,10 @@ module HexaPDF
323
324
  def prepare_decryption(password: '', check_permissions: true)
324
325
  if dict[:Filter] != :Standard
325
326
  raise(HexaPDF::UnsupportedEncryptionError,
326
- "Invalid /Filter value for standard security handler")
327
+ "Invalid /Filter value #{dict[:Filter]} for standard security handler")
327
328
  elsif ![2, 3, 4, 6].include?(dict[:R])
328
329
  raise(HexaPDF::UnsupportedEncryptionError,
329
- "Invalid /R value for standard security handler")
330
+ "Invalid /R value #{dict[:R]} for standard security handler")
330
331
  elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(PDFArray)
331
332
  document.trailer[:ID] = ['', '']
332
333
  end
@@ -171,9 +171,21 @@ module HexaPDF
171
171
  yield("Required field #{field} is not set", false) if self[field].nil?
172
172
  end
173
173
 
174
+ widths = self[:Widths]
174
175
  if key?(:Widths) && key?(:LastChar) && key?(:FirstChar) &&
175
- self[:Widths].length != (self[:LastChar] - self[:FirstChar] + 1)
176
- yield("Invalid number of entries in field Widths", false)
176
+ widths.length != (self[:LastChar] - self[:FirstChar] + 1)
177
+ yield("Invalid number of entries in field Widths", true)
178
+ difference = self[:LastChar] - self[:FirstChar] + 1 - widths.length
179
+ if difference > 0
180
+ missing_value = if widths.count(widths[0]) == widths.length
181
+ widths[0]
182
+ else
183
+ self[:FontDescriptor]&.[](:MissingWidth) || 0
184
+ end
185
+ difference.times { widths << missing_value }
186
+ else
187
+ widths.slice!(difference, -difference)
188
+ end
177
189
  end
178
190
  end
179
191
 
data/lib/hexapdf/type.rb CHANGED
@@ -72,7 +72,6 @@ module HexaPDF
72
72
  autoload(:FontType3, 'hexapdf/type/font_type3')
73
73
  autoload(:IconFit, 'hexapdf/type/icon_fit')
74
74
  autoload(:AcroForm, 'hexapdf/type/acro_form')
75
- autoload(:Signature, 'hexapdf/type/signature')
76
75
  autoload(:Outline, 'hexapdf/type/outline')
77
76
  autoload(:OutlineItem, 'hexapdf/type/outline_item')
78
77
  autoload(:PageLabel, 'hexapdf/type/page_label')
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.28.0'
40
+ VERSION = '0.30.0'
41
41
 
42
42
  end
@@ -6,7 +6,7 @@ module HexaPDF
6
6
  class Certificates
7
7
 
8
8
  def ca_key
9
- @ca_key ||= OpenSSL::PKey::RSA.new(512)
9
+ @ca_key ||= OpenSSL::PKey::RSA.new(2048)
10
10
  end
11
11
 
12
12
  def ca_certificate
@@ -36,13 +36,17 @@ module HexaPDF
36
36
  end
37
37
 
38
38
  def signer_key
39
- @signer_key ||= OpenSSL::PKey::RSA.new(512)
39
+ @signer_key ||= OpenSSL::PKey::RSA.new(2048)
40
+ end
41
+
42
+ def dsa_signer_key
43
+ @dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
40
44
  end
41
45
 
42
46
  def signer_certificate
43
47
  @signer_certificate ||=
44
48
  begin
45
- name = OpenSSL::X509::Name.parse('/CN=signer/DC=gettalong')
49
+ name = OpenSSL::X509::Name.parse('/CN=RSA signer/DC=gettalong')
46
50
 
47
51
  signer_cert = OpenSSL::X509::Certificate.new
48
52
  signer_cert.serial = 2
@@ -65,6 +69,30 @@ module HexaPDF
65
69
  end
66
70
  end
67
71
 
72
+ def dsa_signer_certificate
73
+ @dsa_signer_certificate ||=
74
+ begin
75
+ signer_cert = OpenSSL::X509::Certificate.new
76
+ signer_cert.serial = 3
77
+ signer_cert.version = 2
78
+ signer_cert.not_before = Time.now - 86400
79
+ signer_cert.not_after = Time.now + 86400
80
+ signer_cert.public_key = dsa_signer_key.public_key
81
+ signer_cert.subject = OpenSSL::X509::Name.parse('/CN=DSA signer/DC=gettalong')
82
+ signer_cert.issuer = ca_certificate.subject
83
+
84
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
85
+ extension_factory.subject_certificate = signer_cert
86
+ extension_factory.issuer_certificate = ca_certificate
87
+ signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
88
+ signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
89
+ signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
90
+ signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
91
+
92
+ signer_cert
93
+ end
94
+ end
95
+
68
96
  def timestamp_certificate
69
97
  @timestamp_certificate ||=
70
98
  begin
@@ -0,0 +1,162 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require_relative '../common'
6
+
7
+ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @handler = HexaPDF::DigitalSignature::Signing::DefaultHandler.new(
11
+ certificate: CERTIFICATES.signer_certificate,
12
+ key: CERTIFICATES.signer_key,
13
+ certificate_chain: [CERTIFICATES.ca_certificate]
14
+ )
15
+ end
16
+
17
+ it "defaults to standard CMS signatures" do
18
+ assert_equal(:cms, @handler.signature_type)
19
+ end
20
+
21
+ it "returns the size of serialized signature" do
22
+ assert(@handler.signature_size > 1000)
23
+ @handler.signature_size = 100
24
+ assert_equal(100, @handler.signature_size)
25
+ end
26
+
27
+ it "allows setting the DocMDP permissions" do
28
+ assert_nil(@handler.doc_mdp_permissions)
29
+
30
+ @handler.doc_mdp_permissions = :no_changes
31
+ assert_equal(1, @handler.doc_mdp_permissions)
32
+ @handler.doc_mdp_permissions = 1
33
+ assert_equal(1, @handler.doc_mdp_permissions)
34
+
35
+ @handler.doc_mdp_permissions = :form_filling
36
+ assert_equal(2, @handler.doc_mdp_permissions)
37
+ @handler.doc_mdp_permissions = 2
38
+ assert_equal(2, @handler.doc_mdp_permissions)
39
+
40
+ @handler.doc_mdp_permissions = :form_filling_and_annotations
41
+ assert_equal(3, @handler.doc_mdp_permissions)
42
+ @handler.doc_mdp_permissions = 3
43
+ assert_equal(3, @handler.doc_mdp_permissions)
44
+
45
+ @handler.doc_mdp_permissions = nil
46
+ assert_nil(@handler.doc_mdp_permissions)
47
+
48
+ assert_raises(ArgumentError) { @handler.doc_mdp_permissions = :other }
49
+ end
50
+
51
+ describe "sign" do
52
+ it "can sign the data using the provided certificate and key" do
53
+ data = StringIO.new("data")
54
+ signed_data = @handler.sign(data, [0, data.string.size, 0, 0])
55
+
56
+ pkcs7 = OpenSSL::PKCS7.new(signed_data)
57
+ assert(pkcs7.detached?)
58
+ assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
59
+ pkcs7.certificates)
60
+ store = OpenSSL::X509::Store.new
61
+ store.add_cert(CERTIFICATES.ca_certificate)
62
+ assert(pkcs7.verify([], store, data.string, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY))
63
+ end
64
+
65
+ it "can change the used digest algorithm" do
66
+ @handler.digest_algorithm = 'sha384'
67
+ asn1 = OpenSSL::ASN1.decode(@handler.sign(StringIO.new('data'), [0, 4, 0, 0]))
68
+ assert_equal('SHA384', asn1.value[1].value[0].value[1].value[0].value[0].value)
69
+ end
70
+
71
+ it "can embed a timestamp token" do
72
+ @handler.timestamp_handler = tsh = Object.new
73
+ tsh.define_singleton_method(:sign) {|_, _| OpenSSL::ASN1::OctetString.new("signed-tsh") }
74
+ signed = @handler.sign(StringIO.new('data'), [0, 4, 0, 0])
75
+ asn1 = OpenSSL::ASN1.decode(signed)
76
+ assert_equal('signed-tsh', asn1.value[1].value[0].value[4].value[0].
77
+ value[6].value[0].value[1].value[0].value)
78
+ end
79
+
80
+ it "creates PAdES compatible signatures" do
81
+ @handler.signature_type = :pades
82
+ signed = @handler.sign(StringIO.new('data'), [0, 4, 0, 0])
83
+ asn1 = OpenSSL::ASN1.decode(signed)
84
+ # check by absence of signing-time signed attribute
85
+ refute(asn1.value[1].value[0].value[4].value[0].value[3].value.
86
+ find {|obj| obj.value[0].value == 'signingTime' })
87
+ end
88
+
89
+ it "can use external signing without certificate set" do
90
+ @handler.certificate = nil
91
+ @handler.external_signing = proc { "hallo" }
92
+ assert_equal("hallo", @handler.sign(StringIO.new, [0, 0, 0, 0]))
93
+ end
94
+
95
+ it "can use external signing with certificate set but not the key" do
96
+ @handler.key = nil
97
+ @handler.external_signing = proc do |algorithm, _hash|
98
+ assert_equal('sha256', algorithm)
99
+ "hallo"
100
+ end
101
+ result = @handler.sign(StringIO.new, [0, 0, 0, 0])
102
+ asn1 = OpenSSL::ASN1.decode(result)
103
+ assert_equal("hallo", asn1.value[1].value[0].value[4].value[0].value[5].value)
104
+ end
105
+ end
106
+
107
+ describe "finalize_objects" do
108
+ before do
109
+ @field = @doc.wrap({})
110
+ @obj = @doc.wrap({})
111
+ end
112
+
113
+ it "only sets the mandatory values if no concrete finalization tasks need to be done" do
114
+ @handler.finalize_objects(@field, @obj)
115
+ assert(@field.empty?)
116
+ assert_equal(:'Adobe.PPKLite', @obj[:Filter])
117
+ assert_equal(:'adbe.pkcs7.detached', @obj[:SubFilter])
118
+ assert_kind_of(Time, @obj[:M])
119
+ end
120
+
121
+ it "adjust the /SubFilter if signature type is pades" do
122
+ @handler.signature_type = :pades
123
+ @handler.finalize_objects(@field, @obj)
124
+ assert_equal(:'ETSI.CAdES.detached', @obj[:SubFilter])
125
+ end
126
+
127
+ it "sets the reason, location and contact info fields" do
128
+ @handler.reason = 'Reason'
129
+ @handler.location = 'Location'
130
+ @handler.contact_info = 'Contact'
131
+ @handler.finalize_objects(@field, @obj)
132
+ assert(@field.empty?)
133
+ assert_equal(['Reason', 'Location', 'Contact'], @obj.value.values_at(:Reason, :Location, :ContactInfo))
134
+ end
135
+
136
+ it "fills the build properties dictionary with appropriate application information" do
137
+ @handler.finalize_objects(@field, @obj)
138
+ assert_equal(:HexaPDF, @obj[:Prop_Build][:App][:Name])
139
+ assert_equal(HexaPDF::VERSION, @obj[:Prop_Build][:App][:REx])
140
+ end
141
+
142
+ it "applies the specified DocMDP permissions" do
143
+ @handler.doc_mdp_permissions = :no_changes
144
+ @handler.finalize_objects(@field, @obj)
145
+ ref = @obj[:Reference][0]
146
+ assert_equal(:DocMDP, ref[:TransformMethod])
147
+ assert_equal(:SHA256, ref[:DigestMethod])
148
+ assert_equal(1, ref[:TransformParams][:P])
149
+ assert_equal(:'1.2', ref[:TransformParams][:V])
150
+ assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
151
+ end
152
+
153
+ it "fails if DocMDP should be set but there is already a signature" do
154
+ @handler.doc_mdp_permissions = :no_changes
155
+ 2.times do
156
+ field = @doc.acro_form(create: true).create_signature_field('test')
157
+ field.field_value = :something
158
+ end
159
+ assert_raises(HexaPDF::Error) { @handler.finalize_objects(@field, @obj) }
160
+ end
161
+ end
162
+ end