hanko_xml 0.1.0-aarch64-linux → 0.2.0-aarch64-linux
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 +66 -0
- data/README.md +2 -1
- data/lib/hanko_xml/3.1/hanko_xml.so +0 -0
- data/lib/hanko_xml/3.2/hanko_xml.so +0 -0
- data/lib/hanko_xml/3.3/hanko_xml.so +0 -0
- data/lib/hanko_xml/3.4/hanko_xml.so +0 -0
- data/lib/hanko_xml/signer.rb +4 -1
- data/lib/hanko_xml/verifier.rb +10 -3
- data/lib/hanko_xml/version.rb +1 -1
- data/lib/hanko_xml/xades/augmenter.rb +248 -0
- data/lib/hanko_xml/xades/locator.rb +67 -0
- data/lib/hanko_xml/xades/long_term.rb +252 -0
- data/lib/hanko_xml/xades/timestamp.rb +386 -0
- data/lib/hanko_xml/xades/validation_data.rb +115 -0
- data/lib/hanko_xml/xades/verifier.rb +311 -37
- data/lib/hanko_xml/xades.rb +93 -6
- data/sig/hanko_xml/native.rbs +2 -0
- data/sig/hanko_xml/xades.rbs +91 -1
- metadata +7 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module HankoXml
|
|
6
|
+
module Xades
|
|
7
|
+
# The XAdES-LT validation material (#167): certificates for
|
|
8
|
+
# <xades:CertificateValues>, CRLs and OCSP responses for
|
|
9
|
+
# <xades:RevocationValues>. The caller collects it; the library never
|
|
10
|
+
# fetches. Nothing here is trusted: a verifier treats it as
|
|
11
|
+
# attacker-supplied until it chains to its own anchors (S-22, #168).
|
|
12
|
+
#
|
|
13
|
+
# Each entry is checked for its type only: X.509 certificates, X.509
|
|
14
|
+
# CRLs, and OCSPResponses whose responseStatus is successful.
|
|
15
|
+
ValidationData = Struct.new(:certificates, :crls, :ocsp_responses, keyword_init: true) do
|
|
16
|
+
# @param certificates [Array<OpenSSL::X509::Certificate, String, Key>] Anything Key.coerce reads.
|
|
17
|
+
# @param crls [Array<OpenSSL::X509::CRL, String>] PEM or DER.
|
|
18
|
+
# @param ocsp_responses [Array<OpenSSL::OCSP::Response, String>] DER OCSPResponse.
|
|
19
|
+
def initialize(certificates: [], crls: [], ocsp_responses: [])
|
|
20
|
+
super(
|
|
21
|
+
certificates: Array(certificates).map { |value| ValidationData.certificate(value) }.freeze,
|
|
22
|
+
crls: Array(crls).map { |value| ValidationData.crl(value) }.freeze,
|
|
23
|
+
ocsp_responses: Array(ocsp_responses).map { |value| ValidationData.ocsp_response(value) }.freeze
|
|
24
|
+
)
|
|
25
|
+
if to_h.values.all?(&:empty?)
|
|
26
|
+
raise HankoXml::ConfigurationError, "validation_data: needs certificates:, crls: or ocsp_responses:"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.coerce(value)
|
|
33
|
+
case value
|
|
34
|
+
when nil, ValidationData then value
|
|
35
|
+
when Hash then new(**value)
|
|
36
|
+
else
|
|
37
|
+
raise HankoXml::ConfigurationError,
|
|
38
|
+
"validation_data: must be a Xades::ValidationData, a Hash of its keywords or nil"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.certificate(value)
|
|
43
|
+
key = HankoXml::Key.coerce(value)
|
|
44
|
+
return key.to_openssl if key.certificate?
|
|
45
|
+
|
|
46
|
+
raise HankoXml::ConfigurationError, "validation_data certificates: must be X.509 certificates"
|
|
47
|
+
rescue HankoXml::KeyLoadError, ::ArgumentError
|
|
48
|
+
raise HankoXml::ConfigurationError, "validation_data certificates: must be X.509 certificates"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.crl(value)
|
|
52
|
+
return value if value.is_a?(OpenSSL::X509::CRL)
|
|
53
|
+
raise HankoXml::ConfigurationError, "validation_data crls: must be X.509 CRLs" unless value.is_a?(String)
|
|
54
|
+
|
|
55
|
+
OpenSSL::X509::CRL.new(value)
|
|
56
|
+
rescue OpenSSL::X509::CRLError
|
|
57
|
+
raise HankoXml::ConfigurationError, "validation_data crls: must be X.509 CRLs (PEM or DER)"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.ocsp_response(value)
|
|
61
|
+
response = value.is_a?(OpenSSL::OCSP::Response) ? value : parse_ocsp_response(value)
|
|
62
|
+
unless response.status == OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL && response.basic
|
|
63
|
+
raise HankoXml::ConfigurationError,
|
|
64
|
+
"validation_data ocsp_responses: must be successful OCSP responses " \
|
|
65
|
+
"(responseStatus #{response.status})"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
response
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.parse_ocsp_response(value)
|
|
72
|
+
unless value.is_a?(String)
|
|
73
|
+
raise HankoXml::ConfigurationError, "validation_data ocsp_responses: must be DER OCSPResponses"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
OpenSSL::OCSP::Response.new(value)
|
|
77
|
+
rescue OpenSSL::OCSP::OCSPError
|
|
78
|
+
raise HankoXml::ConfigurationError, "validation_data ocsp_responses: must be DER OCSPResponses"
|
|
79
|
+
end
|
|
80
|
+
private_class_method :parse_ocsp_response
|
|
81
|
+
|
|
82
|
+
# Appends <xades:CertificateValues> and <xades:RevocationValues> (each
|
|
83
|
+
# only when it has entries) to +parent+, an UnsignedSignatureProperties
|
|
84
|
+
# in +namespace+.
|
|
85
|
+
def append_to(parent, namespace)
|
|
86
|
+
add = lambda do |node, name|
|
|
87
|
+
child = parent.document.create_element(name)
|
|
88
|
+
node.add_child(child)
|
|
89
|
+
child.namespace = namespace
|
|
90
|
+
child
|
|
91
|
+
end
|
|
92
|
+
encapsulate = ->(node, name, der) { add.call(node, name).content = [der].pack("m0") }
|
|
93
|
+
|
|
94
|
+
unless certificates.empty?
|
|
95
|
+
values = add.call(parent, "CertificateValues")
|
|
96
|
+
certificates.each do |certificate|
|
|
97
|
+
encapsulate.call(values, "EncapsulatedX509Certificate", certificate.to_der)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
return if crls.empty? && ocsp_responses.empty?
|
|
101
|
+
|
|
102
|
+
# RevocationValuesType is a sequence: CRLValues, then OCSPValues.
|
|
103
|
+
revocation = add.call(parent, "RevocationValues")
|
|
104
|
+
unless crls.empty?
|
|
105
|
+
values = add.call(revocation, "CRLValues")
|
|
106
|
+
crls.each { |crl| encapsulate.call(values, "EncapsulatedCRLValue", crl.to_der) }
|
|
107
|
+
end
|
|
108
|
+
return if ocsp_responses.empty?
|
|
109
|
+
|
|
110
|
+
values = add.call(revocation, "OCSPValues")
|
|
111
|
+
ocsp_responses.each { |response| encapsulate.call(values, "EncapsulatedOCSPValue", response.to_der) }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -11,11 +11,30 @@ module HankoXml
|
|
|
11
11
|
class Verification < HankoXml::Verification
|
|
12
12
|
# @return [SignedProperties, nil] Only when #valid?.
|
|
13
13
|
attr_reader :signed_properties
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# @return [Array<SignatureTimeStamp>, nil] The verified SignatureTimeStamps (#163), in document order;
|
|
15
|
+
# empty when there are none, nil unless #valid? or with timestamps: :ignore.
|
|
16
|
+
attr_reader :signature_timestamps
|
|
17
|
+
# @return [LongTermValidation, nil] The XAdES-LT validation (#168): only when #valid? and the signature
|
|
18
|
+
# carries CertificateValues or RevocationValues that were checked.
|
|
19
|
+
attr_reader :long_term_validation
|
|
20
|
+
# @return [Array<SignatureTimeStamp>, nil] The verified xades141:ArchiveTimeStamps (#169), in document
|
|
21
|
+
# order; empty when there are none, nil unless #valid? or with timestamps: :ignore.
|
|
22
|
+
attr_reader :archive_timestamps
|
|
23
|
+
|
|
24
|
+
def initialize(signed_properties: nil, signature_timestamps: nil, long_term_validation: nil,
|
|
25
|
+
archive_timestamps: nil, **attributes)
|
|
16
26
|
@signed_properties = signed_properties
|
|
27
|
+
@signature_timestamps = signature_timestamps&.freeze
|
|
28
|
+
@long_term_validation = long_term_validation
|
|
29
|
+
@archive_timestamps = archive_timestamps&.freeze
|
|
17
30
|
super(**attributes)
|
|
18
31
|
end
|
|
32
|
+
|
|
33
|
+
# The earliest verified SignatureTimeStamp time: when a trusted third
|
|
34
|
+
# party saw this signature at the latest. Nil without one.
|
|
35
|
+
def timestamp_time
|
|
36
|
+
signature_timestamps&.map(&:time)&.min
|
|
37
|
+
end
|
|
19
38
|
end
|
|
20
39
|
|
|
21
40
|
# Verifies a XAdES-B signature (#58): the XML-DSig signature through
|
|
@@ -31,14 +50,44 @@ module HankoXml
|
|
|
31
50
|
# Reads XAdES 1.3.2 and the older 1.2.2 and 1.1.1 namespaces (#86); the
|
|
32
51
|
# whole signature must use one.
|
|
33
52
|
#
|
|
53
|
+
# XAdES-T (#163): every <xades:SignatureTimeStamp> in the signature's
|
|
54
|
+
# UnsignedSignatureProperties must verify against the TSA trust
|
|
55
|
+
# (tsa_store: / tsa_certificates:), kept apart from the signer's. A
|
|
56
|
+
# timestamp that does not verify fails the signature (fail closed).
|
|
57
|
+
# Xades::Locator finds QualifyingProperties and its unsigned containers.
|
|
58
|
+
#
|
|
59
|
+
# XAdES-LT (#168): when the signature embeds CertificateValues or
|
|
60
|
+
# RevocationValues, the signing certificate is validated at the time of
|
|
61
|
+
# the earliest verified SignatureTimeStamp: XML-DSig trust (chain,
|
|
62
|
+
# validity dates) at that time, then revocation evidence for every
|
|
63
|
+
# certificate below the trust anchor from the embedded CRLs and OCSP
|
|
64
|
+
# responses (Xades::LongTermCheck). A certificate that has expired since
|
|
65
|
+
# still verifies; embedded data that does not hold fails (fail closed).
|
|
66
|
+
# XAdES-LTA (#169): every xades141:ArchiveTimeStamp must verify too, over
|
|
67
|
+
# the EN 319 132-1 input (Xades::ArchiveTimeStampCheck), against the same
|
|
68
|
+
# TSA trust; fail closed, and not read with timestamps: :ignore.
|
|
69
|
+
#
|
|
34
70
|
# Needs certificate-based trust (certificate:, certificates:, store:), and
|
|
35
71
|
# Nokogiri: the signed properties are read from the DOM (#102).
|
|
36
|
-
# Unsigned properties (XAdES-T/LT/LTA) are ignored: #81, #82.
|
|
37
72
|
class Verifier < HankoXml::Verifier # rubocop:disable Metrics/ClassLength
|
|
73
|
+
TIMESTAMP_MODES = %i[verify require ignore].freeze
|
|
74
|
+
LONG_TERM_MODES = %i[verify require ignore].freeze
|
|
75
|
+
|
|
38
76
|
# @param signature_policy [SignaturePolicy, Hash, :implied, nil] When given, the signed
|
|
39
77
|
# SignaturePolicyIdentifier must be exactly this.
|
|
78
|
+
# @param timestamps [Symbol] :verify (default) checks every SignatureTimeStamp present; :require also
|
|
79
|
+
# fails a signature without one; :ignore does not read them, the XAdES-B behavior.
|
|
80
|
+
# @param tsa_store [OpenSSL::X509::Store, nil] Trust for the TSAs, not the signer.
|
|
81
|
+
# @param tsa_certificates [Array<OpenSSL::X509::Certificate, String, Key>, nil] Trusted TSA roots or
|
|
82
|
+
# certificates, instead of tsa_store:.
|
|
83
|
+
# @param long_term [Symbol, nil] :verify checks embedded XAdES-LT values when present; :require also
|
|
84
|
+
# fails a signature without them; :ignore does not read them. nil (default) is :verify, or :ignore with
|
|
85
|
+
# timestamps: :ignore, since the validation time comes from a SignatureTimeStamp.
|
|
86
|
+
# @param revocation_grace [Numeric] Seconds an embedded CRL or OCSP response may predate the validation
|
|
87
|
+
# time and still count as fresh. 0 (default): it must be issued at or after it.
|
|
40
88
|
# All other keywords are {HankoXml::Verifier}'s.
|
|
41
|
-
def initialize(signature_policy: nil,
|
|
89
|
+
def initialize(signature_policy: nil, timestamps: :verify, tsa_store: nil, tsa_certificates: nil,
|
|
90
|
+
long_term: nil, revocation_grace: 0, **options)
|
|
42
91
|
Dom.require!("HankoXml::Xades::Verifier")
|
|
43
92
|
if options[:key] || options[:keys]
|
|
44
93
|
raise HankoXml::ConfigurationError,
|
|
@@ -47,6 +96,12 @@ module HankoXml
|
|
|
47
96
|
end
|
|
48
97
|
|
|
49
98
|
@expected_signature_policy = coerce_signature_policy(signature_policy)
|
|
99
|
+
@timestamps = coerce_timestamps(timestamps)
|
|
100
|
+
@tsa_store = coerce_tsa_store(tsa_store, tsa_certificates)
|
|
101
|
+
@long_term = coerce_long_term(long_term)
|
|
102
|
+
@revocation_grace = coerce_revocation_grace(revocation_grace)
|
|
103
|
+
# Kept to verify the XML-DSig signature again at the validation time.
|
|
104
|
+
@options = options.freeze
|
|
50
105
|
super(**options)
|
|
51
106
|
end
|
|
52
107
|
|
|
@@ -59,25 +114,109 @@ module HankoXml
|
|
|
59
114
|
end
|
|
60
115
|
|
|
61
116
|
def verify(xml)
|
|
62
|
-
|
|
117
|
+
as_xades(super)
|
|
63
118
|
end
|
|
64
119
|
|
|
65
120
|
def verify_all(xml)
|
|
66
|
-
super.map { |verification|
|
|
121
|
+
super.map { |verification| as_xades(verification) }
|
|
67
122
|
end
|
|
68
123
|
|
|
69
124
|
private
|
|
70
125
|
|
|
71
|
-
|
|
126
|
+
# Every signature verified goes through here, with the bytes and the
|
|
127
|
+
# index the native layer used, which the timestamp checks reuse.
|
|
128
|
+
#
|
|
129
|
+
# With XAdES-LT values, the SignatureTimeStamps are verified first, as
|
|
130
|
+
# their earliest time is when the signing certificate is validated
|
|
131
|
+
# (#168): the XML-DSig signature is then verified at that time.
|
|
132
|
+
def verify_single(input, index)
|
|
133
|
+
values, timestamps = long_term_plan(input, index)
|
|
134
|
+
return xades(super, input.bytes, index) unless values
|
|
135
|
+
|
|
136
|
+
time = timestamps.map(&:time).min
|
|
137
|
+
verification = xades(at_time(time, values, input, index), input.bytes, index, timestamps: timestamps)
|
|
138
|
+
return verification unless verification.valid?
|
|
139
|
+
|
|
140
|
+
validation = LongTermCheck.new(grace: @revocation_grace, clock_skew: @policy.clock_skew)
|
|
141
|
+
.verify(time: time, chain: long_term_chain(verification), values: values)
|
|
142
|
+
rebuild(verification, signed_properties: verification.signed_properties,
|
|
143
|
+
signature_timestamps: verification.signature_timestamps,
|
|
144
|
+
archive_timestamps: verification.archive_timestamps,
|
|
145
|
+
long_term_validation: validation)
|
|
146
|
+
rescue HankoXml::XadesError, HankoXml::PolicyError => e
|
|
147
|
+
rebuild(verification || super, error: e)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The embedded XAdES-LT values and the verified SignatureTimeStamps, or
|
|
151
|
+
# nil when there are no values to check.
|
|
152
|
+
def long_term_plan(input, index)
|
|
153
|
+
return nil if @long_term == :ignore
|
|
154
|
+
|
|
155
|
+
signature = signature_nodes(input)[index]
|
|
156
|
+
values = long_term_values(signature)
|
|
157
|
+
unless values
|
|
158
|
+
if @long_term == :require
|
|
159
|
+
fail!("long_term: :require, but the signature has no <xades:CertificateValues> or " \
|
|
160
|
+
"<xades:RevocationValues>")
|
|
161
|
+
end
|
|
162
|
+
return nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
timestamps = signature_timestamps(signature, input.bytes, index)
|
|
166
|
+
if timestamps.empty?
|
|
167
|
+
fail!("XAdES-LT values need a verified <xades:SignatureTimeStamp> for their validation time")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
[values, timestamps]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# A signature whose QualifyingProperties or unsigned containers cannot
|
|
174
|
+
# be located has no values here: the signed-properties and timestamp
|
|
175
|
+
# checks fail it on the same structure, with their own message.
|
|
176
|
+
def long_term_values(signature)
|
|
177
|
+
qualifying, ns = Locator.qualifying_properties(signature)
|
|
178
|
+
properties = Locator.unsigned_signature_properties(qualifying, ns)
|
|
179
|
+
rescue HankoXml::XadesError
|
|
180
|
+
nil
|
|
181
|
+
else
|
|
182
|
+
LongTermValues.read(properties, ns)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The XML-DSig verification at +time+ (unless the caller fixed
|
|
186
|
+
# verification_time:), with the embedded CertificateValues as untrusted
|
|
187
|
+
# intermediates for a store: chain.
|
|
188
|
+
def at_time(time, values, input, index)
|
|
189
|
+
verifier = AtTime.new(intermediates: values.certificates, signature_policy: @expected_signature_policy,
|
|
190
|
+
timestamps: :ignore, long_term: :ignore,
|
|
191
|
+
**@options.merge(verification_time: verification_time || time))
|
|
192
|
+
HankoXml::Verifier.instance_method(:verify_single).bind_call(verifier, input, index)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# store: gives the chain OpenSSL validated; certificate: and
|
|
196
|
+
# certificates: pin the signing certificate itself as the anchor.
|
|
197
|
+
def long_term_chain(verification)
|
|
198
|
+
verification.certificate_chain || [verification.certificate]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# A result that never reached verify_single (no signature found).
|
|
202
|
+
def as_xades(verification)
|
|
203
|
+
verification.is_a?(Verification) ? verification : rebuild(verification)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def xades(verification, xml_bytes, index, timestamps: nil)
|
|
72
207
|
return rebuild(verification) unless verification.valid?
|
|
73
208
|
|
|
74
209
|
properties = signed_properties(verification)
|
|
75
|
-
|
|
210
|
+
timestamps ||= signature_timestamps(verification.signature_node, xml_bytes, index)
|
|
211
|
+
archives = archive_timestamps(verification, xml_bytes, index)
|
|
212
|
+
rebuild(verification, signed_properties: properties, signature_timestamps: timestamps,
|
|
213
|
+
archive_timestamps: archives)
|
|
76
214
|
rescue HankoXml::XadesError, HankoXml::PolicyError => e
|
|
77
215
|
rebuild(verification, error: e)
|
|
78
216
|
end
|
|
79
217
|
|
|
80
|
-
def rebuild(verification, signed_properties: nil,
|
|
218
|
+
def rebuild(verification, signed_properties: nil, signature_timestamps: nil, long_term_validation: nil,
|
|
219
|
+
archive_timestamps: nil, error: nil)
|
|
81
220
|
Verification.new(
|
|
82
221
|
valid: verification.valid? && error.nil?,
|
|
83
222
|
errors: [*verification.errors, *error],
|
|
@@ -94,10 +233,153 @@ module HankoXml
|
|
|
94
233
|
reference_element_indexes: verification.reference_element_indexes,
|
|
95
234
|
reference_valid: verification.reference_valid,
|
|
96
235
|
signature_element_index: verification.signature_element_index,
|
|
97
|
-
signed_properties: signed_properties
|
|
236
|
+
signed_properties: signed_properties,
|
|
237
|
+
signature_timestamps: signature_timestamps,
|
|
238
|
+
long_term_validation: long_term_validation,
|
|
239
|
+
archive_timestamps: archive_timestamps
|
|
98
240
|
)
|
|
99
241
|
end
|
|
100
242
|
|
|
243
|
+
# Each SignatureTimeStamp of this signature, verified (#163). Read only
|
|
244
|
+
# along the path from its own QualifyingProperties, never by a search
|
|
245
|
+
# that a wrapped element elsewhere could satisfy.
|
|
246
|
+
def signature_timestamps(signature, xml_bytes, index)
|
|
247
|
+
return nil if @timestamps == :ignore
|
|
248
|
+
|
|
249
|
+
nodes, path, xades_ns = signature_timestamp_nodes(signature)
|
|
250
|
+
if nodes.empty?
|
|
251
|
+
fail!("timestamps: :require, but the signature has no <xades:SignatureTimeStamp>") if @timestamps == :require
|
|
252
|
+
return []
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
unless @tsa_store
|
|
256
|
+
fail!("the signature has a <xades:SignatureTimeStamp>; pass tsa_store: or tsa_certificates: to " \
|
|
257
|
+
"verify it, or timestamps: :ignore")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
nodes.each_with_index.map do |node, i|
|
|
261
|
+
SignatureTimeStampCheck.new(node, [*path, [xades_ns, "SignatureTimeStamp", i]],
|
|
262
|
+
ns: { "ds" => HankoXml::NS["ds"], "xades" => xades_ns },
|
|
263
|
+
policy: @policy, tsa_store: @tsa_store).verify(xml_bytes, index)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Each xades141:ArchiveTimeStamp of this signature, verified (#169), read
|
|
268
|
+
# along the same path as the SignatureTimeStamps.
|
|
269
|
+
def archive_timestamps(verification, xml_bytes, index)
|
|
270
|
+
return nil if @timestamps == :ignore
|
|
271
|
+
|
|
272
|
+
signature = verification.signature_node
|
|
273
|
+
qualifying, ns = Locator.qualifying_properties(signature)
|
|
274
|
+
properties = Locator.unsigned_signature_properties(qualifying, ns)
|
|
275
|
+
nodes = properties ? properties.xpath("xades141:ArchiveTimeStamp", "xades141" => NS141).to_a : []
|
|
276
|
+
return [] if nodes.empty?
|
|
277
|
+
|
|
278
|
+
fail!("ArchiveTimeStamp is verified only with XAdES 1.3.2 QualifyingProperties") unless ns["xades"] == NS
|
|
279
|
+
unless @tsa_store
|
|
280
|
+
fail!("the signature has a <xades141:ArchiveTimeStamp>; pass tsa_store: or tsa_certificates: to " \
|
|
281
|
+
"verify it, or timestamps: :ignore")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
path = Locator.unsigned_path(signature, qualifying, NS)
|
|
285
|
+
references = reference_data(verification, xml_bytes, index)
|
|
286
|
+
node_set = ArchiveTimeStampCheck.node_set_references?(verification.signature_info)
|
|
287
|
+
nodes.each_with_index.map do |node, i|
|
|
288
|
+
ArchiveTimeStampCheck.new(node, [*path, [NS141, "ArchiveTimeStamp", i]],
|
|
289
|
+
references: references, node_set_references: node_set,
|
|
290
|
+
element_paths: ArchiveTimeStampCheck.element_paths(signature, qualifying,
|
|
291
|
+
properties, node, path),
|
|
292
|
+
ns: { "ds" => HankoXml::NS["ds"], "xades" => NS },
|
|
293
|
+
policy: @policy, tsa_store: @tsa_store).verify(xml_bytes, index)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Each SignedInfo Reference's data as xmlsec digested it, verifying the
|
|
298
|
+
# signature again with the key that verified it.
|
|
299
|
+
def reference_data(verification, xml_bytes, index)
|
|
300
|
+
octets = Native.reference_data(xml_bytes, [verification.certificate.public_key.to_pem], native_id_attributes,
|
|
301
|
+
index, @referenced_documents.to_a, @policy.allows?(:transform, :xpath_filter2))
|
|
302
|
+
fail!("a Reference's data could not be read for the ArchiveTimeStamp") if octets.any?(&:nil?)
|
|
303
|
+
|
|
304
|
+
octets
|
|
305
|
+
rescue HankoXml::NativeError
|
|
306
|
+
fail!("the signature's Reference data could not be read for the ArchiveTimeStamp")
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The SignatureTimeStamp elements, the native path to their parent, and
|
|
310
|
+
# their XAdES namespace.
|
|
311
|
+
def signature_timestamp_nodes(signature)
|
|
312
|
+
qualifying, ns = Locator.qualifying_properties(signature)
|
|
313
|
+
properties = Locator.unsigned_signature_properties(qualifying, ns)
|
|
314
|
+
return [[], nil] unless properties
|
|
315
|
+
|
|
316
|
+
timestamps = properties.xpath("xades:SignatureTimeStamp", ns)
|
|
317
|
+
if timestamps.any? && ns["xades"] != NS
|
|
318
|
+
fail!("SignatureTimeStamp is verified only in the XAdES 1.3.2 namespace")
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
[timestamps.to_a, Locator.unsigned_path(signature, qualifying, ns["xades"]), ns["xades"]]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def coerce_long_term(value)
|
|
325
|
+
value = @timestamps == :ignore ? :ignore : :verify if value.nil?
|
|
326
|
+
unless LONG_TERM_MODES.include?(value)
|
|
327
|
+
raise HankoXml::ConfigurationError, "long_term: must be one of #{LONG_TERM_MODES.inspect}"
|
|
328
|
+
end
|
|
329
|
+
if value != :ignore && @timestamps == :ignore
|
|
330
|
+
raise HankoXml::ConfigurationError,
|
|
331
|
+
"long_term: #{value.inspect} needs SignatureTimeStamps for its validation time; " \
|
|
332
|
+
"it cannot go with timestamps: :ignore"
|
|
333
|
+
end
|
|
334
|
+
if value == :require && @tsa_store.nil?
|
|
335
|
+
raise HankoXml::ConfigurationError, "long_term: :require needs tsa_store: or tsa_certificates:"
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
value
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def coerce_revocation_grace(value)
|
|
342
|
+
return value if value.is_a?(Numeric) && value >= 0
|
|
343
|
+
|
|
344
|
+
raise HankoXml::ConfigurationError, "revocation_grace: must be a number of seconds, 0 or more"
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def coerce_timestamps(value)
|
|
348
|
+
return value if TIMESTAMP_MODES.include?(value)
|
|
349
|
+
|
|
350
|
+
raise HankoXml::ConfigurationError, "timestamps: must be one of #{TIMESTAMP_MODES.inspect}"
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# The TSA trust, separate from the signer's: tsa_store: as given, or a
|
|
354
|
+
# store over tsa_certificates:.
|
|
355
|
+
def coerce_tsa_store(store, certificates)
|
|
356
|
+
raise HankoXml::ConfigurationError, "pass tsa_store: or tsa_certificates:, not both" if store && certificates
|
|
357
|
+
|
|
358
|
+
if @timestamps == :require && !store && !certificates
|
|
359
|
+
raise HankoXml::ConfigurationError, "timestamps: :require needs tsa_store: or tsa_certificates:"
|
|
360
|
+
end
|
|
361
|
+
return tsa_store_from(certificates) if store.nil?
|
|
362
|
+
return store if store.is_a?(OpenSSL::X509::Store)
|
|
363
|
+
|
|
364
|
+
raise HankoXml::ConfigurationError, "tsa_store: must be an OpenSSL::X509::Store"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def tsa_store_from(certificates)
|
|
368
|
+
return nil if certificates.nil?
|
|
369
|
+
|
|
370
|
+
list = Array(certificates)
|
|
371
|
+
raise HankoXml::ConfigurationError, "tsa_certificates: must not be empty" if list.empty?
|
|
372
|
+
|
|
373
|
+
OpenSSL::X509::Store.new.tap do |store|
|
|
374
|
+
list.each do |certificate|
|
|
375
|
+
key = HankoXml::Key.coerce(certificate)
|
|
376
|
+
raise HankoXml::ConfigurationError, "tsa_certificates: must be X.509 certificates" unless key.certificate?
|
|
377
|
+
|
|
378
|
+
store.add_cert(key.to_openssl)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
101
383
|
def signed_properties(verification)
|
|
102
384
|
node, ns = signed_properties_node(verification)
|
|
103
385
|
signature_properties = node.at_xpath("xades:SignedSignatureProperties", ns)
|
|
@@ -122,7 +404,7 @@ module HankoXml
|
|
|
122
404
|
# with.
|
|
123
405
|
def signed_properties_node(verification)
|
|
124
406
|
info, reference = signed_properties_reference(verification)
|
|
125
|
-
qualifying, ns = qualifying_properties(verification.signature_node)
|
|
407
|
+
qualifying, ns = Locator.qualifying_properties(verification.signature_node)
|
|
126
408
|
|
|
127
409
|
type_namespace = VERSIONED_SIGNED_PROPERTIES_TYPES[info.type]
|
|
128
410
|
if type_namespace && type_namespace != ns["xades"]
|
|
@@ -153,22 +435,6 @@ module HankoXml
|
|
|
153
435
|
pairs.first
|
|
154
436
|
end
|
|
155
437
|
|
|
156
|
-
# This signature's single QualifyingProperties, in any XAdES namespace,
|
|
157
|
-
# and the namespaces to read it with.
|
|
158
|
-
def qualifying_properties(signature)
|
|
159
|
-
signature_id = signature["Id"].to_s
|
|
160
|
-
fail!("the <Signature> has no Id for QualifyingProperties to target") if signature_id.empty?
|
|
161
|
-
|
|
162
|
-
qualifying = signature.xpath("ds:Object/*[local-name()='QualifyingProperties']", "ds" => HankoXml::NS["ds"])
|
|
163
|
-
.select { |node| NAMESPACES.include?(node.namespace&.href) }
|
|
164
|
-
fail!("a XAdES signature has exactly one <xades:QualifyingProperties>") unless qualifying.size == 1
|
|
165
|
-
|
|
166
|
-
qualifying = qualifying.first
|
|
167
|
-
fail!("<xades:QualifyingProperties> does not target this <Signature>") unless targets?(qualifying, signature_id)
|
|
168
|
-
|
|
169
|
-
[qualifying, { "ds" => HankoXml::NS["ds"], "xades" => qualifying.namespace.href }]
|
|
170
|
-
end
|
|
171
|
-
|
|
172
438
|
# By its Type, or, with no Type (as some 1.2.2 signatures write it), by
|
|
173
439
|
# what xmlsec resolved and digested: a SignedProperties element in a
|
|
174
440
|
# XAdES namespace. Never by the URI text (#86).
|
|
@@ -179,16 +445,6 @@ module HankoXml
|
|
|
179
445
|
!node.nil? && node.name == "SignedProperties" && NAMESPACES.include?(node.namespace&.href)
|
|
180
446
|
end
|
|
181
447
|
|
|
182
|
-
# Target is "#" + the Signature Id. The older namespaces also had
|
|
183
|
-
# signers write the bare Id (Target="Signature"). Either way the
|
|
184
|
-
# QualifyingProperties must sit in this <Signature>'s own <Object>.
|
|
185
|
-
def targets?(qualifying, signature_id)
|
|
186
|
-
target = qualifying["Target"]
|
|
187
|
-
return true if target == "##{signature_id}"
|
|
188
|
-
|
|
189
|
-
LEGACY_NAMESPACES.include?(qualifying.namespace.href) && target == signature_id
|
|
190
|
-
end
|
|
191
|
-
|
|
192
448
|
def check_signing_certificate!(signature_properties, certificate, ns)
|
|
193
449
|
fail!("no certificate verified the signature, so SigningCertificate cannot be checked") unless certificate
|
|
194
450
|
|
|
@@ -314,6 +570,24 @@ module HankoXml
|
|
|
314
570
|
def fail!(message)
|
|
315
571
|
raise HankoXml::XadesError, message
|
|
316
572
|
end
|
|
573
|
+
|
|
574
|
+
# The XML-DSig verification of an XAdES-LT signature (#168): embedded
|
|
575
|
+
# certificates may complete the store: chain, never be its anchor or
|
|
576
|
+
# its signing certificate.
|
|
577
|
+
#
|
|
578
|
+
# @api private
|
|
579
|
+
class AtTime < Verifier
|
|
580
|
+
def initialize(intermediates:, **options)
|
|
581
|
+
@intermediates = intermediates
|
|
582
|
+
super(**options)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
private
|
|
586
|
+
|
|
587
|
+
def chain_intermediates
|
|
588
|
+
@intermediates
|
|
589
|
+
end
|
|
590
|
+
end
|
|
317
591
|
end
|
|
318
592
|
end
|
|
319
593
|
end
|