hexapdf 0.19.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
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