hexapdf 0.27.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -11
  3. data/examples/019-acro_form.rb +14 -3
  4. data/examples/023-images.rb +30 -0
  5. data/examples/024-digital-signatures.rb +23 -0
  6. data/lib/hexapdf/cli/info.rb +5 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -2
  8. data/lib/hexapdf/cli/split.rb +2 -2
  9. data/lib/hexapdf/configuration.rb +13 -14
  10. data/lib/hexapdf/content/canvas.rb +8 -3
  11. data/lib/hexapdf/dictionary.rb +1 -5
  12. data/lib/hexapdf/dictionary_fields.rb +6 -2
  13. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  14. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  15. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  16. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  17. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  18. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  19. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  20. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  21. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  22. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  23. data/lib/hexapdf/digital_signature.rb +56 -0
  24. data/lib/hexapdf/document.rb +27 -24
  25. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  26. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  27. data/lib/hexapdf/importer.rb +32 -27
  28. data/lib/hexapdf/layout/list_box.rb +1 -5
  29. data/lib/hexapdf/object.rb +5 -0
  30. data/lib/hexapdf/parser.rb +13 -0
  31. data/lib/hexapdf/revision.rb +15 -12
  32. data/lib/hexapdf/revisions.rb +4 -0
  33. data/lib/hexapdf/tokenizer.rb +14 -8
  34. data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
  35. data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
  36. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  37. data/lib/hexapdf/type/acro_form/field.rb +11 -5
  38. data/lib/hexapdf/type/acro_form/form.rb +33 -7
  39. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
  40. data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
  41. data/lib/hexapdf/type/annotations/widget.rb +3 -0
  42. data/lib/hexapdf/type/font_true_type.rb +14 -0
  43. data/lib/hexapdf/type/object_stream.rb +2 -2
  44. data/lib/hexapdf/type/outline.rb +1 -1
  45. data/lib/hexapdf/type/page.rb +56 -46
  46. data/lib/hexapdf/type.rb +0 -1
  47. data/lib/hexapdf/version.rb +1 -1
  48. data/lib/hexapdf/writer.rb +2 -3
  49. data/test/hexapdf/content/test_canvas.rb +5 -0
  50. data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
  51. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  52. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  53. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  54. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  55. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  56. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  57. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  58. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  59. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  60. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  61. data/test/hexapdf/document/test_pages.rb +2 -2
  62. data/test/hexapdf/encryption/test_aes.rb +1 -1
  63. data/test/hexapdf/filter/test_predictor.rb +0 -1
  64. data/test/hexapdf/layout/test_box.rb +2 -1
  65. data/test/hexapdf/layout/test_column_box.rb +1 -1
  66. data/test/hexapdf/layout/test_list_box.rb +1 -1
  67. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  68. data/test/hexapdf/test_document.rb +3 -9
  69. data/test/hexapdf/test_importer.rb +13 -6
  70. data/test/hexapdf/test_parser.rb +17 -0
  71. data/test/hexapdf/test_revision.rb +15 -14
  72. data/test/hexapdf/test_revisions.rb +43 -0
  73. data/test/hexapdf/test_stream.rb +1 -1
  74. data/test/hexapdf/test_tokenizer.rb +3 -4
  75. data/test/hexapdf/test_writer.rb +3 -3
  76. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
  77. data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
  78. data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
  79. data/test/hexapdf/type/acro_form/test_field.rb +4 -4
  80. data/test/hexapdf/type/acro_form/test_form.rb +18 -0
  81. data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
  82. data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
  83. data/test/hexapdf/type/test_font_true_type.rb +20 -0
  84. data/test/hexapdf/type/test_object_stream.rb +2 -1
  85. data/test/hexapdf/type/test_outline.rb +3 -0
  86. data/test/hexapdf/type/test_page.rb +67 -30
  87. data/test/hexapdf/type/test_page_tree_node.rb +4 -2
  88. metadata +69 -16
  89. data/lib/hexapdf/document/signatures.rb +0 -546
  90. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  91. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  92. data/lib/hexapdf/type/signature/handler.rb +0 -140
  93. data/test/hexapdf/document/test_signatures.rb +0 -352
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57b852a0648f47b5e3443e9b8aa4480a7a8cd187085c0067baf86af14cee7d9a
4
- data.tar.gz: 6e28f748f1d8e089b6585748e2f56c3adf79669cf9b00d0d9fe8d83a74e97063
3
+ metadata.gz: 1fdace78c8d34d39c345e2ccd04edda4755e2fc8076cc7320793a4ef16f48520
4
+ data.tar.gz: a0ec03dc2d579eb8663512ec0c84cadbc877b9ec43cabb8ea0d6ab58df337585
5
5
  SHA512:
6
- metadata.gz: a1d2cd75344a3fc9cd54f0dafa6a0fd23b9821d67f54581090a6db2820f7e04a0989da01d7b5f9c558e02bb57cf254f4ebaab791b950d4dacc1e02a29c5f3844
7
- data.tar.gz: ea758cdeb96d8282e3c6581785b690b8d79e9bee318a7cf80bfb4201b47fc0f1d721a78b5c703b32f20b8671211b604e78f83dec6768187321fef46e5dac3a81
6
+ metadata.gz: a8b18782359af03f0eda710658d0839554f0e95cd8068683dcaea56e8493c3b8a0b4cc30c9e39a3fdbe743df4d5e34b84ce4ab27125c8c14300b861ecbff16e9
7
+ data.tar.gz: 8d5c60a368d85fa9c2b118d955933943f5d0cf79e91d744348cf1e3f1c9435d44880033c844b58ab098c8ab8d1e2e7cd4c5e04710133b25543f31457277d0ba2
data/CHANGELOG.md CHANGED
@@ -1,32 +1,121 @@
1
+ ## 0.29.0 - 2023-01-30
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::DigitalSignature::Signing::SignedDataCreator] for creating custom
6
+ CMS signed data objects
7
+
8
+ ### Changed
9
+
10
+ * **Breaking change**: Refactored digital signature support and moved all
11
+ related code under the [HexaPDF::DigitalSignature] module
12
+ * **Breaking change**: New external signing mode without the need for creating
13
+ the PKCS#7/CMS signed data object for
14
+ [HexaPDF::DigitalSignature::Signing::DefaultHandler]
15
+ * **Breaking change**: Use value :pades instead of :etsi for
16
+ [HexaPDF::DigitalSignature::Signing::DefaultHandler#signature_type]
17
+ * [HexaPDF::DigitalSignature::Signing::DefaultHandler] to allow creating PAdES
18
+ level B-B and B-T signatures
19
+ * [HexaPDF::DigitalSignature::Signing::DefaultHandler] to allow specifying the
20
+ used digest algorithm
21
+ * [HexaPDF::DigitalSignature::Signing::DefaultHandler] to allow specifying a
22
+ timestamp handler for including a timestamp token in the signature
23
+ * Moved setting of signature entries /Filter, /SubFilter and /M fields to the
24
+ signing handlers
25
+
26
+ ### Fixed
27
+
28
+ * [HexaPDF::DictionaryFields::DateConverter] to handle invalid timezone hour and
29
+ minute values
30
+
31
+
32
+ ## 0.28.0 - 2022-12-30
33
+
34
+ ### Added
35
+
36
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator#create_push_button_appearances]
37
+ to allow customizing the behaviour
38
+ * [HexaPDF::Parser#linearized?] for determining whether a document is linearized
39
+ * Information on linearization to `hexapdf info` output
40
+ * Support for `AFNumber_Format` Javascript method to the form field appearance
41
+ generator
42
+ * Support for using fully embedded, simple TrueType fonts for drawing operations
43
+
44
+ ### Changed
45
+
46
+ * **Breaking change**: `HexaPDF::Revision#reset_objects` has been removed
47
+ * **Breaking change**: Method signature of [HexaPDF::Importer::for] has been
48
+ changed
49
+ * **Breaking change**: [HexaPDF::Type::AcroForm::Field#each_widget] now has the
50
+ default value of the argument `direct_only` set to `true` instead of `false`
51
+ * [HexaPDF::Revision#each_modified_object] to allow deleting the modified
52
+ objects from the active objects' container
53
+ * [HexaPDF::Revision#each_modified_object] to allow ignoring added object and
54
+ cross-reference stream objects
55
+ * [HexaPDF::Revisions::from_io] to merge the two revisions of a linearized PDF
56
+ * [HexaPDF::Importer] and [HexaPDF::Document#import] to make working with them
57
+ easier by allowing the import of arbitrary objects
58
+ * `HexaPDF::Type::AcroForm::Form#perform_validation` to combine fields with the
59
+ same name
60
+
61
+ ### Fixed
62
+
63
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator#create_check_box_appearances] to
64
+ correctly handle a field value of `nil`
65
+ * Return value of `#type` method for all AcroForm field classes
66
+ * [HexaPDF::Type::Page#flatten_annotations] to work correctly in case no
67
+ annotations are on the page
68
+ * [HexaPDF::Type::AcroForm::ButtonField#create_appearances] to avoid creating
69
+ appearances in case of as-yet unresolved references to existing appearances
70
+ * [HexaPDF::Type::AcroForm::TextField#create_appearances] to avoid creating
71
+ appearances in case of pre-existing ones
72
+ * `HexaPDF::Tokenizer#parse_number` to treat invalid indirect object references
73
+ with an object number of 0 as null values
74
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator] to handle empty appearance
75
+ characteristics dictionary marker style strings
76
+ * Writing of encrypted files containing two or more revisions
77
+ * Generation of object streams to never allow storing the catalog object to
78
+ avoid problems with certain viewers
79
+ * `HexaPDF::Type::Outline#perform_validation` to not show validation error when
80
+ `/Count` is zero
81
+ * Writing of documents with two or more revisions in non-incremental mode when
82
+ `optimize: true` is used and the original document used cross-reference tables
83
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator] to take a widget's rotation
84
+ value into account
85
+ * [HexaPDF::Type::Page#flatten_annotations] to correctly flatten all
86
+ annotations, including ones with custom rotations
87
+ * [HexaPDF::Type::Page#rotate] to also rotate annotations
88
+
89
+
1
90
  ## 0.27.0 - 2022-11-18
2
91
 
3
92
  ### Added
4
93
 
5
94
  * Support for timestamp signatures through the
6
- [HexaPDF::Document::Signatures::TimestampHandler]
95
+ `HexaPDF::Document::Signatures::TimestampHandler`
7
96
  * [HexaPDF::Document::Destinations#resolve] for resolving destination values
8
97
  * [HexaPDF::Document::Destinations::Destination#value] to return the destination
9
98
  array
10
99
  * Support for verifying document timestamp signatures
11
- * [HexaPDF::Document::Signatures::DefaultHandler#signature_size] to support
100
+ * `HexaPDF::Document::Signatures::DefaultHandler#signature_size` to support
12
101
  setting custom signature sizes
13
- * [HexaPDF::Document::Signatures::DefaultHandler#external_signing] to support
102
+ * `HexaPDF::Document::Signatures::DefaultHandler#external_signing` to support
14
103
  signing via custom mechanisms
15
- * [HexaPDF::Document::Signatures::embed_signature] to enable asynchronous
104
+ * `HexaPDF::Document::Signatures::embed_signature` to enable asynchronous
16
105
  external signing
17
106
 
18
107
  ### Changed
19
108
 
20
109
  * **Breaking change**: The crop box is now used instead of the media box in most
21
110
  cases to be in line with the specification
22
- * [HexaPDF::Document::Signatures::DefaultHandler] to allow setting the used
111
+ * `HexaPDF::Document::Signatures::DefaultHandler` to allow setting the used
23
112
  signature method
24
- * **Breaking change**: [HexaPDF::Document::Signatures::DefaultHandler#sign]
113
+ * **Breaking change**: `HexaPDF::Document::Signatures::DefaultHandler#sign`
25
114
  needs to accept the IO object and the byte range instead of just the data
26
115
  * **Breaking change**: Enhanced support for outline items with new methods
27
116
  `#level` and `#destination_page` as well as changes to `#add` and `#each_item`
28
117
  * **Breaking change**: Removed `#filter_name` and `#sub_filter_name` from
29
- [HexaPDF::Document::Signatures::DefaultHandler]
118
+ `HexaPDF::Document::Signatures::DefaultHandler`
30
119
  * `HexaPDF::Type::Resources#perform_validation` to not add a default procedure
31
120
  set since this feature is deprecated
32
121
 
@@ -43,7 +132,7 @@
43
132
  * [HexaPDF::Type::OutlineItem] to always be an indirect object
44
133
  * `HexaPDF::Tokenizer#parse_number` to handle references correctly in all cases
45
134
  * [HexaPDF::Type::Page#rotate] to correctly flatten all page boxes
46
- * [HexaPDF::Document::Signatures#add] to raise an error if the reserved space
135
+ * `HexaPDF::Document::Signatures#add` to raise an error if the reserved space
47
136
  for the signature is not enough
48
137
  * `HexaPDF::Type::AcroForm::Form#perform_validation` to fix broken /Parent
49
138
  entries and to remove invalid objects from the field hierarchy
@@ -218,7 +307,7 @@
218
307
  moved node doesn't change
219
308
  * [HexaPDF::Type::PageTreeNode#move_page] to use the correct target position
220
309
  when the moved node is before the target position
221
- * [HexaPDF::Document::Signatures#add] to work in case the signature object is
310
+ * `HexaPDF::Document::Signatures#add` to work in case the signature object is
222
311
  the last object written
223
312
  * CLI command `hexapdf inspect` to show correct byte range of the last revision
224
313
  * [HexaPDF::Writer#write_incremental] to only use a cross-reference stream if a
@@ -227,7 +316,7 @@
227
316
  disabled
228
317
  * [HexaPDF::Font::Encoding::GlyphList] to use binary reading to avoid problems
229
318
  on Windows
230
- * [HexaPDF::Document::Signatures#add] to use binary writing to avoid problems on
319
+ * `HexaPDF::Document::Signatures#add` to use binary writing to avoid problems on
231
320
  Windows
232
321
 
233
322
 
@@ -236,7 +325,7 @@
236
325
  ### Added
237
326
 
238
327
  - [HexaPDF::Composer#create_stamp] for creating a form Xobject
239
- - [HexaPDF::Revision#reset_objects] for deleting all live loaded and added
328
+ - `HexaPDF::Revision#reset_objects` for deleting all live loaded and added
240
329
  objects
241
330
  - Support for removing or flattening annotations to the `hexapdf modify` command
242
331
  - Option to CLI command `hexapdf form` to allow generation of a template file
@@ -6,6 +6,9 @@
6
6
  # This example show-cases how to create the various form field types and their
7
7
  # possible standard appearances.
8
8
  #
9
+ # Note the 'number format' text field which uses a JavaScript function for
10
+ # formatting a number.
11
+ #
9
12
  # Usage:
10
13
  # : `ruby acro_form.rb`
11
14
  #
@@ -42,13 +45,21 @@ rb = form.create_radio_button("Radio")
42
45
  end
43
46
  rb.field_value = :button0
44
47
 
45
- canvas.text("Text fields", at: [50, 450])
48
+ canvas.text("Text fields", at: [50, 480])
46
49
 
47
- canvas.text("Single line", at: [70, 420])
50
+ canvas.text("Single line", at: [70, 450])
48
51
  tx = form.create_text_field("Single Line", font_size: 16)
49
- widget = tx.create_widget(page, Rect: [200, 415, 500, 435])
52
+ widget = tx.create_widget(page, Rect: [200, 445, 500, 465])
50
53
  tx.field_value = "A sample test string!"
51
54
 
55
+ canvas.text("Number format", at: [70, 420])
56
+ tx = form.create_text_field("Number format", font_size: 16)
57
+ widget = tx.create_widget(page, Rect: [200, 415, 500, 435])
58
+ widget[:AA] = {
59
+ F: {S: :JavaScript, JS: 'AFNumber_Format(2, 2, 0, 0, "EUR ", true);'},
60
+ }
61
+ tx.field_value = "123456,789"
62
+
52
63
  canvas.text("Multiline", at: [70, 390])
53
64
  tx = form.create_multiline_text_field("Multiline", font_size: 0, align: :right)
54
65
  widget = tx.create_widget(page, Rect: [200, 325, 500, 405])
@@ -0,0 +1,30 @@
1
+ # # Images
2
+ #
3
+ # This example shows how to embed images into a PDF document, directly on a
4
+ # page's canvas and through the high-level [HexaPDF::Composer].
5
+ #
6
+ # Usage:
7
+ # : `ruby images.rb`
8
+ #
9
+
10
+ require 'hexapdf'
11
+
12
+ file = File.join(__dir__, 'machupicchu.jpg')
13
+
14
+ doc = HexaPDF::Document.new
15
+ # Image only added to PDF once though used multiple times
16
+ canvas = doc.pages.add.canvas
17
+ canvas.image(file, at: [100, 500]) # auto-size based on image size
18
+ canvas.image(file, at: [100, 300], width: 100) # height based on w/h ratio
19
+ canvas.image(file, at: [300, 300], height: 100) # width based on w/h ratio
20
+ canvas.image(file, at: [100, 100], width: 300, height: 100)
21
+
22
+ HexaPDF::Composer.create('images.pdf') do |composer|
23
+ composer.image(file) # fill current rectangular region
24
+ composer.image(file, width: 100) # height based on w/h ratio
25
+ composer.image(file, height: 100) # width based on w/h ratio
26
+ composer.image(file, width: 300, height: 100)
27
+
28
+ # Add the page created above as second page
29
+ composer.document.pages << composer.document.import(doc.pages[0])
30
+ end
@@ -0,0 +1,23 @@
1
+ # # Images
2
+ #
3
+ # This example shows how to embed images into a PDF document, directly on a
4
+ # page's canvas and through the high-level [HexaPDF::Composer].
5
+ #
6
+ # Usage:
7
+ # : `ruby digital-signatures.rb`
8
+ #
9
+
10
+ require 'hexapdf'
11
+ require HexaPDF.data_dir + '/cert/demo_cert.rb'
12
+
13
+ doc = if ARGV[0]
14
+ HexaPDF::Document.open(ARGV[0])
15
+ else
16
+ HexaPDF::Document.new.pages.add.document
17
+ end
18
+ doc.sign("digital-signatures.pdf",
19
+ reason: 'Some reason',
20
+ certificate: HexaPDF.demo_cert.cert,
21
+ key: HexaPDF.demo_cert.key,
22
+ certificate_chain: [HexaPDF.demo_cert.sub_ca,
23
+ HexaPDF.demo_cert.root_ca])
@@ -131,6 +131,10 @@ module HexaPDF
131
131
  output_line("Encrypted", "yes (no or wrong password given)")
132
132
  end
133
133
 
134
+ if doc.revisions.parser.linearized?
135
+ output_line("Linearized", "yes")
136
+ end
137
+
134
138
  signatures = doc.signatures.to_a
135
139
  unless signatures.empty?
136
140
  nr_sigs = signatures.count
@@ -186,7 +190,7 @@ module HexaPDF
186
190
  end
187
191
 
188
192
  def output_line(header, text) #:nodoc:
189
- puts(("#{header}:").ljust(COLUMN_WIDTH) << text.to_s)
193
+ puts("#{header}:".ljust(COLUMN_WIDTH) << text.to_s)
190
194
  end
191
195
 
192
196
  end
@@ -335,9 +335,9 @@ module HexaPDF
335
335
  # - The signature dictionary if this revision was signed
336
336
  # - The byte offset from the start of the file to the end of the revision
337
337
  def revision_information
338
- signatures = @doc.signatures.map do |sig|
338
+ signatures = @doc.signatures.to_h do |sig|
339
339
  [@doc.revisions.find {|rev| rev.object(sig) == sig }, sig]
340
- end.to_h
340
+ end
341
341
  io = @doc.revisions.parser.io
342
342
 
343
343
  startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
@@ -131,8 +131,8 @@ module HexaPDF
131
131
  @page_name_cache ||= {}
132
132
  return @page_name_cache[box] if @page_name_cache.key?(box)
133
133
 
134
- paper_size = HexaPDF::Type::Page::PAPER_SIZE.find do |_name, box|
135
- box.each_with_index.all? {|entry, index| (entry - box[index]).abs < 5 }
134
+ paper_size = HexaPDF::Type::Page::PAPER_SIZE.find do |_name, paper_box|
135
+ paper_box.each_with_index.all? {|entry, index| (entry - paper_box[index]).abs < 5 }
136
136
  end
137
137
 
138
138
  @page_name_cache[box] =
@@ -393,8 +393,8 @@ module HexaPDF
393
393
  #
394
394
  # signature.sub_filter_map::
395
395
  # A mapping from a PDF name (a Symbol) to a signature handler class (see
396
- # HexaPDF::Type::Signature::Handler). If the value is a String, it should contain the name of a
397
- # constant to such a class.
396
+ # HexaPDF::DigitalSignature::Handler). If the value is a String, it should contain the name of
397
+ # a constant to such a class.
398
398
  #
399
399
  # The sub filter map is used for mapping specific signature algorithms to handler classes. The
400
400
  # filter value of a signature dictionary is ignored since we only support the standard
@@ -422,8 +422,7 @@ module HexaPDF
422
422
  'encryption.filter_map' => {
423
423
  Standard: 'HexaPDF::Encryption::StandardSecurityHandler',
424
424
  },
425
- 'encryption.sub_filter_map' => {
426
- },
425
+ 'encryption.sub_filter_map' => {},
427
426
  'filter.map' => {
428
427
  ASCIIHexDecode: 'HexaPDF::Filter::ASCIIHexDecode',
429
428
  AHx: 'HexaPDF::Filter::ASCIIHexDecode',
@@ -487,14 +486,14 @@ module HexaPDF
487
486
  link: 'HexaPDF::Layout::Style::LinkLayer',
488
487
  },
489
488
  'signature.signing_handler' => {
490
- default: 'HexaPDF::Document::Signatures::DefaultHandler',
491
- timestamp: 'HexaPDF::Document::Signatures::TimestampHandler',
489
+ default: 'HexaPDF::DigitalSignature::Signing::DefaultHandler',
490
+ timestamp: 'HexaPDF::DigitalSignature::Signing::TimestampHandler',
492
491
  },
493
492
  'signature.sub_filter_map' => {
494
- 'adbe.x509.rsa_sha1': 'HexaPDF::Type::Signature::AdbeX509RsaSha1',
495
- 'adbe.pkcs7.detached': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
496
- 'ETSI.CAdES.detached': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
497
- 'ETSI.RFC3161': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
493
+ 'adbe.x509.rsa_sha1': 'HexaPDF::DigitalSignature::PKCS1Handler',
494
+ 'adbe.pkcs7.detached': 'HexaPDF::DigitalSignature::CMSHandler',
495
+ 'ETSI.CAdES.detached': 'HexaPDF::DigitalSignature::CMSHandler',
496
+ 'ETSI.RFC3161': 'HexaPDF::DigitalSignature::CMSHandler',
498
497
  },
499
498
  'task.map' => {
500
499
  optimize: 'HexaPDF::Task::Optimize',
@@ -584,10 +583,10 @@ module HexaPDF
584
583
  SigFieldLock: 'HexaPDF::Type::AcroForm::SignatureField::LockDictionary',
585
584
  SV: 'HexaPDF::Type::AcroForm::SignatureField::SeedValueDictionary',
586
585
  SVCert: 'HexaPDF::Type::AcroForm::SignatureField::CertificateSeedValueDictionary',
587
- Sig: 'HexaPDF::Type::Signature',
588
- DocTimeStamp: 'HexaPDF::Type::Signature',
589
- SigRef: 'HexaPDF::Type::Signature::SignatureReference',
590
- TransformParams: 'HexaPDF::Type::Signature::TransformParams',
586
+ Sig: 'HexaPDF::DigitalSignature::Signature',
587
+ DocTimeStamp: 'HexaPDF::DigitalSignature::Signature',
588
+ SigRef: 'HexaPDF::DigitalSignature::Signature::SignatureReference',
589
+ TransformParams: 'HexaPDF::DigitalSignature::Signature::TransformParams',
591
590
  Outlines: 'HexaPDF::Type::Outline',
592
591
  XXOutlineItem: 'HexaPDF::Type::OutlineItem',
593
592
  PageLabel: 'HexaPDF::Type::PageLabel',
@@ -1626,17 +1626,22 @@ module HexaPDF
1626
1626
  end
1627
1627
  return obj if obj.width == 0 || obj.height == 0
1628
1628
 
1629
+ left, bottom = *at
1629
1630
  width, height = calculate_dimensions(obj.width, obj.height,
1630
1631
  rwidth: width, rheight: height)
1631
1632
  if obj[:Subtype] != :Image
1632
1633
  width /= obj.box.width.to_f
1633
1634
  height /= obj.box.height.to_f
1634
- at[0] -= obj.box.left
1635
- at[1] -= obj.box.bottom
1635
+ left -= obj.box.left
1636
+ bottom -= obj.box.bottom
1636
1637
  end
1637
1638
 
1638
- transform(width, 0, 0, height, at[0], at[1]) do
1639
+ if left == 0 && bottom == 0 && width == 1 && height == 1
1639
1640
  invoke1(:Do, resources.add_xobject(obj))
1641
+ else
1642
+ transform(width, 0, 0, height, left, bottom) do
1643
+ invoke1(:Do, resources.add_xobject(obj))
1644
+ end
1640
1645
  end
1641
1646
 
1642
1647
  obj
@@ -108,11 +108,7 @@ module HexaPDF
108
108
  # The ancestor classes are also searched for such a field entry if none is found for the
109
109
  # current class.
110
110
  def self.field(name)
111
- if defined?(@fields) && @fields.key?(name)
112
- @fields[name]
113
- elsif superclass.respond_to?(:field)
114
- superclass.field(name)
115
- end
111
+ @fields&.[](name) || superclass.field(name)
116
112
  end
117
113
 
118
114
  # :call-seq:
@@ -293,14 +293,18 @@ module HexaPDF
293
293
  end
294
294
 
295
295
  # :nodoc:
296
- DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d\d)(?:'|'([0-5]\d)'?|\z)?)?\z/n
296
+ DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d+)(?:'|'(\d+)'?|\z)?)?\z/n
297
297
 
298
298
  # Checks if the given object is a string and converts into a Time object if possible.
299
299
  # Otherwise returns +nil+.
300
300
  def self.convert(str, _type, _document)
301
301
  return unless str.kind_of?(String) && (m = str.match(DATE_RE))
302
302
 
303
- utc_offset = (m[7].nil? || m[7] == 'Z' ? 0 : "#{m[7]}#{m[8]}:#{m[9] || '00'}")
303
+ utc_offset = if m[7].nil? || m[7] == 'Z'
304
+ 0
305
+ else
306
+ (m[7] == '-' ? -1 : 1) * (m[8].to_i * 3600 + m[9].to_i * 60).clamp(0, 86399)
307
+ end
304
308
  Time.new(m[1].to_i, (m[2] ? m[2].to_i : 1), (m[3] ? m[3].to_i : 1),
305
309
  m[4].to_i, m[5].to_i, m[6].to_i, utc_offset)
306
310
  end
@@ -0,0 +1,137 @@
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/digital_signature/handler'
39
+
40
+ module HexaPDF
41
+ module DigitalSignature
42
+
43
+ # The signature handler for PKCS#7 a.k.a. CMS signatures. Those include, for example, the
44
+ # adbe.pkcs7.detached sub-filter.
45
+ #
46
+ # See: PDF1.7/2.0 s12.8.3.3
47
+ class CMSHandler < Handler
48
+
49
+ # Creates a new signature handler for the given signature dictionary.
50
+ def initialize(signature_dict)
51
+ super
52
+ @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
53
+ end
54
+
55
+ # Returns the common name of the signer.
56
+ def signer_name
57
+ signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
58
+ end
59
+
60
+ # Returns the time of signing.
61
+ def signing_time
62
+ signer_info.signed_time rescue super
63
+ end
64
+
65
+ # Returns the certificate chain.
66
+ def certificate_chain
67
+ @pkcs7.certificates
68
+ end
69
+
70
+ # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
71
+ def signer_certificate
72
+ info = signer_info
73
+ certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
74
+ end
75
+
76
+ # Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
77
+ def signer_info
78
+ @pkcs7.signers.first
79
+ end
80
+
81
+ # Verifies the signature using the provided OpenSSL::X509::Store object.
82
+ def verify(store, allow_self_signed: false)
83
+ result = super
84
+
85
+ signer_info = self.signer_info
86
+ signer_certificate = self.signer_certificate
87
+ certificate_chain = self.certificate_chain
88
+
89
+ if certificate_chain.empty?
90
+ result.log(:error, "No certificates found in signature")
91
+ return result
92
+ end
93
+
94
+ if @pkcs7.signers.size != 1
95
+ result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
96
+ end
97
+
98
+ unless signer_certificate
99
+ result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
100
+ "not found in certificates stored in PKCS7 object")
101
+ return result
102
+ end
103
+
104
+ key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
105
+ unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
106
+ result.log(:error, "Certificate key usage is missing 'Digital Signature'")
107
+ end
108
+
109
+ if signature_dict.signature_type == 'ETSI.RFC3161'
110
+ # Getting the needed values is not directly supported by Ruby OpenSSL
111
+ p7 = OpenSSL::ASN1.decode(signature_dict.contents.sub(/\x00*\z/, ''))
112
+ signed_data = p7.value[1].value[0]
113
+ content_info = signed_data.value[2]
114
+ content = OpenSSL::ASN1.decode(content_info.value[1].value[0].value)
115
+ digest_algorithm = content.value[2].value[0].value[0].value
116
+ original_hash = content.value[2].value[1].value
117
+ recomputed_hash = OpenSSL::Digest.digest(digest_algorithm, signature_dict.signed_data)
118
+ hash_valid = (original_hash == recomputed_hash)
119
+ else
120
+ data = signature_dict.signed_data
121
+ hash_valid = true # hash will be checked by @pkcs7.verify
122
+ end
123
+ if hash_valid && @pkcs7.verify(certificate_chain, store, data,
124
+ OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
125
+ result.log(:info, "Signature valid")
126
+ else
127
+ result.log(:error, "Signature verification failed")
128
+ end
129
+
130
+ result
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+ end
137
+