hanko_xml 0.1.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +44 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +222 -0
  5. data/THIRD_PARTY_LICENSES.md +392 -0
  6. data/dependencies.yml +87 -0
  7. data/lib/hanko_xml/3.1/hanko_xml.so +0 -0
  8. data/lib/hanko_xml/3.2/hanko_xml.so +0 -0
  9. data/lib/hanko_xml/3.3/hanko_xml.so +0 -0
  10. data/lib/hanko_xml/3.4/hanko_xml.so +0 -0
  11. data/lib/hanko_xml/algorithms.rb +228 -0
  12. data/lib/hanko_xml/dom.rb +137 -0
  13. data/lib/hanko_xml/errors.rb +132 -0
  14. data/lib/hanko_xml/exc-c14n.xsd +26 -0
  15. data/lib/hanko_xml/input.rb +50 -0
  16. data/lib/hanko_xml/key.rb +213 -0
  17. data/lib/hanko_xml/key_info.rb +67 -0
  18. data/lib/hanko_xml/native_version.rb +75 -0
  19. data/lib/hanko_xml/nokogiri_ext.rb +34 -0
  20. data/lib/hanko_xml/placement.rb +127 -0
  21. data/lib/hanko_xml/policy.rb +502 -0
  22. data/lib/hanko_xml/reference.rb +49 -0
  23. data/lib/hanko_xml/signature_info.rb +134 -0
  24. data/lib/hanko_xml/signed_document.rb +155 -0
  25. data/lib/hanko_xml/signer.rb +572 -0
  26. data/lib/hanko_xml/template.rb +150 -0
  27. data/lib/hanko_xml/transform.rb +87 -0
  28. data/lib/hanko_xml/verification.rb +377 -0
  29. data/lib/hanko_xml/verifier.rb +694 -0
  30. data/lib/hanko_xml/version.rb +5 -0
  31. data/lib/hanko_xml/xades/verifier.rb +319 -0
  32. data/lib/hanko_xml/xades.rb +287 -0
  33. data/lib/hanko_xml/xml.rb +93 -0
  34. data/lib/hanko_xml/xmldsig-core-schema.xsd +332 -0
  35. data/lib/hanko_xml.rb +122 -0
  36. data/sig/hanko_xml/key.rbs +48 -0
  37. data/sig/hanko_xml/native.rbs +12 -0
  38. data/sig/hanko_xml/native_version.rbs +13 -0
  39. data/sig/hanko_xml/nokogiri_ext.rbs +23 -0
  40. data/sig/hanko_xml/placement.rbs +27 -0
  41. data/sig/hanko_xml/policy.rbs +83 -0
  42. data/sig/hanko_xml/reference.rbs +36 -0
  43. data/sig/hanko_xml/signed_document.rbs +66 -0
  44. data/sig/hanko_xml/signer.rbs +50 -0
  45. data/sig/hanko_xml/template.rbs +41 -0
  46. data/sig/hanko_xml/verification.rbs +100 -0
  47. data/sig/hanko_xml/verifier.rbs +45 -0
  48. data/sig/hanko_xml/xades.rbs +70 -0
  49. data/sig/hanko_xml.rbs +62 -0
  50. metadata +104 -0
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HankoXml
4
+ # Registry of algorithm URIs, keyed by short Symbol names, per slot
5
+ # (:canonicalization, :digest, :signature, :transform). See
6
+ # API_CONTRACT.md §5.7.
7
+ #
8
+ # Every algorithm slot elsewhere in the library accepts either a Symbol
9
+ # (looked up here) or a String URI (used verbatim, subject to Policy).
10
+ #
11
+ # Registration is process-global and additive. It does NOT grant use --
12
+ # a Policy allowlist must separately permit the algorithm. Built-in names
13
+ # and URIs cannot be re-pointed, even with replace: true (S-16).
14
+ module Algorithms # rubocop:disable Metrics/ModuleLength
15
+ Entry = Struct.new(:slot, :name, :uri, :secure, keyword_init: true) do
16
+ def secure?
17
+ !!secure
18
+ end
19
+ end
20
+
21
+ # Name/URI lookup for a single algorithm slot (e.g. :digest).
22
+ class Table
23
+ def initialize(slot)
24
+ @slot = slot
25
+ @by_name = {}
26
+ @by_uri = {}
27
+ @builtin_names = {}.freeze
28
+ @builtin_uris = {}.freeze
29
+ end
30
+
31
+ def register(name, uri, secure:, replace: false)
32
+ name = name.to_sym
33
+ uri = uri.to_s
34
+ check_not_builtin!(name, uri)
35
+
36
+ if @by_name.key?(name) && !replace
37
+ raise ArgumentError,
38
+ "#{@slot} algorithm #{name.inspect} is already registered " \
39
+ "(pass replace: true to override)"
40
+ end
41
+
42
+ previous = @by_name[name]
43
+ @by_uri.delete(previous.uri) if previous && @by_uri[previous.uri].equal?(previous)
44
+
45
+ entry = Entry.new(slot: @slot, name: name, uri: uri, secure: secure).freeze
46
+ @by_name[name] = entry
47
+ @by_uri[uri] = entry
48
+ entry
49
+ end
50
+
51
+ # Marks everything registered so far as built-in (S-16): those names
52
+ # and URIs are what Policy presets are written in terms of, so
53
+ # re-pointing one would silently change what, e.g., Policy.strict
54
+ # accepts for every caller in the process.
55
+ def seal_builtins!
56
+ @builtin_names = @by_name.keys.to_h { |name| [name, true] }.freeze
57
+ @builtin_uris = @by_uri.keys.to_h { |uri| [uri, true] }.freeze
58
+ self
59
+ end
60
+
61
+ def [](name_or_uri)
62
+ case name_or_uri
63
+ when Symbol
64
+ @by_name[name_or_uri]
65
+ when String
66
+ @by_uri[name_or_uri] || @by_name[name_or_uri.to_sym]
67
+ end
68
+ end
69
+
70
+ def uri_for(name_or_uri)
71
+ return name_or_uri if name_or_uri.is_a?(String) && !@by_name.key?(name_or_uri.to_sym)
72
+
73
+ entry = self[name_or_uri]
74
+ entry&.uri || (name_or_uri.is_a?(String) ? name_or_uri : nil)
75
+ end
76
+
77
+ def name_for(uri)
78
+ @by_uri[uri.to_s]&.name
79
+ end
80
+
81
+ def secure?(name_or_uri)
82
+ entry = self[name_or_uri]
83
+ entry.nil? ? false : entry.secure?
84
+ end
85
+
86
+ def names
87
+ @by_name.keys
88
+ end
89
+
90
+ private
91
+
92
+ def check_not_builtin!(name, uri)
93
+ if @builtin_names.key?(name)
94
+ raise ArgumentError, "#{@slot} algorithm #{name.inspect} is built in and cannot be replaced"
95
+ end
96
+ return unless @builtin_uris.key?(uri)
97
+
98
+ raise ArgumentError,
99
+ "#{@slot} URI #{uri} is built in as #{@by_uri[uri].name.inspect} and cannot be re-registered"
100
+ end
101
+ end
102
+
103
+ class << self
104
+ def canonicalization
105
+ @canonicalization ||= build_canonicalization_table
106
+ end
107
+
108
+ def digest
109
+ @digest ||= build_digest_table
110
+ end
111
+
112
+ def signature
113
+ @signature ||= build_signature_table
114
+ end
115
+
116
+ def transform
117
+ @transform ||= build_transform_table
118
+ end
119
+
120
+ def register(slot, name, uri, secure:, replace: false)
121
+ table_for(slot).register(name, uri, secure: secure, replace: replace)
122
+ end
123
+
124
+ def uri_for(slot, name_or_uri)
125
+ table_for(slot).uri_for(name_or_uri)
126
+ end
127
+
128
+ def name_for(slot, uri)
129
+ table_for(slot).name_for(uri)
130
+ end
131
+
132
+ def secure?(slot, name_or_uri)
133
+ table_for(slot).secure?(name_or_uri)
134
+ end
135
+
136
+ private
137
+
138
+ def table_for(slot)
139
+ case slot.to_sym
140
+ when :canonicalization then canonicalization
141
+ when :digest then digest
142
+ when :signature then signature
143
+ when :transform then transform
144
+ else
145
+ raise ArgumentError, "unknown algorithm slot #{slot.inspect}"
146
+ end
147
+ end
148
+
149
+ def build_canonicalization_table
150
+ table = Table.new(:canonicalization)
151
+ table.register(:c14n, "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", secure: true)
152
+ table.register(:c14n_with_comments,
153
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments", secure: true)
154
+ table.register(:exc_c14n, "http://www.w3.org/2001/10/xml-exc-c14n#", secure: true)
155
+ table.register(:exc_c14n_with_comments,
156
+ "http://www.w3.org/2001/10/xml-exc-c14n#WithComments", secure: true)
157
+ table.register(:c14n11, "http://www.w3.org/2006/12/xml-c14n11", secure: true)
158
+ table.register(:c14n11_with_comments,
159
+ "http://www.w3.org/2006/12/xml-c14n11#WithComments", secure: true)
160
+ table.seal_builtins!
161
+ end
162
+
163
+ def build_digest_table
164
+ table = Table.new(:digest)
165
+ table.register(:sha1, "http://www.w3.org/2000/09/xmldsig#sha1", secure: false)
166
+ table.register(:sha224, "http://www.w3.org/2001/04/xmldsig-more#sha224", secure: true)
167
+ table.register(:sha256, "http://www.w3.org/2001/04/xmlenc#sha256", secure: true)
168
+ table.register(:sha384, "http://www.w3.org/2001/04/xmldsig-more#sha384", secure: true)
169
+ table.register(:sha512, "http://www.w3.org/2001/04/xmlenc#sha512", secure: true)
170
+ table.register(:ripemd160, "http://www.w3.org/2001/04/xmlenc#ripemd160", secure: false)
171
+ # RFC 6931 SHA-3. xmlsec 1.3 implements these; xmlsec 1.2 (Ubuntu CI)
172
+ # does not — Native.sign/verify then fails closed (#60).
173
+ table.register(:sha3_224, "http://www.w3.org/2007/05/xmldsig-more#sha3-224", secure: true)
174
+ table.register(:sha3_256, "http://www.w3.org/2007/05/xmldsig-more#sha3-256", secure: true)
175
+ table.register(:sha3_384, "http://www.w3.org/2007/05/xmldsig-more#sha3-384", secure: true)
176
+ table.register(:sha3_512, "http://www.w3.org/2007/05/xmldsig-more#sha3-512", secure: true)
177
+ table.seal_builtins!
178
+ end
179
+
180
+ def build_signature_table
181
+ table = Table.new(:signature)
182
+ table.register(:rsa_sha1, "http://www.w3.org/2000/09/xmldsig#rsa-sha1", secure: false)
183
+ table.register(:rsa_sha224, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224", secure: true)
184
+ table.register(:rsa_sha256, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", secure: true)
185
+ table.register(:rsa_sha384, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", secure: true)
186
+ table.register(:rsa_sha512, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", secure: true)
187
+ # RFC 6931 RSA-PSS (MGF1, default salt = hash length). xmlsec 1.3
188
+ # implements these without <RSAPSSParams>; xmlsec 1.2 does not (#60).
189
+ table.register(:rsa_pss_sha256, "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1",
190
+ secure: true)
191
+ table.register(:rsa_pss_sha384, "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1",
192
+ secure: true)
193
+ table.register(:rsa_pss_sha512, "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1",
194
+ secure: true)
195
+ table.register(:ecdsa_sha256, "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", secure: true)
196
+ table.register(:ecdsa_sha384, "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", secure: true)
197
+ table.register(:ecdsa_sha512, "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", secure: true)
198
+ # RFC 9231 put ECDSA-SHA3 under the 2021/04 namespace, not RFC 6931's
199
+ # 2007/05 one; xmlsec 1.3 uses the same URIs (#60).
200
+ table.register(:ecdsa_sha3_256, "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256",
201
+ secure: true)
202
+ table.register(:ecdsa_sha3_384, "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384",
203
+ secure: true)
204
+ table.register(:ecdsa_sha3_512, "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512",
205
+ secure: true)
206
+ table.register(:dsa_sha1, "http://www.w3.org/2000/09/xmldsig#dsa-sha1", secure: false)
207
+ table.register(:hmac_sha256, "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", secure: true)
208
+ table.seal_builtins!
209
+ end
210
+
211
+ def build_transform_table
212
+ table = Table.new(:transform)
213
+ # Every canonicalization algorithm is also a valid transform.
214
+ canonicalization.names.each do |name|
215
+ entry = canonicalization[name]
216
+ table.register(entry.name, entry.uri, secure: entry.secure?)
217
+ end
218
+ table.register(:enveloped_signature, "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
219
+ secure: true)
220
+ table.register(:base64, "http://www.w3.org/2000/09/xmldsig#base64", secure: true)
221
+ table.register(:xpath, "http://www.w3.org/TR/1999/REC-xpath-19991116", secure: false)
222
+ table.register(:xpath_filter2, "http://www.w3.org/2002/06/xmldsig-filter2", secure: false)
223
+ table.register(:xslt, "http://www.w3.org/TR/1999/REC-xslt-19991116", secure: false)
224
+ table.seal_builtins!
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HankoXml
4
+ # The single place that probes for, or calls into, Nokogiri. Nothing else
5
+ # in the library references the Nokogiri constant directly -- see
6
+ # API_CONTRACT.md §6.1. spec/hanko_xml/dom_spec.rb enforces this, and that
7
+ # nothing in lib/ ever requires Nokogiri (the caller decides load order,
8
+ # §3.1).
9
+ #
10
+ # The DOM operations below take no decision of their own: each is the one
11
+ # Nokogiri call its caller needs, kept here so a DOM-free path (#102) has a
12
+ # single seam to replace or guard.
13
+ #
14
+ # The rule: a missing Nokogiri may remove a capability, it may never weaken
15
+ # one. Every check that runs with Nokogiri present runs identically in its
16
+ # absence; the capabilities that disappear are the ones that cannot be
17
+ # expressed at all without a DOM, and each of them raises
18
+ # HankoXml::NokogiriRequired rather than quietly returning a less careful
19
+ # answer.
20
+ #
21
+ # @api private
22
+ module Dom
23
+ class << self
24
+ def available?
25
+ defined?(::Nokogiri::XML::Document) ? true : false
26
+ end
27
+
28
+ def document?(obj)
29
+ available? && obj.is_a?(::Nokogiri::XML::Document)
30
+ end
31
+
32
+ def node?(obj)
33
+ available? && obj.is_a?(::Nokogiri::XML::Node)
34
+ end
35
+
36
+ def certificate?(obj)
37
+ defined?(::OpenSSL::X509::Certificate) && obj.is_a?(::OpenSSL::X509::Certificate)
38
+ end
39
+
40
+ def pkey?(obj)
41
+ defined?(::OpenSSL::PKey::PKey) && obj.is_a?(::OpenSSL::PKey::PKey)
42
+ end
43
+
44
+ # Raises HankoXml::NokogiriRequired, naming +capability+ and the fix,
45
+ # unless Nokogiri is loaded.
46
+ def require!(capability)
47
+ return if available?
48
+
49
+ raise HankoXml::NokogiriRequired,
50
+ "#{capability} needs a DOM, and Nokogiri is not loaded. Add `gem \"nokogiri\"` to your " \
51
+ "Gemfile and `require \"nokogiri\"` before using it. Verification, signature_present? and " \
52
+ "signing an existing <Signature> template work without it (API_CONTRACT.md §6.1)."
53
+ end
54
+
55
+ # Parses +source+ strict (no RECOVER) and nonet: the one way that does
56
+ # not break XML-DSig (API_CONTRACT.md §6.3). Returns the document and
57
+ # libxml2's errors, or nil and the SyntaxError strict mode raised.
58
+ def parse(source)
59
+ doc = ::Nokogiri::XML(source) { |config| config.strict.nonet }
60
+ [doc, doc.errors]
61
+ rescue ::Nokogiri::XML::SyntaxError => e
62
+ [nil, [e]]
63
+ end
64
+
65
+ # Byte-faithful serialization: AS_XML only, never FORMAT, which reindents
66
+ # the tree and invalidates any signature over it (§6.3).
67
+ def to_xml(node, declaration: true)
68
+ options = ::Nokogiri::XML::Node::SaveOptions::AS_XML
69
+ options |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION unless declaration
70
+ node.to_xml(save_with: options)
71
+ end
72
+
73
+ # Runs +expression+ against +node+. On an XPath syntax error, returns
74
+ # what the block returns (the block usually raises); without a block
75
+ # the error propagates.
76
+ def xpath(node, expression, namespaces)
77
+ node.xpath(expression, namespaces)
78
+ rescue ::Nokogiri::XML::XPath::SyntaxError
79
+ raise unless block_given?
80
+
81
+ yield
82
+ end
83
+
84
+ # Loads the XSD at +path+, with the path as the base URL so its
85
+ # relative imports resolve (#61).
86
+ def schema_from_file(path)
87
+ require!("XML Schema validation")
88
+
89
+ ::Nokogiri::XML::Schema.from_document(::Nokogiri::XML(::File.read(path), path))
90
+ end
91
+
92
+ # Validates a copy of +node+, as the root of a fresh document, against
93
+ # +schema+. Returns the validation errors.
94
+ def schema_errors(schema, node)
95
+ doc = ::Nokogiri::XML::Document.new
96
+ doc.add_child(node.dup)
97
+ schema.validate(doc)
98
+ end
99
+
100
+ # Mixes +mod+ into Nokogiri's Document and Node. A no-op without
101
+ # Nokogiri. Returns whether it was installed.
102
+ def extend_nodes(mod)
103
+ return false unless available?
104
+
105
+ ::Nokogiri::XML::Document.include(mod)
106
+ ::Nokogiri::XML::Node.include(mod)
107
+ true
108
+ end
109
+
110
+ # What works in this process (API_CONTRACT.md §6.1). Verification, and
111
+ # signing a template the document already holds, run on bytes through
112
+ # the native layer, so they never need Nokogiri; the rest need a DOM.
113
+ # #102.
114
+ #
115
+ # - verify: Verifier / HankoXml.valid? / verify! / signature_present?,
116
+ # including `schema: :default`.
117
+ # - sign: Signer, HankoXml.sign / sign!.
118
+ # - sign_existing_template: Signer#sign / HankoXml.sign of a document
119
+ # that already holds the <Signature> template, on bytes (#102).
120
+ # - coverage_checks: Verification#signed?, #signed_nodes, #at_signed.
121
+ # - schema: a caller's own Nokogiri::XML::Schema as `schema:`.
122
+ # - parse: HankoXml.parse / serialize.
123
+ # - template_build: Template, Signer#template.
124
+ def capabilities
125
+ {
126
+ verify: true,
127
+ sign: available?,
128
+ sign_existing_template: true,
129
+ coverage_checks: available?,
130
+ schema: available?,
131
+ parse: available?,
132
+ template_build: available?
133
+ }
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module HankoXml
6
+ # Base class for everything this gem raises. Rescue this to catch anything
7
+ # hanko_xml-specific. See API_CONTRACT.md §5.10 for the full hierarchy and
8
+ # the reasoning behind it (in particular: never KeyError/ArgumentError/
9
+ # TypeError/IOError, which would shadow core Ruby constants inside this
10
+ # module).
11
+ class Error < StandardError
12
+ # A private key in PEM form, including a truncated one. Redacted from
13
+ # every message and log line (API_CONTRACT.md §5.10, #96).
14
+ PRIVATE_KEY_PEM = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----.*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|\z)/m
15
+ FILTERED = "[FILTERED]"
16
+
17
+ # The xmlsec1 error log lines captured for this failure, or libxml2's
18
+ # diagnostics for a ParseError / SchemaError. Untrusted: they can quote
19
+ # the document.
20
+ attr_reader :xmlsec_errors
21
+
22
+ # The <ds:Signature> Nokogiri::XML::Element this error relates to, when
23
+ # one was located.
24
+ attr_accessor :signature_node
25
+
26
+ # Never carries key material: a HankoXml::Key or OpenSSL::PKey passed as
27
+ # +message+ becomes [FILTERED], and private-key PEM text is redacted.
28
+ # (NativeVersionMismatch can be raised before key.rb is loaded.)
29
+ def self.filter(value)
30
+ return nil if value.nil?
31
+ return FILTERED if value.is_a?(OpenSSL::PKey::PKey) || (defined?(HankoXml::Key) && value.is_a?(HankoXml::Key))
32
+
33
+ value.to_s.gsub(PRIVATE_KEY_PEM, FILTERED)
34
+ end
35
+
36
+ def initialize(message = nil, xmlsec_errors: [], signature_node: nil)
37
+ super(Error.filter(message))
38
+ self.xmlsec_errors = xmlsec_errors
39
+ @signature_node = signature_node
40
+ end
41
+
42
+ def xmlsec_errors=(lines)
43
+ @xmlsec_errors = Array(lines).map { |line| Error.filter(line) }
44
+ end
45
+ end
46
+
47
+ # Invalid Signer/Verifier construction.
48
+ class ConfigurationError < Error; end
49
+
50
+ # A Policy allowlist was violated.
51
+ class PolicyError < ConfigurationError; end
52
+
53
+ # A Placement XPath matched zero or more than one node.
54
+ class PlacementError < ConfigurationError; end
55
+
56
+ # The requested capability needs a DOM, and Nokogiri is not loaded. See
57
+ # API_CONTRACT.md §6.1.
58
+ class NokogiriRequired < ConfigurationError; end
59
+
60
+ # <Signature> failed XSD validation (Verifier schema: option).
61
+ class SchemaError < ConfigurationError; end
62
+
63
+ # The XML would not parse.
64
+ class ParseError < Error; end
65
+
66
+ # A key or certificate was unreadable or in an unsupported format.
67
+ class KeyLoadError < Error; end
68
+
69
+ # A Reference target does not exist, is ambiguous, or has no ID attribute.
70
+ class ReferenceError < Error
71
+ attr_accessor :reference_uri
72
+
73
+ def initialize(message = nil, reference_uri: nil, **kwargs)
74
+ super(message, **kwargs)
75
+ @reference_uri = reference_uri
76
+ end
77
+ end
78
+
79
+ # libxmlsec1 could not produce a signature.
80
+ class SigningError < Error; end
81
+
82
+ # Verification did not succeed (raised by #verify!).
83
+ class VerificationError < Error; end
84
+
85
+ # The signature verified as XML-DSig, but its XAdES signed properties do not
86
+ # hold: they are not the ones xmlsec digested, or they do not commit to the
87
+ # certificate that verified (#58).
88
+ class XadesError < VerificationError; end
89
+
90
+ # Cryptographic mismatch.
91
+ class SignatureInvalid < VerificationError; end
92
+
93
+ # No <ds:Signature> in the document.
94
+ class SignatureMissing < VerificationError; end
95
+
96
+ # Verifier#signature was :only and the document has more than one.
97
+ class MultipleSignatures < VerificationError; end
98
+
99
+ # Certificate/key trust resolution (chain or pinning) failed.
100
+ class UntrustedCertificate < VerificationError; end
101
+
102
+ # Certificate is outside notBefore/notAfter as of verification_time.
103
+ class CertificateExpired < VerificationError; end
104
+
105
+ # One <Reference>'s digest did not match.
106
+ class DigestMismatch < VerificationError
107
+ attr_accessor :reference_uri
108
+
109
+ def initialize(message = nil, reference_uri: nil, **kwargs)
110
+ super(message, **kwargs)
111
+ @reference_uri = reference_uri
112
+ end
113
+ end
114
+
115
+ # A caller-supplied `resolver:` and xmlsec's ID table disagreed on what a
116
+ # Reference URI points to. See API_CONTRACT.md §5.9.
117
+ class ResolutionMismatch < VerificationError; end
118
+
119
+ # Verification#at_signed found a node the signature does not cover.
120
+ class NotSignedError < Error; end
121
+
122
+ # A mutator was called on a HankoXml::SignedDocument.
123
+ class FrozenSignatureError < Error; end
124
+
125
+ # libxmlsec1 failed in an unexpected way.
126
+ class NativeError < Error; end
127
+
128
+ # The loaded libxmlsec1 is not ABI compatible with the headers this
129
+ # extension was compiled against. Raised at require time by
130
+ # {HankoXml::NativeVersion}; see that file for why this cannot be a warning.
131
+ class NativeVersionMismatch < NativeError; end
132
+ end
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Schema for Exclusive XML Canonicalization InclusiveNamespaces
3
+ http://www.w3.org/2001/10/xml-exc-c14n#
4
+ As published in https://www.w3.org/TR/xml-exc-c14n/#sec-Schema
5
+
6
+ Copyright 2002 The Internet Society and W3C (Massachusetts Institute
7
+ of Technology, Institut National de Recherche en Informatique et en
8
+ Automatique, Keio University). All Rights Reserved.
9
+ http://www.w3.org/Consortium/Legal/
10
+
11
+ This document is governed by the W3C Software License [1] as described
12
+ in the FAQ [2].
13
+
14
+ [1] http://www.w3.org/Consortium/Legal/copyright-software-19980720
15
+ [2] http://www.w3.org/Consortium/Legal/IPR-FAQ-20000620.html#DTD
16
+ -->
17
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
18
+ xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"
19
+ targetNamespace="http://www.w3.org/2001/10/xml-exc-c14n#"
20
+ version="0.1" elementFormDefault="qualified">
21
+
22
+ <element name="InclusiveNamespaces" type="ec:InclusiveNamespaces"/>
23
+ <complexType name="InclusiveNamespaces">
24
+ <attribute name="PrefixList" type="NMTOKENS"/>
25
+ </complexType>
26
+ </schema>
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HankoXml
4
+ # A document on its way to the native layer: always its bytes, plus the
5
+ # caller's DOM when there is one (API_CONTRACT.md §6.1, #102).
6
+ #
7
+ # Every security check reads the bytes, through the native layer, so it
8
+ # runs the same with or without Nokogiri. The DOM only serves what cannot
9
+ # exist without one: Verification#signed? and friends, XPath signature
10
+ # selection, a caller's own Nokogiri schema, a resolver:.
11
+ #
12
+ # @api private
13
+ class Input
14
+ attr_reader :bytes, :document
15
+
16
+ # +source+ is anything §6.2 accepts. A Nokogiri Document or Node is
17
+ # serialized; anything else is read as bytes (+policy+'s
18
+ # +max_document_size+ applies first, S-12) and, when Nokogiri is loaded,
19
+ # also parsed, so the result carries a DOM either way.
20
+ def self.read(source, policy: nil)
21
+ if Dom.document?(source) || Dom.node?(source)
22
+ document = Dom.document?(source) ? source : source.document
23
+ return new(HankoXml.serialize(document).b, document, dom: true)
24
+ end
25
+
26
+ raw = HankoXml.read_source(source, policy: policy)
27
+ document = Dom.available? ? HankoXml.parse(raw) : nil
28
+ new(raw.b, document, dom: false)
29
+ end
30
+
31
+ def initialize(bytes, document, dom:)
32
+ @bytes = bytes.frozen? ? bytes : bytes.dup.freeze
33
+ @document = document
34
+ @dom = dom
35
+ end
36
+
37
+ # Whether the caller handed over a DOM (rather than bytes).
38
+ def dom?
39
+ @dom
40
+ end
41
+
42
+ # Native.scan of the bytes, memoized per ID-attribute list. Input that
43
+ # arrived as bytes is parsed strictly, as HankoXml.parse does with
44
+ # Nokogiri; a DOM the caller already holds is taken as it is.
45
+ def scan(id_attributes)
46
+ @scans ||= {}
47
+ @scans[id_attributes] ||= Native.scan(bytes, id_attributes, !dom?)
48
+ end
49
+ end
50
+ end