hexapdf 0.28.0 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
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,225 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require_relative '../common'
6
+
7
+ describe HexaPDF::DigitalSignature::Signing::SignedDataCreator do
8
+ before do
9
+ @klass = HexaPDF::DigitalSignature::Signing::SignedDataCreator
10
+ @signed_data = @klass.new
11
+ @signed_data.certificate = CERTIFICATES.signer_certificate
12
+ @signed_data.key = CERTIFICATES.signer_key
13
+ @signed_data.certificates = [CERTIFICATES.ca_certificate]
14
+ end
15
+
16
+ it "allows setting the attributes" do
17
+ obj = @klass.new
18
+ obj.certificate = :cert
19
+ obj.key = :key
20
+ obj.certificates = :certs
21
+ obj.digest_algorithm = 'sha512'
22
+ obj.timestamp_handler = :tsh
23
+ assert_equal(:cert, obj.certificate)
24
+ assert_equal(:key, obj.key)
25
+ assert_equal(:certs, obj.certificates)
26
+ assert_equal('sha512', obj.digest_algorithm)
27
+ assert_equal(:tsh, obj.timestamp_handler)
28
+ end
29
+
30
+ it "doesn't allow setting attributes to nil using ::create" do
31
+ asn1 = @klass.create("data",
32
+ certificate: CERTIFICATES.signer_certificate,
33
+ key: CERTIFICATES.signer_key,
34
+ digest_algorithm: nil)
35
+ assert_equal('2.16.840.1.101.3.4.2.1', asn1.value[1].value[1].value[0].value[0].value)
36
+ end
37
+
38
+ describe "content info structure" do
39
+ it "sets the correct content type value for the outer container" do
40
+ asn1 = @signed_data.create("data")
41
+ assert_equal('1.2.840.113549.1.7.2', asn1.value[0].value)
42
+ end
43
+
44
+ it "has the signed data structure marked as explicit" do
45
+ asn1 = @signed_data.create("data")
46
+ signed_data = asn1.value[1]
47
+ assert_equal(0, signed_data.tag)
48
+ assert_equal(:EXPLICIT, signed_data.tagging)
49
+ assert_equal(:CONTEXT_SPECIFIC, signed_data.tag_class)
50
+ end
51
+ end
52
+
53
+ describe "signed data structure" do
54
+ before do
55
+ @structure = @signed_data.create("data").value[1]
56
+ end
57
+
58
+ it "sets the correct version" do
59
+ assert_equal(1, @structure.value[0].value)
60
+ end
61
+
62
+ it "contains a reference to the used digest algorithm" do
63
+ assert_equal('2.16.840.1.101.3.4.2.1', @structure.value[1].value[0].value[0].value)
64
+ assert_nil(@structure.value[1].value[0].value[1].value)
65
+ end
66
+
67
+ it "contains an empty encapsulated content structure" do
68
+ assert_equal(1, @structure.value[2].value.size)
69
+ assert_equal('1.2.840.113549.1.7.1', @structure.value[2].value[0].value)
70
+ end
71
+
72
+ it "contains the assigned certificates" do
73
+ assert_equal(2, @structure.value[3].value.size)
74
+ assert_equal(0, @structure.value[3].tag)
75
+ assert_equal(:IMPLICIT, @structure.value[3].tagging)
76
+ assert_equal(:CONTEXT_SPECIFIC, @structure.value[3].tag_class)
77
+ assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
78
+ @structure.value[3].value)
79
+ end
80
+
81
+ it "contains a single signer info structure" do
82
+ assert_equal(1, @structure.value[4].value.size)
83
+ end
84
+ end
85
+
86
+ describe "signer info" do
87
+ before do
88
+ @structure = @signed_data.create("data").value[1].value[4].value[0]
89
+ end
90
+
91
+ it "has the expected number of entries" do
92
+ assert_equal(6, @structure.value.size)
93
+ end
94
+
95
+ it "sets the correct version" do
96
+ assert_equal(1, @structure.value[0].value)
97
+ end
98
+
99
+ it "uses issuer and serial for the signer identifer" do
100
+ assert_equal(CERTIFICATES.signer_certificate.issuer, @structure.value[1].value[0])
101
+ assert_equal(CERTIFICATES.signer_certificate.serial, @structure.value[1].value[1].value)
102
+ end
103
+
104
+ it "contains a reference to the used digest algorithm" do
105
+ assert_equal('2.16.840.1.101.3.4.2.1', @structure.value[2].value[0].value)
106
+ assert_nil(@structure.value[2].value[1].value)
107
+ end
108
+
109
+ describe "signed attributes" do
110
+ it "uses the correct tagging for the attributes" do
111
+ assert_equal(0, @structure.value[3].tag)
112
+ assert_equal(:IMPLICIT, @structure.value[3].tagging)
113
+ assert_equal(:CONTEXT_SPECIFIC, @structure.value[3].tag_class)
114
+ end
115
+
116
+ it "contains the content type identifier" do
117
+ attr = @structure.value[3].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.3' }
118
+ assert_equal('1.2.840.113549.1.7.1', attr.value[1].value[0].value)
119
+ end
120
+
121
+ it "contains the message digest attribute" do
122
+ attr = @structure.value[3].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.4' }
123
+ assert_equal(OpenSSL::Digest.digest('SHA256', 'data'), attr.value[1].value[0].value)
124
+ end
125
+
126
+ it "contains the signing certificate attribute" do
127
+ attr = @structure.value[3].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.16.2.47' }
128
+ signing_cert = attr.value[1].value[0]
129
+ assert_equal(1, signing_cert.value.size)
130
+ assert_equal(1, signing_cert.value[0].value.size)
131
+ assert_equal(2, signing_cert.value[0].value[0].value.size)
132
+ assert_equal(OpenSSL::Digest.digest('sha256', CERTIFICATES.signer_certificate.to_der),
133
+ signing_cert.value[0].value[0].value[0].value)
134
+ assert_equal(2, signing_cert.value[0].value[0].value[1].value.size)
135
+ assert_equal(1, signing_cert.value[0].value[0].value[1].value[0].value.size)
136
+ assert_equal(1, signing_cert.value[0].value[0].value[1].value[0].value[0].value.size)
137
+ assert_equal(4, signing_cert.value[0].value[0].value[1].value[0].value[0].tag)
138
+ assert_equal(:IMPLICIT, signing_cert.value[0].value[0].value[1].value[0].value[0].tagging)
139
+ assert_equal(:CONTEXT_SPECIFIC, signing_cert.value[0].value[0].value[1].value[0].value[0].tag_class)
140
+ assert_equal(CERTIFICATES.signer_certificate.issuer,
141
+ signing_cert.value[0].value[0].value[1].value[0].value[0].value[0])
142
+ assert_equal(CERTIFICATES.signer_certificate.serial,
143
+ signing_cert.value[0].value[0].value[1].value[1].value)
144
+ end
145
+ end
146
+
147
+ it "contains the signature algorithm reference" do
148
+ assert_equal('1.2.840.113549.1.1.1', @structure.value[4].value[0].value)
149
+ assert_nil(@structure.value[4].value[1].value)
150
+ end
151
+
152
+ it "contains the signature itself" do
153
+ to_sign = OpenSSL::ASN1::Set.new(@structure.value[3].value).to_der
154
+ assert_equal(CERTIFICATES.signer_key.sign('SHA256', to_sign), @structure.value[5].value)
155
+ end
156
+
157
+ it "fails if the signature algorithm is not supported" do
158
+ @signed_data.certificate = CERTIFICATES.dsa_signer_certificate
159
+ @signed_data.key = CERTIFICATES.dsa_signer_key
160
+ assert_raises(HexaPDF::Error) { @signed_data.create("data") }
161
+ end
162
+
163
+ it "can use a different digest algorithm" do
164
+ @signed_data.digest_algorithm = 'sha384'
165
+ structure = @signed_data.create("data").value[1].value[4].value[0]
166
+ to_sign = OpenSSL::ASN1::Set.new(structure.value[3].value).to_der
167
+ assert_equal('2.16.840.1.101.3.4.2.2', structure.value[2].value[0].value)
168
+ assert_equal(CERTIFICATES.signer_key.sign('SHA384', to_sign), structure.value[5].value)
169
+ end
170
+
171
+ it "allows delegating the signature to a provided signing block" do
172
+ @signed_data.key = nil
173
+ digest_algorithm = nil
174
+ calculated_hash = nil
175
+ structure = @signed_data.create("data") do |algorithm, hash|
176
+ digest_algorithm = algorithm
177
+ calculated_hash = hash
178
+ "signed"
179
+ end.value[1].value[4].value[0]
180
+ to_sign = OpenSSL::Digest.digest('SHA256', OpenSSL::ASN1::Set.new(structure.value[3].value).to_der)
181
+ assert_equal('sha256', digest_algorithm)
182
+ assert_equal(calculated_hash, to_sign)
183
+ assert_equal('signed', structure.value[5].value)
184
+ end
185
+
186
+ describe "unsigned attributes" do
187
+ it "allows adding a timestamp token" do
188
+ tsh = Object.new
189
+ io = nil
190
+ byte_range = nil
191
+ tsh.define_singleton_method(:sign) do |i_io, i_byte_range|
192
+ io = i_io
193
+ byte_range = i_byte_range
194
+ "timestamp"
195
+ end
196
+ @signed_data.timestamp_handler = tsh
197
+
198
+ structure = @signed_data.create("data").value[1].value[4].value[0]
199
+ assert_equal(structure.value[5].value, io.string)
200
+ assert_equal([0, io.string.size, 0, 0], byte_range)
201
+
202
+ attr = structure.value[6].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.16.2.14' }
203
+ assert_equal("timestamp", attr.value[1].value[0])
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "cms signature" do
209
+ it "includes the current time as signing time" do
210
+ Time.stub(:now, Time.at(0)) do
211
+ asn1 = OpenSSL::ASN1.decode(@signed_data.create("data"))
212
+ attr = asn1.value[1].value[0].value[4].value[0].value[3].value.
213
+ find {|obj| obj.value[0].value == 'signingTime' }
214
+ assert_equal(Time.now.utc, attr.value[1].value[0].value)
215
+ end
216
+ end
217
+ end
218
+
219
+ describe "pades signature" do
220
+ it "doesn't include the signing-time attribute" do
221
+ signer_info = @signed_data.create("data", type: :pades).value[1].value[4].value[0]
222
+ refute(signer_info.value[3].value.find {|obj| obj.value[0].value == 'signingTime' })
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,88 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require_relative '../common'
6
+
7
+ describe HexaPDF::DigitalSignature::Signing::TimestampHandler do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @handler = HexaPDF::DigitalSignature::Signing::TimestampHandler.new
11
+ end
12
+
13
+ it "allows setting the attributes in the constructor" do
14
+ handler = @handler.class.new(
15
+ tsa_url: "url", tsa_hash_algorithm: "MD5", tsa_policy_id: "5",
16
+ reason: "Reason", location: "Location", contact_info: "Contact",
17
+ signature_size: 1_000
18
+ )
19
+ assert_equal("url", handler.tsa_url)
20
+ assert_equal("MD5", handler.tsa_hash_algorithm)
21
+ assert_equal("5", handler.tsa_policy_id)
22
+ assert_equal("Reason", handler.reason)
23
+ assert_equal("Location", handler.location)
24
+ assert_equal("Contact", handler.contact_info)
25
+ assert_equal(1_000, handler.signature_size)
26
+ end
27
+
28
+ it "finalizes the signature field and signature objects" do
29
+ @field = @doc.wrap({})
30
+ @sig = @doc.wrap({})
31
+ @handler.reason = 'Reason'
32
+ @handler.location = 'Location'
33
+ @handler.contact_info = 'Contact'
34
+
35
+ @handler.finalize_objects(@field, @sig)
36
+ assert_equal('2.0', @doc.version)
37
+ assert_equal(:DocTimeStamp, @sig[:Type])
38
+ assert_equal(:'Adobe.PPKLite', @sig[:Filter])
39
+ assert_equal(:'ETSI.RFC3161', @sig[:SubFilter])
40
+ assert_equal('Reason', @sig[:Reason])
41
+ assert_equal('Location', @sig[:Location])
42
+ assert_equal('Contact', @sig[:ContactInfo])
43
+ end
44
+
45
+ it "returns the size of serialized signature" do
46
+ @handler.tsa_url = "http://127.0.0.1:34567"
47
+ CERTIFICATES.start_tsa_server
48
+ assert(@handler.signature_size > 1000)
49
+ end
50
+
51
+ describe "sign" do
52
+ before do
53
+ @data = StringIO.new("data")
54
+ @range = [0, 4, 0, 0]
55
+ @handler.tsa_url = "http://127.0.0.1:34567"
56
+ CERTIFICATES.start_tsa_server
57
+ end
58
+
59
+ it "respects the set hash algorithm and policy id" do
60
+ @handler.tsa_hash_algorithm = 'SHA256'
61
+ @handler.tsa_policy_id = '1.2.3.4.2'
62
+ token = OpenSSL::ASN1.decode(@handler.sign(@data, @range))
63
+ content = OpenSSL::ASN1.decode(token.value[1].value[0].value[2].value[1].value[0].value)
64
+ policy_id = content.value[1].value
65
+ digest_algorithm = content.value[2].value[0].value[0].value
66
+ assert_equal('SHA256', digest_algorithm)
67
+ assert_equal("1.2.3.4.2", policy_id)
68
+ end
69
+
70
+ it "returns the serialized timestamp token" do
71
+ token = OpenSSL::PKCS7.new(@handler.sign(@data, @range))
72
+ assert_equal(CERTIFICATES.ca_certificate.subject, token.signers[0].issuer)
73
+ assert_equal(CERTIFICATES.timestamp_certificate.serial, token.signers[0].serial)
74
+ end
75
+
76
+ it "fails if the timestamp token could not be created" do
77
+ @handler.tsa_hash_algorithm = 'SHA1'
78
+ msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
79
+ assert_match(/BAD_ALG/, msg.message)
80
+ end
81
+
82
+ it "fails if the timestamp server couldn't process the request" do
83
+ @handler.tsa_policy_id = '1.2.3.4.1'
84
+ msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
85
+ assert_match(/Invalid TSA server response/, msg.message)
86
+ end
87
+ end
88
+ end
@@ -3,10 +3,10 @@
3
3
  require 'digest'
4
4
  require 'test_helper'
5
5
  require_relative 'common'
6
- require 'hexapdf/type/signature'
6
+ require 'hexapdf/digital_signature'
7
7
  require 'ostruct'
8
8
 
9
- describe HexaPDF::Type::Signature::AdbePkcs7Detached do
9
+ describe HexaPDF::DigitalSignature::CMSHandler do
10
10
  before do
11
11
  @data = 'Some data'
12
12
  @dict = OpenStruct.new
@@ -15,11 +15,11 @@ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
15
15
  OpenSSL::PKCS7::DETACHED)
16
16
  @dict.contents = @pkcs7.to_der
17
17
  @dict.signed_data = @data
18
- @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
18
+ @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
19
19
  end
20
20
 
21
21
  it "returns the signer name" do
22
- assert_equal("signer", @handler.signer_name)
22
+ assert_equal("RSA signer", @handler.signer_name)
23
23
  end
24
24
 
25
25
  it "returns the signing time" do
@@ -60,7 +60,7 @@ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
60
60
  @pkcs7.add_signer(OpenSSL::PKCS7::SignerInfo.new(CERTIFICATES.signer_certificate,
61
61
  CERTIFICATES.signer_key, 'SHA1'))
62
62
  @dict.contents = @pkcs7.to_der
63
- @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
63
+ @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
64
64
  result = @handler.verify(@store)
65
65
  assert_equal(2, result.messages.size)
66
66
  assert_equal(:error, result.messages.first.type)
@@ -80,7 +80,7 @@ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
80
80
  @data, [CERTIFICATES.ca_certificate],
81
81
  OpenSSL::PKCS7::DETACHED)
82
82
  @dict.contents = @pkcs7.to_der
83
- @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
83
+ @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
84
84
  result = @handler.verify(@store)
85
85
  assert_equal(:error, result.messages.first.type)
86
86
  assert_match(/key usage is missing 'Digital Signature'/, result.messages.first.content)
@@ -110,7 +110,7 @@ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
110
110
  res = fac.create_timestamp(CERTIFICATES.signer_key, CERTIFICATES.timestamp_certificate, req)
111
111
  @dict.contents = res.token.to_der
112
112
  @dict.signature_type = 'ETSI.RFC3161'
113
- @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
113
+ @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
114
114
 
115
115
  result = @handler.verify(@store)
116
116
  assert_equal(:info, result.messages.last.type)
@@ -1,17 +1,17 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
- require 'hexapdf/type/signature'
4
+ require 'hexapdf/digital_signature'
5
5
  require 'hexapdf/document'
6
6
  require 'time'
7
7
  require 'ostruct'
8
8
 
9
- describe HexaPDF::Type::Signature::Handler do
9
+ describe HexaPDF::DigitalSignature::Handler do
10
10
  before do
11
11
  @time = Time.parse("2021-11-14 7:00")
12
12
  @dict = {Name: "handler", M: @time}
13
- @handler = HexaPDF::Type::Signature::Handler.new(@dict)
14
- @result = HexaPDF::Type::Signature::VerificationResult.new
13
+ @handler = HexaPDF::DigitalSignature::Handler.new(@dict)
14
+ @result = HexaPDF::DigitalSignature::VerificationResult.new
15
15
  end
16
16
 
17
17
  it "returns the signer name" do
@@ -2,10 +2,10 @@
2
2
 
3
3
  require 'test_helper'
4
4
  require_relative 'common'
5
- require 'hexapdf/type/signature'
5
+ require 'hexapdf/digital_signature'
6
6
  require 'ostruct'
7
7
 
8
- describe HexaPDF::Type::Signature::AdbeX509RsaSha1 do
8
+ describe HexaPDF::DigitalSignature::PKCS1Handler do
9
9
  before do
10
10
  @data = 'Some data'
11
11
  @dict = OpenStruct.new
@@ -14,7 +14,7 @@ describe HexaPDF::Type::Signature::AdbeX509RsaSha1 do
14
14
  @dict.contents = OpenSSL::ASN1::OctetString.new(encoded_data).to_der
15
15
  @dict.Cert = [CERTIFICATES.signer_certificate.to_der]
16
16
  def @dict.key?(*); true; end
17
- @handler = HexaPDF::Type::Signature::AdbeX509RsaSha1.new(@dict)
17
+ @handler = HexaPDF::DigitalSignature::PKCS1Handler.new(@dict)
18
18
  end
19
19
 
20
20
  it "returns the certificate chain" do
@@ -2,11 +2,11 @@
2
2
 
3
3
  require 'test_helper'
4
4
  require 'hexapdf/document'
5
- require 'hexapdf/type/signature'
6
- require_relative 'signature/common'
5
+ require 'hexapdf/digital_signature'
6
+ require_relative 'common'
7
7
  require 'stringio'
8
8
 
9
- describe HexaPDF::Type::Signature::TransformParams do
9
+ describe HexaPDF::DigitalSignature::Signature::TransformParams do
10
10
  before do
11
11
  @doc = HexaPDF::Document.new
12
12
  @params = @doc.add({Type: :TransformParams})
@@ -36,7 +36,7 @@ describe HexaPDF::Type::Signature::TransformParams do
36
36
  end
37
37
  end
38
38
 
39
- describe HexaPDF::Type::Signature::SignatureReference do
39
+ describe HexaPDF::DigitalSignature::Signature::SignatureReference do
40
40
  before do
41
41
  @doc = HexaPDF::Document.new
42
42
  @sigref = @doc.add({Type: :SigRef})
@@ -52,7 +52,7 @@ describe HexaPDF::Type::Signature::SignatureReference do
52
52
  end
53
53
  end
54
54
 
55
- describe HexaPDF::Type::Signature do
55
+ describe HexaPDF::DigitalSignature::Signature do
56
56
  before do
57
57
  @doc = HexaPDF::Document.new
58
58
  @sig = @doc.add({Type: :Sig, Filter: :'Adobe.PPKLite', SubFilter: :'ETSI.CAdES.detached'})
@@ -65,7 +65,7 @@ describe HexaPDF::Type::Signature do
65
65
  end
66
66
 
67
67
  it "returns the signer name" do
68
- assert_equal('signer', @sig.signer_name)
68
+ assert_equal('RSA signer', @sig.signer_name)
69
69
  end
70
70
 
71
71
  it "returns the signing time" do
@@ -88,7 +88,7 @@ describe HexaPDF::Type::Signature do
88
88
 
89
89
  describe "signature_handler" do
90
90
  it "returns the signature handler" do
91
- assert_kind_of(HexaPDF::Type::Signature::Handler, @sig.signature_handler)
91
+ assert_kind_of(HexaPDF::DigitalSignature::Handler, @sig.signature_handler)
92
92
  end
93
93
 
94
94
  it "fails if the required handler is not available" do
@@ -0,0 +1,137 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'stringio'
5
+ require 'tempfile'
6
+ require 'hexapdf/document'
7
+ require_relative 'common'
8
+
9
+ describe HexaPDF::DigitalSignature::Signatures do
10
+ before do
11
+ @doc = HexaPDF::Document.new
12
+ @form = @doc.acro_form(create: true)
13
+ @sig1 = @form.create_signature_field("test1")
14
+ @sig2 = @form.create_signature_field("test2")
15
+ end
16
+
17
+ it "iterates over all signature dictionaries" do
18
+ assert_equal([], @doc.signatures.to_a)
19
+ @sig1.field_value = :sig1
20
+ @sig2.field_value = :sig2
21
+ assert_equal([:sig1, :sig2], @doc.signatures.to_a)
22
+ end
23
+
24
+ it "returns the number of signature dictionaries" do
25
+ @sig1.field_value = :sig1
26
+ assert_equal(1, @doc.signatures.count)
27
+ end
28
+
29
+ describe "signing_handler" do
30
+ it "return the initialized handler" do
31
+ handler = @doc.signatures.signing_handler(certificate: 'cert', reason: 'reason')
32
+ assert_equal('cert', handler.certificate)
33
+ assert_equal('reason', handler.reason)
34
+ end
35
+
36
+ it "fails if the given task is not available" do
37
+ assert_raises(HexaPDF::Error) { @doc.signatures.signing_handler(name: :unknown) }
38
+ end
39
+ end
40
+
41
+ describe "add" do
42
+ before do
43
+ @doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
44
+ @io = StringIO.new(''.b)
45
+ @handler = @doc.signatures.signing_handler(
46
+ certificate: CERTIFICATES.signer_certificate,
47
+ key: CERTIFICATES.signer_key,
48
+ certificate_chain: [CERTIFICATES.ca_certificate]
49
+ )
50
+ end
51
+
52
+ it "uses the provided signature dictionary" do
53
+ sig = @doc.add({Type: :Sig, Key: :value})
54
+ @doc.signatures.add(@io, @handler, signature: sig)
55
+ assert_equal(1, @doc.signatures.to_a.compact.size)
56
+ assert_equal(:value, @doc.signatures.to_a[0][:Key])
57
+ refute_equal(:value, @doc.acro_form.each_field.first[:Key])
58
+ end
59
+
60
+ it "creates the signature dictionary if none is provided" do
61
+ @doc.signatures.add(@io, @handler)
62
+ assert_equal(1, @doc.signatures.to_a.compact.size)
63
+ refute(@doc.acro_form.each_field.first.key?(:Contents))
64
+ end
65
+
66
+ it "sets the needed information on the signature dictionary" do
67
+ def @handler.finalize_objects(sigfield, sig)
68
+ sig[:key] = :sig
69
+ sigfield[:key] = :sig_field
70
+ end
71
+ @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
72
+ sig = @doc.signatures.first
73
+ assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2, 2501], sig[:ByteRange].value)
74
+ assert_equal(:sig, sig[:key])
75
+ assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
76
+ assert(sig.key?(:Contents))
77
+ end
78
+
79
+ it "creates the main form dictionary if necessary" do
80
+ @doc.signatures.add(@io, @handler)
81
+ assert(@doc.acro_form)
82
+ assert_equal([:signatures_exist, :append_only], @doc.acro_form.signature_flags)
83
+ end
84
+
85
+ it "uses the provided signature field" do
86
+ field = @doc.acro_form(create: true).create_signature_field('Signature2')
87
+ @doc.signatures.add(@io, @handler, signature: field)
88
+ assert_nil(@doc.acro_form.field_by_name("Signature3"))
89
+ refute_nil(field.field_value)
90
+ assert_nil(@doc.signatures.first[:T])
91
+ end
92
+
93
+ it "uses an existing signature field if possible" do
94
+ field = @doc.acro_form(create: true).create_signature_field('Signature2')
95
+ field.field_value = sig = @doc.add({Type: :Sig, key: :value})
96
+ @doc.signatures.add(@io, @handler, signature: sig)
97
+ assert_nil(@doc.acro_form.field_by_name("Signature3"))
98
+ assert_same(sig, @doc.signatures.first)
99
+ end
100
+
101
+ it "creates the signature field if necessary" do
102
+ @doc.acro_form(create: true).create_text_field('Signature2')
103
+ @doc.signatures.add(@io, @handler)
104
+ field = @doc.acro_form.field_by_name("Signature3")
105
+ assert_equal(:Sig, field.field_type)
106
+ refute_nil(field.field_value)
107
+ assert_equal(1, field.each_widget.count)
108
+ end
109
+
110
+ it "handles different xref section types correctly when determing the offsets" do
111
+ @doc.delete(7)
112
+ sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
113
+ assert_equal([0, 1036, 1036 + sig[:Contents].size * 2 + 2, 2483], sig[:ByteRange].value)
114
+ end
115
+
116
+ it "works if the signature object is the last object of the xref section" do
117
+ field = @doc.acro_form(create: true).create_signature_field('Signature2')
118
+ field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
119
+ sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
120
+ assert_equal([0, 3143, 3143 + sig[:Contents].size * 2 + 2, 380], sig[:ByteRange].value)
121
+ end
122
+
123
+ it "allows writing to a file in addition to writing to an IO" do
124
+ tempfile = Tempfile.new('hexapdf-signature')
125
+ tempfile.close
126
+ @doc.signatures.add(tempfile.path, @handler)
127
+ doc = HexaPDF::Document.open(tempfile.path)
128
+ assert(doc.signatures.first.verify(allow_self_signed: true).success?)
129
+ end
130
+
131
+ it "adds a new revision with the signature" do
132
+ @doc.signatures.add(@io, @handler)
133
+ signed_doc = HexaPDF::Document.new(io: @io)
134
+ assert(signed_doc.signatures.first.verify)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/digital_signature'
6
+ require_relative 'common'
7
+
8
+ describe HexaPDF::DigitalSignature::Signing do
9
+ before do
10
+ @handler = HexaPDF::DigitalSignature::Signing::DefaultHandler.new(
11
+ certificate: CERTIFICATES.signer_certificate,
12
+ key: CERTIFICATES.signer_key,
13
+ certificate_chain: [CERTIFICATES.ca_certificate]
14
+ )
15
+ end
16
+
17
+ it "allows embedding an external signature value" do
18
+ # Create first signature normally for testing the signature-finding code later
19
+ doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
20
+ io = StringIO.new(''.b)
21
+ doc.signatures.add(io, @handler)
22
+ doc = HexaPDF::Document.new(io: io)
23
+ io = StringIO.new(''.b)
24
+
25
+ byte_range = nil
26
+ @handler.signature_size = 5000
27
+ @handler.certificate = nil
28
+ @handler.external_signing = proc {|_, br| byte_range = br; "" }
29
+ doc.signatures.add(io, @handler)
30
+
31
+ io.pos = byte_range[0]
32
+ data = io.read(byte_range[1])
33
+ io.pos = byte_range[2]
34
+ data << io.read(byte_range[3])
35
+ contents = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, @handler.key, data,
36
+ @handler.certificate_chain,
37
+ OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
38
+ HexaPDF::DigitalSignature::Signing.embed_signature(io, contents)
39
+ doc = HexaPDF::Document.new(io: io)
40
+ assert_equal(2, doc.signatures.each.count)
41
+ doc.signatures.each do |signature|
42
+ assert(signature.verify(allow_self_signed: true).messages.find {|m| m.content == 'Signature valid' })
43
+ end
44
+ end
45
+
46
+ it "fails if the reserved signature space is too small" do
47
+ doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
48
+ io = StringIO.new(''.b)
49
+ def @handler.signature_size; 200; end
50
+ msg = assert_raises(HexaPDF::Error) { doc.signatures.add(io, @handler) }
51
+ assert_match(/space.*too small.*200 vs/, msg.message)
52
+ end
53
+ end