hexapdf 0.19.0 → 0.20.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 +69 -0
- data/data/hexapdf/cert/demo_cert.rb +22 -0
- data/data/hexapdf/cert/root-ca.crt +119 -0
- data/data/hexapdf/cert/signing.crt +125 -0
- data/data/hexapdf/cert/signing.key +52 -0
- data/data/hexapdf/cert/sub-ca.crt +125 -0
- data/lib/hexapdf/cli/info.rb +21 -1
- data/lib/hexapdf/configuration.rb +26 -0
- data/lib/hexapdf/content/graphics_state.rb +24 -5
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/document/signatures.rb +327 -0
- data/lib/hexapdf/document.rb +26 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -2
- data/lib/hexapdf/importer.rb +1 -1
- data/lib/hexapdf/layout/style.rb +2 -1
- data/lib/hexapdf/object.rb +5 -3
- data/lib/hexapdf/parser.rb +21 -9
- data/lib/hexapdf/rectangle.rb +0 -6
- data/lib/hexapdf/revision.rb +13 -6
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +2 -4
- data/lib/hexapdf/type/acro_form/field.rb +2 -0
- data/lib/hexapdf/type/acro_form/form.rb +9 -1
- data/lib/hexapdf/type/annotation.rb +36 -3
- data/lib/hexapdf/type/font.rb +5 -0
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_type3.rb +20 -0
- data/lib/hexapdf/type/object_stream.rb +3 -1
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +99 -0
- data/lib/hexapdf/type/signature/handler.rb +112 -0
- data/lib/hexapdf/type/signature/verification_result.rb +92 -0
- data/lib/hexapdf/type/signature.rb +236 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +24 -10
- data/test/hexapdf/content/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/content/test_processor.rb +1 -1
- data/test/hexapdf/document/test_signatures.rb +225 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +8 -6
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/test_document.rb +28 -0
- data/test/hexapdf/test_object.rb +7 -2
- data/test/hexapdf/test_parser.rb +14 -0
- data/test/hexapdf/test_rectangle.rb +0 -7
- data/test/hexapdf/test_revision.rb +44 -14
- data/test/hexapdf/test_writer.rb +44 -14
- data/test/hexapdf/type/acro_form/test_field.rb +11 -1
- data/test/hexapdf/type/acro_form/test_form.rb +5 -0
- data/test/hexapdf/type/signature/common.rb +71 -0
- data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +99 -0
- data/test/hexapdf/type/signature/test_adbe_x509_rsa_sha1.rb +66 -0
- data/test/hexapdf/type/signature/test_handler.rb +76 -0
- data/test/hexapdf/type/signature/test_verification_result.rb +47 -0
- data/test/hexapdf/type/test_annotation.rb +40 -2
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_simple.rb +5 -5
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- data/test/hexapdf/type/test_object_stream.rb +9 -0
- data/test/hexapdf/type/test_signature.rb +131 -0
- metadata +21 -33
- 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
@@ -78,6 +78,25 @@ module HexaPDF
|
|
78
78
|
self[:D] || self[:N]
|
79
79
|
end
|
80
80
|
|
81
|
+
APPEARANCE_TYPE_TO_KEY = {normal: :N, rollover: :R, down: :D}.freeze #:nodoc:
|
82
|
+
|
83
|
+
# Sets the appearance of the given appearance +type+, which can either be :normal, :rollover
|
84
|
+
# or :down, to +appearance+.
|
85
|
+
#
|
86
|
+
# If the +state_name+ argument is provided, the +appearance+ is stored under the
|
87
|
+
# +state_name+ key in a sub-dictionary of the appearance.
|
88
|
+
def set_appearance(appearance, type: :normal, state_name: nil)
|
89
|
+
key = APPEARANCE_TYPE_TO_KEY.fetch(type) do
|
90
|
+
raise ArgumentError, "Invalid value for type specified: #{type.inspect}"
|
91
|
+
end
|
92
|
+
if state_name
|
93
|
+
self[key] = {} unless value[key].kind_of?(Hash)
|
94
|
+
self[key][state_name] = appearance
|
95
|
+
else
|
96
|
+
self[key] = appearance
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
81
100
|
end
|
82
101
|
|
83
102
|
# Border style dictionary used by various annotation types.
|
@@ -132,11 +151,12 @@ module HexaPDF
|
|
132
151
|
# Returns the annotation's appearance stream of the given type (:normal, :rollover, or :down)
|
133
152
|
# or +nil+ if it doesn't exist.
|
134
153
|
#
|
135
|
-
# The appearance state is taken into account if
|
136
|
-
|
154
|
+
# The appearance state in /AS or the one provided via +state_name+ is taken into account if
|
155
|
+
# necessary.
|
156
|
+
def appearance(type: :normal, state_name: self[:AS])
|
137
157
|
entry = appearance_dict&.send("#{type}_appearance")
|
138
158
|
if entry.kind_of?(HexaPDF::Dictionary) && !entry.kind_of?(HexaPDF::Stream)
|
139
|
-
entry = entry[
|
159
|
+
entry = entry[state_name]
|
140
160
|
end
|
141
161
|
return unless entry.kind_of?(HexaPDF::Stream)
|
142
162
|
|
@@ -149,6 +169,19 @@ module HexaPDF
|
|
149
169
|
end
|
150
170
|
alias appearance? appearance
|
151
171
|
|
172
|
+
# Creates an empty appearance stream (a Form XObject) of the given type (:normal, :rollover,
|
173
|
+
# or :down) and returns it. If an appearance stream already exist, it is overwritten.
|
174
|
+
#
|
175
|
+
# If there can be multiple appearance streams for the annotation, use the +state_name+
|
176
|
+
# argument to provide the appearance state name.
|
177
|
+
def create_appearance(type: :normal, state_name: self[:AS])
|
178
|
+
xobject = document.add({Type: :XObject, Subtype: :Form,
|
179
|
+
BBox: [0, 0, self[:Rect].width, self[:Rect].height]})
|
180
|
+
self[:AP] ||= {}
|
181
|
+
appearance_dict.set_appearance(xobject, type: type, state_name: state_name)
|
182
|
+
xobject
|
183
|
+
end
|
184
|
+
|
152
185
|
private
|
153
186
|
|
154
187
|
# Helper method for bit field getter access.
|
data/lib/hexapdf/type/font.rb
CHANGED
@@ -133,7 +133,7 @@ module HexaPDF
|
|
133
133
|
#
|
134
134
|
# This method has to be implemented in subclasses.
|
135
135
|
def encoding_from_font
|
136
|
-
raise
|
136
|
+
raise "Needs to be implemented in subclass"
|
137
137
|
end
|
138
138
|
|
139
139
|
# Uses the given base encoding and the differences array to create a DifferenceEncoding
|
@@ -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
|
@@ -178,7 +178,9 @@ module HexaPDF
|
|
178
178
|
# Due to a bug in Adobe Acrobat, the Catalog may not be in an object stream if the
|
179
179
|
# document is encrypted
|
180
180
|
if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) || obj == encrypt_dict ||
|
181
|
-
(encrypt_dict && obj.type == :Catalog)
|
181
|
+
(encrypt_dict && obj.type == :Catalog) ||
|
182
|
+
obj.type == :Sig || obj.type == :DocTimeStamp ||
|
183
|
+
(obj.respond_to?(:key?) && obj.key?(:ByteRange) && obj.key?(:Contents))
|
182
184
|
delete_object(objects[index])
|
183
185
|
next
|
184
186
|
end
|
@@ -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.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
|
@@ -0,0 +1,99 @@
|
|
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.x509.rsa_sha1 sub-filter.
|
45
|
+
#
|
46
|
+
# Since this handler is deprecated with PDF 2.0 it only provides the implementation for
|
47
|
+
# reading and verifying signatures.
|
48
|
+
class AdbeX509RsaSha1 < Handler
|
49
|
+
|
50
|
+
# Returns the certificate chain.
|
51
|
+
def certificate_chain
|
52
|
+
return [] unless signature_dict.key?(:Cert)
|
53
|
+
[signature_dict[:Cert]].flatten.map {|str| OpenSSL::X509::Certificate.new(str) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
|
57
|
+
def signer_certificate
|
58
|
+
certificate_chain.first
|
59
|
+
end
|
60
|
+
|
61
|
+
# Verifies the signature using the provided OpenSSL::X509::Store object.
|
62
|
+
def verify(store, allow_self_signed: false)
|
63
|
+
result = VerificationResult.new
|
64
|
+
|
65
|
+
signer_certificate = self.signer_certificate
|
66
|
+
certificate_chain = self.certificate_chain
|
67
|
+
|
68
|
+
if certificate_chain.empty?
|
69
|
+
result.log(:error, "No certificates for verification found")
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
|
73
|
+
signature = OpenSSL::ASN1.decode(signature_dict.contents)
|
74
|
+
if signature.tag != OpenSSL::ASN1::OCTET_STRING
|
75
|
+
result.log(:error, "PKCS1 signature object invalid, octet string expected")
|
76
|
+
return result
|
77
|
+
end
|
78
|
+
|
79
|
+
verify_signing_time(result)
|
80
|
+
|
81
|
+
store.verify_callback = store_verification_callback(result,
|
82
|
+
allow_self_signed: allow_self_signed)
|
83
|
+
store.verify(signer_certificate, certificate_chain)
|
84
|
+
|
85
|
+
if signer_certificate.public_key.verify(OpenSSL::Digest.new('SHA1'),
|
86
|
+
signature.value, signature_dict.signed_data)
|
87
|
+
result.log(:info, "Signature valid")
|
88
|
+
else
|
89
|
+
result.log(:error, "Signature verification failed")
|
90
|
+
end
|
91
|
+
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,112 @@
|
|
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 'hexapdf/type/signature'
|
38
|
+
|
39
|
+
module HexaPDF
|
40
|
+
module Type
|
41
|
+
class Signature
|
42
|
+
|
43
|
+
# The base signature handler providing common functionality.
|
44
|
+
#
|
45
|
+
# Specific signature handler need to override methods if necessary and implement the needed
|
46
|
+
# ones that don't have a default implementation.
|
47
|
+
class Handler
|
48
|
+
|
49
|
+
# The signature dictionary used by the handler.
|
50
|
+
attr_reader :signature_dict
|
51
|
+
|
52
|
+
# Creates a new signature handler for the given signature dictionary.
|
53
|
+
def initialize(signature_dict)
|
54
|
+
@signature_dict = signature_dict
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the common name of the signer (/Name field of the signature dictionary).
|
58
|
+
def signer_name
|
59
|
+
@signature_dict[:Name]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the time of signing (/M field of the signature dictionary).
|
63
|
+
def signing_time
|
64
|
+
@signature_dict[:M]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the certificate chain.
|
68
|
+
#
|
69
|
+
# Needs to be implemented by specific handlers.
|
70
|
+
def certificate_chain
|
71
|
+
raise "Needs to be implemented by specific handlers"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the certificate used for signing.
|
75
|
+
#
|
76
|
+
# Needs to be implemented by specific handlers.
|
77
|
+
def signer_certificate
|
78
|
+
raise "Needs to be implemented by specific handlers"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the block that should be used as the OpenSSL::X509::Store verification callback.
|
82
|
+
#
|
83
|
+
# +result+:: The VerificationResult object that should be updated if problems are found.
|
84
|
+
#
|
85
|
+
# +allow_self_signed+:: Specifies whether self-signed certificates are allowed.
|
86
|
+
def store_verification_callback(result, allow_self_signed: false)
|
87
|
+
lambda do |_success, context|
|
88
|
+
if context.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
89
|
+
context.error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
|
90
|
+
result.log(allow_self_signed ? :info : :error, "Self-signed certificate found")
|
91
|
+
end
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
# Verifies that the signing time was within the validity period of the signer certificate.
|
100
|
+
def verify_signing_time(result)
|
101
|
+
time = signing_time
|
102
|
+
cert = signer_certificate
|
103
|
+
if time && (time < cert.not_before || time > cert.not_after)
|
104
|
+
result.log(:error, "Signer certificate not valid at signing time")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,92 @@
|
|
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 'hexapdf/type/signature'
|
38
|
+
|
39
|
+
module HexaPDF
|
40
|
+
module Type
|
41
|
+
class Signature
|
42
|
+
|
43
|
+
# Holds the result information when verifying a signature.
|
44
|
+
class VerificationResult
|
45
|
+
|
46
|
+
# :nodoc:
|
47
|
+
MESSAGE_SORT_MAP = {
|
48
|
+
info: {warning: 1, error: 1, info: 0},
|
49
|
+
warning: {info: -1, error: 1, warning: 0},
|
50
|
+
error: {info: -1, warning: -1, error: 0},
|
51
|
+
}
|
52
|
+
|
53
|
+
# This structure represents a single status message, containing the type (:info, :warning,
|
54
|
+
# :error) and the content of the message.
|
55
|
+
Message = Struct.new(:type, :content) do
|
56
|
+
def <=>(other)
|
57
|
+
MESSAGE_SORT_MAP[type][other.type]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# An array with all result messages.
|
62
|
+
attr_reader :messages
|
63
|
+
|
64
|
+
# Creates an empty result object.
|
65
|
+
def initialize
|
66
|
+
@messages = []
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns +true+ if there are no error messages.
|
70
|
+
def success?
|
71
|
+
@messages.none? {|message| message.type == :error }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns +true+ if there is at least one error message.
|
75
|
+
def failure?
|
76
|
+
!success?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Adds a new message of the given type to this result object.
|
80
|
+
#
|
81
|
+
# +type+:: One of :info, :warning or :error.
|
82
|
+
#
|
83
|
+
# +content+:: The log message.
|
84
|
+
def log(type, content)
|
85
|
+
@messages << Message.new(type, content)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|