Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ jobs:
fail-fast: false
matrix:
include:
- rust: stable
xml-backend: legacy-fat-runtime
cargo-args: --no-default-features --features xmldsig,xmlenc,c14n,xml-backends-all,legacy-algorithms
- rust: "1.92.0"
xml-backend: legacy-aws-fips
native-fips: true
cargo-args: --no-default-features --features aws-lc-fips,xmlenc,c14n,xml-backend-xmloxide,legacy-algorithms
- rust: "1.92.0"
xml-backend: aws-fips-xmloxide
native-fips: true
Expand Down
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ snafu = { version = "0.9", optional = true }

# Crypto
aws-lc-rs = { version = "1.18.1", default-features = false, features = ["fips"], optional = true }
rsa = { package = "sad-rsa", version = "0.10.2", features = ["sha1", "sha2"], optional = true }
rsa = { package = "sad-rsa", version = "0.10.2", features = ["sha1", "sha2", "hazmat"], optional = true }
sha1 = { version = "0.11", features = ["oid"], optional = true }
sha2 = { version = "0.11", features = ["oid"], optional = true }
sha3 = { version = "0.12", default-features = false, features = ["oid"], optional = true }
Expand Down Expand Up @@ -117,7 +117,8 @@ aes-gcm = { version = "0.11.1", optional = true }
aes-kw = { version = "0.3.1", optional = true }
cbc = { version = "0.2.1", optional = true }
des = { version = "0.9", optional = true }
md-5 = { version = "0.11", optional = true }
md-5 = { version = "0.11", features = ["oid"], optional = true }
ripemd = { version = "0.2.0", default-features = false, features = ["oid"], optional = true }
pem = { version = "4", optional = true }
pkcs12 = { version = "=0.2.0-pre.0", default-features = false, features = ["kdf"], optional = true }
pbkdf2 = { version = "0.13", default-features = false, features = ["hmac"], optional = true }
Expand Down Expand Up @@ -148,6 +149,7 @@ time = "0.3.55"
tempfile = "3"

[features]
legacy-algorithms = ["dep:ripemd", "dep:md-5", "dep:des"]
default = ["std", "xmldsig", "xmlenc", "c14n", "xml-backend-xmloxide"]
std = ["thiserror/std", "xmlparser/std"]
aws-lc-fips = ["std", "xmldsig", "dep:aws-lc-rs"]
Expand Down
27 changes: 27 additions & 0 deletions LICENSE-THIRD-PARTY
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
The xml-sec crate includes code derived from the following projects. Their
copyright and permission notices are preserved below.

sad-rsa 0.10.2 (src/provider/rsa_pkcs1v15.rs)
------------------------------------------

Source: https://github.com/sadco-io/sad-rsa
Local fixed-width recovery adaptation: docs/rsa-recovery-patch.md

Copyright (c) 2025-2026 Sadco Security Team
Copyright (c) 2015-2025 RustCrypto Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

RustCrypto ml-dsa 0.1.1 (src/rustcrypto_ml_dsa/)
------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ xml-sec = { version = "0.1", default-features = false, features = ["xmldsig", "c
| Canonicalization | Canonical XML 1.0/1.1, Exclusive C14N, comments, and document subsets |
| XML signatures | XMLDSig signing and verification, RSA/DSA/ECDSA/HMAC, XPath transforms, `Manifest`, `KeyInfo`, and caller-provided references |
| XML encryption | AES-CBC/GCM, RSA-OAEP, AES Key Wrap, multiple recipients, and Element/Content replacement |
| Optional compatibility algorithms | `legacy-algorithms`: MD5/RIPEMD-160 signatures and digests, AES-192, TripleDES-CBC/CMS wrap, and RSA-1.5 transport; library policy denies these by default |
| X.509 | Certificate key extraction, chain validation, CRLs, required key authorization by default, and typed trust evidence |
| Key management | Caller-owned named inventory, usage-restricted keys, xmlsec `keys.xml`, encrypted PKCS#8, and bounded RustCrypto-backed PKCS#12 import |
| SAML 2.0 | Signed assertions and encrypted-assertion workflows covered by integration tests |
Expand Down
10 changes: 9 additions & 1 deletion compatibility/libxmlsec1-1.3.13-rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"legacy-policy-tests": {
"test": "capability_ledger::legacy_algorithm_claims_are_policy_gated",
"description": "Legacy SHA-1 signature paths are available only through explicit compatibility policy opt-ins."
"description": "Legacy SHA-1 and optional historical mechanisms require explicit compatibility policy permission; optional methods also require the legacy-algorithms build feature."
},
"sha1-verification-tests": {
"test": "capability_ledger::legacy_algorithm_claims_are_policy_gated",
Expand Down Expand Up @@ -79,6 +79,14 @@
"rationale": "The implementation retains this legacy algorithm for interoperability, but the compiled policy must explicitly permit it.",
"evidence": "legacy-policy-tests"
},
{
"id": "feature-gated-legacy-uri",
"kinds": ["algorithm-uri"],
"name_regex": "^xmlSecHref(?:Aes192Cbc|Aes192Gcm|KWAes192|Des3Cbc|KWDes3|RsaPkcs1|Md5|Ripemd160|RsaMd5|RsaRipemd160|HmacMd5|HmacRipemd160|EcdsaRipemd160)$",
"outcome": "compatibility-profile-only",
"rationale": "The legacy-algorithms feature implements this mechanism through RustCrypto. Library defaults deny it; an explicit immutable algorithm allowlist is required. Other providers may report the capability unavailable without fallback.",
"evidence": "legacy-policy-tests"
},
{
"id": "remaining-uri-surface",
"kinds": ["algorithm-uri"],
Expand Down
33 changes: 19 additions & 14 deletions compatibility/libxmlsec1-1.3.13.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"legacy-policy-tests": {
"test": "capability_ledger::legacy_algorithm_claims_are_policy_gated",
"description": "Legacy SHA-1 signature paths are available only through explicit compatibility policy opt-ins."
"description": "Legacy SHA-1 and optional historical mechanisms require explicit compatibility policy permission; optional methods also require the legacy-algorithms build feature."
},
"native-algorithm-tests": {
"test": "capability_ledger::native_algorithm_claims_match_the_rust_api",
Expand Down Expand Up @@ -69,6 +69,11 @@
"rationale": "Deprecated C aliases are excluded until the compatibility layer has a concrete ABI contract.",
"evidence": "unsupported-legacy-surface"
},
"feature-gated-legacy-uri": {
"outcome": "compatibility-profile-only",
"rationale": "The legacy-algorithms feature implements this mechanism through RustCrypto. Library defaults deny it; an explicit immutable algorithm allowlist is required. Other providers may report the capability unavailable without fallback.",
"evidence": "legacy-policy-tests"
},
"native-cli-behavior-options": {
"outcome": "behavior-compatible",
"rationale": "The native CLI implements this provider-independent option with donor-compatible parsing and command behavior.",
Expand Down Expand Up @@ -4209,15 +4214,15 @@
"source": "src/strings.c",
"line": 110,
"detail": "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
"name": "xmlSecHrefAes192Gcm",
"source": "src/strings.c",
"line": 119,
"detail": "http://www.w3.org/2009/xmlenc11#aes192-gcm",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4393,7 +4398,7 @@
"source": "src/strings.c",
"line": 254,
"detail": "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4441,7 +4446,7 @@
"source": "src/strings.c",
"line": 390,
"detail": "http://www.w3.org/2007/05/xmldsig-more#ecdsa-ripemd160",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4697,15 +4702,15 @@
"source": "src/strings.c",
"line": 600,
"detail": "http://www.w3.org/2001/04/xmldsig-more#hmac-md5",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
"name": "xmlSecHrefHmacRipemd160",
"source": "src/strings.c",
"line": 603,
"detail": "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4761,7 +4766,7 @@
"source": "src/strings.c",
"line": 128,
"detail": "http://www.w3.org/2001/04/xmlenc#kw-aes192",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4801,7 +4806,7 @@
"source": "src/strings.c",
"line": 257,
"detail": "http://www.w3.org/2001/04/xmlenc#kw-tripledes",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4873,7 +4878,7 @@
"source": "src/strings.c",
"line": 659,
"detail": "http://www.w3.org/2001/04/xmldsig-more#md5",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -4961,15 +4966,15 @@
"source": "src/strings.c",
"line": 710,
"detail": "http://www.w3.org/2001/04/xmlenc#ripemd160",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
"name": "xmlSecHrefRsaMd5",
"source": "src/strings.c",
"line": 725,
"detail": "http://www.w3.org/2001/04/xmldsig-more#rsa-md5",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand All @@ -4993,7 +4998,7 @@
"source": "src/strings.c",
"line": 746,
"detail": "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down Expand Up @@ -5073,7 +5078,7 @@
"source": "src/strings.c",
"line": 728,
"detail": "http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160",
"classification": "remaining-uri-surface"
"classification": "feature-gated-legacy-uri"
},
{
"kind": "algorithm-uri",
Expand Down
3 changes: 1 addition & 2 deletions crates/xml-sec-xslt/src/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,7 @@ fn render(
// than imposing a must-not requirement. Pinned libxslt appends this final newline when
// `indent` is omitted but suppresses it for explicit indent=no, so preserve that observable
// compatibility distinction: https://www.w3.org/TR/1999/REC-xslt-19991116#output
if definition.method != OutputMethod::Text
&& !(definition.indent_explicit && !definition.indent)
if definition.method != OutputMethod::Text && (!definition.indent_explicit || definition.indent)
{
text.push('\n');
}
Expand Down
2 changes: 1 addition & 1 deletion crates/xml-sec-xslt/src/xpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9329,7 +9329,7 @@ fn validate_decimal_subpattern(
));
}
}
if !has_digit && !(number.len() == 1 && saw_decimal) {
if !(has_digit || number.len() == 1 && saw_decimal) {
return Err(invalid_decimal_pattern("subpattern has no digit"));
}
Ok(())
Expand Down
12 changes: 12 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ legacy donor documents. This opt-in is local to the CLI. Library callers retain
the secure defaults and must explicitly allow each legacy algorithm through
their immutable signing or verification policy.

Building with `--features legacy-algorithms` extends that explicit CLI compatibility policy to
MD5/RIPEMD-160 signatures and reference digests, AES-192, TripleDES, and RSA-1.5 transport.
Use `--deskey[:NAME] FILE` for a 24-byte TripleDES content key and `--aeskey[:NAME] FILE`
for AES; equal key lengths do not make these types interchangeable. For `kw-aes192` or
`kw-tripledes` recipient templates,
the corresponding symmetric option supplies a KEK, and its name matches the recipient's
`KeyName`, not the generated content key. Symmetric key-store entries work through the same
wrap/unwrap path. RSA-1.5 is selected by the
recipient template's method URI and cannot carry OAEP parameters. `list-transforms` reports only
mechanisms available through the selected provider. Library defaults remain unchanged, and
selecting `--crypto aws-lc-fips` never falls back for an unavailable compatibility method.

Certificate and CRL signatures independently use the CLI's explicit `AllSupported`
compatibility permission, including PQ methods when compiled. This does not grant trust:
verification still requires caller-supplied anchors, valid paths and provider capability.
Expand Down
7 changes: 7 additions & 0 deletions docs/crypto-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ Without it, that selection is an explicit unavailable-provider error.

## Mechanisms

`legacy-algorithms` adds RustCrypto MD5/RIPEMD-160 signatures and digests, AES-192
content encryption and key wrap, TripleDES content encryption and CMS wrap, and RSA-1.5
transport. All operations still go through `CryptoProvider` and require explicit library policy
permission. The AWS FIPS adapter reports these optional mechanisms unavailable; compiling both
providers never falls back to RustCrypto. This describes the adapter's supported operations, not
every primitive present in the underlying AWS-LC library or an approved-service claim.

The AWS provider supports SHA-1/SHA-2 and SHA3-256/384/512 digests; RSA PKCS#1 v1.5
signing with SHA-256/384/512 and verification with those digests or SHA-1; native
ECDSA curve/digest combinations exposed by AWS-LC; AES-128/256 CBC and GCM;
Expand Down
33 changes: 33 additions & 0 deletions docs/rsa-recovery-patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Fixed-Width RSA Recovery Adaptation

`src/provider/rsa_pkcs1v15.rs` adapts the PKCS#1 v1.5 padding recovery of
[`sad-rsa` 0.10.2](https://github.com/sadco-io/sad-rsa), specifically
`src/pkcs1v15.rs::decrypt` and `src/algorithms/pkcs1v15.rs::decrypt_inner`.
The existing dependency remains responsible for RSA arithmetic, blinding and fault checking
through its `hazmat::rsa_decrypt_and_check` primitive. No second RSA engine is introduced.

The general-message donor API implements implicit rejection but discards padding validity.
That API cannot safely decide whether an enclosing unauthenticated CBC content operation
may return plaintext: fallback bytes may accidentally satisfy CBC padding. The local adaptation
returns an opaque fixed-width candidate with the original constant-time validity mask.
The operation performs content work first, then rejects an invalid recovery and zeroizes any
successful fallback plaintext. Key-ring wrappers preserve the opaque candidate.

[RFC 8017 §7.2.2](https://www.rfc-editor.org/rfc/rfc8017#section-7.2.2) defines the encoding
checks. Since the CEK length is known from the content algorithm, its delimiter position is
public. A single linear padding scan replaces the donor's variable-length alignment buffers.
All width-correct ciphertexts, including representatives at least as large as the modulus,
perform blinded RSA; out-of-range values are normalized but keep an invalid mask. Operational
RNG or arithmetic failures still fail closed. Secret decoded integers, encoded blocks and
candidate bytes are zeroized on drop.

This is a narrow embedded adaptation, not a whole dependency fork or a separately published
crate. It remains in the single `xml-sec` package. Updating `sad-rsa` requires checking the
named donor functions and its arithmetic API against this adaptation. Regression tests cover
all supported CEK lengths, header/delimiter/nonzero-padding checks, out-of-range values, and
successful CBC output following invalid recovery.

The mask does not make CBC authenticated or claim comprehensive padding-oracle resistance.
[XMLEnc 1.1 §6.1.2](https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-bleichenbacher-attack)
specifically warns that random-key fallback does not defeat CBC-based chosen-ciphertext attacks.
Prefer RSA-OAEP with authenticated content; legacy CBC requires external authentication.
11 changes: 11 additions & 0 deletions docs/xmldsig.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,17 @@ reported as an invalid per-reference result without changing core `SignedInfo` v

## Current Scope

The optional `legacy-algorithms` feature adds MD5 and RIPEMD-160 reference digests,
RSA-MD5/RSA-RIPEMD160, HMAC-MD5/HMAC-RIPEMD160, and ECDSA-RIPEMD160. Secure library
defaults reject these methods even when compiled. Explicitly allow signature methods through
`VerificationPolicy::signature_algorithms` / `SigningPolicy::signature_algorithms` and reference
digests independently through `digest_algorithms`. Permitting a signature never permits its
reference digest implicitly. The same digest policy gates `X509Digest` certificate selectors,
including embedded certificates, configured lookup certificates, `KeyInfoReference`, and
`RetrievalMethod`, before selector hashing. ECDSA-RIPEMD160 retains independent curve selection.
These are
historical interoperability mechanisms, not recommended algorithms for new documents.

Implemented signature methods include DSA-SHA1/SHA256; HMAC-SHA1/SHA224/SHA256/SHA384/SHA512;
RSA PKCS#1 v1.5 with SHA-1/SHA224/SHA256/SHA384/SHA512; and ECDSA
SHA-1/SHA224/SHA256/SHA384/SHA512 and SHA3-224/256/384/512. ECDSA selects P-256, P-384, or P-521
Expand Down
Loading
Loading