hexapdf 0.28.0 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/cli/command.rb +16 -1
  5. data/lib/hexapdf/cli/info.rb +9 -1
  6. data/lib/hexapdf/cli/inspect.rb +2 -2
  7. data/lib/hexapdf/composer.rb +76 -28
  8. data/lib/hexapdf/configuration.rb +29 -16
  9. data/lib/hexapdf/dictionary_fields.rb +13 -4
  10. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  11. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  12. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  13. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  14. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  15. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  16. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  17. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  18. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  19. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  20. data/lib/hexapdf/digital_signature.rb +56 -0
  21. data/lib/hexapdf/document/pages.rb +31 -18
  22. data/lib/hexapdf/document.rb +29 -15
  23. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  24. data/lib/hexapdf/filter/flate_decode.rb +20 -8
  25. data/lib/hexapdf/layout/page_style.rb +144 -0
  26. data/lib/hexapdf/layout.rb +1 -0
  27. data/lib/hexapdf/task/optimize.rb +8 -6
  28. data/lib/hexapdf/type/font_simple.rb +14 -2
  29. data/lib/hexapdf/type/object_stream.rb +7 -2
  30. data/lib/hexapdf/type/outline.rb +1 -1
  31. data/lib/hexapdf/type/outline_item.rb +1 -1
  32. data/lib/hexapdf/type/page.rb +29 -8
  33. data/lib/hexapdf/type/xref_stream.rb +11 -4
  34. data/lib/hexapdf/type.rb +0 -1
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +1 -1
  37. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  38. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  39. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  40. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  41. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  42. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  43. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  44. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  45. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  46. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  47. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  48. data/test/hexapdf/document/test_pages.rb +25 -0
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  50. data/test/hexapdf/filter/test_flate_decode.rb +19 -5
  51. data/test/hexapdf/layout/test_page_style.rb +70 -0
  52. data/test/hexapdf/task/test_optimize.rb +11 -9
  53. data/test/hexapdf/test_composer.rb +35 -10
  54. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  55. data/test/hexapdf/test_document.rb +1 -1
  56. data/test/hexapdf/test_writer.rb +8 -8
  57. data/test/hexapdf/type/test_font_simple.rb +18 -6
  58. data/test/hexapdf/type/test_object_stream.rb +16 -7
  59. data/test/hexapdf/type/test_outline.rb +3 -1
  60. data/test/hexapdf/type/test_outline_item.rb +3 -1
  61. data/test/hexapdf/type/test_page.rb +42 -11
  62. data/test/hexapdf/type/test_xref_stream.rb +6 -1
  63. metadata +27 -15
  64. data/lib/hexapdf/document/signatures.rb +0 -546
  65. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  66. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  67. data/lib/hexapdf/type/signature/handler.rb +0 -140
  68. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -1,135 +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.pkcs7.detached sub-filter.
45
- class AdbePkcs7Detached < Handler
46
-
47
- # Creates a new signature handler for the given signature dictionary.
48
- def initialize(signature_dict)
49
- super
50
- @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
51
- end
52
-
53
- # Returns the common name of the signer.
54
- def signer_name
55
- signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
56
- end
57
-
58
- # Returns the time of signing.
59
- def signing_time
60
- signer_info.signed_time rescue super
61
- end
62
-
63
- # Returns the certificate chain.
64
- def certificate_chain
65
- @pkcs7.certificates
66
- end
67
-
68
- # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
69
- def signer_certificate
70
- info = signer_info
71
- certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
72
- end
73
-
74
- # Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
75
- def signer_info
76
- @pkcs7.signers.first
77
- end
78
-
79
- # Verifies the signature using the provided OpenSSL::X509::Store object.
80
- def verify(store, allow_self_signed: false)
81
- result = super
82
-
83
- signer_info = self.signer_info
84
- signer_certificate = self.signer_certificate
85
- certificate_chain = self.certificate_chain
86
-
87
- if certificate_chain.empty?
88
- result.log(:error, "No certificates found in signature")
89
- return result
90
- end
91
-
92
- if @pkcs7.signers.size != 1
93
- result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
94
- end
95
-
96
- unless signer_certificate
97
- result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
98
- "not found in certificates stored in PKCS7 object")
99
- return result
100
- end
101
-
102
- key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
103
- unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
104
- result.log(:error, "Certificate key usage is missing 'Digital Signature'")
105
- end
106
-
107
- if signature_dict.signature_type == 'ETSI.RFC3161'
108
- # Getting the needed values is not directly supported by Ruby OpenSSL
109
- p7 = OpenSSL::ASN1.decode(signature_dict.contents.sub(/\x00*\z/, ''))
110
- signed_data = p7.value[1].value[0]
111
- content_info = signed_data.value[2]
112
- content = OpenSSL::ASN1.decode(content_info.value[1].value[0].value)
113
- digest_algorithm = content.value[2].value[0].value[0].value
114
- original_hash = content.value[2].value[1].value
115
- recomputed_hash = OpenSSL::Digest.digest(digest_algorithm, signature_dict.signed_data)
116
- hash_valid = (original_hash == recomputed_hash)
117
- else
118
- data = signature_dict.signed_data
119
- hash_valid = true # hash will be checked by @pkcs7.verify
120
- end
121
- if hash_valid && @pkcs7.verify(certificate_chain, store, data,
122
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
123
- result.log(:info, "Signature valid")
124
- else
125
- result.log(:error, "Signature verification failed")
126
- end
127
-
128
- result
129
- end
130
-
131
- end
132
-
133
- end
134
- end
135
- end
@@ -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