hexapdf 0.45.0 → 0.47.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +120 -47
- data/examples/019-acro_form.rb +5 -0
- data/lib/hexapdf/cli/inspect.rb +5 -0
- data/lib/hexapdf/composer.rb +1 -1
- data/lib/hexapdf/configuration.rb +19 -0
- data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
- data/lib/hexapdf/document/layout.rb +48 -27
- data/lib/hexapdf/document.rb +24 -2
- data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
- data/lib/hexapdf/importer.rb +15 -5
- data/lib/hexapdf/layout/box.rb +25 -28
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +17 -23
- data/lib/hexapdf/layout/list_box.rb +24 -29
- data/lib/hexapdf/layout/page_style.rb +23 -16
- data/lib/hexapdf/layout/style.rb +2 -2
- data/lib/hexapdf/layout/table_box.rb +57 -10
- data/lib/hexapdf/layout/text_box.rb +2 -6
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/stream.rb +3 -3
- data/lib/hexapdf/task/optimize.rb +4 -4
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
- data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +8 -0
- data/lib/hexapdf/type/acro_form/form.rb +10 -6
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +11 -3
- data/lib/hexapdf/type/annotations/widget.rb +4 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
- data/test/hexapdf/digital_signature/common.rb +66 -84
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
- data/test/hexapdf/digital_signature/test_handler.rb +2 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +4 -4
- data/test/hexapdf/document/test_layout.rb +28 -5
- data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
- data/test/hexapdf/layout/test_box.rb +12 -5
- data/test/hexapdf/layout/test_frame.rb +12 -2
- data/test/hexapdf/layout/test_inline_box.rb +17 -28
- data/test/hexapdf/layout/test_list_box.rb +5 -5
- data/test/hexapdf/layout/test_page_style.rb +7 -2
- data/test/hexapdf/layout/test_table_box.rb +52 -0
- data/test/hexapdf/layout/test_text_box.rb +3 -9
- data/test/hexapdf/layout/test_text_layouter.rb +0 -3
- data/test/hexapdf/task/test_optimize.rb +2 -0
- data/test/hexapdf/test_document.rb +30 -3
- data/test/hexapdf/test_importer.rb +24 -0
- data/test/hexapdf/test_revisions.rb +54 -41
- data/test/hexapdf/test_writer.rb +11 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +22 -5
- data/test/hexapdf/type/acro_form/test_form.rb +9 -5
- data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
- data/test/hexapdf/type/annotations/test_widget.rb +4 -0
- metadata +6 -2
@@ -0,0 +1,44 @@
|
|
1
|
+
%PDF-1.7
|
2
|
+
%����
|
3
|
+
1 0 obj
|
4
|
+
<< /Extensions << /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >> /Pages 3 0 R /Type /Catalog >>
|
5
|
+
endobj
|
6
|
+
2 0 obj
|
7
|
+
<< /ModDate <0a09febf75d913e5eff6197f52f28b31321a43f49e0735a25a93dd77b1a2701e73ae05be0803747ca030ee4917544cbb> >>
|
8
|
+
endobj
|
9
|
+
3 0 obj
|
10
|
+
<< /Count 1 /Kids [ 4 0 R ] /MediaBox [ 0 0 612 446 ] /Type /Pages >>
|
11
|
+
endobj
|
12
|
+
4 0 obj
|
13
|
+
<< /Contents 5 0 R /Parent 3 0 R /Resources 6 0 R /Type /Page >>
|
14
|
+
endobj
|
15
|
+
5 0 obj
|
16
|
+
<< /Length 80 /Filter /FlateDecode >>
|
17
|
+
stream
|
18
|
+
\��sy%1����a��
|
19
|
+
�/���F�.�L:pb���C�`��D�5 �� �کU"�ʇ�3�Fy/�9�<V��ڦ;�?5�S�8endstream
|
20
|
+
endobj
|
21
|
+
6 0 obj
|
22
|
+
<< /Font << /F1 7 0 R >> /ProcSet [ /PDF /Text ] >>
|
23
|
+
endobj
|
24
|
+
7 0 obj
|
25
|
+
<< /BaseFont /Helvetica /Name /F1 /Subtype /Type1 /Type /Font >>
|
26
|
+
endobj
|
27
|
+
8 0 obj
|
28
|
+
<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <8034f99b1eff9e91054d7ee490155e22f65170f607d6a614236b089e602517d9a22abe7c1ea53f52dbc9ae8d9701a065> /OE <bdc5c4b46519af7b4041b7341f70e4d8b1c6087dc12d3f16f060313f18e73386> /P -4 /Perms <64028d6bceb0d927477ecf45e6be7f3f> /R 5 /StmF /StdCF /StrF /StdCF /U <5db7b7b5bd8bbe9fac28477756a930cb079e1dcbdcd3b50c1817d3de4ce5184b5189848832df0043f8e1fe19ff696a36> /UE <dc3fed7b473148238ba0689409edd3d80c91bd26802003c152594aa27a50a81e> /V 5 >>
|
29
|
+
endobj
|
30
|
+
xref
|
31
|
+
0 9
|
32
|
+
0000000000 65535 f
|
33
|
+
0000000015 00000 n
|
34
|
+
0000000130 00000 n
|
35
|
+
0000000259 00000 n
|
36
|
+
0000000344 00000 n
|
37
|
+
0000000424 00000 n
|
38
|
+
0000000574 00000 n
|
39
|
+
0000000641 00000 n
|
40
|
+
0000000721 00000 n
|
41
|
+
trailer << /Info 2 0 R /Root 1 0 R /Size 9 /ID [<6790ffa610024e78369114311fc0df96><da8e0d03302398724f68ef24a831285e>] /Encrypt 8 0 R >>
|
42
|
+
startxref
|
43
|
+
1268
|
44
|
+
%%EOF
|
@@ -0,0 +1,43 @@
|
|
1
|
+
%PDF-1.7
|
2
|
+
%����
|
3
|
+
1 0 obj
|
4
|
+
<< /Extensions << /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >> /Pages 3 0 R /Type /Catalog >>
|
5
|
+
endobj
|
6
|
+
2 0 obj
|
7
|
+
<< /ModDate <d2aa32b8d87666bd29df499a424d2e6d23c0b475ebb67598733b5f06429470a37062d3d6a32fc9603301c5eb99a20605> >>
|
8
|
+
endobj
|
9
|
+
3 0 obj
|
10
|
+
<< /Count 1 /Kids [ 4 0 R ] /MediaBox [ 0 0 612 446 ] /Type /Pages >>
|
11
|
+
endobj
|
12
|
+
4 0 obj
|
13
|
+
<< /Contents 5 0 R /Parent 3 0 R /Resources 6 0 R /Type /Page >>
|
14
|
+
endobj
|
15
|
+
5 0 obj
|
16
|
+
<< /Length 80 /Filter /FlateDecode >>
|
17
|
+
stream
|
18
|
+
j��D3�(���"�ڛ�������xvY$B\4^Y����$��
|
19
|
+
endobj
|
20
|
+
6 0 obj
|
21
|
+
<< /Font << /F1 7 0 R >> /ProcSet [ /PDF /Text ] >>
|
22
|
+
endobj
|
23
|
+
7 0 obj
|
24
|
+
<< /BaseFont /Helvetica /Name /F1 /Subtype /Type1 /Type /Font >>
|
25
|
+
endobj
|
26
|
+
8 0 obj
|
27
|
+
<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <95d20f6277a6955bf4bda243c2144e94889bd5fa4225a4cf4e0d496fa1ffa1d991e1a37d46e4afe9e2dfc207eba9ec53> /OE <432a7d086b4338f1020bbc5847e6dd4f49ce586ab2c5de0f5301450e45e3bb3e> /P -4 /Perms <7df865105074d365f2ce50407e8b6dc8> /R 5 /StmF /StdCF /StrF /StdCF /U <930a631e8b2b95ff6b024e9bde92cb73c5c43f0106ec4fb1c336e49608c0740d87395c6ea79b99ee07eeae5ffbacd031> /UE <3f7eb6b9a897049bfca85a5ae71470eca3dfedbe9101e8532b217e4d95bcf51a> /V 5 >>
|
28
|
+
endobj
|
29
|
+
xref
|
30
|
+
0 9
|
31
|
+
0000000000 65535 f
|
32
|
+
0000000015 00000 n
|
33
|
+
0000000130 00000 n
|
34
|
+
0000000259 00000 n
|
35
|
+
0000000344 00000 n
|
36
|
+
0000000424 00000 n
|
37
|
+
0000000574 00000 n
|
38
|
+
0000000641 00000 n
|
39
|
+
0000000721 00000 n
|
40
|
+
trailer << /Info 2 0 R /Root 1 0 R /Size 9 /ID [<6790ffa610024e78369114311fc0df96><dd2e6bd65a9735c0bef37d88d5291ff1>] /Encrypt 8 0 R >>
|
41
|
+
startxref
|
42
|
+
1268
|
43
|
+
%%EOF
|
Binary file
|
@@ -12,26 +12,10 @@ module HexaPDF
|
|
12
12
|
def ca_certificate
|
13
13
|
@ca_certificate ||=
|
14
14
|
begin
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
ca_cert.version = 2
|
20
|
-
ca_cert.not_before = Time.now - 86400
|
21
|
-
ca_cert.not_after = Time.now + 86400
|
22
|
-
ca_cert.public_key = ca_key.public_key
|
23
|
-
ca_cert.subject = ca_name
|
24
|
-
ca_cert.issuer = ca_name
|
25
|
-
|
26
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
27
|
-
extension_factory.subject_certificate = ca_cert
|
28
|
-
extension_factory.issuer_certificate = ca_cert
|
29
|
-
ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
30
|
-
ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
31
|
-
ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
|
32
|
-
ca_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
33
|
-
|
34
|
-
ca_cert
|
15
|
+
cert = create_cert(name: '/C=AT/O=HexaPDF/CN=HexaPDF Test Root CA', serial: 0,
|
16
|
+
public_key: ca_key.public_key)
|
17
|
+
add_extensions(cert, cert, ca_key, is_ca: true, key_usage: 'cRLSign,keyCertSign')
|
18
|
+
cert
|
35
19
|
end
|
36
20
|
end
|
37
21
|
|
@@ -39,88 +23,86 @@ module HexaPDF
|
|
39
23
|
@signer_key ||= OpenSSL::PKey::RSA.new(2048)
|
40
24
|
end
|
41
25
|
|
42
|
-
def dsa_signer_key
|
43
|
-
@dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
|
44
|
-
end
|
45
|
-
|
46
26
|
def signer_certificate
|
47
27
|
@signer_certificate ||=
|
48
28
|
begin
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
64
|
-
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
65
|
-
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
66
|
-
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
67
|
-
|
68
|
-
signer_cert
|
29
|
+
cert = create_cert(name: '/CN=RSA signer/DC=gettalong', serial: 2,
|
30
|
+
public_key: signer_key.public_key, issuer: ca_certificate)
|
31
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
|
32
|
+
cert
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def non_repudiation_signer_certificate
|
37
|
+
@non_repudiation_signer_certificate ||=
|
38
|
+
begin
|
39
|
+
cert = create_cert(name: '/CN=Non repudiation signer/DC=gettalong', serial: 2,
|
40
|
+
public_key: signer_key.public_key, issuer: ca_certificate)
|
41
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'nonRepudiation')
|
42
|
+
cert
|
69
43
|
end
|
70
44
|
end
|
71
45
|
|
46
|
+
def dsa_signer_key
|
47
|
+
@dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
|
48
|
+
end
|
49
|
+
|
72
50
|
def dsa_signer_certificate
|
73
51
|
@dsa_signer_certificate ||=
|
74
52
|
begin
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
signer_cert.not_after = Time.now + 86400
|
80
|
-
signer_cert.public_key = dsa_signer_key.public_key
|
81
|
-
signer_cert.subject = OpenSSL::X509::Name.parse('/CN=DSA signer/DC=gettalong')
|
82
|
-
signer_cert.issuer = ca_certificate.subject
|
83
|
-
|
84
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
85
|
-
extension_factory.subject_certificate = signer_cert
|
86
|
-
extension_factory.issuer_certificate = ca_certificate
|
87
|
-
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
88
|
-
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
89
|
-
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
90
|
-
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
91
|
-
|
92
|
-
signer_cert
|
53
|
+
cert = create_cert(name: '/CN=DSA signer/DC=gettalong', serial: 3,
|
54
|
+
public_key: dsa_signer_key.public_key, issuer: ca_certificate)
|
55
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
|
56
|
+
cert
|
93
57
|
end
|
94
58
|
end
|
95
59
|
|
96
60
|
def timestamp_certificate
|
97
61
|
@timestamp_certificate ||=
|
98
62
|
begin
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
signer_cert.not_before = Time.now - 86400
|
105
|
-
signer_cert.not_after = Time.now + 86400
|
106
|
-
signer_cert.public_key = signer_key.public_key
|
107
|
-
signer_cert.subject = name
|
108
|
-
signer_cert.issuer = ca_certificate.subject
|
109
|
-
|
110
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
111
|
-
extension_factory.subject_certificate = signer_cert
|
112
|
-
extension_factory.issuer_certificate = ca_certificate
|
113
|
-
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
114
|
-
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
115
|
-
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
116
|
-
signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
117
|
-
'timeStamping', true))
|
118
|
-
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
119
|
-
|
120
|
-
signer_cert
|
63
|
+
cert = create_cert(name: '/CN=timestamp/DC=gettalong', serial: 3,
|
64
|
+
public_key: signer_key.public_key, issuer: ca_certificate)
|
65
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
|
66
|
+
extended_key_usage: 'timeStamping')
|
67
|
+
cert
|
121
68
|
end
|
122
69
|
end
|
123
70
|
|
71
|
+
def create_cert(name:, serial:, public_key:, issuer: nil)
|
72
|
+
name = OpenSSL::X509::Name.parse(name)
|
73
|
+
cert = OpenSSL::X509::Certificate.new
|
74
|
+
cert.serial = serial
|
75
|
+
cert.version = 2
|
76
|
+
cert.not_before = Time.now - 86400
|
77
|
+
cert.not_after = Time.now + 86400
|
78
|
+
cert.public_key = public_key
|
79
|
+
cert.subject = name
|
80
|
+
cert.issuer = (issuer ? issuer.subject : name)
|
81
|
+
cert
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_extensions(subject_cert, issuer_cert, signing_key, is_ca: false, key_usage: nil,
|
85
|
+
extended_key_usage: nil)
|
86
|
+
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
87
|
+
extension_factory.subject_certificate = subject_cert
|
88
|
+
extension_factory.issuer_certificate = issuer_cert
|
89
|
+
subject_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
90
|
+
if is_ca
|
91
|
+
subject_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
92
|
+
else
|
93
|
+
subject_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
94
|
+
end
|
95
|
+
if key_usage
|
96
|
+
subject_cert.add_extension(extension_factory.create_extension('keyUsage', key_usage, true))
|
97
|
+
end
|
98
|
+
if extended_key_usage
|
99
|
+
subject_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
100
|
+
extended_key_usage, true))
|
101
|
+
end
|
102
|
+
subject_cert.sign(signing_key, OpenSSL::Digest.new('SHA1'))
|
103
|
+
end
|
104
|
+
private :add_extensions
|
105
|
+
|
124
106
|
def start_tsa_server
|
125
107
|
return if defined?(@tsa_server)
|
126
108
|
require 'webrick'
|
@@ -133,6 +133,13 @@ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
|
|
133
133
|
assert_equal(['Reason', 'Location', 'Contact'], @obj.value.values_at(:Reason, :Location, :ContactInfo))
|
134
134
|
end
|
135
135
|
|
136
|
+
it "sets the signing time" do
|
137
|
+
time = Time.now
|
138
|
+
@handler.signing_time = time
|
139
|
+
@handler.finalize_objects(@field, @obj)
|
140
|
+
assert_equal(time, @obj[:M])
|
141
|
+
end
|
142
|
+
|
136
143
|
it "fills the build properties dictionary with appropriate application information" do
|
137
144
|
@handler.finalize_objects(@field, @obj)
|
138
145
|
assert_equal(:HexaPDF, @obj[:Prop_Build][:App][:Name])
|
@@ -214,6 +214,15 @@ describe HexaPDF::DigitalSignature::Signing::SignedDataCreator do
|
|
214
214
|
assert_equal(Time.now.utc, attr.value[1].value[0].value)
|
215
215
|
end
|
216
216
|
end
|
217
|
+
|
218
|
+
it "can use a user-defined time as signing time" do
|
219
|
+
current_time = Time.now
|
220
|
+
@signed_data.signing_time = current_time
|
221
|
+
asn1 = OpenSSL::ASN1.decode(@signed_data.create("data"))
|
222
|
+
attr = asn1.value[1].value[0].value[4].value[0].value[3].value.
|
223
|
+
find {|obj| obj.value[0].value == 'signingTime' }
|
224
|
+
assert_equal(current_time.floor.utc, attr.value[1].value[0].value)
|
225
|
+
end
|
217
226
|
end
|
218
227
|
|
219
228
|
describe "pades signature" do
|
@@ -22,7 +22,7 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
22
22
|
assert_equal("RSA signer", @handler.signer_name)
|
23
23
|
end
|
24
24
|
|
25
|
-
it "returns the signing time" do
|
25
|
+
it "returns the signing time from the signed attributes" do
|
26
26
|
assert_equal(@pkcs7.signers.first.signed_time, @handler.signing_time)
|
27
27
|
end
|
28
28
|
|
@@ -86,6 +86,18 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
86
86
|
assert_match(/key usage is missing 'Digital Signature'/, result.messages.first.content)
|
87
87
|
end
|
88
88
|
|
89
|
+
it "provides info for a non-repudiation signature" do
|
90
|
+
@pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.non_repudiation_signer_certificate,
|
91
|
+
CERTIFICATES.signer_key,
|
92
|
+
@data, [CERTIFICATES.ca_certificate],
|
93
|
+
OpenSSL::PKCS7::DETACHED)
|
94
|
+
@dict.contents = @pkcs7.to_der
|
95
|
+
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
96
|
+
result = @handler.verify(@store)
|
97
|
+
assert_equal(:info, result.messages.first.type)
|
98
|
+
assert_match(/Certificate used for non-repudiation/, result.messages.first.content)
|
99
|
+
end
|
100
|
+
|
89
101
|
it "verifies the signature itself" do
|
90
102
|
result = @handler.verify(@store)
|
91
103
|
assert_equal(:info, result.messages.last.type)
|
@@ -117,4 +129,32 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
117
129
|
assert_match(/Signature valid/, result.messages.last.content)
|
118
130
|
end
|
119
131
|
end
|
132
|
+
|
133
|
+
describe "with embedded TSA signature" do
|
134
|
+
before do
|
135
|
+
CERTIFICATES.start_tsa_server
|
136
|
+
tsh = HexaPDF::DigitalSignature::Signing::TimestampHandler.new(
|
137
|
+
signature_size: 10_000, tsa_url: 'http://127.0.0.1:34567'
|
138
|
+
)
|
139
|
+
cms = HexaPDF::DigitalSignature::Signing::SignedDataCreator.create(
|
140
|
+
@data, type: :pades, certificate: CERTIFICATES.signer_certificate,
|
141
|
+
key: CERTIFICATES.signer_key, timestamp_handler: tsh,
|
142
|
+
certificates: [CERTIFICATES.ca_certificate]
|
143
|
+
)
|
144
|
+
@dict.contents = cms.to_der
|
145
|
+
@dict.signed_data = @data
|
146
|
+
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "returns the signing time from the TSA signature" do
|
150
|
+
assert_equal(@handler.embedded_tsa_signature.signers.first.signed_time, @handler.signing_time)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "provides informational output if the time is from a TSA signature" do
|
154
|
+
store = OpenSSL::X509::Store.new
|
155
|
+
result = @handler.verify(store)
|
156
|
+
assert_equal(:info, result.messages.first.type)
|
157
|
+
assert_match(/Signing time.*timestamp authority/, result.messages.first.content)
|
158
|
+
end
|
159
|
+
end
|
120
160
|
end
|
@@ -5,6 +5,7 @@ require 'hexapdf/digital_signature'
|
|
5
5
|
require 'hexapdf/document'
|
6
6
|
require 'time'
|
7
7
|
require 'ostruct'
|
8
|
+
require 'openssl'
|
8
9
|
|
9
10
|
describe HexaPDF::DigitalSignature::Handler do
|
10
11
|
before do
|
@@ -36,7 +37,7 @@ describe HexaPDF::DigitalSignature::Handler do
|
|
36
37
|
end
|
37
38
|
|
38
39
|
it "can allow self-signed certificates" do
|
39
|
-
[OpenSSL::X509::
|
40
|
+
[OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
|
40
41
|
OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN].each do |error|
|
41
42
|
[true, false].each do |allow_self_signed|
|
42
43
|
@result.messages.clear
|
@@ -16,13 +16,13 @@ describe HexaPDF::DigitalSignature::Signatures do
|
|
16
16
|
|
17
17
|
it "iterates over all signature dictionaries" do
|
18
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)
|
19
|
+
@sig1.field_value = {k: :sig1}
|
20
|
+
@sig2.field_value = {k: :sig2}
|
21
|
+
assert_equal([{k: :sig1}, {k: :sig2}], @doc.signatures.to_a)
|
22
22
|
end
|
23
23
|
|
24
24
|
it "returns the number of signature dictionaries" do
|
25
|
-
@sig1.field_value = :sig1
|
25
|
+
@sig1.field_value = {k: :sig1}
|
26
26
|
assert_equal(1, @doc.signatures.count)
|
27
27
|
end
|
28
28
|
|
@@ -35,11 +35,11 @@ describe HexaPDF::Document::Layout::ChildrenCollector do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
it "allows appending boxes created by the Layout class" do
|
38
|
-
@collector.lorem_ipsum
|
39
|
-
@collector.lorem_ipsum_box
|
40
|
-
@collector.column
|
41
|
-
@collector.column_box
|
42
|
-
assert_equal(
|
38
|
+
box1 = @collector.lorem_ipsum
|
39
|
+
box2 = @collector.lorem_ipsum_box
|
40
|
+
box3 = @collector.column
|
41
|
+
box4 = @collector.column_box
|
42
|
+
assert_equal([box1, box2, box3, box4], @collector.children)
|
43
43
|
assert_kind_of(HexaPDF::Layout::TextBox, @collector.children[0])
|
44
44
|
assert_kind_of(HexaPDF::Layout::TextBox, @collector.children[1])
|
45
45
|
assert_kind_of(HexaPDF::Layout::ColumnBox, @collector.children[2])
|
@@ -95,6 +95,11 @@ describe HexaPDF::Document::Layout::CellArgumentCollector do
|
|
95
95
|
@args[-3..-1, -5..-2] = {key: :value}
|
96
96
|
check_argument_info(@args.argument_infos.first, 17..19, 5..8, {key: :value})
|
97
97
|
end
|
98
|
+
|
99
|
+
it "allows using stepped ranges" do
|
100
|
+
@args[(0..-1).step(2)] = {key: :value}
|
101
|
+
check_argument_info(@args.argument_infos.first, (0..19).step(2), 0..9, {key: :value})
|
102
|
+
end
|
98
103
|
end
|
99
104
|
|
100
105
|
describe "retrieve_arguments_for" do
|
@@ -157,6 +162,24 @@ describe HexaPDF::Document::Layout do
|
|
157
162
|
end
|
158
163
|
end
|
159
164
|
|
165
|
+
describe "private retrieve_style" do
|
166
|
+
it "resolves a font name to a font wrapper" do
|
167
|
+
style = @layout.send(:retrieve_style, {font: 'Helvetica'})
|
168
|
+
assert_kind_of(HexaPDF::Font::Type1Wrapper, style.font)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "sets the :base style's font if no font is set" do
|
172
|
+
@layout.style(:base, font: 'Helvetica')
|
173
|
+
style = @layout.send(:retrieve_style, {})
|
174
|
+
assert_equal('Helvetica', style.font.wrapped_font.font_name)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "sets the font specified in the config option font.default as fallback" do
|
178
|
+
style = @layout.send(:retrieve_style, {})
|
179
|
+
assert_equal('Times-Roman', style.font.wrapped_font.font_name)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
160
183
|
describe "inline_box" do
|
161
184
|
it "takes a box as argument" do
|
162
185
|
box = HexaPDF::Layout::Box.create(width: 10, height: 10)
|
@@ -228,11 +228,14 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
228
228
|
end
|
229
229
|
|
230
230
|
it "fails if the /R value is incorrect" do
|
231
|
+
HexaPDF::Encryption::StandardEncryptionDictionary.field(:R).allowed_values << 7
|
231
232
|
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
232
|
-
@handler.set_up_decryption({Filter: :Standard, V: 2, R:
|
233
|
+
@handler.set_up_decryption({Filter: :Standard, V: 2, R: 7, O: 't' * 32, U: 't' * 32, P: 0,
|
233
234
|
Length: 128})
|
234
235
|
end
|
235
|
-
assert_match(/Invalid \/R value
|
236
|
+
assert_match(/Invalid \/R value 7/i, exp.message)
|
237
|
+
ensure
|
238
|
+
HexaPDF::Encryption::StandardEncryptionDictionary.field(:R).allowed_values.pop
|
236
239
|
end
|
237
240
|
|
238
241
|
it "fails if the supplied password is invalid" do
|
@@ -148,29 +148,36 @@ describe HexaPDF::Layout::Box do
|
|
148
148
|
assert_equal(49.9999996, box.height)
|
149
149
|
end
|
150
150
|
|
151
|
-
it "
|
151
|
+
it "works for boxes with no space for the content" do
|
152
|
+
box = create_box(height: 1, style: {border: {width: [1, 0, 0]}})
|
153
|
+
assert(box.fit(100, 100, @frame).success?)
|
154
|
+
assert_equal(1, box.height)
|
155
|
+
assert_equal(100, box.width)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "fails if position != :flow and its width is greater than the available width" do
|
152
159
|
box = create_box(width: 101)
|
153
160
|
assert(box.fit(100, 100, @frame).failure?)
|
154
161
|
end
|
155
162
|
|
156
|
-
it "fails if
|
163
|
+
it "fails if position != :flow and its width is greater than the available width" do
|
157
164
|
box = create_box(height: 101)
|
158
165
|
assert(box.fit(100, 100, @frame).failure?)
|
159
166
|
end
|
160
167
|
|
161
|
-
it "fails if
|
168
|
+
it "fails if position != :flow and the reserved width is greater than the width" do
|
162
169
|
box = create_box(height: 100)
|
163
170
|
box.style.padding = [0, 100]
|
164
171
|
assert(box.fit(150, 150, @frame).failure?)
|
165
172
|
end
|
166
173
|
|
167
|
-
it "fails if
|
174
|
+
it "fails if position != :flow and the reserved height is greater than the height" do
|
168
175
|
box = create_box(width: 100)
|
169
176
|
box.style.padding = [100, 0]
|
170
177
|
assert(box.fit(150, 150, @frame).failure?)
|
171
178
|
end
|
172
179
|
|
173
|
-
it "can use the #
|
180
|
+
it "can use the #update_content_width/#update_content_height helper methods" do
|
174
181
|
box = create_box
|
175
182
|
box.define_singleton_method(:fit_content) do |_aw, _ah, _frame|
|
176
183
|
update_content_width { 10 }
|
@@ -316,10 +316,20 @@ describe HexaPDF::Layout::Frame do
|
|
316
316
|
|
317
317
|
describe "flowing boxes" do
|
318
318
|
it "flows inside the frame's outline" do
|
319
|
+
remove_area(:left)
|
319
320
|
check_box({width: 10, height: 20, margin: 10, position: :flow},
|
320
|
-
[
|
321
|
+
[10, 90],
|
321
322
|
[10, 80, 110, 110],
|
322
|
-
[[[
|
323
|
+
[[[20, 10], [110, 10], [110, 80], [20, 80]]])
|
324
|
+
assert_equal(10, @box.fit_result.x)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "doesn't overwrite fit_result.x" do
|
328
|
+
box = HexaPDF::Layout::Box.create(position: :flow) {}
|
329
|
+
box.define_singleton_method(:supports_position_flow?) { true }
|
330
|
+
box.define_singleton_method(:fit_content) {|*args| fit_result.x = 30; super(*args) }
|
331
|
+
fit_result = @frame.fit(box)
|
332
|
+
assert_equal(30, fit_result.x)
|
323
333
|
end
|
324
334
|
|
325
335
|
it "uses position=default if the box indicates it doesn't support flowing contents" do
|
@@ -23,47 +23,36 @@ describe HexaPDF::Layout::InlineBox do
|
|
23
23
|
ibox = inline_box(box, valign: :top)
|
24
24
|
assert_equal(:top, ibox.valign)
|
25
25
|
end
|
26
|
-
|
27
|
-
it "fails if the wrapped box has not width set" do
|
28
|
-
box = HexaPDF::Document.new.layout.text("test is not going good")
|
29
|
-
assert_raises(HexaPDF::Error) { inline_box(box) }
|
30
|
-
end
|
31
26
|
end
|
32
27
|
|
33
28
|
describe "fit_wrapped_box" do
|
34
|
-
it "automatically fits the provided box
|
35
|
-
ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good",
|
29
|
+
it "automatically fits the provided box when given a frame" do
|
30
|
+
ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good", margin: [5, 10]))
|
36
31
|
ibox.fit_wrapped_box(HexaPDF::Layout::Frame.new(0, 0, 50, 50))
|
37
|
-
assert_equal(
|
38
|
-
assert_equal(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
assert_equal(20, ibox.width)
|
45
|
-
assert_equal(45, ibox.height)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "fails if the wrapped box could not be fit" do
|
49
|
-
box = HexaPDF::Document.new.layout.text("test is not going good", width: 1)
|
50
|
-
assert_raises(HexaPDF::Error) { inline_box(box).fit_wrapped_box(nil) }
|
32
|
+
assert_equal(90.84, ibox.width)
|
33
|
+
assert_equal(19, ibox.height)
|
34
|
+
fit_result = ibox.instance_variable_get(:@fit_result)
|
35
|
+
assert_equal(10, fit_result.x)
|
36
|
+
assert_equal(5, fit_result.y)
|
37
|
+
assert_equal(70.84 + 2 * 10, fit_result.mask.width)
|
38
|
+
assert_equal(9 + 2 * 5, fit_result.mask.height)
|
51
39
|
end
|
52
40
|
|
53
|
-
it "
|
54
|
-
|
55
|
-
|
56
|
-
|
41
|
+
it "automatically fits the provided box without a given frame" do
|
42
|
+
ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good"))
|
43
|
+
ibox.fit_wrapped_box
|
44
|
+
assert_equal(70.84, ibox.width)
|
45
|
+
assert_equal(9, ibox.height)
|
57
46
|
end
|
58
47
|
end
|
59
48
|
|
60
49
|
it "draws the wrapped box at the correct position" do
|
61
50
|
doc = HexaPDF::Document.new
|
62
51
|
canvas = doc.pages.add.canvas
|
63
|
-
box =
|
64
|
-
box.fit_wrapped_box
|
52
|
+
box = HexaPDF::Layout::InlineBox.create(width: 10, margin: [15, 10]) {}
|
53
|
+
box.fit_wrapped_box
|
65
54
|
box.draw(canvas, 100, 200)
|
66
|
-
assert_equal("q\n1 0 0 1 110
|
55
|
+
assert_equal("q\n1 0 0 1 110 215 cm\nQ\n", canvas.contents)
|
67
56
|
end
|
68
57
|
|
69
58
|
it "returns true if the inline box is empty with no drawing operations" do
|
@@ -65,13 +65,13 @@ describe HexaPDF::Layout::ListBox do
|
|
65
65
|
describe "fit" do
|
66
66
|
[:default, :flow].each do |position|
|
67
67
|
it "respects the set initial width, position #{position}" do
|
68
|
-
box = create_box(children: @text_boxes[0, 2], width:
|
69
|
-
check_box(box,
|
68
|
+
box = create_box(children: @text_boxes[0, 2], width: 55, style: {position: position})
|
69
|
+
check_box(box, 55, 80)
|
70
70
|
end
|
71
71
|
|
72
72
|
it "respects the set initial height, position #{position}" do
|
73
|
-
box = create_box(children: @text_boxes[0, 2], height:
|
74
|
-
check_box(box, 100,
|
73
|
+
box = create_box(children: @text_boxes[0, 2], height: 55, style: {position: position})
|
74
|
+
check_box(box, 100, 55)
|
75
75
|
end
|
76
76
|
|
77
77
|
it "respects the set initial height even when it doesn't fit completely" do
|
@@ -123,7 +123,7 @@ describe HexaPDF::Layout::ListBox do
|
|
123
123
|
|
124
124
|
it "fails if not even a part of the first list item fits" do
|
125
125
|
box = create_box(children: @text_boxes[0, 2], height: 5)
|
126
|
-
check_box(box, 100,
|
126
|
+
check_box(box, 100, 5, status: :failure)
|
127
127
|
end
|
128
128
|
|
129
129
|
it "fails for unknown marker types" do
|
@@ -44,8 +44,13 @@ describe HexaPDF::Layout::PageStyle do
|
|
44
44
|
|
45
45
|
it "works when no template is set" do
|
46
46
|
style = HexaPDF::Layout::PageStyle.new
|
47
|
-
|
48
|
-
|
47
|
+
page1 = style.create_page(@doc)
|
48
|
+
frame1 = style.frame
|
49
|
+
assert_equal("", page1.contents)
|
50
|
+
assert_equal(523.275591, style.frame.width)
|
51
|
+
|
52
|
+
page2 = style.create_page(@doc)
|
53
|
+
refute_same(frame1, style.frame)
|
49
54
|
end
|
50
55
|
|
51
56
|
it "creates a default frame if none is set beforehand or during template execution" do
|