hexapdf 0.19.0 → 0.19.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/hexapdf/content/graphics_state.rb +24 -5
- data/lib/hexapdf/document/signatures.rb +221 -0
- data/lib/hexapdf/layout/style.rb +2 -1
- data/lib/hexapdf/type/font.rb +5 -0
- data/lib/hexapdf/type/font_type3.rb +20 -0
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- metadata +4 -32
- data/test/data/cert/create.sh +0 -171
- data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF39.pem +0 -119
- data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF3A.pem +0 -125
- data/test/data/cert/root-ca/db/crlnumber +0 -1
- data/test/data/cert/root-ca/db/index +0 -2
- data/test/data/cert/root-ca/db/index.attr +0 -1
- data/test/data/cert/root-ca/db/index.attr.old +0 -1
- data/test/data/cert/root-ca/db/index.old +0 -1
- data/test/data/cert/root-ca/db/serial +0 -1
- data/test/data/cert/root-ca/db/serial.old +0 -1
- data/test/data/cert/root-ca/private/root-ca.key +0 -52
- data/test/data/cert/root-ca/root-ca.conf +0 -65
- data/test/data/cert/root-ca/root-ca.crt +0 -119
- data/test/data/cert/root-ca/root-ca.csr +0 -28
- data/test/data/cert/signature-1-pkcs7-detached.pdf +0 -182
- data/test/data/cert/sub-ca/certs/453FF080E3EDCD6A388D5368DFC320D9.pem +0 -125
- data/test/data/cert/sub-ca/db/crlnumber +0 -1
- data/test/data/cert/sub-ca/db/index +0 -1
- data/test/data/cert/sub-ca/db/index.attr +0 -1
- data/test/data/cert/sub-ca/db/index.old +0 -0
- data/test/data/cert/sub-ca/db/serial +0 -1
- data/test/data/cert/sub-ca/db/serial.old +0 -1
- data/test/data/cert/sub-ca/private/signing.key +0 -52
- data/test/data/cert/sub-ca/private/sub-ca.key +0 -52
- data/test/data/cert/sub-ca/signing.crt +0 -125
- data/test/data/cert/sub-ca/signing.csr +0 -28
- data/test/data/cert/sub-ca/signing.p12 +0 -0
- data/test/data/cert/sub-ca/sub-ca.conf +0 -65
- data/test/data/cert/sub-ca/sub-ca.crt +0 -125
- data/test/data/cert/sub-ca/sub-ca.csr +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 496c87d6cffcfde7b155fbfcdd78673fb34931d4d0fd997bcee934decfba175d
|
4
|
+
data.tar.gz: ce72598c9d33735c6ea4738c072b1f00202dc7bac24577c9c27a062add38ea8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cee5a6f86084490ba5602c40ec48945b9a3c0bdecb22fe03e2d7b8b6ad02c791900f9fd7354557a9ac3dfdebb4a8d95c7335fc329c0054156082853f33717144
|
7
|
+
data.tar.gz: eaed67cce68e703eddbbb357e790a9f0bc62083ff8ba0b0856aa360d499f75e0e022b1ccd41ebcdd53e2fe269a93b5f587c47db41311c3f6a0beb9ed293e02cd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.19.1 - 2021-12-12
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Type::FontType3#bounding_box] to fix content stream processing error
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
* Calculation of scaled font size for [HexaPDF::Content::GraphicsState] and
|
10
|
+
[HexaPDF::Layout::Style] when Type3 fonts are used
|
11
|
+
|
12
|
+
|
1
13
|
## 0.19.0 - 2021-11-24
|
2
14
|
|
3
15
|
### Added
|
@@ -512,7 +512,7 @@ module HexaPDF
|
|
512
512
|
attr_accessor :leading
|
513
513
|
|
514
514
|
# The font for the text.
|
515
|
-
|
515
|
+
attr_reader :font
|
516
516
|
|
517
517
|
# The font size.
|
518
518
|
attr_reader :font_size
|
@@ -552,9 +552,11 @@ module HexaPDF
|
|
552
552
|
|
553
553
|
# The scaled font size used in glyph displacement calculations.
|
554
554
|
#
|
555
|
-
# This returns the font size
|
555
|
+
# This returns the font size multiplied by the scaling factor from glyph space to text space
|
556
|
+
# (0.001 for all fonts except Type3 fonts or the scaling specified in /FontMatrix for Type3
|
557
|
+
# fonts) and multiplied by #scaled_horizontal_scaling.
|
556
558
|
#
|
557
|
-
# See PDF1.7 s9.4.4
|
559
|
+
# See PDF1.7 s9.4.4, HexaPDF::Type::FontType3
|
558
560
|
attr_reader :scaled_font_size
|
559
561
|
|
560
562
|
# The scaled horizontal scaling used in glyph displacement calculations.
|
@@ -665,6 +667,15 @@ module HexaPDF
|
|
665
667
|
self.fill_color = color_space.default_color
|
666
668
|
end
|
667
669
|
|
670
|
+
##
|
671
|
+
# :attr_writer: font
|
672
|
+
#
|
673
|
+
# Sets the font and updates the glyph space to text space scaling.
|
674
|
+
def font=(font)
|
675
|
+
@font = font
|
676
|
+
update_scaled_font_size
|
677
|
+
end
|
678
|
+
|
668
679
|
##
|
669
680
|
# :attr_writer: character_spacing
|
670
681
|
#
|
@@ -689,7 +700,7 @@ module HexaPDF
|
|
689
700
|
# Sets the font size and updates the scaled font size.
|
690
701
|
def font_size=(size)
|
691
702
|
@font_size = size
|
692
|
-
|
703
|
+
update_scaled_font_size
|
693
704
|
end
|
694
705
|
|
695
706
|
##
|
@@ -702,7 +713,15 @@ module HexaPDF
|
|
702
713
|
@scaled_horizontal_scaling = scaling / 100.0
|
703
714
|
@scaled_character_spacing = @character_spacing * @scaled_horizontal_scaling
|
704
715
|
@scaled_word_spacing = @word_spacing * @scaled_horizontal_scaling
|
705
|
-
|
716
|
+
update_scaled_font_size
|
717
|
+
end
|
718
|
+
|
719
|
+
private
|
720
|
+
|
721
|
+
# Updates the cached value for the scaled font size.
|
722
|
+
def update_scaled_font_size
|
723
|
+
@scaled_font_size = @font_size * (@font&.glyph_scaling_factor || 0.001) *
|
724
|
+
@scaled_horizontal_scaling
|
706
725
|
end
|
707
726
|
|
708
727
|
end
|
@@ -0,0 +1,221 @@
|
|
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-2021 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
|
+
|
39
|
+
module HexaPDF
|
40
|
+
class Document
|
41
|
+
|
42
|
+
# This class provides methods for interacting with digital signatures of a PDF file.
|
43
|
+
class Signatures
|
44
|
+
|
45
|
+
# This is the default signing handler which provides the ability to sign a document with a
|
46
|
+
# provided certificate using the adb.pkcs7.detached algorithm.
|
47
|
+
class DefaultHandler
|
48
|
+
|
49
|
+
# Creates a new DefaultHandler with the given signing certificate, the associated signing
|
50
|
+
# key and an optional array of certificates that should also be present in the signature.
|
51
|
+
def initialize(certificate, key, certificate_chain = [])
|
52
|
+
@certificate = certificate
|
53
|
+
@key = key
|
54
|
+
@certificate_chain = certificate_chain
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the name to be set on the /Filter key when using this signing handler.
|
58
|
+
def filter_name
|
59
|
+
:"Adobe.PPKLite"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the name to be set on the /SubFilter key when using this signing handler.
|
63
|
+
def sub_filter_name
|
64
|
+
:"adbe.pkcs7.detached"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the size of the signature that would be created.
|
68
|
+
def signature_size
|
69
|
+
sign("").size
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the DER serialized OpenSSL::PKCS7 structure containing the signature for the given
|
73
|
+
# data.
|
74
|
+
def sign(data)
|
75
|
+
OpenSSL::PKCS7.sign(@certificate, @key, data, @certificate_chain,
|
76
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
include Enumerable
|
82
|
+
|
83
|
+
# Creates a new Signatures object for the given PDF document.
|
84
|
+
def initialize(document)
|
85
|
+
@document = document
|
86
|
+
end
|
87
|
+
|
88
|
+
# Adds a signature to the document and returns the corresponding signature object.
|
89
|
+
#
|
90
|
+
# This method will add a new signature to the document and write the updated document to the
|
91
|
+
# given file or IO stream. Afterwards the document can't be modified anymore and still retain
|
92
|
+
# a correct digital signature; create a new document based on the file or IO stream instead.
|
93
|
+
#
|
94
|
+
# +signature+::
|
95
|
+
# Can either be a signature object or +nil+. Providing a signature object provides for
|
96
|
+
# more control, e.g.:
|
97
|
+
#
|
98
|
+
# * Setting values for optional fields like /Reason and /Location.
|
99
|
+
# * Indirectly specifying which signature field should be used.
|
100
|
+
#
|
101
|
+
# If the +signature+ is not associated with an AcroForm signature field, a new signature
|
102
|
+
# field is created and added to the main AcroForm object, creating that if necessary.
|
103
|
+
#
|
104
|
+
# If the associated signature field doesn't have a widget, a non-visible one is created on
|
105
|
+
# the first page.
|
106
|
+
#
|
107
|
+
# +handler+::
|
108
|
+
# The signature handler that provides the necessary methods for signing, see
|
109
|
+
# DefaultHandler.
|
110
|
+
#
|
111
|
+
# +write_options+::
|
112
|
+
# These options will be passed on to the HexaPDF::Document#write command. Note that
|
113
|
+
# +incremental+ will be automatically set if signing an already existing file.
|
114
|
+
def add(file_or_io, handler, signature: nil, **write_options)
|
115
|
+
signature ||= @document.add({Type: :Sig})
|
116
|
+
signature[:Filter] = handler.filter_name
|
117
|
+
signature[:SubFilter] = handler.sub_filter_name
|
118
|
+
signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
|
119
|
+
signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
|
120
|
+
|
121
|
+
# Prepare signature field
|
122
|
+
form = @document.acro_form(create: true)
|
123
|
+
form.signature_flag(:signatures_exist)
|
124
|
+
|
125
|
+
signature_field = each.find {|value| value == signature }
|
126
|
+
unless signature_field
|
127
|
+
signature_field = form.create_signature_field(generate_field_name)
|
128
|
+
signature_field.field_value = signature
|
129
|
+
end
|
130
|
+
|
131
|
+
if signature_field.each_widget.to_a.empty?
|
132
|
+
signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
|
133
|
+
end
|
134
|
+
|
135
|
+
io = if file_or_io.kind_of?(String)
|
136
|
+
File.open(file_or_io, 'w+')
|
137
|
+
else
|
138
|
+
file_or_io
|
139
|
+
end
|
140
|
+
|
141
|
+
# Save the current state so that we can determine the correct /ByteRange value and set the
|
142
|
+
# values
|
143
|
+
section = @document.write(io, incremental: true, **write_options)
|
144
|
+
data = section.map {|oid, _gen, entry| [entry.pos.to_i, oid] }.sort
|
145
|
+
index = data.index {|_pos, oid| oid == signature.oid }
|
146
|
+
signature_offset = data[index][0]
|
147
|
+
signature_length = data[index + 1][0] - data[index][0]
|
148
|
+
io.pos = signature_offset
|
149
|
+
signature_data = io.read(signature_length)
|
150
|
+
|
151
|
+
io.rewind
|
152
|
+
file_data = io.read
|
153
|
+
|
154
|
+
# Calculate the offsets for the /ByteRange
|
155
|
+
contents_offset = signature_offset + signature_data.index('Contents(') + 8
|
156
|
+
offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
|
157
|
+
length2 = file_data.size - offset2
|
158
|
+
signature[:ByteRange] = [0, contents_offset, offset2, length2]
|
159
|
+
|
160
|
+
# Set the correct /ByteRange value
|
161
|
+
signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
|
162
|
+
length = match.size
|
163
|
+
result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
|
164
|
+
result.ljust(length)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Now everything besides the /Contents value is correct, so we can read the contents for
|
168
|
+
# signing
|
169
|
+
file_data[signature_offset, signature_length] = signature_data
|
170
|
+
signed_contents = file_data[0, contents_offset] << file_data[offset2, length2]
|
171
|
+
signature[:Contents] = handler.sign(signed_contents)
|
172
|
+
|
173
|
+
# Set the correct /Contents value as hexstring
|
174
|
+
signature_data.sub!(/Contents\(0+\)/) do |match|
|
175
|
+
length = match.size
|
176
|
+
result = "Contents<#{signature[:Contents].unpack1('H*')}"
|
177
|
+
"#{result.ljust(length - 1, '0')}>"
|
178
|
+
end
|
179
|
+
|
180
|
+
io.pos = signature_offset
|
181
|
+
io.write(signature_data)
|
182
|
+
|
183
|
+
signature
|
184
|
+
ensure
|
185
|
+
io.close if io && io != file_or_io
|
186
|
+
end
|
187
|
+
|
188
|
+
# :call-seq:
|
189
|
+
# signatures.each {|signature| block } -> signatures
|
190
|
+
# signatures.each -> Enumerator
|
191
|
+
#
|
192
|
+
# Iterates over all signatures in the order they are found.
|
193
|
+
def each
|
194
|
+
return to_enum(__method__) unless block_given?
|
195
|
+
|
196
|
+
return [] unless (form = @document.acro_form)
|
197
|
+
form.each_field do |field|
|
198
|
+
yield(field.field_value) if field.field_type == :Sig && field.field_value
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the number of signatures in the PDF document. May be zero if the document has no
|
203
|
+
# signatures.
|
204
|
+
def count
|
205
|
+
each.to_a.size
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
# Generates a field name for a signature field.
|
211
|
+
def generate_field_name
|
212
|
+
index = (@document.acro_form.each_field.
|
213
|
+
map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
|
214
|
+
max || 0) + 1
|
215
|
+
"Signature#{index}"
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -1069,7 +1069,8 @@ module HexaPDF
|
|
1069
1069
|
|
1070
1070
|
# The font size scaled appropriately.
|
1071
1071
|
def scaled_font_size
|
1072
|
-
@scaled_font_size ||= calculated_font_size
|
1072
|
+
@scaled_font_size ||= calculated_font_size * font.pdf_object.glyph_scaling_factor *
|
1073
|
+
scaled_horizontal_scaling
|
1073
1074
|
end
|
1074
1075
|
|
1075
1076
|
# The character spacing scaled appropriately.
|
data/lib/hexapdf/type/font.rb
CHANGED
@@ -41,6 +41,10 @@ module HexaPDF
|
|
41
41
|
|
42
42
|
# Represents a Type 3 font.
|
43
43
|
#
|
44
|
+
# Note: We assume the /FontMatrix is only used for scaling, i.e. of the form [x 0 0 +/-x 0 0].
|
45
|
+
# If it is of a different form, things won't work correctly. This will be handled once such a
|
46
|
+
# case is found.
|
47
|
+
#
|
44
48
|
# See: PDF1.7 s9.6.5
|
45
49
|
class FontType3 < FontSimple
|
46
50
|
|
@@ -51,6 +55,22 @@ module HexaPDF
|
|
51
55
|
define_field :CharProcs, type: Dictionary, required: true
|
52
56
|
define_field :Resources, type: Dictionary, version: '1.2'
|
53
57
|
|
58
|
+
# Returns the bounding box of the font.
|
59
|
+
def bounding_box
|
60
|
+
matrix = self[:FontMatrix]
|
61
|
+
bbox = self[:FontBBox].value
|
62
|
+
if matrix[3] < 0 # Some writers invert the y-axis
|
63
|
+
bbox = bbox.dup
|
64
|
+
bbox[1], bbox[3] = -bbox[3], -bbox[1]
|
65
|
+
end
|
66
|
+
bbox
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the glyph scaling factor for transforming from glyph space to text space.
|
70
|
+
def glyph_scaling_factor
|
71
|
+
self[:FontMatrix][0]
|
72
|
+
end
|
73
|
+
|
54
74
|
private
|
55
75
|
|
56
76
|
def perform_validation
|
@@ -0,0 +1,125 @@
|
|
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-2021 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/type/signature'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module Type
|
42
|
+
class Signature
|
43
|
+
|
44
|
+
# The signature handler for the adbe.pkcs7.detached sub-filter.
|
45
|
+
class AdbePkcs7Detached < Handler
|
46
|
+
|
47
|
+
# Creates a new signature handler for the given signature dictionary.
|
48
|
+
def initialize(signature_dict)
|
49
|
+
super
|
50
|
+
@pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the common name of the signer.
|
54
|
+
def signer_name
|
55
|
+
signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the time of signing.
|
59
|
+
def signing_time
|
60
|
+
signer_info.signed_time rescue super
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the certificate chain.
|
64
|
+
def certificate_chain
|
65
|
+
@pkcs7.certificates
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
|
69
|
+
def signer_certificate
|
70
|
+
info = signer_info
|
71
|
+
certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
|
75
|
+
def signer_info
|
76
|
+
@pkcs7.signers.first
|
77
|
+
end
|
78
|
+
|
79
|
+
# Verifies the signature using the provided OpenSSL::X509::Store object.
|
80
|
+
def verify(store, allow_self_signed: false)
|
81
|
+
result = VerificationResult.new
|
82
|
+
|
83
|
+
signer_info = self.signer_info
|
84
|
+
signer_certificate = self.signer_certificate
|
85
|
+
certificate_chain = self.certificate_chain
|
86
|
+
|
87
|
+
if certificate_chain.empty?
|
88
|
+
result.log(:error, "No certificates found in signature")
|
89
|
+
return result
|
90
|
+
end
|
91
|
+
|
92
|
+
if @pkcs7.signers.size != 1
|
93
|
+
result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
|
94
|
+
end
|
95
|
+
|
96
|
+
unless signer_certificate
|
97
|
+
result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
|
98
|
+
"not found in certificates stored in PKCS7 object")
|
99
|
+
return result
|
100
|
+
end
|
101
|
+
|
102
|
+
key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
|
103
|
+
unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
|
104
|
+
result.log(:error, "Certificate key usage is missing 'Digital Signature'")
|
105
|
+
end
|
106
|
+
|
107
|
+
verify_signing_time(result)
|
108
|
+
|
109
|
+
store.verify_callback = store_verification_callback(result,
|
110
|
+
allow_self_signed: allow_self_signed)
|
111
|
+
if @pkcs7.verify(certificate_chain, store, signature_dict.signed_data,
|
112
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
|
113
|
+
result.log(:info, "Signature valid")
|
114
|
+
else
|
115
|
+
result.log(:error, "Signature verification failed")
|
116
|
+
end
|
117
|
+
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/hexapdf/version.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
require 'hexapdf/content/graphics_state'
|
5
|
+
require 'ostruct'
|
5
6
|
|
6
7
|
# Dummy class used as wrapper so that constant lookup works correctly
|
7
8
|
class GraphicsStateWrapper < Minitest::Spec
|
@@ -146,6 +147,13 @@ class GraphicsStateWrapper < Minitest::Spec
|
|
146
147
|
it "fails when restoring the graphics state if the stack is empty" do
|
147
148
|
assert_raises(HexaPDF::Error) { @gs.restore }
|
148
149
|
end
|
149
|
-
end
|
150
150
|
|
151
|
+
it "uses the correct glyph to text space scaling" do
|
152
|
+
font = OpenStruct.new
|
153
|
+
font.glyph_scaling_factor = 0.002
|
154
|
+
@gs.font = font
|
155
|
+
@gs.font_size = 10
|
156
|
+
assert_equal(0.02, @gs.scaled_font_size)
|
157
|
+
end
|
158
|
+
end
|
151
159
|
end
|
@@ -4,6 +4,7 @@ require 'test_helper'
|
|
4
4
|
require 'hexapdf/content/operator'
|
5
5
|
require 'hexapdf/content/processor'
|
6
6
|
require 'hexapdf/serializer'
|
7
|
+
require 'ostruct'
|
7
8
|
|
8
9
|
describe HexaPDF::Content::Operator::BaseOperator do
|
9
10
|
before do
|
@@ -190,9 +191,11 @@ end
|
|
190
191
|
|
191
192
|
describe_operator :SetGraphicsStateParameters, :gs do
|
192
193
|
it "applies parameters from an ExtGState dictionary" do
|
194
|
+
font = OpenStruct.new
|
195
|
+
font.glyph_scaling_factor = 0.01
|
193
196
|
@processor.resources[:ExtGState] = {Name: {LW: 10, LC: 2, LJ: 2, ML: 2, D: [[3, 5], 2],
|
194
197
|
RI: 2, SA: true, BM: :Multiply, CA: 0.5, ca: 0.5,
|
195
|
-
AIS: true, TK: false, Font: [
|
198
|
+
AIS: true, TK: false, Font: [font, 10]}}
|
196
199
|
@processor.resources.define_singleton_method(:document) do
|
197
200
|
Object.new.tap {|obj| obj.define_singleton_method(:deref) {|o| o } }
|
198
201
|
end
|
@@ -210,7 +213,7 @@ describe_operator :SetGraphicsStateParameters, :gs do
|
|
210
213
|
assert_equal(0.5, gs.stroke_alpha)
|
211
214
|
assert_equal(0.5, gs.fill_alpha)
|
212
215
|
assert(gs.alpha_source)
|
213
|
-
assert_equal(
|
216
|
+
assert_equal(font, gs.font)
|
214
217
|
assert_equal(10, gs.font_size)
|
215
218
|
refute(gs.text_knockout)
|
216
219
|
end
|
@@ -448,7 +451,9 @@ describe_operator :SetFontAndSize, :Tf do
|
|
448
451
|
self[:Font] && self[:Font][name]
|
449
452
|
end
|
450
453
|
|
451
|
-
|
454
|
+
font = OpenStruct.new
|
455
|
+
font.glyph_scaling_factor = 0.01
|
456
|
+
@processor.resources[:Font] = {F1: font}
|
452
457
|
invoke(:F1, 10)
|
453
458
|
assert_equal(@processor.resources.font(:F1), @processor.graphics_state.font)
|
454
459
|
assert_equal(10, @processor.graphics_state.font_size)
|
@@ -597,6 +597,11 @@ end
|
|
597
597
|
describe HexaPDF::Layout::Style do
|
598
598
|
before do
|
599
599
|
@style = HexaPDF::Layout::Style.new
|
600
|
+
@style.font = Object.new.tap do |obj|
|
601
|
+
obj.define_singleton_method(:pdf_object) do
|
602
|
+
Object.new.tap {|pdf| pdf.define_singleton_method(:glyph_scaling_factor) { 0.001 } }
|
603
|
+
end
|
604
|
+
end
|
600
605
|
end
|
601
606
|
|
602
607
|
it "can assign values on initialization" do
|
@@ -644,6 +649,7 @@ describe HexaPDF::Layout::Style do
|
|
644
649
|
end
|
645
650
|
|
646
651
|
it "has several simple and dynamically generated properties with default values" do
|
652
|
+
@style = HexaPDF::Layout::Style.new
|
647
653
|
assert_raises(HexaPDF::Error) { @style.font }
|
648
654
|
assert_equal(10, @style.font_size)
|
649
655
|
assert_equal(0, @style.character_spacing)
|
@@ -725,6 +731,11 @@ describe HexaPDF::Layout::Style do
|
|
725
731
|
font = Object.new
|
726
732
|
font.define_singleton_method(:scaling_factor) { 1 }
|
727
733
|
font.define_singleton_method(:wrapped_font) { wrapped_font }
|
734
|
+
font.define_singleton_method(:pdf_object) do
|
735
|
+
obj = Object.new
|
736
|
+
obj.define_singleton_method(:glyph_scaling_factor) { 0.001 }
|
737
|
+
obj
|
738
|
+
end
|
728
739
|
@style.font = font
|
729
740
|
end
|
730
741
|
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.19.
|
43
|
+
<</Producer(HexaPDF version 0.19.1)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.19.
|
75
|
+
<</Producer(HexaPDF version 0.19.1)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -9,10 +9,25 @@ describe HexaPDF::Type::FontType3 do
|
|
9
9
|
@doc = HexaPDF::Document.new
|
10
10
|
@font = @doc.add({Type: :Font, Subtype: :Type3, Encoding: :WinAnsiEncoding,
|
11
11
|
FirstChar: 32, LastChar: 34, Widths: [600, 0, 700],
|
12
|
-
FontBBox: [0,
|
12
|
+
FontBBox: [0, 100, 100, 0], FontMatrix: [0.002, 0, 0, 0.002, 0, 0],
|
13
13
|
CharProcs: {}})
|
14
14
|
end
|
15
15
|
|
16
|
+
describe "bounding_box" do
|
17
|
+
it "returns the font's bounding box" do
|
18
|
+
assert_equal([0, 0, 100, 100], @font.bounding_box)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "inverts the y-values if necessary based on /FontMatrix" do
|
22
|
+
@font[:FontMatrix][3] *= -1
|
23
|
+
assert_equal([0, -100, 100, 0], @font.bounding_box)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the glyph scaling factor" do
|
28
|
+
assert_equal(0.002, @font.glyph_scaling_factor)
|
29
|
+
end
|
30
|
+
|
16
31
|
describe "validation" do
|
17
32
|
it "works for valid objects" do
|
18
33
|
assert(@font.validate)
|