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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +44 -0
- data/LICENSE.txt +21 -0
- data/README.md +222 -0
- data/THIRD_PARTY_LICENSES.md +392 -0
- data/dependencies.yml +87 -0
- 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/algorithms.rb +228 -0
- data/lib/hanko_xml/dom.rb +137 -0
- data/lib/hanko_xml/errors.rb +132 -0
- data/lib/hanko_xml/exc-c14n.xsd +26 -0
- data/lib/hanko_xml/input.rb +50 -0
- data/lib/hanko_xml/key.rb +213 -0
- data/lib/hanko_xml/key_info.rb +67 -0
- data/lib/hanko_xml/native_version.rb +75 -0
- data/lib/hanko_xml/nokogiri_ext.rb +34 -0
- data/lib/hanko_xml/placement.rb +127 -0
- data/lib/hanko_xml/policy.rb +502 -0
- data/lib/hanko_xml/reference.rb +49 -0
- data/lib/hanko_xml/signature_info.rb +134 -0
- data/lib/hanko_xml/signed_document.rb +155 -0
- data/lib/hanko_xml/signer.rb +572 -0
- data/lib/hanko_xml/template.rb +150 -0
- data/lib/hanko_xml/transform.rb +87 -0
- data/lib/hanko_xml/verification.rb +377 -0
- data/lib/hanko_xml/verifier.rb +694 -0
- data/lib/hanko_xml/version.rb +5 -0
- data/lib/hanko_xml/xades/verifier.rb +319 -0
- data/lib/hanko_xml/xades.rb +287 -0
- data/lib/hanko_xml/xml.rb +93 -0
- data/lib/hanko_xml/xmldsig-core-schema.xsd +332 -0
- data/lib/hanko_xml.rb +122 -0
- data/sig/hanko_xml/key.rbs +48 -0
- data/sig/hanko_xml/native.rbs +12 -0
- data/sig/hanko_xml/native_version.rbs +13 -0
- data/sig/hanko_xml/nokogiri_ext.rbs +23 -0
- data/sig/hanko_xml/placement.rbs +27 -0
- data/sig/hanko_xml/policy.rbs +83 -0
- data/sig/hanko_xml/reference.rbs +36 -0
- data/sig/hanko_xml/signed_document.rbs +66 -0
- data/sig/hanko_xml/signer.rbs +50 -0
- data/sig/hanko_xml/template.rbs +41 -0
- data/sig/hanko_xml/verification.rbs +100 -0
- data/sig/hanko_xml/verifier.rbs +45 -0
- data/sig/hanko_xml/xades.rbs +70 -0
- data/sig/hanko_xml.rbs +62 -0
- 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
|