hexapdf 0.28.0 → 0.29.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -10
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/configuration.rb +12 -12
- data/lib/hexapdf/dictionary_fields.rb +6 -2
- data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
- data/lib/hexapdf/digital_signature/handler.rb +138 -0
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
- data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
- data/lib/hexapdf/digital_signature/signatures.rb +210 -0
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
- data/lib/hexapdf/digital_signature/signing.rb +101 -0
- data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
- data/lib/hexapdf/digital_signature.rb +56 -0
- data/lib/hexapdf/document.rb +21 -14
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
- data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
- data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
- data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
- data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
- data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
- data/test/hexapdf/digital_signature/test_signing.rb +53 -0
- data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +1 -1
- data/test/hexapdf/test_writer.rb +3 -3
- metadata +25 -15
- data/lib/hexapdf/document/signatures.rb +0 -546
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
- data/lib/hexapdf/type/signature/handler.rb +0 -140
- data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -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/
|
6
|
+
require 'hexapdf/digital_signature'
|
7
7
|
require 'ostruct'
|
8
8
|
|
9
|
-
describe HexaPDF::
|
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::
|
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::
|
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::
|
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::
|
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/
|
4
|
+
require 'hexapdf/digital_signature'
|
5
5
|
require 'hexapdf/document'
|
6
6
|
require 'time'
|
7
7
|
require 'ostruct'
|
8
8
|
|
9
|
-
describe HexaPDF::
|
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::
|
14
|
-
@result = HexaPDF::
|
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/
|
5
|
+
require 'hexapdf/digital_signature'
|
6
6
|
require 'ostruct'
|
7
7
|
|
8
|
-
describe HexaPDF::
|
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::
|
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/
|
6
|
-
require_relative '
|
5
|
+
require 'hexapdf/digital_signature'
|
6
|
+
require_relative 'common'
|
7
7
|
require 'stringio'
|
8
8
|
|
9
|
-
describe HexaPDF::
|
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::
|
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::
|
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::
|
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
|
@@ -1,26 +1,26 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
|
-
require 'hexapdf/
|
4
|
+
require 'hexapdf/digital_signature'
|
5
5
|
|
6
|
-
describe HexaPDF::
|
6
|
+
describe HexaPDF::DigitalSignature::VerificationResult do
|
7
7
|
describe "Message" do
|
8
8
|
it "accepts a type and a content argument on creation" do
|
9
|
-
m = HexaPDF::
|
9
|
+
m = HexaPDF::DigitalSignature::VerificationResult::Message.new(:type, 'content')
|
10
10
|
assert_equal(:type, m.type)
|
11
11
|
assert_equal('content', m.content)
|
12
12
|
end
|
13
13
|
|
14
14
|
it "allows sorting by type" do
|
15
|
-
info = HexaPDF::
|
16
|
-
warning = HexaPDF::
|
17
|
-
error = HexaPDF::
|
15
|
+
info = HexaPDF::DigitalSignature::VerificationResult::Message.new(:info, 'c')
|
16
|
+
warning = HexaPDF::DigitalSignature::VerificationResult::Message.new(:warning, 'c')
|
17
|
+
error = HexaPDF::DigitalSignature::VerificationResult::Message.new(:error, 'c')
|
18
18
|
assert_equal([error, warning, info], [info, error, warning].sort)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
before do
|
23
|
-
@result = HexaPDF::
|
23
|
+
@result = HexaPDF::DigitalSignature::VerificationResult.new
|
24
24
|
end
|
25
25
|
|
26
26
|
it "can add new messages" do
|
@@ -175,7 +175,6 @@ describe HexaPDF::DictionaryFields do
|
|
175
175
|
|
176
176
|
it "allows conversion to a Time object from a binary string" do
|
177
177
|
refute(@field.convert('test'.b, self))
|
178
|
-
refute(@field.convert('D:01211016165909+00\'64'.b, self))
|
179
178
|
|
180
179
|
[
|
181
180
|
["D:1998", [1998, 01, 01, 00, 00, 00, "-00:00"]],
|
@@ -191,6 +190,8 @@ describe HexaPDF::DictionaryFields do
|
|
191
190
|
["D:1998-08'00'", [1998, 01, 01, 00, 00, 00, "-08:00"]],
|
192
191
|
["D:19981223195210-08", [1998, 12, 23, 19, 52, 10, "-08:00"]], # non-standard, missing '
|
193
192
|
["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # non-standard, missing '
|
193
|
+
["D:19981223195210-54'00", [1998, 12, 23, 19, 52, 10, "-23:59:59"]], # non-standard, TZ hour to large
|
194
|
+
["D:19981223195210+10'65", [1998, 12, 23, 19, 52, 10, "+11:05"]], # non-standard, TZ min to large
|
194
195
|
].each do |str, data|
|
195
196
|
obj = @field.convert(str, self)
|
196
197
|
assert_equal(Time.new(*data), obj, "date str used: #{str}")
|
@@ -504,7 +504,7 @@ describe HexaPDF::Document do
|
|
504
504
|
|
505
505
|
it "allows to conveniently sign a document" do
|
506
506
|
mock = Minitest::Mock.new
|
507
|
-
mock.expect(:
|
507
|
+
mock.expect(:signing_handler, :handler, name: :handler, opt: :key)
|
508
508
|
mock.expect(:add, :added, [:io, :handler], signature: :sig, write_options: :write_options)
|
509
509
|
@doc.instance_variable_set(:@signatures, mock)
|
510
510
|
result = @doc.sign(:io, handler: :handler, write_options: :write_options, signature: :sig, opt: :key)
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.
|
43
|
+
<</Producer(HexaPDF version 0.29.0)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.
|
75
|
+
<</Producer(HexaPDF version 0.29.0)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -214,7 +214,7 @@ describe HexaPDF::Writer do
|
|
214
214
|
<</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
|
215
215
|
endobj
|
216
216
|
5 0 obj
|
217
|
-
<</Producer(HexaPDF version 0.
|
217
|
+
<</Producer(HexaPDF version 0.29.0)>>
|
218
218
|
endobj
|
219
219
|
4 0 obj
|
220
220
|
<</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.29.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -291,6 +291,7 @@ files:
|
|
291
291
|
- examples/021-list_box.rb
|
292
292
|
- examples/022-outline.rb
|
293
293
|
- examples/023-images.rb
|
294
|
+
- examples/024-digital-signatures.rb
|
294
295
|
- examples/emoji-smile.png
|
295
296
|
- examples/emoji-wink.png
|
296
297
|
- examples/machupicchu.jpg
|
@@ -328,6 +329,17 @@ files:
|
|
328
329
|
- lib/hexapdf/data_dir.rb
|
329
330
|
- lib/hexapdf/dictionary.rb
|
330
331
|
- lib/hexapdf/dictionary_fields.rb
|
332
|
+
- lib/hexapdf/digital_signature.rb
|
333
|
+
- lib/hexapdf/digital_signature/cms_handler.rb
|
334
|
+
- lib/hexapdf/digital_signature/handler.rb
|
335
|
+
- lib/hexapdf/digital_signature/pkcs1_handler.rb
|
336
|
+
- lib/hexapdf/digital_signature/signature.rb
|
337
|
+
- lib/hexapdf/digital_signature/signatures.rb
|
338
|
+
- lib/hexapdf/digital_signature/signing.rb
|
339
|
+
- lib/hexapdf/digital_signature/signing/default_handler.rb
|
340
|
+
- lib/hexapdf/digital_signature/signing/signed_data_creator.rb
|
341
|
+
- lib/hexapdf/digital_signature/signing/timestamp_handler.rb
|
342
|
+
- lib/hexapdf/digital_signature/verification_result.rb
|
331
343
|
- lib/hexapdf/document.rb
|
332
344
|
- lib/hexapdf/document/destinations.rb
|
333
345
|
- lib/hexapdf/document/files.rb
|
@@ -335,7 +347,6 @@ files:
|
|
335
347
|
- lib/hexapdf/document/images.rb
|
336
348
|
- lib/hexapdf/document/layout.rb
|
337
349
|
- lib/hexapdf/document/pages.rb
|
338
|
-
- lib/hexapdf/document/signatures.rb
|
339
350
|
- lib/hexapdf/encryption.rb
|
340
351
|
- lib/hexapdf/encryption/aes.rb
|
341
352
|
- lib/hexapdf/encryption/arc4.rb
|
@@ -486,11 +497,6 @@ files:
|
|
486
497
|
- lib/hexapdf/type/page_label.rb
|
487
498
|
- lib/hexapdf/type/page_tree_node.rb
|
488
499
|
- lib/hexapdf/type/resources.rb
|
489
|
-
- lib/hexapdf/type/signature.rb
|
490
|
-
- lib/hexapdf/type/signature/adbe_pkcs7_detached.rb
|
491
|
-
- lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb
|
492
|
-
- lib/hexapdf/type/signature/handler.rb
|
493
|
-
- lib/hexapdf/type/signature/verification_result.rb
|
494
500
|
- lib/hexapdf/type/trailer.rb
|
495
501
|
- lib/hexapdf/type/viewer_preferences.rb
|
496
502
|
- lib/hexapdf/type/xref_stream.rb
|
@@ -587,13 +593,23 @@ files:
|
|
587
593
|
- test/hexapdf/content/test_parser.rb
|
588
594
|
- test/hexapdf/content/test_processor.rb
|
589
595
|
- test/hexapdf/content/test_transformation_matrix.rb
|
596
|
+
- test/hexapdf/digital_signature/common.rb
|
597
|
+
- test/hexapdf/digital_signature/signing/test_default_handler.rb
|
598
|
+
- test/hexapdf/digital_signature/signing/test_signed_data_creator.rb
|
599
|
+
- test/hexapdf/digital_signature/signing/test_timestamp_handler.rb
|
600
|
+
- test/hexapdf/digital_signature/test_cms_handler.rb
|
601
|
+
- test/hexapdf/digital_signature/test_handler.rb
|
602
|
+
- test/hexapdf/digital_signature/test_pkcs1_handler.rb
|
603
|
+
- test/hexapdf/digital_signature/test_signature.rb
|
604
|
+
- test/hexapdf/digital_signature/test_signatures.rb
|
605
|
+
- test/hexapdf/digital_signature/test_signing.rb
|
606
|
+
- test/hexapdf/digital_signature/test_verification_result.rb
|
590
607
|
- test/hexapdf/document/test_destinations.rb
|
591
608
|
- test/hexapdf/document/test_files.rb
|
592
609
|
- test/hexapdf/document/test_fonts.rb
|
593
610
|
- test/hexapdf/document/test_images.rb
|
594
611
|
- test/hexapdf/document/test_layout.rb
|
595
612
|
- test/hexapdf/document/test_pages.rb
|
596
|
-
- test/hexapdf/document/test_signatures.rb
|
597
613
|
- test/hexapdf/encryption/common.rb
|
598
614
|
- test/hexapdf/encryption/test_aes.rb
|
599
615
|
- test/hexapdf/encryption/test_arc4.rb
|
@@ -705,11 +721,6 @@ files:
|
|
705
721
|
- test/hexapdf/type/annotations/test_markup_annotation.rb
|
706
722
|
- test/hexapdf/type/annotations/test_text.rb
|
707
723
|
- test/hexapdf/type/annotations/test_widget.rb
|
708
|
-
- test/hexapdf/type/signature/common.rb
|
709
|
-
- test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb
|
710
|
-
- test/hexapdf/type/signature/test_adbe_x509_rsa_sha1.rb
|
711
|
-
- test/hexapdf/type/signature/test_handler.rb
|
712
|
-
- test/hexapdf/type/signature/test_verification_result.rb
|
713
724
|
- test/hexapdf/type/test_annotation.rb
|
714
725
|
- test/hexapdf/type/test_catalog.rb
|
715
726
|
- test/hexapdf/type/test_cid_font.rb
|
@@ -732,7 +743,6 @@ files:
|
|
732
743
|
- test/hexapdf/type/test_page_label.rb
|
733
744
|
- test/hexapdf/type/test_page_tree_node.rb
|
734
745
|
- test/hexapdf/type/test_resources.rb
|
735
|
-
- test/hexapdf/type/test_signature.rb
|
736
746
|
- test/hexapdf/type/test_trailer.rb
|
737
747
|
- test/hexapdf/type/test_xref_stream.rb
|
738
748
|
- test/hexapdf/utils/test_bit_field.rb
|