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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7cc2774d36325300a82de4dbe4ecc3b1f11358e27394808dec89e3ef47a82906
|
|
4
|
+
data.tar.gz: cd33797d179d71633a6d5de829ef3a7d2e2d9e7a747c1a30d976fa4d0bec52a7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5f047a411900dafac1a993e2bd5ea6fefc31799df928935f685e9a01296013cec047f0d4c7f4332375dc9442509537af4cb20b5ea06a33cbe29d490afcf54562
|
|
7
|
+
data.tar.gz: e6312aed3220326fc16f0093585c573be6d27928d6dfc2317a5074f6e605f71ce995ddcd7ccb92afc7d46b181e066f2a81f1f9c72a9f692c9b9049b515065d93
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the gem version
|
|
5
|
+
follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) **over the
|
|
6
|
+
Ruby API only** — the libxmlsec1 version the extension is built against is
|
|
7
|
+
tracked separately, and is reported by `HankoXml::NativeVersion.build_versions`.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.1.0] - 2026-10-15
|
|
12
|
+
|
|
13
|
+
First release.
|
|
14
|
+
|
|
15
|
+
- **XML-DSig signing and verification** of enveloped, enveloping and detached
|
|
16
|
+
signatures, through libxmlsec1: canonicalization, digests, signature math and
|
|
17
|
+
reference resolution all happen in xmlsec, never in Ruby.
|
|
18
|
+
- **Secure by default.** `Policy.strict` denies SHA-1, HMAC, XSLT, XPath and
|
|
19
|
+
external references, requires 2048-bit RSA and DSA and 256-bit EC keys, and
|
|
20
|
+
checks the signing certificate's key usage. Opt-in presets cover other needs:
|
|
21
|
+
`Policy.fragment` (SAML-style signed fragments), `Policy.legacy_sha1`,
|
|
22
|
+
`Policy.xades` (RSA-PSS, SHA-3, ECDSA-SHA3) and `Policy.interop` for older
|
|
23
|
+
signatures.
|
|
24
|
+
- **The caller chooses the key.** Verification uses the certificate, key or
|
|
25
|
+
`OpenSSL::X509::Store` you pass, never one selected by the document's
|
|
26
|
+
`<KeyInfo>`, and `Verification` reports which references were signed, so
|
|
27
|
+
XML Signature Wrapping is caught.
|
|
28
|
+
- **XAdES-B** signing (`Xades::Signer`) and verification (`Xades::Verifier`),
|
|
29
|
+
including XAdES 1.2.2 and 1.1.1 signed properties, tested against SignXML
|
|
30
|
+
and the EU DSS corpus.
|
|
31
|
+
- **Works without Nokogiri.** Signing and verification run on Strings, files
|
|
32
|
+
and IOs. With Nokogiri, the documents you already hold can be signed in
|
|
33
|
+
place, and `require "hanko_xml/nokogiri_ext"` adds `doc.sign!` and
|
|
34
|
+
`doc.signature_valid?`.
|
|
35
|
+
- **Detailed results.** `Verification` exposes the certificate chain,
|
|
36
|
+
`SignedInfo`, `KeyInfo` and per-reference status, with
|
|
37
|
+
`Verifier.without_trust_verification` for integrity-only checks.
|
|
38
|
+
- **External references** are served only from `referenced_documents:`: the
|
|
39
|
+
library never reads files or opens network connections.
|
|
40
|
+
- **Precompiled gems** for `x86_64-linux`, `aarch64-linux`, `arm64-darwin` and
|
|
41
|
+
`x64-mingw-ucrt`, with libxml2 2.15.4, xmlsec1 1.3.12 and OpenSSL 3.5.9
|
|
42
|
+
statically linked and isolated from Nokogiri's libxml2. Other platforms build
|
|
43
|
+
the source gem against the system libraries (libxmlsec1 `>= 1.2.30, < 1.4`).
|
|
44
|
+
- **Ruby 3.1 to 3.4**, with RBS signatures in `sig/`.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danimar Ribeiro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/hanko-mark.png" alt="HankoXml" width="120" height="120">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">HankoXml</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
A high-performance, strictly secure XML Digital Signature (XML-DSig) library for Ruby.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
HankoXml is backed by a native Rust extension that binds directly to [libxmlsec1](https://www.aleksey.com/xmlsec/) using [Magnus](https://github.com/matsadler/magnus). It is designed to be completely byte-faithful, blazingly fast, and natively immune to XML Signature Wrapping (XSW) attacks.
|
|
14
|
+
|
|
15
|
+
It works nicely with Nokogiri, signing and verifying the documents you already hold without fragile re-serialization, and it works without Nokogiri too: verification runs on plain bytes.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
- **Full XML-DSig Support:** Sign and verify Enveloped, Enveloping, and Detached signatures.
|
|
19
|
+
- **Nokogiri Native, Nokogiri Optional:** Signs and verifies the Nokogiri documents you already hold, with opt-in `doc.sign!` / `doc.signature_valid?` shorthands (`require "hanko_xml/nokogiri_ext"`), and verifies Strings, files and IOs without Nokogiri.
|
|
20
|
+
- **XSW Defense:** Provides a robust `Policy` engine and structural `signed?(node)` checks to defeat XML Signature Wrapping (XSW) attacks.
|
|
21
|
+
- **Rust-Backed:** Memory-safe FFI boundaries, zero shell-outs to CLI binaries, and massive performance gains over pure-Ruby cryptography.
|
|
22
|
+
|
|
23
|
+
A feature and security comparison with Python [SignXML](https://pypi.org/project/signxml/), including a plan to close the remaining gaps (XAdES first), lives in [`docs/src/content/guide/comparison-signxml.md`](docs/src/content/guide/comparison-signxml.md).
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
Most users install a precompiled platform gem and do **not** need a local build
|
|
28
|
+
toolchain.
|
|
29
|
+
|
|
30
|
+
- Ruby 3.1+
|
|
31
|
+
|
|
32
|
+
Precompiled gems are published for:
|
|
33
|
+
|
|
34
|
+
- `x86_64-linux`
|
|
35
|
+
- `aarch64-linux`
|
|
36
|
+
- `arm64-darwin`
|
|
37
|
+
- `x64-mingw-ucrt`
|
|
38
|
+
|
|
39
|
+
### Building from source (fallback)
|
|
40
|
+
|
|
41
|
+
If RubyGems falls back to the source gem for your platform, HankoXml compiles
|
|
42
|
+
its Rust extension against the local `libxmlsec1`, so the build toolchain has
|
|
43
|
+
to be present:
|
|
44
|
+
|
|
45
|
+
- Rust toolchain (stable)
|
|
46
|
+
- `libxmlsec1` and `libxml2` development headers, `pkg-config`, and libclang
|
|
47
|
+
(bindgen generates the FFI bindings during the build):
|
|
48
|
+
- **macOS:** `brew install libxmlsec1 libxml2 pkg-config`
|
|
49
|
+
- **Debian/Ubuntu:** `apt install libxmlsec1-dev libxml2-dev pkg-config libclang-dev`
|
|
50
|
+
|
|
51
|
+
Supported libxmlsec1: `>= 1.2.30, < 1.4`.
|
|
52
|
+
|
|
53
|
+
*(Note for macOS: Homebrew's `libxml2` is keg-only. HankoXml's `build.rs` automatically works around this by resolving absolute library paths during compilation.)*
|
|
54
|
+
|
|
55
|
+
### Version safety
|
|
56
|
+
|
|
57
|
+
bindgen bakes xmlsec's and libxml2's struct layouts into the compiled
|
|
58
|
+
extension, and a later mismatch is invisible to the dynamic linker — every
|
|
59
|
+
symbol still resolves, and a field is then read at the wrong offset. HankoXml
|
|
60
|
+
checks for this when it loads and raises `HankoXml::NativeVersionMismatch`
|
|
61
|
+
rather than returning a wrong answer from `verify!`:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
HankoXml::NativeVersion.build_versions
|
|
65
|
+
# => { xmlsec: "1.3.12", libxml2: "2.15.3", crypto: "openssl" }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If you installed from source and later upgrade libxmlsec1 through your OS
|
|
69
|
+
package manager, rebuild the extension with `gem pristine hanko_xml`.
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
Add this line to your application's Gemfile:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
gem "hanko_xml"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Nokogiri is optional, and `hanko_xml` does not depend on it or load it. Pick
|
|
80
|
+
the setup that fits:
|
|
81
|
+
|
|
82
|
+
- **With Nokogiri** (most projects already have it): add `gem "nokogiri"` and
|
|
83
|
+
`require "nokogiri"`. Everything is available, including signing, the
|
|
84
|
+
Nokogiri API below, and node-level `signed?` checks.
|
|
85
|
+
- **Bytes only**: leave Nokogiri out. Verification works on a String, a path
|
|
86
|
+
or an IO, with the same policy, schema and structural checks. Signing works
|
|
87
|
+
when the document already holds the `<Signature>` template. Building one, and
|
|
88
|
+
the DOM features, raise `HankoXml::NokogiriRequired`, naming what needs
|
|
89
|
+
Nokogiri.
|
|
90
|
+
|
|
91
|
+
| Capability | With Nokogiri | Bytes only |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `valid?`, `verify!`, `Verifier`, `signature_present?` | yes | yes |
|
|
94
|
+
| `sign` into a template the document already holds | yes | yes, one template per call |
|
|
95
|
+
| `sign` that builds the template, `sign!`, `Signer#template` | yes | raises `NokogiriRequired` |
|
|
96
|
+
| `Verification#signed?`, `#at_signed`, `#signed_nodes` | yes | raises; `#covers_root?` still answers |
|
|
97
|
+
| Custom `Nokogiri::XML::Schema`, `signature:` XPath, `resolver:`, XAdES | yes | raises `NokogiriRequired` |
|
|
98
|
+
|
|
99
|
+
`HankoXml.capabilities` reports the same thing at runtime. On Linux with
|
|
100
|
+
system libraries, whichever of hanko_xml and Nokogiri loads first decides which
|
|
101
|
+
libxml2 the other runs on, so HankoXml leaves the load order to you.
|
|
102
|
+
|
|
103
|
+
## Usage
|
|
104
|
+
|
|
105
|
+
### 1. The Nokogiri API (Recommended)
|
|
106
|
+
|
|
107
|
+
HankoXml can extend Nokogiri directly, making signing and verifying incredibly
|
|
108
|
+
ergonomic. The extension is opt-in: `require "hanko_xml"` alone does not touch
|
|
109
|
+
Nokogiri's classes, and `require "hanko_xml/nokogiri_ext"` adds `#sign`,
|
|
110
|
+
`#sign!`, `#signature_valid?`, `#signatures` and `#signature_present?` to
|
|
111
|
+
`Nokogiri::XML::Document` and `Node`.
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
require "nokogiri"
|
|
115
|
+
require "hanko_xml"
|
|
116
|
+
require "hanko_xml/nokogiri_ext"
|
|
117
|
+
|
|
118
|
+
doc = Nokogiri::XML(File.read("document.xml"))
|
|
119
|
+
private_key = File.read("private.pem")
|
|
120
|
+
certificate = File.read("public.crt")
|
|
121
|
+
|
|
122
|
+
# Sign the document in place
|
|
123
|
+
doc.sign!(key: private_key, certificate: certificate)
|
|
124
|
+
|
|
125
|
+
# Safely serialize without destroying the signature bytes
|
|
126
|
+
File.write("signed.xml", doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML))
|
|
127
|
+
|
|
128
|
+
# Verify it later
|
|
129
|
+
signed_doc = Nokogiri::XML(File.read("signed.xml"))
|
|
130
|
+
if signed_doc.signature_valid?(certificate: certificate)
|
|
131
|
+
puts "Signature is valid!"
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2. The Module API
|
|
136
|
+
|
|
137
|
+
If you prefer not to load the Nokogiri extension, use the top-level module:
|
|
138
|
+
|
|
139
|
+
#### Verification
|
|
140
|
+
|
|
141
|
+
This works with or without Nokogiri:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
require "hanko_xml"
|
|
145
|
+
require "pathname"
|
|
146
|
+
|
|
147
|
+
xml = File.read("signed.xml")
|
|
148
|
+
|
|
149
|
+
# Raises HankoXml::VerificationError if the signature is invalid or tampered with
|
|
150
|
+
result = HankoXml.verify!(xml, certificate: Pathname("public.crt"))
|
|
151
|
+
|
|
152
|
+
puts result.valid? # => true
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Signing (Immutable)
|
|
156
|
+
```ruby
|
|
157
|
+
# Returns a safe `SignedDocument` wrapper without mutating the original
|
|
158
|
+
# A String is PEM content, not a filename: pass files as Pathname (or File.read them).
|
|
159
|
+
signed_doc = HankoXml.sign(xml, key: Pathname("private.pem"), certificate: Pathname("public.crt"))
|
|
160
|
+
|
|
161
|
+
puts signed_doc.to_s
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Defending against XML Signature Wrapping (XSW)
|
|
165
|
+
|
|
166
|
+
A signature is only secure if it actually signed the data you are looking at. Attackers often wrap or move the `<Signature>` block to trick naive verifiers. HankoXml provides a structural `signed?` check on the verification result to prove a specific node was cryptographically covered by the digest.
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
result = HankoXml.verify!(doc, certificate: cert)
|
|
170
|
+
|
|
171
|
+
# Find the critical data in your document
|
|
172
|
+
payload = doc.at_xpath("//Invoice")
|
|
173
|
+
|
|
174
|
+
# Ensure the attacker didn't move it outside the signed block
|
|
175
|
+
unless result.signed?(payload)
|
|
176
|
+
raise "XSW Attack Detected: The invoice was tampered with!"
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Trust Anchors & Verification Options
|
|
181
|
+
|
|
182
|
+
You can verify signatures against multiple certificates, strict schemas, or specific validation times.
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
HankoXml.verify!(
|
|
186
|
+
doc,
|
|
187
|
+
# Pass a single cert, an array of certs, an OpenSSL::X509::Store, or a mapping of <KeyName>s
|
|
188
|
+
store: my_trusted_ca_store,
|
|
189
|
+
|
|
190
|
+
# Ensure the certificate was valid at a specific time
|
|
191
|
+
verification_time: Time.parse("2023-01-01"),
|
|
192
|
+
|
|
193
|
+
# Pre-validate the <Signature> structure against the W3C XSD to prevent parser exploits
|
|
194
|
+
schema: :default,
|
|
195
|
+
|
|
196
|
+
# Enforce strict security policies (no SHA-1, no external references)
|
|
197
|
+
policy: HankoXml::Policy.strict
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Contributing
|
|
202
|
+
|
|
203
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/danimaribeiro/HankoXml.
|
|
204
|
+
|
|
205
|
+
`bundle exec rake compile spec` by default builds the Rust extension against
|
|
206
|
+
your system's `libxmlsec1` (see "Building from source" above), which is fine
|
|
207
|
+
for day-to-day development. But the system library's build configuration can
|
|
208
|
+
differ from the pinned, vendored `libxmlsec1` that CI and the released gem
|
|
209
|
+
actually use — on macOS with Homebrew's `libxmlsec1`, this has been observed
|
|
210
|
+
to make some native URI-restriction and `KeyValue` specs fail or hang. Before
|
|
211
|
+
relying on a full local `bundle exec rspec` run (e.g. to validate a security
|
|
212
|
+
fix), rebuild against the vendored library instead:
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
HANKO_XML_VENDOR=1 bundle exec rake compile spec
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
See [VENDORING.md](VENDORING.md) for details on what gets vendored and why.
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|