hexapdf 0.28.0 → 0.31.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/cli/command.rb +16 -1
  5. data/lib/hexapdf/cli/info.rb +9 -1
  6. data/lib/hexapdf/cli/inspect.rb +2 -2
  7. data/lib/hexapdf/composer.rb +76 -28
  8. data/lib/hexapdf/configuration.rb +29 -16
  9. data/lib/hexapdf/dictionary_fields.rb +13 -4
  10. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  11. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  12. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  13. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  14. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  15. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  16. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  17. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  18. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  19. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  20. data/lib/hexapdf/digital_signature.rb +56 -0
  21. data/lib/hexapdf/document/pages.rb +31 -18
  22. data/lib/hexapdf/document.rb +29 -15
  23. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  24. data/lib/hexapdf/filter/flate_decode.rb +20 -8
  25. data/lib/hexapdf/layout/page_style.rb +144 -0
  26. data/lib/hexapdf/layout.rb +1 -0
  27. data/lib/hexapdf/task/optimize.rb +8 -6
  28. data/lib/hexapdf/type/font_simple.rb +14 -2
  29. data/lib/hexapdf/type/object_stream.rb +7 -2
  30. data/lib/hexapdf/type/outline.rb +1 -1
  31. data/lib/hexapdf/type/outline_item.rb +1 -1
  32. data/lib/hexapdf/type/page.rb +29 -8
  33. data/lib/hexapdf/type/xref_stream.rb +11 -4
  34. data/lib/hexapdf/type.rb +0 -1
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +1 -1
  37. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  38. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  39. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  40. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  41. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  42. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  43. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  44. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  45. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  46. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  47. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  48. data/test/hexapdf/document/test_pages.rb +25 -0
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  50. data/test/hexapdf/filter/test_flate_decode.rb +19 -5
  51. data/test/hexapdf/layout/test_page_style.rb +70 -0
  52. data/test/hexapdf/task/test_optimize.rb +11 -9
  53. data/test/hexapdf/test_composer.rb +35 -10
  54. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  55. data/test/hexapdf/test_document.rb +1 -1
  56. data/test/hexapdf/test_writer.rb +8 -8
  57. data/test/hexapdf/type/test_font_simple.rb +18 -6
  58. data/test/hexapdf/type/test_object_stream.rb +16 -7
  59. data/test/hexapdf/type/test_outline.rb +3 -1
  60. data/test/hexapdf/type/test_outline_item.rb +3 -1
  61. data/test/hexapdf/type/test_page.rb +42 -11
  62. data/test/hexapdf/type/test_xref_stream.rb +6 -1
  63. metadata +27 -15
  64. data/lib/hexapdf/document/signatures.rb +0 -546
  65. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  66. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  67. data/lib/hexapdf/type/signature/handler.rb +0 -140
  68. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -0,0 +1,317 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'openssl'
38
+ require 'hexapdf/error'
39
+ require 'hexapdf/version'
40
+ require 'stringio'
41
+
42
+ module HexaPDF
43
+ module DigitalSignature
44
+ module Signing
45
+
46
+ # This is the default signing handler which provides the ability to sign a document with the
47
+ # adbe.pkcs7.detached or ETSI.CAdES.detached algorithms. It is registered under the :default
48
+ # name.
49
+ #
50
+ # == Usage
51
+ #
52
+ # The signing handler is used by default by all methods that need a signing handler. Therefore
53
+ # it is usually only necessary to provide the actual attribute values.
54
+ #
55
+ # *Note*: Currently only RSA is supported, DSA and ECDSA are not. See the examples below for
56
+ # how to handle them using external signing.
57
+ #
58
+ #
59
+ # == CMS and PAdES Signatures
60
+ #
61
+ # The handler supports the older standard of CMS signatures as well as the newer PAdES
62
+ # signatures specified in PDF 2.0. By default, CMS signatures are created but this can be
63
+ # changed by setting #signature_type to :pades.
64
+ #
65
+ # When creating PAdES signatures the following two PAdES baseline signatures are supported:
66
+ # B-B and B-T. The difference between those two is that a timestamp handler was defined for
67
+ # B-T compatibility.
68
+ #
69
+ #
70
+ # == Signing Modes - Internal, External, External/Asynchronous
71
+ #
72
+ # This handler provides two ways to create the CMS signed-data structure required by
73
+ # Signatures#add:
74
+ #
75
+ # * By providing the signing certificate together with the signing key and the certificate
76
+ # chain, HexaPDF itself does the signing *internally*. It is the preferred way if all the
77
+ # needed information is available.
78
+ #
79
+ # Assign the respective data to the #certificate, #key and #certificate_chain attributes.
80
+ #
81
+ # * By using an *external signing mechanism*, a callable object assigned to #external_signing.
82
+ # Here the actual signing happens "outside" of HexaPDF, for example, in custom code or even
83
+ # asynchronously. This is needed in case the signing key is not directly available but only
84
+ # an interface to it (e.g. when dealing with a HSM).
85
+ #
86
+ # Depending on whether #certificate is set the signing happens differently:
87
+ #
88
+ # * If #certificate is not set, the callable object is used instead of #sign, so it needs to
89
+ # accept the same arguments as #sign and needs to return a complete, DER-serialized CMS
90
+ # signed data object.
91
+ #
92
+ # * If #certificate is set, the CMS signed data object is created by HexaPDF. The
93
+ # callable #external_signing object is called with the used digest algorithm and the
94
+ # already digested data which needs to be signed (but *not* digested) and the signature
95
+ # returned.
96
+ #
97
+ # If the signing process needs to be *asynchronous*, make sure to set the #signature_size
98
+ # appropriately, return an empty string during signing and later use
99
+ # Signatures.embed_signature to embed the actual signature.
100
+ #
101
+ #
102
+ # == Optional Data
103
+ #
104
+ # Besides the required data, some optional attributes can also be specified:
105
+ #
106
+ # * Reason, location and contact information
107
+ # * Making the signature a certification signature by applying the DocMDP transform method and
108
+ # a DoCMDP permission
109
+ #
110
+ #
111
+ # == Examples
112
+ #
113
+ # # Signing using certificate + key
114
+ # document.sign("output.pdf", certificate: my_cert, key: my_key,
115
+ # certificate_chain: my_chain)
116
+ #
117
+ # # Signing using an external mechanism without certificate set
118
+ # signing_proc = lambda do |io, byte_range|
119
+ # io.pos = byte_range[0]
120
+ # data = io.read(byte_range[1])
121
+ # io.pos = byte_range[2]
122
+ # data << io.read(byte_range[3])
123
+ # signing_service.pkcs7_sign(data).to_der
124
+ # end
125
+ # document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
126
+ #
127
+ # # Signing using external mechanism with certificate set
128
+ # signing_proc = lambda do |digest_method, hash|
129
+ # signing_service.sign_raw(digest_method, hash)
130
+ # end
131
+ # document.sign("output.pdf", certificate: my_cert, certificate_chain: my_chain,
132
+ # external_signing: signing_proc)
133
+ #
134
+ # # Signing with DSA or ECDSA certificate/keys
135
+ # signing_proc = lambda do |io, byte_range|
136
+ # io.pos = byte_range[0]
137
+ # data = io.read(byte_range[1])
138
+ # io.pos = byte_range[2]
139
+ # data << io.read(byte_range[3])
140
+ # OpenSSL::PKCS7.sign(certificate, key, data, certificate_chain,
141
+ # OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
142
+ # end
143
+ # document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
144
+ #
145
+ #
146
+ # == Implementing a Signing Handler
147
+ #
148
+ # This class also serves as an example on how to create a custom handler: The public methods
149
+ # #signature_size, #finalize_objects and #sign are used by the digital signature algorithm.
150
+ # See their descriptions for details.
151
+ #
152
+ # Once a custom signing handler has been created, it can be registered under the
153
+ # 'signature.signing_handler' configuration option for easy use. It has to take keyword
154
+ # arguments in its initialize method to be compatible with the Signatures#handler method.
155
+ class DefaultHandler
156
+
157
+ # The certificate with which to sign the PDF.
158
+ #
159
+ # If the certificate is provided, HexaPDF creates the signature object. Otherwise the
160
+ # #external_signing callable object has to create it.
161
+ attr_accessor :certificate
162
+
163
+ # The private key for the #certificate.
164
+ #
165
+ # If the key is provided, HexaPDF does the signing. Otherwise the #external_signing callable
166
+ # object has to sign the data.
167
+ attr_accessor :key
168
+
169
+ # The certificate chain that should be embedded in the PDF; usually contains all
170
+ # certificates up to the root certificate.
171
+ attr_accessor :certificate_chain
172
+
173
+ # The digest algorithm that should be used when creating the signature.
174
+ #
175
+ # See SignedDataCreator#digest_algorithm for the default value (if nothing is set) and for
176
+ # the allowed values.
177
+ attr_accessor :digest_algorithm
178
+
179
+ # The timestamp handler that should be used for timestamping the signature.
180
+ #
181
+ # If this attribute is set, a timestamp token is embedded into the CMS object.
182
+ attr_accessor :timestamp_handler
183
+
184
+ # A callable object for custom signing mechanisms.
185
+ #
186
+ # The callable object has two different uses depending on whether #certificate is set:
187
+ #
188
+ # * If #certificate is not set, it fulfills the same role as the #sign method and needs to
189
+ # conform to that interface.
190
+ #
191
+ # * If #certificate is set and #key is not, it is just used for signing. Here it needs to
192
+ # accept the used digest algorithm and the already digested data as arguments and return
193
+ # the signature.
194
+ attr_accessor :external_signing
195
+
196
+ # The reason for signing. If used, will be set on the signature object.
197
+ attr_accessor :reason
198
+
199
+ # The signing location. If used, will be set on the signature object.
200
+ attr_accessor :location
201
+
202
+ # The contact information. If used, will be set on the signature object.
203
+ attr_accessor :contact_info
204
+
205
+ # The size of the serialized signature that should be reserved.
206
+ #
207
+ # If this attribute has not been set, an empty string will be signed using #sign to
208
+ # determine the signature size.
209
+ #
210
+ # The size needs to be at least as big as the final signature, otherwise signing results in
211
+ # an error.
212
+ attr_writer :signature_size
213
+
214
+ # The type of signature to be written (i.e. the value of the /SubFilter key).
215
+ #
216
+ # The value can either be :cms (the default; uses a detached CMS signature) or :pades
217
+ # (uses an ETSI CAdES compatible signature).
218
+ attr_accessor :signature_type
219
+
220
+ # The DocMDP permissions that should be set on the document.
221
+ #
222
+ # See #doc_mdp_permissions=
223
+ attr_reader :doc_mdp_permissions
224
+
225
+ # Creates a new DefaultHandler instance with the given attributes.
226
+ def initialize(**arguments)
227
+ @signature_size = nil
228
+ @signature_type = :cms
229
+ arguments.each {|name, value| send("#{name}=", value) }
230
+ end
231
+
232
+ # Sets the DocMDP permissions that should be applied to the document.
233
+ #
234
+ # Valid values for +permissions+ are:
235
+ #
236
+ # +nil+::
237
+ # Don't set any DocMDP permissions (default).
238
+ #
239
+ # +:no_changes+ or 1::
240
+ # No changes whatsoever are allowed.
241
+ #
242
+ # +:form_filling+ or 2::
243
+ # Only filling in forms and signing are allowed.
244
+ #
245
+ # +:form_filling_and_annotations+ or 3::
246
+ # Only filling in forms, signing and annotation creation/deletion/modification are
247
+ # allowed.
248
+ def doc_mdp_permissions=(permissions)
249
+ case permissions
250
+ when :no_changes, 1 then @doc_mdp_permissions = 1
251
+ when :form_filling, 2 then @doc_mdp_permissions = 2
252
+ when :form_filling_and_annotations, 3 then @doc_mdp_permissions = 3
253
+ when nil then @doc_mdp_permissions = nil
254
+ else
255
+ raise ArgumentError, "Invalid permissions value '#{permissions.inspect}'"
256
+ end
257
+ end
258
+
259
+ # Returns the size of the serialized signature that should be reserved.
260
+ #
261
+ # If a custom size is set using #signature_size=, it used. Otherwise the size is determined
262
+ # by using #sign to sign an empty string.
263
+ def signature_size
264
+ @signature_size || sign(StringIO.new, [0, 0, 0, 0]).size
265
+ end
266
+
267
+ # Finalizes the signature field as well as the signature dictionary before writing.
268
+ def finalize_objects(_signature_field, signature)
269
+ signature[:Filter] = :'Adobe.PPKLite'
270
+ signature[:SubFilter] = (signature_type == :pades ? :'ETSI.CAdES.detached' : :'adbe.pkcs7.detached')
271
+ signature[:M] = Time.now
272
+ signature[:Reason] = reason if reason
273
+ signature[:Location] = location if location
274
+ signature[:ContactInfo] = contact_info if contact_info
275
+ signature[:Prop_Build] = {App: {Name: :HexaPDF, REx: HexaPDF::VERSION}}
276
+
277
+ if doc_mdp_permissions
278
+ doc = signature.document
279
+ if doc.signatures.count > 1
280
+ raise HexaPDF::Error, "Can set DocMDP access permissions only on first signature"
281
+ end
282
+ params = doc.add({Type: :TransformParams, V: :'1.2', P: doc_mdp_permissions})
283
+ sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, DigestMethod: :SHA256,
284
+ TransformParams: params})
285
+ signature[:Reference] = [sigref]
286
+ (doc.catalog[:Perms] ||= {})[:DocMDP] = signature
287
+ end
288
+ end
289
+
290
+ # Returns the DER serialized CMS signed data object containing the signature for the given
291
+ # IO byte ranges.
292
+ #
293
+ # The +byte_range+ argument is an array containing four numbers [offset1, length1, offset2,
294
+ # length2]. The offset numbers are byte positions in the +io+ argument and the to-be-signed
295
+ # data can be determined by reading length bytes at the offsets.
296
+ def sign(io, byte_range)
297
+ if certificate
298
+ io.pos = byte_range[0]
299
+ data = io.read(byte_range[1])
300
+ io.pos = byte_range[2]
301
+ data << io.read(byte_range[3])
302
+ SignedDataCreator.create(data,
303
+ type: signature_type,
304
+ certificate: certificate, key: key,
305
+ digest_algorithm: digest_algorithm,
306
+ timestamp_handler: timestamp_handler,
307
+ certificates: certificate_chain, &external_signing).to_der
308
+ else
309
+ external_signing.call(io, byte_range)
310
+ end
311
+ end
312
+
313
+ end
314
+
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,308 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'openssl'
38
+ require 'stringio'
39
+ require 'hexapdf/error'
40
+
41
+ module HexaPDF
42
+ module DigitalSignature
43
+ module Signing
44
+
45
+ # This class is used for creating a CMS SignedData binary data object, as needed for PDF
46
+ # signing.
47
+ #
48
+ # OpenSSL already provides the ability to access, sign and create such CMS objects but is
49
+ # limited in what it offers in terms of data added to it. Since HexaPDF needs to follow the
50
+ # PDF standard, it needs control over the created structure so as to make it compatible with
51
+ # the various requirements.
52
+ #
53
+ # As the created CMS object is only meant to be used in the context of PDF signing, it also
54
+ # restricts certain things, like allowing only a single signer.
55
+ #
56
+ # Additionally, only RSA signatures are currently supported!
57
+ #
58
+ # See: PDF1.7/2.0 s12.8.3.3, PDF2.0 s12.8.3.4, RFC5652, ETSI TS 102 778 Parts 1-4
59
+ class SignedDataCreator
60
+
61
+ # Creates a SignedDataCreator, sets the given attributes if they are not nil and then calls
62
+ # #create with the given data, type and block.
63
+ def self.create(data, type: :cms, **attributes, &block)
64
+ instance = new
65
+ attributes.each {|key, value| instance.send("#{key}=", value) unless value.nil? }
66
+ instance.create(data, type: type, &block)
67
+ end
68
+
69
+ # The OpenSSL certificate object which is used to sign the data.
70
+ attr_accessor :certificate
71
+
72
+ # The OpenSSL key object which is used for signing. Needs to correspond to #certificate.
73
+ #
74
+ # If the key is not set, a block for signing will need to be provided to #sign.
75
+ attr_accessor :key
76
+
77
+ # Array of additional OpenSSL certificate objects that should be included.
78
+ #
79
+ # Should include all certificates of the hierarchy of the signing certificate.
80
+ attr_accessor :certificates
81
+
82
+ # The digest algorithm that should be used. Defaults to 'sha256'.
83
+ #
84
+ # Allowed values: sha256, sha384, sha512.
85
+ attr_accessor :digest_algorithm
86
+
87
+ # The timestamp handler instance that should be used for timestamping.
88
+ attr_accessor :timestamp_handler
89
+
90
+ # Creates a new SignedData object.
91
+ #
92
+ # Use the attribute accessor methods to set the required attributes.
93
+ def initialize
94
+ @certificate = nil
95
+ @key = nil
96
+ @certificates = []
97
+ @digest_algorithm = 'sha256'
98
+ @timestamp_handler = nil
99
+ end
100
+
101
+ # Creates a CMS SignedData binary data object for the given data using the set attributes
102
+ # and returns it in DER-serialized form.
103
+ #
104
+ # If the #key attribute is not set, the digest algorithm and the already digested data to be
105
+ # signed is yielded and the block needs to return the signature.
106
+ #
107
+ # +type+::
108
+ # The type can either be :cms when creating standard PDF CMS signatures or :pades when
109
+ # creating PAdES compatible signatures. PAdES signatures are part of PDF 2.0.
110
+ def create(data, type: :cms, &block) # :yield: digested_data
111
+ signed_attrs = create_signed_attrs(data, signing_time: (type == :cms))
112
+ signature = digest_and_sign_data(set(*signed_attrs.value).to_der, &block)
113
+ unsigned_attrs = create_unsigned_attrs(signature)
114
+
115
+ signer_info = create_signer_info(signature, signed_attrs, unsigned_attrs)
116
+ signed_data = create_signed_data(signer_info)
117
+ create_content_info(signed_data)
118
+ end
119
+
120
+ private
121
+
122
+ # Creates the set of signed attributes for the signer information structure.
123
+ def create_signed_attrs(data, signing_time: true)
124
+ set(
125
+ attribute('content-type', oid('id-data')),
126
+ (attribute('id-signingTime', utc_time(Time.now.utc)) if signing_time),
127
+ attribute(
128
+ 'message-digest',
129
+ binary(OpenSSL::Digest.digest(@digest_algorithm, data))
130
+ ),
131
+ attribute(
132
+ 'id-aa-signingCertificateV2',
133
+ sequence( # SigningCertificateV2
134
+ sequence( # Seq of ESSCertIDv2
135
+ sequence( # ESSCertIDv2
136
+ #TODO: Does not validate on ETSI checker if used, doesn't matter if SHA256 or 512
137
+ #oid('sha512'),
138
+ binary(OpenSSL::Digest.digest('sha256', @certificate.to_der)), # certHash
139
+ sequence( # issuerSerial
140
+ sequence( # issuer
141
+ implicit(4, sequence(@certificate.issuer)) # choice 4 directoryName
142
+ ),
143
+ integer(@certificate.serial) # serial
144
+ )
145
+ )
146
+ )
147
+ )
148
+ )
149
+ )
150
+ end
151
+
152
+ # Creates the set of unsigned attributes for the signer information structure.
153
+ def create_unsigned_attrs(signature)
154
+ attrs = set
155
+ if @timestamp_handler
156
+ time_stamp_token = @timestamp_handler.sign(StringIO.new(signature),
157
+ [0, signature.size, 0, 0])
158
+ attrs.value << attribute('id-aa-timeStampToken', time_stamp_token)
159
+ end
160
+ attrs.value.empty? ? nil : attrs
161
+ end
162
+
163
+ # Creates a single attribute for use in the (un)signed attributes set.
164
+ def attribute(name, value)
165
+ sequence(
166
+ oid(name), # attrType
167
+ set(value) # attrValues
168
+ )
169
+ end
170
+
171
+ # Digests the data and then signs it using the assigned key, or if the key is not available,
172
+ # by yielding to the caller.
173
+ def digest_and_sign_data(data)
174
+ hash = OpenSSL::Digest.digest(@digest_algorithm, data)
175
+ if @key
176
+ @key.sign_raw(@digest_algorithm, hash)
177
+ else
178
+ yield(@digest_algorithm, hash)
179
+ end
180
+ end
181
+
182
+ # Creates a signer information structure containing the actual meat of the whole CMS object.
183
+ def create_signer_info(signature, signed_attrs, unsigned_attrs = nil)
184
+ certificate_pkey_algorithm = @certificate.public_key.oid
185
+ signature_algorithm = if certificate_pkey_algorithm == 'rsaEncryption'
186
+ sequence( # signatureAlgorithm
187
+ oid('rsaEncryption'), # algorithmID
188
+ null # params
189
+ )
190
+ else
191
+ raise HexaPDF::Error, "Unsupported key type/signature algorithm"
192
+ end
193
+
194
+ sequence(
195
+ integer(1), # version
196
+ sequence( # sid (choice: issuerAndSerialNumber)
197
+ @certificate.issuer, # issuer
198
+ integer(@certificate.serial) # serial
199
+ ),
200
+ sequence( # digestAlgorithm
201
+ oid(@digest_algorithm), # algorithmID
202
+ null # params
203
+ ),
204
+ implicit(0, signed_attrs), # signedAttrs 0 implicit
205
+ signature_algorithm, # signatureAlgorithm
206
+ binary(signature), # signature
207
+ (implicit(1, unsigned_attrs) if unsigned_attrs) # unsignedAttrs 1 implicit
208
+ )
209
+ end
210
+
211
+ # Creates the signed data structure which is the actual content of the CMS object.
212
+ def create_signed_data(signer_info)
213
+ certificates = set(*[@certificate, @certificates].flatten)
214
+
215
+ sequence(
216
+ integer(1), # version
217
+ set( # digestAlgorithms
218
+ sequence( # digestAlgorithm
219
+ oid(@digest_algorithm), # algorithmID
220
+ null # params
221
+ )
222
+ ),
223
+ sequence( # encapContentInfo (detached signature)
224
+ oid('id-data') # eContentType
225
+ ),
226
+ implicit(0, certificates), # certificates 0 implicit
227
+ set( # signerInfos
228
+ signer_info # signerInfo
229
+ )
230
+ )
231
+ end
232
+
233
+ # Creates the content info structure which is the main structure containing everything else.
234
+ def create_content_info(signed_data)
235
+ signed_data.tag = 0
236
+ signed_data.tagging = :EXPLICIT
237
+ signed_data.tag_class = :CONTEXT_SPECIFIC
238
+ sequence(
239
+ oid('id-signedData'), # contentType
240
+ signed_data # content 0 explicit
241
+ )
242
+ end
243
+
244
+ # Changes the given ASN1Data object to use implicit tagging with the given +tag+ and a tag
245
+ # class of :CONTEXT_SPECIFIC.
246
+ def implicit(tag, data)
247
+ data.tag = tag
248
+ data.tagging = :IMPLICIT
249
+ data.tag_class = :CONTEXT_SPECIFIC
250
+ data
251
+ end
252
+
253
+ # Creates an ASN.1 set instance.
254
+ def set(*contents, tag: nil, tagging: nil)
255
+ OpenSSL::ASN1::Set.new(contents.compact, *tag, *tagging)
256
+ end
257
+
258
+ # Creates an ASN.1 sequence instance.
259
+ def sequence(*contents, tag: nil, tagging: nil)
260
+ OpenSSL::ASN1::Sequence.new(contents.compact, *tag, *tagging)
261
+ end
262
+
263
+ # Mapping of ASN.1 object ID names to object ID strings.
264
+ OIDS = {
265
+ 'content-type' => '1.2.840.113549.1.9.3',
266
+ 'message-digest' => '1.2.840.113549.1.9.4',
267
+ 'id-data' => '1.2.840.113549.1.7.1',
268
+ 'id-signedData' => '1.2.840.113549.1.7.2',
269
+ 'id-signingTime' => '1.2.840.113549.1.9.5',
270
+ 'sha256' => '2.16.840.1.101.3.4.2.1',
271
+ 'sha384' => '2.16.840.1.101.3.4.2.2',
272
+ 'sha512' => '2.16.840.1.101.3.4.2.3',
273
+ 'rsaEncryption' => '1.2.840.113549.1.1.1',
274
+ 'id-aa-signingCertificate' => '1.2.840.113549.1.9.16.2.12',
275
+ 'id-aa-timeStampToken' => '1.2.840.113549.1.9.16.2.14',
276
+ 'id-aa-signingCertificateV2' => '1.2.840.113549.1.9.16.2.47',
277
+ }
278
+
279
+ # Creates an ASN.1 object ID instance for the given object ID name.
280
+ def oid(name)
281
+ OpenSSL::ASN1::ObjectId.new(OIDS[name])
282
+ end
283
+
284
+ # Creates an ASN.1 octet string instance.
285
+ def binary(str)
286
+ OpenSSL::ASN1::OctetString.new(str)
287
+ end
288
+
289
+ # Creates an ASN.1 integer instance.
290
+ def integer(int)
291
+ OpenSSL::ASN1::Integer.new(int)
292
+ end
293
+
294
+ # Creates an ASN.1 UTC time instance.
295
+ def utc_time(value)
296
+ OpenSSL::ASN1::UTCTime.new(value)
297
+ end
298
+
299
+ # Creates an ASN.1 null instance.
300
+ def null
301
+ OpenSSL::ASN1::Null.new(nil)
302
+ end
303
+
304
+ end
305
+
306
+ end
307
+ end
308
+ end