hanko_xml 0.1.0-x86_64-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,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # XML digital signature verification backed by libxmlsec1.
4
+ module HankoXml
5
+ # XML-DSig namespace prefixes used in every XPath this library runs
6
+ # internally -- never //Signature, never local-name(). See
7
+ # API_CONTRACT.md §6.8.
8
+ NS = {
9
+ "ds" => "http://www.w3.org/2000/09/xmldsig#",
10
+ "ds11" => "http://www.w3.org/2009/xmldsig11#",
11
+ "ec" => "http://www.w3.org/2001/10/xml-exc-c14n#"
12
+ }.freeze
13
+
14
+ class << self
15
+ # Parses +source+ (a String, IO, or path-like object) the one way that
16
+ # does not break XML-DSig: strict (no RECOVER), nonet, never NOBLANKS,
17
+ # never NOENT, never HUGE. See API_CONTRACT.md §6.3.
18
+ #
19
+ # Raises HankoXml::NokogiriRequired without Nokogiri, HankoXml::ParseError
20
+ # on malformed input.
21
+ def parse(source)
22
+ Dom.require!("HankoXml.parse")
23
+
24
+ doc, errors = Dom.parse(source)
25
+ raise parse_error(errors) if errors.any?
26
+
27
+ doc
28
+ end
29
+
30
+ # libxml2's diagnostics can quote the document (tag names, text), so they
31
+ # go in #xmlsec_errors, never in #message (#96, API_CONTRACT.md §5.10).
32
+ def parse_error(errors)
33
+ HankoXml::ParseError.new("the document is not well-formed XML", xmlsec_errors: errors.map(&:message))
34
+ end
35
+ private :parse_error
36
+
37
+ # Byte-faithful serialization: AS_XML only, never FORMAT (which
38
+ # reindents the tree and invalidates any signature over it). See
39
+ # API_CONTRACT.md §6.3.
40
+ def serialize(doc)
41
+ Dom.require!("HankoXml.serialize")
42
+
43
+ Dom.to_xml(doc)
44
+ end
45
+
46
+ # Coerces any of the accepted document input shapes (§6.2) into a
47
+ # Nokogiri::XML::Document: an existing Document as-is, a Node via its
48
+ # #document, a String of markup, a path-like object, or anything
49
+ # responding to #read.
50
+ #
51
+ # +policy+, when given, applies its +max_document_size+ to unparsed
52
+ # input before it is read or parsed (S-12): see #read_source. An
53
+ # existing Document/Node is not checked here; Signer/Verifier check its
54
+ # serialized form before any native call.
55
+ #
56
+ # @api private
57
+ def coerce_document(input, policy: nil)
58
+ return input if Dom.document?(input)
59
+ return input.document if Dom.node?(input)
60
+
61
+ parse(read_source(input, policy: policy))
62
+ end
63
+
64
+ # Reads unparsed input (§6.2) without parsing it: a String as-is, a
65
+ # path-like object's file, or anything responding to #read. +policy+'s
66
+ # +max_document_size+ applies before the input is read (S-12): a String
67
+ # by #bytesize, a path by File.size, an IO by reading at most one byte
68
+ # past the limit.
69
+ #
70
+ # @api private
71
+ def read_source(input, policy: nil)
72
+ limit = policy&.max_document_size
73
+ limit = nil unless limit&.finite?
74
+
75
+ case input
76
+ when String
77
+ policy&.enforce_document_size!(input.bytesize)
78
+ input
79
+ when ->(obj) { obj.respond_to?(:to_path) }
80
+ policy&.enforce_document_size!(::File.size(input.to_path))
81
+ ::File.binread(input.to_path)
82
+ when ->(obj) { obj.respond_to?(:read) }
83
+ source = limit ? input.read(limit + 1) : input.read
84
+ policy&.enforce_document_size!(source.to_s.bytesize)
85
+ source.to_s
86
+ else
87
+ raise ArgumentError,
88
+ "expected a Nokogiri::XML::Document/Node, an XML String, a path-like object " \
89
+ "(Pathname/File), or an IO, got #{input.class}"
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,332 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE schema
3
+ PUBLIC "-//W3C//DTD XMLSchema 200102//EN" "http://www.w3.org/2001/XMLSchema.dtd"
4
+ [
5
+ <!ATTLIST schema
6
+ xmlns:ds CDATA #FIXED "http://www.w3.org/2000/09/xmldsig#">
7
+ <!ENTITY dsig 'http://www.w3.org/2000/09/xmldsig#'>
8
+ <!ENTITY % p ''>
9
+ <!ENTITY % s ''>
10
+ ]>
11
+
12
+ <!-- Schema for XML Signatures
13
+ http://www.w3.org/2000/09/xmldsig#
14
+ $Revision: 1.2 $ on $Date: 2013/04/16 12:48:49 $ by $Author: denis $
15
+
16
+ Copyright 2001 The Internet Society and W3C (Massachusetts Institute
17
+ of Technology, Institut National de Recherche en Informatique et en
18
+ Automatique, Keio University). All Rights Reserved.
19
+ http://www.w3.org/Consortium/Legal/
20
+
21
+ This document is governed by the W3C Software License [1] as described
22
+ in the FAQ [2].
23
+
24
+ [1] http://www.w3.org/Consortium/Legal/copyright-software-19980720
25
+ [2] http://www.w3.org/Consortium/Legal/IPR-FAQ-20000620.html#DTD
26
+ -->
27
+
28
+
29
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
30
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
31
+ targetNamespace="http://www.w3.org/2000/09/xmldsig#"
32
+ version="0.1" elementFormDefault="qualified">
33
+
34
+ <!-- HankoXml: <ec:InclusiveNamespaces> may appear under the strict
35
+ CanonicalizationMethod wildcard (#61). -->
36
+ <import namespace="http://www.w3.org/2001/10/xml-exc-c14n#" schemaLocation="exc-c14n.xsd"/>
37
+
38
+ <!-- Basic Types Defined for Signatures -->
39
+
40
+ <simpleType name="CryptoBinary">
41
+ <restriction base="base64Binary">
42
+ </restriction>
43
+ </simpleType>
44
+
45
+ <!-- Start Signature -->
46
+
47
+ <element name="Signature" type="ds:SignatureType"/>
48
+ <complexType name="SignatureType">
49
+ <sequence>
50
+ <element ref="ds:SignedInfo"/>
51
+ <element ref="ds:SignatureValue"/>
52
+ <element ref="ds:KeyInfo" minOccurs="0"/>
53
+ <element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
54
+ </sequence>
55
+ <attribute name="Id" type="ID" use="optional"/>
56
+ </complexType>
57
+
58
+ <element name="SignatureValue" type="ds:SignatureValueType"/>
59
+ <complexType name="SignatureValueType">
60
+ <simpleContent>
61
+ <extension base="base64Binary">
62
+ <attribute name="Id" type="ID" use="optional"/>
63
+ </extension>
64
+ </simpleContent>
65
+ </complexType>
66
+
67
+ <!-- Start SignedInfo -->
68
+
69
+ <element name="SignedInfo" type="ds:SignedInfoType"/>
70
+ <complexType name="SignedInfoType">
71
+ <sequence>
72
+ <element ref="ds:CanonicalizationMethod"/>
73
+ <element ref="ds:SignatureMethod"/>
74
+ <element ref="ds:Reference" maxOccurs="unbounded"/>
75
+ </sequence>
76
+ <attribute name="Id" type="ID" use="optional"/>
77
+ </complexType>
78
+
79
+ <element name="CanonicalizationMethod" type="ds:CanonicalizationMethodType"/>
80
+ <complexType name="CanonicalizationMethodType" mixed="true">
81
+ <sequence>
82
+ <any namespace="##any" minOccurs="0" maxOccurs="unbounded"/>
83
+ <!-- (0,unbounded) elements from (1,1) namespace -->
84
+ </sequence>
85
+ <attribute name="Algorithm" type="anyURI" use="required"/>
86
+ </complexType>
87
+
88
+ <element name="SignatureMethod" type="ds:SignatureMethodType"/>
89
+ <complexType name="SignatureMethodType" mixed="true">
90
+ <sequence>
91
+ <element name="HMACOutputLength" minOccurs="0" type="ds:HMACOutputLengthType"/>
92
+ <any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
93
+ <!-- (0,unbounded) elements from (1,1) external namespace -->
94
+ </sequence>
95
+ <attribute name="Algorithm" type="anyURI" use="required"/>
96
+ </complexType>
97
+
98
+ <!-- Start Reference -->
99
+
100
+ <element name="Reference" type="ds:ReferenceType"/>
101
+ <complexType name="ReferenceType">
102
+ <sequence>
103
+ <element ref="ds:Transforms" minOccurs="0"/>
104
+ <element ref="ds:DigestMethod"/>
105
+ <element ref="ds:DigestValue"/>
106
+ </sequence>
107
+ <attribute name="Id" type="ID" use="optional"/>
108
+ <attribute name="URI" type="anyURI" use="optional"/>
109
+ <attribute name="Type" type="anyURI" use="optional"/>
110
+ </complexType>
111
+
112
+ <element name="Transforms" type="ds:TransformsType"/>
113
+ <complexType name="TransformsType">
114
+ <sequence>
115
+ <element ref="ds:Transform" maxOccurs="unbounded"/>
116
+ </sequence>
117
+ </complexType>
118
+
119
+ <element name="Transform" type="ds:TransformType"/>
120
+ <complexType name="TransformType" mixed="true">
121
+ <choice minOccurs="0" maxOccurs="unbounded">
122
+ <any namespace="##other" processContents="lax"/>
123
+ <!-- (1,1) elements from (0,unbounded) namespaces -->
124
+ <element name="XPath" type="string"/>
125
+ </choice>
126
+ <attribute name="Algorithm" type="anyURI" use="required"/>
127
+ </complexType>
128
+
129
+ <!-- End Reference -->
130
+
131
+ <element name="DigestMethod" type="ds:DigestMethodType"/>
132
+ <complexType name="DigestMethodType" mixed="true">
133
+ <sequence>
134
+ <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
135
+ </sequence>
136
+ <attribute name="Algorithm" type="anyURI" use="required"/>
137
+ </complexType>
138
+
139
+ <element name="DigestValue" type="ds:DigestValueType"/>
140
+ <simpleType name="DigestValueType">
141
+ <restriction base="base64Binary"/>
142
+ </simpleType>
143
+
144
+ <!-- End SignedInfo -->
145
+
146
+ <!-- Start KeyInfo -->
147
+
148
+ <element name="KeyInfo" type="ds:KeyInfoType"/>
149
+ <complexType name="KeyInfoType" mixed="true">
150
+ <choice maxOccurs="unbounded">
151
+ <element ref="ds:KeyName"/>
152
+ <element ref="ds:KeyValue"/>
153
+ <element ref="ds:RetrievalMethod"/>
154
+ <element ref="ds:X509Data"/>
155
+ <element ref="ds:PGPData"/>
156
+ <element ref="ds:SPKIData"/>
157
+ <element ref="ds:MgmtData"/>
158
+ <any processContents="lax" namespace="##other"/>
159
+ <!-- (1,1) elements from (0,unbounded) namespaces -->
160
+ </choice>
161
+ <attribute name="Id" type="ID" use="optional"/>
162
+ </complexType>
163
+
164
+ <element name="KeyName" type="string"/>
165
+ <element name="MgmtData" type="string"/>
166
+
167
+ <element name="KeyValue" type="ds:KeyValueType"/>
168
+ <complexType name="KeyValueType" mixed="true">
169
+ <choice>
170
+ <element ref="ds:DSAKeyValue"/>
171
+ <element ref="ds:RSAKeyValue"/>
172
+ <any namespace="##other" processContents="lax"/>
173
+ </choice>
174
+ </complexType>
175
+
176
+ <element name="RetrievalMethod" type="ds:RetrievalMethodType"/>
177
+ <complexType name="RetrievalMethodType">
178
+ <sequence>
179
+ <element ref="ds:Transforms" minOccurs="0"/>
180
+ </sequence>
181
+ <attribute name="URI" type="anyURI"/>
182
+ <attribute name="Type" type="anyURI" use="optional"/>
183
+ </complexType>
184
+
185
+ <!-- Start X509Data -->
186
+
187
+ <element name="X509Data" type="ds:X509DataType"/>
188
+ <complexType name="X509DataType">
189
+ <sequence maxOccurs="unbounded">
190
+ <choice>
191
+ <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
192
+ <element name="X509SKI" type="base64Binary"/>
193
+ <element name="X509SubjectName" type="string"/>
194
+ <element name="X509Certificate" type="base64Binary"/>
195
+ <element name="X509CRL" type="base64Binary"/>
196
+ <any namespace="##other" processContents="lax"/>
197
+ </choice>
198
+ </sequence>
199
+ </complexType>
200
+
201
+ <complexType name="X509IssuerSerialType">
202
+ <sequence>
203
+ <element name="X509IssuerName" type="string"/>
204
+ <!-- HankoXml: the lexical space of xs:integer as a pattern. libxml2
205
+ before 2.12 rejects any xs:integer over 24 digits, and an X.509
206
+ serial may be 20 octets (49 digits), so a valid signature failed
207
+ schema: :default wherever Nokogiri ran on an older libxml2. -->
208
+ <element name="X509SerialNumber">
209
+ <simpleType>
210
+ <restriction base="token">
211
+ <pattern value="[+\-]?[0-9]+"/>
212
+ </restriction>
213
+ </simpleType>
214
+ </element>
215
+ </sequence>
216
+ </complexType>
217
+
218
+ <!-- End X509Data -->
219
+
220
+ <!-- Begin PGPData -->
221
+
222
+ <element name="PGPData" type="ds:PGPDataType"/>
223
+ <complexType name="PGPDataType">
224
+ <choice>
225
+ <sequence>
226
+ <element name="PGPKeyID" type="base64Binary"/>
227
+ <element name="PGPKeyPacket" type="base64Binary" minOccurs="0"/>
228
+ <any namespace="##other" processContents="lax" minOccurs="0"
229
+ maxOccurs="unbounded"/>
230
+ </sequence>
231
+ <sequence>
232
+ <element name="PGPKeyPacket" type="base64Binary"/>
233
+ <any namespace="##other" processContents="lax" minOccurs="0"
234
+ maxOccurs="unbounded"/>
235
+ </sequence>
236
+ </choice>
237
+ </complexType>
238
+
239
+ <!-- End PGPData -->
240
+
241
+ <!-- Begin SPKIData -->
242
+
243
+ <element name="SPKIData" type="ds:SPKIDataType"/>
244
+ <complexType name="SPKIDataType">
245
+ <sequence maxOccurs="unbounded">
246
+ <element name="SPKISexp" type="base64Binary"/>
247
+ <any namespace="##other" processContents="lax" minOccurs="0"/>
248
+ </sequence>
249
+ </complexType>
250
+
251
+ <!-- End SPKIData -->
252
+
253
+ <!-- End KeyInfo -->
254
+
255
+ <!-- Start Object (Manifest, SignatureProperty) -->
256
+
257
+ <element name="Object" type="ds:ObjectType"/>
258
+ <complexType name="ObjectType" mixed="true">
259
+ <sequence minOccurs="0" maxOccurs="unbounded">
260
+ <any namespace="##any" processContents="lax"/>
261
+ </sequence>
262
+ <attribute name="Id" type="ID" use="optional"/>
263
+ <attribute name="MimeType" type="string" use="optional"/> <!-- add a grep facet -->
264
+ <attribute name="Encoding" type="anyURI" use="optional"/>
265
+ </complexType>
266
+
267
+ <element name="Manifest" type="ds:ManifestType"/>
268
+ <complexType name="ManifestType">
269
+ <sequence>
270
+ <element ref="ds:Reference" maxOccurs="unbounded"/>
271
+ </sequence>
272
+ <attribute name="Id" type="ID" use="optional"/>
273
+ </complexType>
274
+
275
+ <element name="SignatureProperties" type="ds:SignaturePropertiesType"/>
276
+ <complexType name="SignaturePropertiesType">
277
+ <sequence>
278
+ <element ref="ds:SignatureProperty" maxOccurs="unbounded"/>
279
+ </sequence>
280
+ <attribute name="Id" type="ID" use="optional"/>
281
+ </complexType>
282
+
283
+ <element name="SignatureProperty" type="ds:SignaturePropertyType"/>
284
+ <complexType name="SignaturePropertyType" mixed="true">
285
+ <choice maxOccurs="unbounded">
286
+ <any namespace="##other" processContents="lax"/>
287
+ <!-- (1,1) elements from (1,unbounded) namespaces -->
288
+ </choice>
289
+ <attribute name="Target" type="anyURI" use="required"/>
290
+ <attribute name="Id" type="ID" use="optional"/>
291
+ </complexType>
292
+
293
+ <!-- End Object (Manifest, SignatureProperty) -->
294
+
295
+ <!-- Start Algorithm Parameters -->
296
+
297
+ <simpleType name="HMACOutputLengthType">
298
+ <restriction base="integer"/>
299
+ </simpleType>
300
+
301
+ <!-- Start KeyValue Element-types -->
302
+
303
+ <element name="DSAKeyValue" type="ds:DSAKeyValueType"/>
304
+ <complexType name="DSAKeyValueType">
305
+ <sequence>
306
+ <sequence minOccurs="0">
307
+ <element name="P" type="ds:CryptoBinary"/>
308
+ <element name="Q" type="ds:CryptoBinary"/>
309
+ </sequence>
310
+ <element name="G" type="ds:CryptoBinary" minOccurs="0"/>
311
+ <element name="Y" type="ds:CryptoBinary"/>
312
+ <element name="J" type="ds:CryptoBinary" minOccurs="0"/>
313
+ <sequence minOccurs="0">
314
+ <element name="Seed" type="ds:CryptoBinary"/>
315
+ <element name="PgenCounter" type="ds:CryptoBinary"/>
316
+ </sequence>
317
+ </sequence>
318
+ </complexType>
319
+
320
+ <element name="RSAKeyValue" type="ds:RSAKeyValueType"/>
321
+ <complexType name="RSAKeyValueType">
322
+ <sequence>
323
+ <element name="Modulus" type="ds:CryptoBinary"/>
324
+ <element name="Exponent" type="ds:CryptoBinary"/>
325
+ </sequence>
326
+ </complexType>
327
+
328
+ <!-- End KeyValue Element-types -->
329
+
330
+ <!-- End Signature -->
331
+
332
+ </schema>
data/lib/hanko_xml.rb ADDED
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hanko_xml/version"
4
+
5
+ # A precompiled platform gem serves several Ruby versions at once, so
6
+ # rake-compiler ships one extension per ABI under lib/hanko_xml/<major.minor>/.
7
+ # A gem built from source has a single lib/hanko_xml/hanko_xml.<so|bundle>
8
+ # instead. Try the versioned path first and fall back, so both layouts load.
9
+ begin
10
+ require_relative "hanko_xml/#{RUBY_VERSION[/\d+\.\d+/]}/hanko_xml"
11
+ rescue LoadError
12
+ require_relative "hanko_xml/hanko_xml"
13
+ end
14
+ require_relative "hanko_xml/errors"
15
+ require_relative "hanko_xml/native_version"
16
+ require_relative "hanko_xml/dom"
17
+ require_relative "hanko_xml/algorithms"
18
+ require_relative "hanko_xml/xml"
19
+ require_relative "hanko_xml/input"
20
+ require_relative "hanko_xml/key"
21
+ require_relative "hanko_xml/transform"
22
+ require_relative "hanko_xml/reference"
23
+ require_relative "hanko_xml/key_info"
24
+ require_relative "hanko_xml/placement"
25
+ require_relative "hanko_xml/signature_info"
26
+ require_relative "hanko_xml/policy"
27
+ require_relative "hanko_xml/template"
28
+ require_relative "hanko_xml/signer"
29
+ require_relative "hanko_xml/verification"
30
+ require_relative "hanko_xml/verifier"
31
+ require_relative "hanko_xml/signed_document"
32
+ require_relative "hanko_xml/xades"
33
+
34
+ # Refuse to run against a libxmlsec1 the extension was not built for, before
35
+ # any signing or verification can produce a wrong answer. See
36
+ # HankoXml::NativeVersion.
37
+ HankoXml::NativeVersion.check!
38
+
39
+ # XML digital signature verification backed by libxmlsec1.
40
+ module HankoXml
41
+ class << self
42
+ # True when Nokogiri is loaded. See §6.1.
43
+ def nokogiri?
44
+ Dom.available?
45
+ end
46
+
47
+ # Which capabilities are available given whether Nokogiri is loaded.
48
+ # See §6.1.
49
+ def capabilities
50
+ Dom.capabilities
51
+ end
52
+
53
+ # Signs an XML document, returning a safely-serializable +SignedDocument+ wrapper.
54
+ # This is a convenience shortcut that creates a +Signer+ and calls +#sign+.
55
+ # The original document is not modified (a deep clone is created).
56
+ #
57
+ # @param doc_or_xml [String, Nokogiri::XML::Node, Nokogiri::XML::Document, IO] The XML to sign.
58
+ # @param kwargs [Hash] Configuration options passed to {Signer#initialize}.
59
+ # @return [HankoXml::SignedDocument] A wrapper around the cloned, signed document.
60
+ def sign(doc_or_xml, **kwargs)
61
+ HankoXml::Signer.new(**kwargs).sign(doc_or_xml)
62
+ end
63
+
64
+ # Signs a Nokogiri XML document in place.
65
+ # This is a convenience shortcut that creates a +Signer+ and calls +#sign!+.
66
+ #
67
+ # @param doc [Nokogiri::XML::Node, Nokogiri::XML::Document] The document to mutate.
68
+ # @param kwargs [Hash] Configuration options passed to {Signer#initialize}.
69
+ # @return [Nokogiri::XML::Node, Nokogiri::XML::Document] The mutated original document.
70
+ def sign!(doc, **kwargs)
71
+ HankoXml::Signer.new(**kwargs).sign!(doc)
72
+ end
73
+
74
+ # Verifies an XML signature and answers with a plain boolean, so it is safe
75
+ # directly in a conditional. This is a convenience shortcut that creates a
76
+ # +Verifier+ and calls +#valid?+.
77
+ #
78
+ # There is deliberately no +HankoXml.verify+: a {Verification} is always
79
+ # truthy, so +if HankoXml.verify(doc, ...)+ would be a security bug that reads
80
+ # like correct code (API_CONTRACT §5.1). For the rich object use {verify!} or
81
+ # {Verifier#verify}.
82
+ #
83
+ # @param xml [String, Nokogiri::XML::Node, Nokogiri::XML::Document, IO] The signed XML.
84
+ # @param kwargs [Hash] Configuration options passed to {Verifier#initialize}.
85
+ # @raise [HankoXml::Error] If parsing fails or trust sources are misconfigured.
86
+ # @return [Boolean] True only when the signature verified under the policy.
87
+ def valid?(xml, **kwargs)
88
+ HankoXml::Verifier.new(**kwargs).valid?(xml)
89
+ end
90
+
91
+ # Verifies an XML signature, raising an exception if invalid.
92
+ # This is a convenience shortcut that creates a +Verifier+ and calls +#verify!+.
93
+ #
94
+ # @param xml [String, Nokogiri::XML::Node, Nokogiri::XML::Document, IO] The signed XML.
95
+ # @param kwargs [Hash] Configuration options passed to {Verifier#initialize}.
96
+ # @raise [HankoXml::VerificationError] If the signature is cryptographically invalid or violates policy.
97
+ # @raise [HankoXml::Error] If parsing fails or trust sources are misconfigured.
98
+ # @return [HankoXml::Verification] The valid verification result.
99
+ def verify!(xml, **kwargs)
100
+ HankoXml::Verifier.new(**kwargs).verify!(xml)
101
+ end
102
+
103
+ # Checks whether the document contains at least one populated <ds:Signature>.
104
+ # Does NOT verify the signature's cryptographic validity.
105
+ #
106
+ # @param xml [String, Nokogiri::XML::Node, Nokogiri::XML::Document, IO] The XML to check.
107
+ # @return [Boolean] True if a non-empty <ds:SignatureValue> exists, false otherwise.
108
+ def signature_present?(xml)
109
+ # Structural, from the native parse; needs no DOM (#102).
110
+ HankoXml::Input.read(xml).scan([])[:signature_values].any?
111
+ end
112
+
113
+ # Returns whether zero-copy DOM verification is available on this platform.
114
+ #
115
+ # @return [Boolean] Always false (libxmlsec1 requires serialization).
116
+ def zero_copy_available?
117
+ false
118
+ end
119
+ end
120
+
121
+ ::Xmlsec = HankoXml unless defined?(::Xmlsec)
122
+ end
@@ -0,0 +1,48 @@
1
+ module HankoXml
2
+ class Key
3
+ @material: OpenSSL::PKey::PKey | OpenSSL::X509::Certificate
4
+ @disposed: bool
5
+
6
+ def self.from_pem: (String pem, ?password: String?) -> instance
7
+ def self.from_der: (String der) -> instance
8
+ def self.from_file: (String | _ToPath path, ?password: String?) -> instance
9
+ def self.from_pkcs12: (String | _ToPath path, password: String) -> Bundle
10
+ def self.coerce: (untyped input, ?password: String?) -> instance
11
+
12
+ def initialize: (OpenSSL::PKey::PKey | OpenSSL::X509::Certificate material) -> void
13
+ def certificate?: () -> bool
14
+ def private_key?: () -> bool
15
+ def to_pem: () -> String
16
+ def to_openssl: () -> (OpenSSL::PKey::PKey | OpenSSL::X509::Certificate)
17
+ def dispose!: () -> void
18
+ def disposed?: () -> bool
19
+
20
+ class Bundle
21
+ attr_reader key: Key
22
+ attr_reader certificate: Key
23
+ attr_reader certificates: Array[Key]
24
+
25
+ def initialize: (key: Key, certificate: Key, certificates: Array[Key]) -> void
26
+ def to_h: () -> { key: Key, certificate: Key, certificates: Array[Key] }
27
+ end
28
+ end
29
+
30
+ module KeyInfo
31
+ class Clause
32
+ attr_reader name: Symbol
33
+ attr_reader value: String?
34
+
35
+ def initialize: (Symbol | String name, ?String? value) -> void
36
+ end
37
+
38
+ def self.coerce: (Symbol | Hash[Symbol, String] | Array[Symbol | Hash[Symbol, String] | Clause] info) -> Array[Clause]
39
+
40
+ class Parsed
41
+ attr_reader key_names: Array[String]
42
+ # PEM Strings, as <KeyInfo> carried them: untrusted, and never parsed here.
43
+ attr_reader certificates: Array[String]
44
+
45
+ def initialize: (?key_names: Array[String], ?certificates: Array[String]) -> void
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ module HankoXml
2
+ module Native
3
+ def self.check_version: () -> bool
4
+ def self.abi_compatible?: () -> bool
5
+ def self.versions: () -> Hash[Symbol, String]
6
+ def self.inspect_sig: (String xml_bytes, Integer index) -> Hash[Symbol, untyped]
7
+ def self.verify: (String xml_bytes, Array[String] trusted, Array[String] untrusted, Array[String] id_attributes, Integer index, Array[[String, String]] referenced_documents, bool allow_xpath_filter2) -> Hash[Symbol, untyped]
8
+ def self.sign: (String xml_bytes, String key_pem, Array[String] cert_pems, Array[String] id_attributes, Integer index) -> String
9
+ def self.scan: (String xml_bytes, Array[String] id_attributes, bool strict) -> Hash[Symbol, untyped]
10
+ def self.validate_schema: (String xml_bytes, Integer index, String xsd_path) -> Array[String]
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module HankoXml
2
+ module NativeVersion
3
+ SKIP_ENV: String
4
+
5
+ def self.check!: () -> (:ok | :abi_compatible | :skipped)
6
+ def self.build_versions: () -> Hash[Symbol, String]
7
+
8
+ private
9
+
10
+ def self.skip: () -> String?
11
+ def self.mismatch_message: () -> String
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ # These methods exist only after `require "hanko_xml/nokogiri_ext"`, which is
2
+ # opt-in (API_CONTRACT.md §6.6). `require "hanko_xml"` alone does not add them.
3
+ module HankoXml
4
+ module NokogiriExt
5
+ def sign: (**untyped kwargs) -> HankoXml::SignedDocument
6
+ def sign!: (**untyped kwargs) -> self
7
+ def signature_valid?: (**untyped kwargs) -> bool
8
+ def signatures: () -> Nokogiri::XML::NodeSet
9
+ def signature_present?: () -> bool
10
+ end
11
+ end
12
+
13
+ module Nokogiri
14
+ module XML
15
+ class Node
16
+ # Only after require "hanko_xml/nokogiri_ext".
17
+ include HankoXml::NokogiriExt
18
+ end
19
+
20
+ class Document < Node
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module HankoXml
2
+ class Placement
3
+ type target = String | Nokogiri::XML::Node
4
+
5
+ MODES: Array[Symbol]
6
+
7
+ attr_reader mode: Symbol
8
+ attr_reader target: target?
9
+ attr_reader namespaces: Hash[String | Symbol, String]
10
+ attr_reader block: (^(Nokogiri::XML::Document, Nokogiri::XML::Node) -> void)?
11
+
12
+ def self.append_to_root: () -> instance
13
+ def self.prepend_to_root: () -> instance
14
+ def self.after: (target node_or_xpath, ?namespaces: Hash[String | Symbol, String]) -> instance
15
+ def self.before: (target node_or_xpath, ?namespaces: Hash[String | Symbol, String]) -> instance
16
+ def self.append_to: (target node_or_xpath, ?namespaces: Hash[String | Symbol, String]) -> instance
17
+ def self.prepend_to: (target node_or_xpath, ?namespaces: Hash[String | Symbol, String]) -> instance
18
+ def self.detached: () -> instance
19
+ def self.custom: () { (Nokogiri::XML::Document, Nokogiri::XML::Node) -> void } -> instance
20
+ def self.coerce: (instance | Symbol | nil input) -> instance?
21
+
22
+ def initialize: (Symbol | String mode, ?target: target?, ?namespaces: Hash[String | Symbol, String]) ?{ (Nokogiri::XML::Document, Nokogiri::XML::Node) -> void } -> void
23
+ def detached?: () -> bool
24
+ def resolve: (Nokogiri::XML::Document doc, ?namespaces: Hash[String | Symbol, String]?) -> Nokogiri::XML::Node
25
+ def insert: (Nokogiri::XML::Document doc, Nokogiri::XML::Node signature_node, ?namespaces: Hash[String | Symbol, String]?) -> Nokogiri::XML::Node
26
+ end
27
+ end