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,298 @@
|
|
|
1
|
+
use std::env;
|
|
2
|
+
use std::path::PathBuf;
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
|
|
5
|
+
#[path = "build/vendor.rs"]
|
|
6
|
+
mod vendor;
|
|
7
|
+
|
|
8
|
+
/// Everything bindgen needs to parse the xmlsec/libxml2 headers, produced by
|
|
9
|
+
/// whichever of the two paths below ran. The defines matter as much as the
|
|
10
|
+
/// include paths: xmlsec's headers refuse to compile without a crypto backend
|
|
11
|
+
/// selected (`XMLSEC_CRYPTO_OPENSSL`), and the rest of the `-D` set records
|
|
12
|
+
/// which algorithms the library was actually built with.
|
|
13
|
+
pub struct Headers {
|
|
14
|
+
pub include_paths: Vec<PathBuf>,
|
|
15
|
+
pub defines: Vec<(String, Option<String>)>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Homebrew installs several of our dependencies keg-only (not symlinked
|
|
19
|
+
/// into the default prefix), so their .pc files aren't on the default
|
|
20
|
+
/// pkg-config search path. Add every keg-only formula we depend on
|
|
21
|
+
/// ourselves, rather than requiring every contributor to export
|
|
22
|
+
/// PKG_CONFIG_PATH by hand.
|
|
23
|
+
///
|
|
24
|
+
/// This matters for more than convenience: `xmlsec1-openssl.pc`'s own
|
|
25
|
+
/// `Libs:`/`Cflags:` lines reference libxml2 with a bare `-lxml2` and no
|
|
26
|
+
/// `-I`/`-L`, because pkg-config couldn't resolve libxml2's keg-only .pc
|
|
27
|
+
/// file either. Left alone, that makes our linker and bindgen fall back to
|
|
28
|
+
/// macOS's own ancient system libxml2 (`/usr/lib/libxml2.2.dylib`) instead
|
|
29
|
+
/// of the Homebrew libxml2 that `libxmlsec1-openssl.dylib` itself actually
|
|
30
|
+
/// links against — two different libxml2 builds in the same process,
|
|
31
|
+
/// silently handing structs allocated by one ABI to code compiled against
|
|
32
|
+
/// the other. So every formula below is probed explicitly, not left to
|
|
33
|
+
/// `Requires:` chasing.
|
|
34
|
+
fn add_homebrew_pkgconfig_paths(formulas: &[&str]) {
|
|
35
|
+
let mut paths = match env::var_os("PKG_CONFIG_PATH") {
|
|
36
|
+
Some(existing) => env::split_paths(&existing).collect::<Vec<_>>(),
|
|
37
|
+
None => Vec::new(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for formula in formulas {
|
|
41
|
+
let Ok(output) = Command::new("brew").args(["--prefix", formula]).output() else {
|
|
42
|
+
continue;
|
|
43
|
+
};
|
|
44
|
+
if !output.status.success() {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let Ok(prefix) = String::from_utf8(output.stdout) else {
|
|
48
|
+
continue;
|
|
49
|
+
};
|
|
50
|
+
let pkgconfig_dir = PathBuf::from(prefix.trim()).join("lib/pkgconfig");
|
|
51
|
+
if pkgconfig_dir.is_dir() {
|
|
52
|
+
paths.push(pkgconfig_dir);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if let Ok(joined) = env::join_paths(paths) {
|
|
57
|
+
env::set_var("PKG_CONFIG_PATH", joined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Links libxml2 by its exact, absolute dylib path instead of the ordinary
|
|
62
|
+
/// `-lxml2` + `-L<dir>` name search.
|
|
63
|
+
///
|
|
64
|
+
/// Why: rustc always emits every `-l<name>` flag *before* any `-L<dir>`
|
|
65
|
+
/// search path in the linker invocation it generates (verified empirically
|
|
66
|
+
/// with a linker-trace wrapper: `-lxml2` lands at argument position ~11,
|
|
67
|
+
/// our own `-L` flags land at ~40+, no matter what order we print
|
|
68
|
+
/// `cargo:rustc-link-lib`/`cargo:rustc-link-search` from build.rs). ld64
|
|
69
|
+
/// resolves `-lxml2` using only the search paths it has seen *so far* at
|
|
70
|
+
/// that position — none of ours — so it silently falls back to its default
|
|
71
|
+
/// search path and picks up macOS's own ancient system libxml2
|
|
72
|
+
/// (`/usr/lib/libxml2.2.dylib`) instead of the Homebrew build that
|
|
73
|
+
/// `libxmlsec1-openssl.dylib` itself is linked against. That leaves our
|
|
74
|
+
/// direct calls to `xmlReadMemory`/`xmlFreeDoc`/`xmlDocGetRootElement`
|
|
75
|
+
/// bound to a *different* libxml2 ABI than the one xmlsec's own internal
|
|
76
|
+
/// calls (`xmlSecFindNode`, ID-attribute lookups, etc.) use on the exact
|
|
77
|
+
/// same `xmlDocPtr` — two different struct layouts for the same pointer.
|
|
78
|
+
/// A direct path reference isn't resolved by name search at all, so the
|
|
79
|
+
/// ordering problem above doesn't apply to it.
|
|
80
|
+
fn link_homebrew_libxml2_by_absolute_path(libxml2: &pkg_config::Library) {
|
|
81
|
+
let dylib_path = libxml2
|
|
82
|
+
.link_paths
|
|
83
|
+
.first()
|
|
84
|
+
.map(|dir| dir.join("libxml2.dylib"))
|
|
85
|
+
.filter(|path| path.exists())
|
|
86
|
+
.expect("could not locate libxml2.dylib in the Homebrew libxml2 keg");
|
|
87
|
+
println!("cargo:rustc-link-arg={}", dylib_path.display());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn main() {
|
|
91
|
+
println!("cargo:rerun-if-env-changed=HANKO_XML_VENDOR");
|
|
92
|
+
println!("cargo:rerun-if-env-changed=RUBY_TARGET");
|
|
93
|
+
println!("cargo:rerun-if-env-changed=HANKO_XML_OPENSSL_PREFIX");
|
|
94
|
+
println!("cargo:rerun-if-changed=../../dependencies.yml");
|
|
95
|
+
|
|
96
|
+
// Two mutually exclusive ways to find libxmlsec1 and libxml2:
|
|
97
|
+
//
|
|
98
|
+
// vendored (HANKO_XML_VENDOR=1) -- build both from the pinned source in
|
|
99
|
+
// dependencies.yml and link them statically. Used by CI to produce the
|
|
100
|
+
// precompiled platform gems. Headers and archives then come from one
|
|
101
|
+
// source and cannot disagree.
|
|
102
|
+
//
|
|
103
|
+
// system (default) -- probe pkg-config, as below. This is what an
|
|
104
|
+
// ordinary `gem install` does, so the vendored path never runs on a
|
|
105
|
+
// user's machine unless they opt in.
|
|
106
|
+
//
|
|
107
|
+
// Everything downstream (the bindgen allowlist, the generated bindings) is
|
|
108
|
+
// shared; only the include and link paths differ.
|
|
109
|
+
let headers = if should_vendor() {
|
|
110
|
+
vendor::build()
|
|
111
|
+
} else {
|
|
112
|
+
system_libraries()
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
generate_bindings(&headers);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Whether to build libxml2 and xmlsec1 from the pinned source rather than
|
|
119
|
+
/// probing the system for them.
|
|
120
|
+
///
|
|
121
|
+
/// `HANKO_XML_VENDOR` decides it explicitly when set (`0` or empty forces the
|
|
122
|
+
/// system path). Otherwise it follows `RUBY_TARGET`, which rake-compiler and
|
|
123
|
+
/// rb-sys set when building a precompiled platform gem.
|
|
124
|
+
///
|
|
125
|
+
/// Keying off `RUBY_TARGET` rather than requiring the variable to be threaded
|
|
126
|
+
/// in is both more robust and more correct. More correct because a precompiled
|
|
127
|
+
/// gem ships to machines we know nothing about, so it has to carry its own
|
|
128
|
+
/// libxml2 and xmlsec1 — there is no system copy to rely on, and the build
|
|
129
|
+
/// host's copy is usually the wrong architecture anyway. More robust because
|
|
130
|
+
/// passing an environment variable into the rb-sys-dock container depends on
|
|
131
|
+
/// the `pre-script` input, which older releases of
|
|
132
|
+
/// `oxidize-rb/actions/cross-gem` do not have; when it is missing the build
|
|
133
|
+
/// silently falls back to the system path instead of failing loudly.
|
|
134
|
+
fn should_vendor() -> bool {
|
|
135
|
+
if let Ok(value) = env::var("HANKO_XML_VENDOR") {
|
|
136
|
+
return !(value.is_empty() || value == "0");
|
|
137
|
+
}
|
|
138
|
+
env::var_os("RUBY_TARGET").is_some()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Probes the system libxmlsec1/libxml2 and emits their link metadata.
|
|
142
|
+
fn system_libraries() -> Headers {
|
|
143
|
+
add_homebrew_pkgconfig_paths(&["libxmlsec1", "libxml2"]);
|
|
144
|
+
|
|
145
|
+
// We emit link metadata ourselves below instead of letting pkg-config
|
|
146
|
+
// auto-emit it, so that we can single out libxml2 for absolute-path
|
|
147
|
+
// linking (macOS only — see link_homebrew_libxml2_by_absolute_path).
|
|
148
|
+
let mut xmlsec_cfg = pkg_config::Config::new();
|
|
149
|
+
xmlsec_cfg.cargo_metadata(cfg!(not(target_os = "macos")));
|
|
150
|
+
let xmlsec = xmlsec_cfg.probe("xmlsec1-openssl").expect(
|
|
151
|
+
"xmlsec1-openssl not found via pkg-config. Install the xmlsec1 \
|
|
152
|
+
development package first (macOS: `brew install xmlsec1`, \
|
|
153
|
+
Debian/Ubuntu: `apt install libxmlsec1-dev`).",
|
|
154
|
+
);
|
|
155
|
+
// Probed for its own sake: makes sure our libxml2 linkage and headers
|
|
156
|
+
// resolve to the exact same build that xmlsec1-openssl.dylib links
|
|
157
|
+
// against, instead of silently falling back to the OS's libxml2. See
|
|
158
|
+
// the comment on add_homebrew_pkgconfig_paths above.
|
|
159
|
+
let mut libxml2_cfg = pkg_config::Config::new();
|
|
160
|
+
libxml2_cfg.cargo_metadata(cfg!(not(target_os = "macos")));
|
|
161
|
+
let libxml2 = libxml2_cfg.probe("libxml-2.0").expect(
|
|
162
|
+
"libxml2 not found via pkg-config. Install its development package \
|
|
163
|
+
first (macOS: `brew install libxml2`, Debian/Ubuntu: `apt install libxml2-dev`).",
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if cfg!(target_os = "macos") {
|
|
167
|
+
for path in &xmlsec.link_paths {
|
|
168
|
+
println!("cargo:rustc-link-search=native={}", path.display());
|
|
169
|
+
}
|
|
170
|
+
for lib in &xmlsec.libs {
|
|
171
|
+
if lib != "xml2" {
|
|
172
|
+
println!("cargo:rustc-link-lib=dylib={lib}");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
link_homebrew_libxml2_by_absolute_path(&libxml2);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// libxml2's include path goes first so its headers win over whatever
|
|
179
|
+
// libxml2 copy the system's default clang search path would otherwise
|
|
180
|
+
// find, keeping bindgen's struct layout in lockstep with the dylib we
|
|
181
|
+
// actually link.
|
|
182
|
+
Headers {
|
|
183
|
+
include_paths: libxml2
|
|
184
|
+
.include_paths
|
|
185
|
+
.iter()
|
|
186
|
+
.chain(&xmlsec.include_paths)
|
|
187
|
+
.cloned()
|
|
188
|
+
.collect(),
|
|
189
|
+
defines: libxml2
|
|
190
|
+
.defines
|
|
191
|
+
.iter()
|
|
192
|
+
.chain(&xmlsec.defines)
|
|
193
|
+
.map(|(name, value)| (name.clone(), value.clone()))
|
|
194
|
+
.collect(),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Runs bindgen over the xmlsec/libxml2 headers found at `include_paths` and
|
|
199
|
+
/// writes the result to OUT_DIR. Shared by both the vendored and system paths:
|
|
200
|
+
/// the allowlist and the generated surface must not depend on which one ran.
|
|
201
|
+
fn generate_bindings(headers: &Headers) {
|
|
202
|
+
let mut builder = bindgen::Builder::default()
|
|
203
|
+
.header_contents(
|
|
204
|
+
"hanko_xml_wrapper.h",
|
|
205
|
+
"#include <xmlsec/xmlsec.h>\n\
|
|
206
|
+
#include <xmlsec/crypto.h>\n\
|
|
207
|
+
#include <xmlsec/xmldsig.h>\n\
|
|
208
|
+
#include <xmlsec/xmltree.h>\n\
|
|
209
|
+
#include <xmlsec/errors.h>\n\
|
|
210
|
+
#include <xmlsec/keys.h>\n\
|
|
211
|
+
#include <xmlsec/keyinfo.h>\n\
|
|
212
|
+
#include <xmlsec/app.h>\n\
|
|
213
|
+
#include <xmlsec/io.h>\n\
|
|
214
|
+
#include <xmlsec/openssl/app.h>\n\
|
|
215
|
+
#include <xmlsec/openssl/crypto.h>\n\
|
|
216
|
+
#include <libxml/tree.h>\n\
|
|
217
|
+
#include <libxml/parser.h>\n\
|
|
218
|
+
#include <libxml/xmlerror.h>\n\
|
|
219
|
+
#include <libxml/xmlschemas.h>\n\
|
|
220
|
+
#include <libxml/valid.h>\n\
|
|
221
|
+
#include <libxml/xmlmemory.h>\n\
|
|
222
|
+
#include <libxml/xmlversion.h>\n",
|
|
223
|
+
)
|
|
224
|
+
.allowlist_function("xmlSec.*")
|
|
225
|
+
.allowlist_type("xmlSec.*")
|
|
226
|
+
.allowlist_var("xmlSec.*")
|
|
227
|
+
.allowlist_var("XMLSEC_.*")
|
|
228
|
+
.allowlist_var("LIBXML_.*")
|
|
229
|
+
// libxml2 parsing and document management
|
|
230
|
+
.allowlist_function("xmlReadMemory")
|
|
231
|
+
.allowlist_function("xmlFreeDoc")
|
|
232
|
+
.allowlist_function("xmlDocGetRootElement")
|
|
233
|
+
// libxml2 ID table lookup (same table xmlsec uses for URI="#id")
|
|
234
|
+
.allowlist_function("xmlGetID")
|
|
235
|
+
// libxml2 serialization (for sign → return signed bytes)
|
|
236
|
+
.allowlist_function("xmlDocDumpMemory")
|
|
237
|
+
.allowlist_function("xmlDocDumpFormatMemory")
|
|
238
|
+
// libxml2 node content and attribute extraction (for inspect)
|
|
239
|
+
.allowlist_function("xmlNodeGetContent")
|
|
240
|
+
.allowlist_function("xmlGetProp")
|
|
241
|
+
// In-scope namespaces of an XPath Filter 2.0 <XPath>, so Policy can
|
|
242
|
+
// check what its prefixes mean (#85)
|
|
243
|
+
.allowlist_function("xmlGetNsList")
|
|
244
|
+
.allowlist_function("__xmlFree")
|
|
245
|
+
.allowlist_function("xmlMemFree")
|
|
246
|
+
// libxml2 node/child traversal
|
|
247
|
+
.allowlist_function("xmlNodeListGetString")
|
|
248
|
+
.allowlist_type("xmlNode")
|
|
249
|
+
.allowlist_type("xmlDoc")
|
|
250
|
+
.allowlist_type("xmlAttr")
|
|
251
|
+
// Named directly: libxml2 2.15's prototypes (xmlGetID) stopped using
|
|
252
|
+
// the typedef, so no allowlisted function pulls it in there (#102)
|
|
253
|
+
.allowlist_type("xmlAttrPtr")
|
|
254
|
+
.allowlist_type("xmlNs")
|
|
255
|
+
.allowlist_type("xmlID")
|
|
256
|
+
// libxml2 error capture: keeps diagnostics off stderr, and lets the
|
|
257
|
+
// bytes path reject what a strict Nokogiri parse rejects (#102)
|
|
258
|
+
.allowlist_function("xmlSetStructuredErrorFunc")
|
|
259
|
+
.allowlist_type("xmlError")
|
|
260
|
+
.allowlist_type("xmlErrorLevel")
|
|
261
|
+
.allowlist_type("xmlParserOption")
|
|
262
|
+
// XSD validation of a <Signature>, for schema: :default without a
|
|
263
|
+
// DOM (#102). A copy of the element is validated, as Nokogiri did.
|
|
264
|
+
.allowlist_function("xmlNewDoc")
|
|
265
|
+
.allowlist_function("xmlDocCopyNode")
|
|
266
|
+
.allowlist_function("xmlDocSetRootElement")
|
|
267
|
+
.allowlist_function("xmlSchemaNewParserCtxt")
|
|
268
|
+
.allowlist_function("xmlSchemaSetParserStructuredErrors")
|
|
269
|
+
.allowlist_function("xmlSchemaParse")
|
|
270
|
+
.allowlist_function("xmlSchemaFreeParserCtxt")
|
|
271
|
+
.allowlist_function("xmlSchemaFree")
|
|
272
|
+
.allowlist_function("xmlSchemaNewValidCtxt")
|
|
273
|
+
.allowlist_function("xmlSchemaSetValidStructuredErrors")
|
|
274
|
+
.allowlist_function("xmlSchemaValidateDoc")
|
|
275
|
+
.allowlist_function("xmlSchemaFreeValidCtxt")
|
|
276
|
+
.derive_default(true)
|
|
277
|
+
.generate_comments(false)
|
|
278
|
+
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
|
|
279
|
+
|
|
280
|
+
for include_path in &headers.include_paths {
|
|
281
|
+
builder = builder.clang_arg(format!("-I{}", include_path.display()));
|
|
282
|
+
}
|
|
283
|
+
for (name, value) in &headers.defines {
|
|
284
|
+
builder = builder.clang_arg(match value {
|
|
285
|
+
Some(v) => format!("-D{name}={v}"),
|
|
286
|
+
None => format!("-D{name}"),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let bindings = builder
|
|
291
|
+
.generate()
|
|
292
|
+
.expect("failed to generate xmlsec bindgen bindings");
|
|
293
|
+
|
|
294
|
+
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
295
|
+
bindings
|
|
296
|
+
.write_to_file(out_path.join("bindings.rs"))
|
|
297
|
+
.expect("failed to write xmlsec bindgen bindings");
|
|
298
|
+
}
|