hexapdf 0.28.0 → 0.30.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/configuration.rb +12 -12
  5. data/lib/hexapdf/dictionary_fields.rb +13 -4
  6. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  7. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  8. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  9. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  10. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  11. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  12. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  13. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  14. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  15. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  16. data/lib/hexapdf/digital_signature.rb +56 -0
  17. data/lib/hexapdf/document/pages.rb +35 -18
  18. data/lib/hexapdf/document.rb +21 -14
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  20. data/lib/hexapdf/type/font_simple.rb +14 -2
  21. data/lib/hexapdf/type.rb +0 -1
  22. data/lib/hexapdf/version.rb +1 -1
  23. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  24. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  25. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  26. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  27. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  28. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  29. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  30. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  31. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  32. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  33. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  34. data/test/hexapdf/document/test_pages.rb +25 -0
  35. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  36. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  37. data/test/hexapdf/test_document.rb +1 -1
  38. data/test/hexapdf/test_writer.rb +6 -6
  39. data/test/hexapdf/type/test_font_simple.rb +18 -6
  40. metadata +25 -15
  41. data/lib/hexapdf/document/signatures.rb +0 -546
  42. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  43. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  44. data/lib/hexapdf/type/signature/handler.rb +0 -140
  45. 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