hexapdf 0.27.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
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
+