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.
- 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
|
+
|