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
@@ -1,95 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
-
#
|
3
|
-
#--
|
4
|
-
# This file is part of HexaPDF.
|
5
|
-
#
|
6
|
-
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
-
# Copyright (C) 2014-2022 Thomas Leitner
|
8
|
-
#
|
9
|
-
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
-
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
-
# published by the Free Software Foundation with the addition of the
|
12
|
-
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
-
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
-
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
-
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
-
#
|
17
|
-
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
-
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
-
# License for more details.
|
21
|
-
#
|
22
|
-
# You should have received a copy of the GNU Affero General Public License
|
23
|
-
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
-
#
|
25
|
-
# The interactive user interfaces in modified source and object code
|
26
|
-
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
-
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
-
#
|
29
|
-
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
-
# License, a covered work must retain the producer line in every PDF that
|
31
|
-
# is created or manipulated using HexaPDF.
|
32
|
-
#
|
33
|
-
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
-
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
-
#++
|
36
|
-
|
37
|
-
require 'openssl'
|
38
|
-
require 'hexapdf/type/signature'
|
39
|
-
|
40
|
-
module HexaPDF
|
41
|
-
module Type
|
42
|
-
class Signature
|
43
|
-
|
44
|
-
# The signature handler for the adbe.x509.rsa_sha1 sub-filter.
|
45
|
-
#
|
46
|
-
# Since this handler is deprecated with PDF 2.0 it only provides the implementation for
|
47
|
-
# reading and verifying signatures.
|
48
|
-
class AdbeX509RsaSha1 < Handler
|
49
|
-
|
50
|
-
# Returns the certificate chain.
|
51
|
-
def certificate_chain
|
52
|
-
return [] unless signature_dict.key?(:Cert)
|
53
|
-
[signature_dict[:Cert]].flatten.map {|str| OpenSSL::X509::Certificate.new(str) }
|
54
|
-
end
|
55
|
-
|
56
|
-
# Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
|
57
|
-
def signer_certificate
|
58
|
-
certificate_chain.first
|
59
|
-
end
|
60
|
-
|
61
|
-
# Verifies the signature using the provided OpenSSL::X509::Store object.
|
62
|
-
def verify(store, allow_self_signed: false)
|
63
|
-
result = super
|
64
|
-
|
65
|
-
signer_certificate = self.signer_certificate
|
66
|
-
certificate_chain = self.certificate_chain
|
67
|
-
|
68
|
-
if certificate_chain.empty?
|
69
|
-
result.log(:error, "No certificates for verification found")
|
70
|
-
return result
|
71
|
-
end
|
72
|
-
|
73
|
-
signature = OpenSSL::ASN1.decode(signature_dict.contents)
|
74
|
-
if signature.tag != OpenSSL::ASN1::OCTET_STRING
|
75
|
-
result.log(:error, "PKCS1 signature object invalid, octet string expected")
|
76
|
-
return result
|
77
|
-
end
|
78
|
-
|
79
|
-
store.verify(signer_certificate, certificate_chain)
|
80
|
-
|
81
|
-
if signer_certificate.public_key.verify(OpenSSL::Digest.new('SHA1'),
|
82
|
-
signature.value, signature_dict.signed_data)
|
83
|
-
result.log(:info, "Signature valid")
|
84
|
-
else
|
85
|
-
result.log(:error, "Signature verification failed")
|
86
|
-
end
|
87
|
-
|
88
|
-
result
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
@@ -1,140 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
-
#
|
3
|
-
#--
|
4
|
-
# This file is part of HexaPDF.
|
5
|
-
#
|
6
|
-
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
-
# Copyright (C) 2014-2022 Thomas Leitner
|
8
|
-
#
|
9
|
-
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
-
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
-
# published by the Free Software Foundation with the addition of the
|
12
|
-
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
-
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
-
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
-
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
-
#
|
17
|
-
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
-
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
-
# License for more details.
|
21
|
-
#
|
22
|
-
# You should have received a copy of the GNU Affero General Public License
|
23
|
-
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
-
#
|
25
|
-
# The interactive user interfaces in modified source and object code
|
26
|
-
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
-
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
-
#
|
29
|
-
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
-
# License, a covered work must retain the producer line in every PDF that
|
31
|
-
# is created or manipulated using HexaPDF.
|
32
|
-
#
|
33
|
-
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
-
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
-
#++
|
36
|
-
|
37
|
-
require 'hexapdf/type/signature'
|
38
|
-
|
39
|
-
module HexaPDF
|
40
|
-
module Type
|
41
|
-
class Signature
|
42
|
-
|
43
|
-
# The base signature handler providing common functionality.
|
44
|
-
#
|
45
|
-
# Specific signature handler need to override methods if necessary and implement the needed
|
46
|
-
# ones that don't have a default implementation.
|
47
|
-
class Handler
|
48
|
-
|
49
|
-
# The signature dictionary used by the handler.
|
50
|
-
attr_reader :signature_dict
|
51
|
-
|
52
|
-
# Creates a new signature handler for the given signature dictionary.
|
53
|
-
def initialize(signature_dict)
|
54
|
-
@signature_dict = signature_dict
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns the common name of the signer (/Name field of the signature dictionary).
|
58
|
-
def signer_name
|
59
|
-
@signature_dict[:Name]
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns the time of signing (/M field of the signature dictionary).
|
63
|
-
def signing_time
|
64
|
-
@signature_dict[:M]
|
65
|
-
end
|
66
|
-
|
67
|
-
# Returns the certificate chain.
|
68
|
-
#
|
69
|
-
# Needs to be implemented by specific handlers.
|
70
|
-
def certificate_chain
|
71
|
-
raise "Needs to be implemented by specific handlers"
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns the certificate used for signing.
|
75
|
-
#
|
76
|
-
# Needs to be implemented by specific handlers.
|
77
|
-
def signer_certificate
|
78
|
-
raise "Needs to be implemented by specific handlers"
|
79
|
-
end
|
80
|
-
|
81
|
-
# Verifies general signature properties and prepares the provided OpenSSL::X509::Store
|
82
|
-
# object for use by concrete implementations.
|
83
|
-
#
|
84
|
-
# Needs to be called by specific handlers.
|
85
|
-
def verify(store, allow_self_signed: false)
|
86
|
-
result = VerificationResult.new
|
87
|
-
check_certified_signature(result)
|
88
|
-
verify_signing_time(result)
|
89
|
-
store.verify_callback =
|
90
|
-
store_verification_callback(result, allow_self_signed: allow_self_signed)
|
91
|
-
result
|
92
|
-
end
|
93
|
-
|
94
|
-
protected
|
95
|
-
|
96
|
-
# Verifies that the signing time was within the validity period of the signer certificate.
|
97
|
-
def verify_signing_time(result)
|
98
|
-
time = signing_time
|
99
|
-
cert = signer_certificate
|
100
|
-
if time && cert && (time < cert.not_before || time > cert.not_after)
|
101
|
-
result.log(:error, "Signer certificate not valid at signing time")
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
DOCMDP_PERMS_MESSAGE_MAP = { # :nodoc:
|
106
|
-
1 => "No changes allowed",
|
107
|
-
2 => "Form filling and signing allowed",
|
108
|
-
3 => "Form filling, signing and annotation manipulation allowed",
|
109
|
-
}
|
110
|
-
|
111
|
-
# Sets an informational message on +result+ whether the signature is a certified signature.
|
112
|
-
def check_certified_signature(result)
|
113
|
-
sigref = signature_dict[:Reference]&.find {|ref| ref[:TransformMethod] == :DocMDP }
|
114
|
-
if sigref && signature_dict.document.catalog[:Perms]&.[](:DocMDP) == signature_dict
|
115
|
-
perms = sigref[:TransformParams]&.[](:P) || 2
|
116
|
-
result.log(:info, "Certified signature (#{DOCMDP_PERMS_MESSAGE_MAP[perms]})")
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns the block that should be used as the OpenSSL::X509::Store verification callback.
|
121
|
-
#
|
122
|
-
# +result+:: The VerificationResult object that should be updated if problems are found.
|
123
|
-
#
|
124
|
-
# +allow_self_signed+:: Specifies whether self-signed certificates are allowed.
|
125
|
-
def store_verification_callback(result, allow_self_signed: false)
|
126
|
-
lambda do |_success, context|
|
127
|
-
if context.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
128
|
-
context.error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
|
129
|
-
result.log(allow_self_signed ? :info : :error, "Self-signed certificate found")
|
130
|
-
end
|
131
|
-
|
132
|
-
true
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
end
|
137
|
-
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
@@ -1,352 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require 'test_helper'
|
4
|
-
require 'stringio'
|
5
|
-
require 'tempfile'
|
6
|
-
require 'hexapdf/document'
|
7
|
-
require_relative '../type/signature/common'
|
8
|
-
|
9
|
-
describe HexaPDF::Document::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
|
-
@handler = HexaPDF::Document::Signatures::DefaultHandler.new(
|
16
|
-
certificate: CERTIFICATES.signer_certificate,
|
17
|
-
key: CERTIFICATES.signer_key,
|
18
|
-
certificate_chain: [CERTIFICATES.ca_certificate]
|
19
|
-
)
|
20
|
-
end
|
21
|
-
|
22
|
-
it "allows embedding an external signature value" do
|
23
|
-
doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
24
|
-
io = StringIO.new(''.b)
|
25
|
-
doc.signatures.add(io, @handler)
|
26
|
-
doc = HexaPDF::Document.new(io: io)
|
27
|
-
io = StringIO.new(''.b)
|
28
|
-
|
29
|
-
byte_range = nil
|
30
|
-
@handler.signature_size = 5000
|
31
|
-
@handler.external_signing = proc {|_, br| byte_range = br; "" }
|
32
|
-
doc.signatures.add(io, @handler)
|
33
|
-
|
34
|
-
io.pos = byte_range[0]
|
35
|
-
data = io.read(byte_range[1])
|
36
|
-
io.pos = byte_range[2]
|
37
|
-
data << io.read(byte_range[3])
|
38
|
-
contents = OpenSSL::PKCS7.sign(@handler.certificate, @handler.key, data, @handler.certificate_chain,
|
39
|
-
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
|
40
|
-
HexaPDF::Document::Signatures.embed_signature(io, contents)
|
41
|
-
doc = HexaPDF::Document.new(io: io)
|
42
|
-
assert_equal(2, doc.signatures.each.count)
|
43
|
-
doc.signatures.each do |signature|
|
44
|
-
assert(signature.verify(allow_self_signed: true).messages.find {|m| m.content == 'Signature valid' })
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe "DefaultHandler" do
|
49
|
-
it "returns the size of serialized signature" do
|
50
|
-
assert_equal(1310, @handler.signature_size)
|
51
|
-
@handler.signature_size = 100
|
52
|
-
assert_equal(100, @handler.signature_size)
|
53
|
-
end
|
54
|
-
|
55
|
-
it "allows setting the DocMDP permissions" do
|
56
|
-
assert_nil(@handler.doc_mdp_permissions)
|
57
|
-
|
58
|
-
@handler.doc_mdp_permissions = :no_changes
|
59
|
-
assert_equal(1, @handler.doc_mdp_permissions)
|
60
|
-
@handler.doc_mdp_permissions = 1
|
61
|
-
assert_equal(1, @handler.doc_mdp_permissions)
|
62
|
-
|
63
|
-
@handler.doc_mdp_permissions = :form_filling
|
64
|
-
assert_equal(2, @handler.doc_mdp_permissions)
|
65
|
-
@handler.doc_mdp_permissions = 2
|
66
|
-
assert_equal(2, @handler.doc_mdp_permissions)
|
67
|
-
|
68
|
-
@handler.doc_mdp_permissions = :form_filling_and_annotations
|
69
|
-
assert_equal(3, @handler.doc_mdp_permissions)
|
70
|
-
@handler.doc_mdp_permissions = 3
|
71
|
-
assert_equal(3, @handler.doc_mdp_permissions)
|
72
|
-
|
73
|
-
@handler.doc_mdp_permissions = nil
|
74
|
-
assert_nil(@handler.doc_mdp_permissions)
|
75
|
-
|
76
|
-
assert_raises(ArgumentError) { @handler.doc_mdp_permissions = :other }
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "sign" do
|
80
|
-
it "can sign the data using PKCS7" do
|
81
|
-
data = StringIO.new("data")
|
82
|
-
store = OpenSSL::X509::Store.new
|
83
|
-
store.add_cert(CERTIFICATES.ca_certificate)
|
84
|
-
|
85
|
-
pkcs7 = OpenSSL::PKCS7.new(@handler.sign(data, [0, 4, 0, 0]))
|
86
|
-
assert(pkcs7.detached?)
|
87
|
-
assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
|
88
|
-
pkcs7.certificates)
|
89
|
-
assert(pkcs7.verify([], store, data.string, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY))
|
90
|
-
end
|
91
|
-
|
92
|
-
it "can use external signing" do
|
93
|
-
@handler.external_signing = proc { "hallo" }
|
94
|
-
assert_equal("hallo", @handler.sign(StringIO.new, [0, 0, 0, 0]))
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
describe "finalize_objects" do
|
99
|
-
before do
|
100
|
-
@field = @doc.wrap({})
|
101
|
-
@obj = @doc.wrap({})
|
102
|
-
end
|
103
|
-
|
104
|
-
it "does nothing if no finalization tasks need to be done" do
|
105
|
-
@handler.finalize_objects(@field, @obj)
|
106
|
-
assert(@field.empty?)
|
107
|
-
assert(@obj.empty?)
|
108
|
-
end
|
109
|
-
|
110
|
-
it "adjust the /SubFilter if signature type is etsi" do
|
111
|
-
@handler.signature_type = :etsi
|
112
|
-
@handler.finalize_objects(@field, @obj)
|
113
|
-
assert_equal(:'ETSI.CAdES.detached', @obj[:SubFilter])
|
114
|
-
end
|
115
|
-
|
116
|
-
it "sets the reason, location and contact info fields" do
|
117
|
-
@handler.reason = 'Reason'
|
118
|
-
@handler.location = 'Location'
|
119
|
-
@handler.contact_info = 'Contact'
|
120
|
-
@handler.finalize_objects(@field, @obj)
|
121
|
-
assert(@field.empty?)
|
122
|
-
assert_equal({Reason: 'Reason', Location: 'Location', ContactInfo: 'Contact'}, @obj.value)
|
123
|
-
end
|
124
|
-
|
125
|
-
it "applies the specified DocMDP permissions" do
|
126
|
-
@handler.doc_mdp_permissions = :no_changes
|
127
|
-
@handler.finalize_objects(@field, @obj)
|
128
|
-
ref = @obj[:Reference][0]
|
129
|
-
assert_equal(:DocMDP, ref[:TransformMethod])
|
130
|
-
assert_equal(:SHA1, ref[:DigestMethod])
|
131
|
-
assert_equal(1, ref[:TransformParams][:P])
|
132
|
-
assert_equal(:'1.2', ref[:TransformParams][:V])
|
133
|
-
assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
|
134
|
-
end
|
135
|
-
|
136
|
-
it "fails if DocMDP should be set but there is already a signature" do
|
137
|
-
@handler.doc_mdp_permissions = :no_changes
|
138
|
-
2.times do
|
139
|
-
field = @doc.acro_form(create: true).create_signature_field('test')
|
140
|
-
field.field_value = :something
|
141
|
-
end
|
142
|
-
assert_raises(HexaPDF::Error) { @handler.finalize_objects(@field, @obj) }
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
describe "TimestampHandler" do
|
148
|
-
before do
|
149
|
-
@handler = HexaPDF::Document::Signatures::TimestampHandler.new
|
150
|
-
end
|
151
|
-
|
152
|
-
it "allows setting the attributes in the constructor" do
|
153
|
-
handler = HexaPDF::Document::Signatures::TimestampHandler.new(
|
154
|
-
tsa_url: "url", tsa_hash_algorithm: "MD5", tsa_policy_id: "5",
|
155
|
-
reason: "Reason", location: "Location", contact_info: "Contact",
|
156
|
-
signature_size: 1_000
|
157
|
-
)
|
158
|
-
assert_equal("url", handler.tsa_url)
|
159
|
-
assert_equal("MD5", handler.tsa_hash_algorithm)
|
160
|
-
assert_equal("5", handler.tsa_policy_id)
|
161
|
-
assert_equal("Reason", handler.reason)
|
162
|
-
assert_equal("Location", handler.location)
|
163
|
-
assert_equal("Contact", handler.contact_info)
|
164
|
-
assert_equal(1_000, handler.signature_size)
|
165
|
-
end
|
166
|
-
|
167
|
-
it "finalizes the signature field and signature objects" do
|
168
|
-
@field = @doc.wrap({})
|
169
|
-
@sig = @doc.wrap({})
|
170
|
-
@handler.reason = 'Reason'
|
171
|
-
@handler.location = 'Location'
|
172
|
-
@handler.contact_info = 'Contact'
|
173
|
-
|
174
|
-
@handler.finalize_objects(@field, @sig)
|
175
|
-
assert_equal('2.0', @doc.version)
|
176
|
-
assert_equal(:DocTimeStamp, @sig[:Type])
|
177
|
-
assert_equal(:'ETSI.RFC3161', @sig[:SubFilter])
|
178
|
-
assert_equal('Reason', @sig[:Reason])
|
179
|
-
assert_equal('Location', @sig[:Location])
|
180
|
-
assert_equal('Contact', @sig[:ContactInfo])
|
181
|
-
end
|
182
|
-
|
183
|
-
it "returns the size of serialized signature" do
|
184
|
-
@handler.tsa_url = "http://127.0.0.1:34567"
|
185
|
-
CERTIFICATES.start_tsa_server
|
186
|
-
assert_equal(1420, @handler.signature_size)
|
187
|
-
end
|
188
|
-
|
189
|
-
describe "sign" do
|
190
|
-
before do
|
191
|
-
@data = StringIO.new("data")
|
192
|
-
@range = [0, 4, 0, 0]
|
193
|
-
@handler.tsa_url = "http://127.0.0.1:34567"
|
194
|
-
CERTIFICATES.start_tsa_server
|
195
|
-
end
|
196
|
-
|
197
|
-
it "respects the set hash algorithm and policy id" do
|
198
|
-
@handler.tsa_hash_algorithm = 'SHA256'
|
199
|
-
@handler.tsa_policy_id = '1.2.3.4.2'
|
200
|
-
token = OpenSSL::ASN1.decode(@handler.sign(@data, @range))
|
201
|
-
content = OpenSSL::ASN1.decode(token.value[1].value[0].value[2].value[1].value[0].value)
|
202
|
-
policy_id = content.value[1].value
|
203
|
-
digest_algorithm = content.value[2].value[0].value[0].value
|
204
|
-
assert_equal('SHA256', digest_algorithm)
|
205
|
-
assert_equal("1.2.3.4.2", policy_id)
|
206
|
-
end
|
207
|
-
|
208
|
-
it "returns the serialized timestamp token" do
|
209
|
-
token = OpenSSL::PKCS7.new(@handler.sign(@data, @range))
|
210
|
-
assert_equal(CERTIFICATES.ca_certificate.subject, token.signers[0].issuer)
|
211
|
-
assert_equal(CERTIFICATES.timestamp_certificate.serial, token.signers[0].serial)
|
212
|
-
end
|
213
|
-
|
214
|
-
it "fails if the timestamp token could not be created" do
|
215
|
-
@handler.tsa_hash_algorithm = 'SHA1'
|
216
|
-
msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
|
217
|
-
assert_match(/BAD_ALG/, msg.message)
|
218
|
-
end
|
219
|
-
|
220
|
-
it "fails if the timestamp server couldn't process the request" do
|
221
|
-
@handler.tsa_policy_id = '1.2.3.4.1'
|
222
|
-
msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
|
223
|
-
assert_match(/Invalid TSA server response/, msg.message)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
it "iterates over all signature dictionaries" do
|
229
|
-
assert_equal([], @doc.signatures.to_a)
|
230
|
-
@sig1.field_value = :sig1
|
231
|
-
@sig2.field_value = :sig2
|
232
|
-
assert_equal([:sig1, :sig2], @doc.signatures.to_a)
|
233
|
-
end
|
234
|
-
|
235
|
-
it "returns the number of signature dictionaries" do
|
236
|
-
@sig1.field_value = :sig1
|
237
|
-
assert_equal(1, @doc.signatures.count)
|
238
|
-
end
|
239
|
-
|
240
|
-
describe "handler" do
|
241
|
-
it "return the initialized handler" do
|
242
|
-
handler = @doc.signatures.handler(certificate: 'cert', reason: 'reason')
|
243
|
-
assert_equal('cert', handler.certificate)
|
244
|
-
assert_equal('reason', handler.reason)
|
245
|
-
end
|
246
|
-
|
247
|
-
it "fails if the given task is not available" do
|
248
|
-
assert_raises(HexaPDF::Error) { @doc.signatures.handler(name: :unknown) }
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
describe "add" do
|
253
|
-
before do
|
254
|
-
@doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
255
|
-
@io = StringIO.new(''.b)
|
256
|
-
end
|
257
|
-
|
258
|
-
it "uses the provided signature dictionary" do
|
259
|
-
sig = @doc.add({Type: :Sig, Key: :value})
|
260
|
-
@doc.signatures.add(@io, @handler, signature: sig)
|
261
|
-
assert_equal(1, @doc.signatures.to_a.compact.size)
|
262
|
-
assert_equal(:value, @doc.signatures.to_a[0][:Key])
|
263
|
-
refute_equal(:value, @doc.acro_form.each_field.first[:Key])
|
264
|
-
end
|
265
|
-
|
266
|
-
it "creates the signature dictionary if none is provided" do
|
267
|
-
@doc.signatures.add(@io, @handler)
|
268
|
-
assert_equal(1, @doc.signatures.to_a.compact.size)
|
269
|
-
refute(@doc.acro_form.each_field.first.key?(:Contents))
|
270
|
-
end
|
271
|
-
|
272
|
-
it "sets the needed information on the signature dictionary" do
|
273
|
-
def @handler.finalize_objects(sigfield, sig)
|
274
|
-
sig[:key] = :sig
|
275
|
-
sigfield[:key] = :sig_field
|
276
|
-
end
|
277
|
-
@doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
278
|
-
sig = @doc.signatures.first
|
279
|
-
assert_equal(:'Adobe.PPKLite', sig[:Filter])
|
280
|
-
assert_equal(:'adbe.pkcs7.detached', sig[:SubFilter])
|
281
|
-
assert_equal([0, 996, 3618, 2501], sig[:ByteRange].value)
|
282
|
-
assert_equal(:sig, sig[:key])
|
283
|
-
assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
|
284
|
-
assert(sig.key?(:Contents))
|
285
|
-
assert(sig.key?(:M))
|
286
|
-
end
|
287
|
-
|
288
|
-
it "creates the main form dictionary if necessary" do
|
289
|
-
@doc.signatures.add(@io, @handler)
|
290
|
-
assert(@doc.acro_form)
|
291
|
-
assert_equal([:signatures_exist, :append_only], @doc.acro_form.signature_flags)
|
292
|
-
end
|
293
|
-
|
294
|
-
it "uses the provided signature field" do
|
295
|
-
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
296
|
-
@doc.signatures.add(@io, @handler, signature: field)
|
297
|
-
assert_nil(@doc.acro_form.field_by_name("Signature3"))
|
298
|
-
refute_nil(field.field_value)
|
299
|
-
assert_nil(@doc.signatures.first[:T])
|
300
|
-
end
|
301
|
-
|
302
|
-
it "uses an existing signature field if possible" do
|
303
|
-
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
304
|
-
field.field_value = sig = @doc.add({Type: :Sig, key: :value})
|
305
|
-
@doc.signatures.add(@io, @handler, signature: sig)
|
306
|
-
assert_nil(@doc.acro_form.field_by_name("Signature3"))
|
307
|
-
assert_same(sig, @doc.signatures.first)
|
308
|
-
end
|
309
|
-
|
310
|
-
it "creates the signature field if necessary" do
|
311
|
-
@doc.acro_form(create: true).create_text_field('Signature2')
|
312
|
-
@doc.signatures.add(@io, @handler)
|
313
|
-
field = @doc.acro_form.field_by_name("Signature3")
|
314
|
-
assert_equal(:Sig, field.field_type)
|
315
|
-
refute_nil(field.field_value)
|
316
|
-
assert_equal(1, field.each_widget.count)
|
317
|
-
end
|
318
|
-
|
319
|
-
it "handles different xref section types correctly when determing the offsets" do
|
320
|
-
@doc.delete(7)
|
321
|
-
sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
322
|
-
assert_equal([0, 988, 3610, 2483], sig[:ByteRange].value)
|
323
|
-
end
|
324
|
-
|
325
|
-
it "works if the signature object is the last object of the xref section" do
|
326
|
-
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
327
|
-
field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
|
328
|
-
sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
|
329
|
-
assert_equal([0, 3095, 5717, 380], sig[:ByteRange].value)
|
330
|
-
end
|
331
|
-
|
332
|
-
it "allows writing to a file in addition to writing to an IO" do
|
333
|
-
tempfile = Tempfile.new('hexapdf-signature')
|
334
|
-
tempfile.close
|
335
|
-
@doc.signatures.add(tempfile.path, @handler)
|
336
|
-
doc = HexaPDF::Document.open(tempfile.path)
|
337
|
-
assert(doc.signatures.first.verify(allow_self_signed: true).success?)
|
338
|
-
end
|
339
|
-
|
340
|
-
it "adds a new revision with the signature" do
|
341
|
-
@doc.signatures.add(@io, @handler)
|
342
|
-
signed_doc = HexaPDF::Document.new(io: @io)
|
343
|
-
assert(signed_doc.signatures.first.verify)
|
344
|
-
end
|
345
|
-
|
346
|
-
it "fails if the reserved signature space is too small" do
|
347
|
-
def @handler.signature_size; 200; end
|
348
|
-
msg = assert_raises(HexaPDF::Error) { @doc.signatures.add(@io, @handler) }
|
349
|
-
assert_match(/space.*too small.*200 vs/, msg.message)
|
350
|
-
end
|
351
|
-
end
|
352
|
-
end
|