hexapdf 0.27.0 → 0.29.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +100 -11
- data/examples/019-acro_form.rb +14 -3
- data/examples/023-images.rb +30 -0
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +2 -2
- data/lib/hexapdf/configuration.rb +13 -14
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- data/lib/hexapdf/dictionary_fields.rb +6 -2
- data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
- data/lib/hexapdf/digital_signature/handler.rb +138 -0
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
- data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
- data/lib/hexapdf/digital_signature/signatures.rb +210 -0
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
- data/lib/hexapdf/digital_signature/signing.rb +101 -0
- data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
- data/lib/hexapdf/digital_signature.rb +56 -0
- data/lib/hexapdf/document.rb +27 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +33 -7
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
- data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
- data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
- data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
- data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
- data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
- data/test/hexapdf/digital_signature/test_signing.rb +53 -0
- data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +3 -9
- data/test/hexapdf/test_importer.rb +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +18 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +69 -16
- data/lib/hexapdf/document/signatures.rb +0 -546
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
- data/lib/hexapdf/type/signature/handler.rb +0 -140
- data/test/hexapdf/document/test_signatures.rb +0 -352
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fdace78c8d34d39c345e2ccd04edda4755e2fc8076cc7320793a4ef16f48520
|
4
|
+
data.tar.gz: a0ec03dc2d579eb8663512ec0c84cadbc877b9ec43cabb8ea0d6ab58df337585
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
*
|
100
|
+
* `HexaPDF::Document::Signatures::DefaultHandler#signature_size` to support
|
12
101
|
setting custom signature sizes
|
13
|
-
*
|
102
|
+
* `HexaPDF::Document::Signatures::DefaultHandler#external_signing` to support
|
14
103
|
signing via custom mechanisms
|
15
|
-
*
|
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
|
-
*
|
111
|
+
* `HexaPDF::Document::Signatures::DefaultHandler` to allow setting the used
|
23
112
|
signature method
|
24
|
-
* **Breaking change**:
|
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
|
-
|
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
|
-
*
|
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
|
-
*
|
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
|
-
*
|
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
|
-
-
|
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
|
data/examples/019-acro_form.rb
CHANGED
@@ -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,
|
48
|
+
canvas.text("Text fields", at: [50, 480])
|
46
49
|
|
47
|
-
canvas.text("Single line", at: [70,
|
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,
|
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])
|
data/lib/hexapdf/cli/info.rb
CHANGED
@@ -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(
|
193
|
+
puts("#{header}:".ljust(COLUMN_WIDTH) << text.to_s)
|
190
194
|
end
|
191
195
|
|
192
196
|
end
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -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.
|
338
|
+
signatures = @doc.signatures.to_h do |sig|
|
339
339
|
[@doc.revisions.find {|rev| rev.object(sig) == sig }, sig]
|
340
|
-
end
|
340
|
+
end
|
341
341
|
io = @doc.revisions.parser.io
|
342
342
|
|
343
343
|
startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
|
data/lib/hexapdf/cli/split.rb
CHANGED
@@ -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,
|
135
|
-
|
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::
|
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::
|
491
|
-
timestamp: 'HexaPDF::
|
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::
|
495
|
-
'adbe.pkcs7.detached': 'HexaPDF::
|
496
|
-
'ETSI.CAdES.detached': 'HexaPDF::
|
497
|
-
'ETSI.RFC3161': 'HexaPDF::
|
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::
|
588
|
-
DocTimeStamp: 'HexaPDF::
|
589
|
-
SigRef: 'HexaPDF::
|
590
|
-
TransformParams: 'HexaPDF::
|
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
|
-
|
1635
|
-
|
1635
|
+
left -= obj.box.left
|
1636
|
+
bottom -= obj.box.bottom
|
1636
1637
|
end
|
1637
1638
|
|
1638
|
-
|
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
|
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -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
|
-
|
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
|
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 =
|
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
|
+
|