hexapdf 0.19.0 → 0.20.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/data/hexapdf/cert/demo_cert.rb +22 -0
  4. data/data/hexapdf/cert/root-ca.crt +119 -0
  5. data/data/hexapdf/cert/signing.crt +125 -0
  6. data/data/hexapdf/cert/signing.key +52 -0
  7. data/data/hexapdf/cert/sub-ca.crt +125 -0
  8. data/lib/hexapdf/cli/info.rb +21 -1
  9. data/lib/hexapdf/configuration.rb +26 -0
  10. data/lib/hexapdf/content/graphics_state.rb +24 -5
  11. data/lib/hexapdf/content/processor.rb +1 -1
  12. data/lib/hexapdf/document/signatures.rb +327 -0
  13. data/lib/hexapdf/document.rb +26 -0
  14. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -2
  15. data/lib/hexapdf/importer.rb +1 -1
  16. data/lib/hexapdf/layout/style.rb +2 -1
  17. data/lib/hexapdf/object.rb +5 -3
  18. data/lib/hexapdf/parser.rb +21 -9
  19. data/lib/hexapdf/rectangle.rb +0 -6
  20. data/lib/hexapdf/revision.rb +13 -6
  21. data/lib/hexapdf/type/acro_form/appearance_generator.rb +2 -4
  22. data/lib/hexapdf/type/acro_form/field.rb +2 -0
  23. data/lib/hexapdf/type/acro_form/form.rb +9 -1
  24. data/lib/hexapdf/type/annotation.rb +36 -3
  25. data/lib/hexapdf/type/font.rb +5 -0
  26. data/lib/hexapdf/type/font_simple.rb +1 -1
  27. data/lib/hexapdf/type/font_type3.rb +20 -0
  28. data/lib/hexapdf/type/object_stream.rb +3 -1
  29. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
  30. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +99 -0
  31. data/lib/hexapdf/type/signature/handler.rb +112 -0
  32. data/lib/hexapdf/type/signature/verification_result.rb +92 -0
  33. data/lib/hexapdf/type/signature.rb +236 -0
  34. data/lib/hexapdf/type.rb +1 -0
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +24 -10
  37. data/test/hexapdf/content/test_graphics_state.rb +9 -1
  38. data/test/hexapdf/content/test_operator.rb +8 -3
  39. data/test/hexapdf/content/test_processor.rb +1 -1
  40. data/test/hexapdf/document/test_signatures.rb +225 -0
  41. data/test/hexapdf/encryption/test_standard_security_handler.rb +8 -6
  42. data/test/hexapdf/layout/test_style.rb +11 -0
  43. data/test/hexapdf/test_document.rb +28 -0
  44. data/test/hexapdf/test_object.rb +7 -2
  45. data/test/hexapdf/test_parser.rb +14 -0
  46. data/test/hexapdf/test_rectangle.rb +0 -7
  47. data/test/hexapdf/test_revision.rb +44 -14
  48. data/test/hexapdf/test_writer.rb +44 -14
  49. data/test/hexapdf/type/acro_form/test_field.rb +11 -1
  50. data/test/hexapdf/type/acro_form/test_form.rb +5 -0
  51. data/test/hexapdf/type/signature/common.rb +71 -0
  52. data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +99 -0
  53. data/test/hexapdf/type/signature/test_adbe_x509_rsa_sha1.rb +66 -0
  54. data/test/hexapdf/type/signature/test_handler.rb +76 -0
  55. data/test/hexapdf/type/signature/test_verification_result.rb +47 -0
  56. data/test/hexapdf/type/test_annotation.rb +40 -2
  57. data/test/hexapdf/type/test_font.rb +4 -0
  58. data/test/hexapdf/type/test_font_simple.rb +5 -5
  59. data/test/hexapdf/type/test_font_type3.rb +16 -1
  60. data/test/hexapdf/type/test_object_stream.rb +9 -0
  61. data/test/hexapdf/type/test_signature.rb +131 -0
  62. metadata +21 -33
  63. data/test/data/cert/create.sh +0 -171
  64. data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF39.pem +0 -119
  65. data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF3A.pem +0 -125
  66. data/test/data/cert/root-ca/db/crlnumber +0 -1
  67. data/test/data/cert/root-ca/db/index +0 -2
  68. data/test/data/cert/root-ca/db/index.attr +0 -1
  69. data/test/data/cert/root-ca/db/index.attr.old +0 -1
  70. data/test/data/cert/root-ca/db/index.old +0 -1
  71. data/test/data/cert/root-ca/db/serial +0 -1
  72. data/test/data/cert/root-ca/db/serial.old +0 -1
  73. data/test/data/cert/root-ca/private/root-ca.key +0 -52
  74. data/test/data/cert/root-ca/root-ca.conf +0 -65
  75. data/test/data/cert/root-ca/root-ca.crt +0 -119
  76. data/test/data/cert/root-ca/root-ca.csr +0 -28
  77. data/test/data/cert/signature-1-pkcs7-detached.pdf +0 -182
  78. data/test/data/cert/sub-ca/certs/453FF080E3EDCD6A388D5368DFC320D9.pem +0 -125
  79. data/test/data/cert/sub-ca/db/crlnumber +0 -1
  80. data/test/data/cert/sub-ca/db/index +0 -1
  81. data/test/data/cert/sub-ca/db/index.attr +0 -1
  82. data/test/data/cert/sub-ca/db/index.old +0 -0
  83. data/test/data/cert/sub-ca/db/serial +0 -1
  84. data/test/data/cert/sub-ca/db/serial.old +0 -1
  85. data/test/data/cert/sub-ca/private/signing.key +0 -52
  86. data/test/data/cert/sub-ca/private/sub-ca.key +0 -52
  87. data/test/data/cert/sub-ca/signing.crt +0 -125
  88. data/test/data/cert/sub-ca/signing.csr +0 -28
  89. data/test/data/cert/sub-ca/signing.p12 +0 -0
  90. data/test/data/cert/sub-ca/sub-ca.conf +0 -65
  91. data/test/data/cert/sub-ca/sub-ca.crt +0 -125
  92. 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 necessary.
136
- def appearance(type = :normal)
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[self[:AS]]
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.
@@ -98,6 +98,11 @@ module HexaPDF
98
98
  embedded?
99
99
  end
100
100
 
101
+ # Returns the glyph scaling factor for transforming from glyph space to text space.
102
+ def glyph_scaling_factor
103
+ 0.001
104
+ end
105
+
101
106
  private
102
107
 
103
108
  # Parses and caches the ToUnicode CMap.
@@ -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 NotImplementedError
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