hanko_xml 0.1.0
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/Cargo.lock +401 -0
- data/Cargo.toml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +222 -0
- data/THIRD_PARTY_LICENSES.md +392 -0
- data/dependencies.yml +87 -0
- data/ext/hanko_xml/Cargo.toml +20 -0
- data/ext/hanko_xml/build/vendor.rs +817 -0
- data/ext/hanko_xml/build.rs +298 -0
- data/ext/hanko_xml/extconf.rb +6 -0
- data/ext/hanko_xml/src/dsig.rs +1851 -0
- data/ext/hanko_xml/src/errors.rs +151 -0
- data/ext/hanko_xml/src/lib.rs +490 -0
- data/ext/hanko_xml/src/sys.rs +43 -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 +105 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HankoXml
|
|
4
|
+
# A safe-serialization wrapper around a signed Nokogiri::XML::Document.
|
|
5
|
+
# See API_CONTRACT.md §6.7.
|
|
6
|
+
#
|
|
7
|
+
# A bare Nokogiri document's #to_xml defaults to formatting/reindenting
|
|
8
|
+
# the tree, which invalidates the signature that was just computed. This
|
|
9
|
+
# wrapper intercepts serialization to ensure it is always byte-faithful.
|
|
10
|
+
#
|
|
11
|
+
# Read-only Nokogiri methods are delegated; mutators raise
|
|
12
|
+
# FrozenSignatureError (S-17). Nodes returned by the readers are still the
|
|
13
|
+
# live tree, so serialization also checks that the document is unchanged
|
|
14
|
+
# since signing and raises FrozenSignatureError if it is not.
|
|
15
|
+
#
|
|
16
|
+
# Signed without Nokogiri (#102), it holds only the signed bytes: the
|
|
17
|
+
# serializers, #valid? and #inspect work, #document is nil, and the
|
|
18
|
+
# Nokogiri readers and #signatures raise NokogiriRequired.
|
|
19
|
+
class SignedDocument
|
|
20
|
+
READ_METHODS = %i[at at_css at_xpath css search xpath root encoding errors namespaces
|
|
21
|
+
children version url].freeze
|
|
22
|
+
|
|
23
|
+
MUTATORS = %i[<< add_child add_next_sibling add_previous_sibling after before children= content=
|
|
24
|
+
inner_html= native_content= parent= remove remove_attribute replace root= set_attribute
|
|
25
|
+
[]= delete encoding= unlink add_namespace add_namespace_definition default_namespace=
|
|
26
|
+
do_xinclude canonicalize! remove_namespaces! swap wrap].freeze
|
|
27
|
+
|
|
28
|
+
# @return [Nokogiri::XML::Document] The underlying Nokogiri document.
|
|
29
|
+
# Escape hatch: mutating it invalidates the signature, and this
|
|
30
|
+
# wrapper's serializers then raise FrozenSignatureError. Serialize an
|
|
31
|
+
# intentionally modified document with HankoXml.serialize(document).
|
|
32
|
+
attr_reader :document
|
|
33
|
+
|
|
34
|
+
# The signed bytes, without a DOM (#102). Signer#sign returns one when
|
|
35
|
+
# Nokogiri is not loaded.
|
|
36
|
+
# @param bytes [String] The signed document, as Native.sign returned it.
|
|
37
|
+
def self.from_bytes(bytes)
|
|
38
|
+
allocate.tap { |signed| signed.send(:initialize_from_bytes, bytes) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Initializes the wrapper with a signed document.
|
|
42
|
+
# @param document [Nokogiri::XML::Document] The signed document.
|
|
43
|
+
def initialize(document)
|
|
44
|
+
@document = document
|
|
45
|
+
@signed_xml = serialize(declaration: true).freeze
|
|
46
|
+
freeze
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
READ_METHODS.each do |name|
|
|
50
|
+
define_method(name) do |*args, **kwargs, &block|
|
|
51
|
+
require_document!("SignedDocument##{name}")
|
|
52
|
+
@document.public_send(name, *args, **kwargs, &block)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
MUTATORS.each do |name|
|
|
57
|
+
define_method(name) do |*|
|
|
58
|
+
raise HankoXml::FrozenSignatureError,
|
|
59
|
+
"SignedDocument##{name} would invalidate the signature; use #document if you really mean to " \
|
|
60
|
+
"modify it, and HankoXml.serialize to write the result"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Safely serializes the document to an XML String without re-formatting.
|
|
65
|
+
#
|
|
66
|
+
# @param declaration [Boolean] Whether to include the `<?xml ...?>` declaration.
|
|
67
|
+
# @return [String] The serialized XML.
|
|
68
|
+
# @raise [HankoXml::FrozenSignatureError] if the document changed since signing.
|
|
69
|
+
def to_xml(declaration: true)
|
|
70
|
+
return bytes_xml(declaration: declaration) unless @document
|
|
71
|
+
|
|
72
|
+
current = serialize(declaration: true)
|
|
73
|
+
unless current == @signed_xml
|
|
74
|
+
raise HankoXml::FrozenSignatureError,
|
|
75
|
+
"the signed document was modified after signing, so this serialization would not verify; " \
|
|
76
|
+
"use HankoXml.serialize(signed.document) if the change is intentional"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
declaration ? current : serialize(declaration: false)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Alias for {#to_xml}.
|
|
83
|
+
# @return [String]
|
|
84
|
+
def to_s
|
|
85
|
+
to_xml
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Serializes the document to a byte string.
|
|
89
|
+
# @return [String] The serialized XML in binary encoding.
|
|
90
|
+
def bytes
|
|
91
|
+
@document ? to_xml.b : @signed_xml
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Writes the safely serialized XML to an IO object.
|
|
95
|
+
#
|
|
96
|
+
# @param io [IO] The stream to write to.
|
|
97
|
+
# @param declaration [Boolean] Whether to include the `<?xml ...?>` declaration.
|
|
98
|
+
def write_to(io, declaration: true)
|
|
99
|
+
io.write(to_xml(declaration: declaration))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns all `<ds:Signature>` nodes in the document.
|
|
103
|
+
# @return [Nokogiri::XML::NodeSet]
|
|
104
|
+
def signatures
|
|
105
|
+
require_document!("SignedDocument#signatures")
|
|
106
|
+
@document.xpath(".//ds:Signature", ds: NS["ds"])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Verifies the signatures against a given trust source.
|
|
110
|
+
#
|
|
111
|
+
# @param kwargs [Hash] Configuration options passed to {Verifier#initialize}
|
|
112
|
+
# (certificate:, certificates:, store:, key:, keys:, policy:, schema:,
|
|
113
|
+
# signature:, etc).
|
|
114
|
+
# @return [Boolean] True if the signature is valid.
|
|
115
|
+
def valid?(**kwargs)
|
|
116
|
+
HankoXml.valid?(@document || @signed_xml, **kwargs)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def inspect
|
|
120
|
+
count = @document ? signatures.size : Native.scan(@signed_xml, [], false)[:signature_count]
|
|
121
|
+
"#<HankoXml::SignedDocument signatures=#{count}>"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def initialize_from_bytes(bytes)
|
|
127
|
+
@document = nil
|
|
128
|
+
@signed_xml = bytes.b.freeze
|
|
129
|
+
freeze
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The signed bytes, tagged with the encoding their declaration names.
|
|
133
|
+
# Leaving the declaration out drops it and the whitespace after it, which
|
|
134
|
+
# is outside the root element and so outside every signature.
|
|
135
|
+
def bytes_xml(declaration:)
|
|
136
|
+
xml = declaration ? @signed_xml : @signed_xml.sub(/\A<\?xml[^>]*\?>\s*/n, "")
|
|
137
|
+
xml.dup.force_encoding(declared_encoding)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def declared_encoding
|
|
141
|
+
name = @signed_xml[/\A<\?xml[^>]*\bencoding=["']([A-Za-z0-9._-]+)["']/n, 1]
|
|
142
|
+
name ? Encoding.find(name) : Encoding::UTF_8
|
|
143
|
+
rescue ArgumentError
|
|
144
|
+
Encoding::UTF_8
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def require_document!(capability)
|
|
148
|
+
Dom.require!(capability) unless @document
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def serialize(declaration:)
|
|
152
|
+
HankoXml::Dom.to_xml(@document, declaration: declaration)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|