hexapdf 0.27.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 +100 -11
- data/examples/019-acro_form.rb +14 -3
- data/examples/023-images.rb +30 -0
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +2 -2
- data/lib/hexapdf/configuration.rb +13 -14
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- 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 +27 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +33 -7
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
- 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/document/test_pages.rb +2 -2
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +3 -9
- data/test/hexapdf/test_importer.rb +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +18 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +69 -16
- 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,162 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
require_relative '../common'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
|
|
8
|
+
before do
|
|
9
|
+
@doc = HexaPDF::Document.new
|
|
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 "defaults to standard CMS signatures" do
|
|
18
|
+
assert_equal(:cms, @handler.signature_type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "returns the size of serialized signature" do
|
|
22
|
+
assert(@handler.signature_size > 1000)
|
|
23
|
+
@handler.signature_size = 100
|
|
24
|
+
assert_equal(100, @handler.signature_size)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "allows setting the DocMDP permissions" do
|
|
28
|
+
assert_nil(@handler.doc_mdp_permissions)
|
|
29
|
+
|
|
30
|
+
@handler.doc_mdp_permissions = :no_changes
|
|
31
|
+
assert_equal(1, @handler.doc_mdp_permissions)
|
|
32
|
+
@handler.doc_mdp_permissions = 1
|
|
33
|
+
assert_equal(1, @handler.doc_mdp_permissions)
|
|
34
|
+
|
|
35
|
+
@handler.doc_mdp_permissions = :form_filling
|
|
36
|
+
assert_equal(2, @handler.doc_mdp_permissions)
|
|
37
|
+
@handler.doc_mdp_permissions = 2
|
|
38
|
+
assert_equal(2, @handler.doc_mdp_permissions)
|
|
39
|
+
|
|
40
|
+
@handler.doc_mdp_permissions = :form_filling_and_annotations
|
|
41
|
+
assert_equal(3, @handler.doc_mdp_permissions)
|
|
42
|
+
@handler.doc_mdp_permissions = 3
|
|
43
|
+
assert_equal(3, @handler.doc_mdp_permissions)
|
|
44
|
+
|
|
45
|
+
@handler.doc_mdp_permissions = nil
|
|
46
|
+
assert_nil(@handler.doc_mdp_permissions)
|
|
47
|
+
|
|
48
|
+
assert_raises(ArgumentError) { @handler.doc_mdp_permissions = :other }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "sign" do
|
|
52
|
+
it "can sign the data using the provided certificate and key" do
|
|
53
|
+
data = StringIO.new("data")
|
|
54
|
+
signed_data = @handler.sign(data, [0, data.string.size, 0, 0])
|
|
55
|
+
|
|
56
|
+
pkcs7 = OpenSSL::PKCS7.new(signed_data)
|
|
57
|
+
assert(pkcs7.detached?)
|
|
58
|
+
assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
|
|
59
|
+
pkcs7.certificates)
|
|
60
|
+
store = OpenSSL::X509::Store.new
|
|
61
|
+
store.add_cert(CERTIFICATES.ca_certificate)
|
|
62
|
+
assert(pkcs7.verify([], store, data.string, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "can change the used digest algorithm" do
|
|
66
|
+
@handler.digest_algorithm = 'sha384'
|
|
67
|
+
asn1 = OpenSSL::ASN1.decode(@handler.sign(StringIO.new('data'), [0, 4, 0, 0]))
|
|
68
|
+
assert_equal('SHA384', asn1.value[1].value[0].value[1].value[0].value[0].value)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "can embed a timestamp token" do
|
|
72
|
+
@handler.timestamp_handler = tsh = Object.new
|
|
73
|
+
tsh.define_singleton_method(:sign) {|_, _| OpenSSL::ASN1::OctetString.new("signed-tsh") }
|
|
74
|
+
signed = @handler.sign(StringIO.new('data'), [0, 4, 0, 0])
|
|
75
|
+
asn1 = OpenSSL::ASN1.decode(signed)
|
|
76
|
+
assert_equal('signed-tsh', asn1.value[1].value[0].value[4].value[0].
|
|
77
|
+
value[6].value[0].value[1].value[0].value)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "creates PAdES compatible signatures" do
|
|
81
|
+
@handler.signature_type = :pades
|
|
82
|
+
signed = @handler.sign(StringIO.new('data'), [0, 4, 0, 0])
|
|
83
|
+
asn1 = OpenSSL::ASN1.decode(signed)
|
|
84
|
+
# check by absence of signing-time signed attribute
|
|
85
|
+
refute(asn1.value[1].value[0].value[4].value[0].value[3].value.
|
|
86
|
+
find {|obj| obj.value[0].value == 'signingTime' })
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "can use external signing without certificate set" do
|
|
90
|
+
@handler.certificate = nil
|
|
91
|
+
@handler.external_signing = proc { "hallo" }
|
|
92
|
+
assert_equal("hallo", @handler.sign(StringIO.new, [0, 0, 0, 0]))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "can use external signing with certificate set but not the key" do
|
|
96
|
+
@handler.key = nil
|
|
97
|
+
@handler.external_signing = proc do |algorithm, _hash|
|
|
98
|
+
assert_equal('sha256', algorithm)
|
|
99
|
+
"hallo"
|
|
100
|
+
end
|
|
101
|
+
result = @handler.sign(StringIO.new, [0, 0, 0, 0])
|
|
102
|
+
asn1 = OpenSSL::ASN1.decode(result)
|
|
103
|
+
assert_equal("hallo", asn1.value[1].value[0].value[4].value[0].value[5].value)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe "finalize_objects" do
|
|
108
|
+
before do
|
|
109
|
+
@field = @doc.wrap({})
|
|
110
|
+
@obj = @doc.wrap({})
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "only sets the mandatory values if no concrete finalization tasks need to be done" do
|
|
114
|
+
@handler.finalize_objects(@field, @obj)
|
|
115
|
+
assert(@field.empty?)
|
|
116
|
+
assert_equal(:'Adobe.PPKLite', @obj[:Filter])
|
|
117
|
+
assert_equal(:'adbe.pkcs7.detached', @obj[:SubFilter])
|
|
118
|
+
assert_kind_of(Time, @obj[:M])
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "adjust the /SubFilter if signature type is pades" do
|
|
122
|
+
@handler.signature_type = :pades
|
|
123
|
+
@handler.finalize_objects(@field, @obj)
|
|
124
|
+
assert_equal(:'ETSI.CAdES.detached', @obj[:SubFilter])
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "sets the reason, location and contact info fields" do
|
|
128
|
+
@handler.reason = 'Reason'
|
|
129
|
+
@handler.location = 'Location'
|
|
130
|
+
@handler.contact_info = 'Contact'
|
|
131
|
+
@handler.finalize_objects(@field, @obj)
|
|
132
|
+
assert(@field.empty?)
|
|
133
|
+
assert_equal(['Reason', 'Location', 'Contact'], @obj.value.values_at(:Reason, :Location, :ContactInfo))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "fills the build properties dictionary with appropriate application information" do
|
|
137
|
+
@handler.finalize_objects(@field, @obj)
|
|
138
|
+
assert_equal(:HexaPDF, @obj[:Prop_Build][:App][:Name])
|
|
139
|
+
assert_equal(HexaPDF::VERSION, @obj[:Prop_Build][:App][:REx])
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "applies the specified DocMDP permissions" do
|
|
143
|
+
@handler.doc_mdp_permissions = :no_changes
|
|
144
|
+
@handler.finalize_objects(@field, @obj)
|
|
145
|
+
ref = @obj[:Reference][0]
|
|
146
|
+
assert_equal(:DocMDP, ref[:TransformMethod])
|
|
147
|
+
assert_equal(:SHA256, ref[:DigestMethod])
|
|
148
|
+
assert_equal(1, ref[:TransformParams][:P])
|
|
149
|
+
assert_equal(:'1.2', ref[:TransformParams][:V])
|
|
150
|
+
assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "fails if DocMDP should be set but there is already a signature" do
|
|
154
|
+
@handler.doc_mdp_permissions = :no_changes
|
|
155
|
+
2.times do
|
|
156
|
+
field = @doc.acro_form(create: true).create_signature_field('test')
|
|
157
|
+
field.field_value = :something
|
|
158
|
+
end
|
|
159
|
+
assert_raises(HexaPDF::Error) { @handler.finalize_objects(@field, @obj) }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -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/
|
|
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
|